Skip to content
On this page

Internationalization (i18n)

MageObsidian translates phrases in the Vue/ESM layer with the same dictionary as the rest of the storefront — Magento's native per-locale js-translation.json. You write $t('…') in components exactly like Magento's $t / $.mage.__, and the standard CSV → language-pack flow applies.


Translating in Components

The i18n plugin (obsidianI18n) is wired into every Vue island automatically — you don't register anything. In templates, use $t:

<template>
    <button>{{ $t('Add to cart') }}</button>
    <p>{{ $t('Hello %1', userName) }}</p>
</template>

In <script setup>, use the useTranslate() composable:

<script setup>
import { useTranslate } from 'MageObsidian_ModernFrontend::js/i18n';

const { t, locale } = useTranslate();
const label = t('Items in cart: %1', count);
// `locale` is the active store locale, e.g. "en_US"
</script>

%1, %2, … are positional placeholders, substituted in order. An out-of-range placeholder is left untouched, so a malformed phrase never throws.


How It Works

  1. PHP publishes the runtime config on the page as window.__MAGE_OBSIDIAN_I18N__ = { locale, dictionaryUrl } (emitted once by the I18nRuntime block).
  2. The browser fetches the dictionary from dictionaryUrl — Magento's native js-translation.json for the active locale — once, shared across every mounted app, held reactively so $t re-renders as soon as it resolves.
  3. A phrase is looked up in the dictionary, falling back to the phrase itself when absent; then placeholders are interpolated.

Because the dictionary is Magento's own js-translation.json, translations come from the same CSV / language packs as the rest of the storefront — there is no parallel translation system.


Collecting Phrases

$t('…') calls written in .vue files are not visible to Magento's PHP-based phrase scanners, so MageObsidian ships a collector:

bin/magento mage-obsidian:i18n:collect --locale=en_US

It scans every compatible module and theme and merges what it finds into that component's standard Magento dictionary, i18n/<locale>.csv. New phrases default to themselves, so they flow into js-translation.json through the native static-content deploy once translated.

Source What is collected
.vue, .ts, .js $t('…') calls, including the i18n.$t('…') facade
.twig __('…') calls
Option Description
--locale Locale of the CSV to write (default en_US).

What the Twig reader accepts

Twig templates are read with a lexical scanner, never executed, so the collector needs no Twig engine — it works whether or not the optional Twig module is installed. It only looks inside Twig's own {{ … }} and {% … %} blocks, and it takes a phrase only when the first argument is a complete literal:

{{ __('Add to Cart') }}                        {# collected #}
{{ __("Items %1 to %2 of %3", first, last, n) }}  {# collected, arguments are not phrases #}
{{ __('It\'s on its way') }}                    {# collected, escapes are resolved #}

{# {{ __('Commented out') }} #}                {# not collected: a Twig comment #}
{% verbatim %}{{ __('Shown as an example') }}{% endverbatim %}  {# not collected #}
{{ __(label) }}                                {# not collected: dynamic #}
{{ __('Hello ' ~ name) }}                      {# not collected: concatenated #}
{{ hint("write __('x') here") }}               {# not collected: it is a string, not a call #}

A call spread over several lines is read in full. Text outside a Twig block is page content, so __('…') written there is never collected.

Idempotence and ownership

Each component writes to its own i18n/<locale>.csv: a theme's phrases never land in a module's dictionary. Existing rows are kept exactly as they are — an already translated phrase is never overwritten, and a row whose phrase no longer appears in the sources is never removed. Running the command twice over the same sources produces byte-identical files.

End-to-end

# 1. Write $t('...') phrases in your .vue components
# 2. Collect them into i18n/<locale>.csv
bin/magento mage-obsidian:i18n:collect --locale=en_US

# 3. Translate the CSV rows (or ship a language pack)
# 4. Deploy — js-translation.json is regenerated with the translations
bin/magento setup:static-content:deploy

Locales Are Exact

Magento resolves a dictionary by the exact locale code of the store view. A theme that ships i18n/es_ES.csv translates a store configured as es_ES and nothing else: a store on es_VE, es_MX or es_AR reads i18n/es_VE.csv, i18n/es_MX.csv or i18n/es_AR.csv, and falls back to the untranslated phrase when that file is absent. There is no implicit fallback from es_VE to es_ES.

To adapt a shipped dictionary to the locale your store actually uses:

# 1. Generate the dictionary for the exact locale
bin/magento mage-obsidian:i18n:collect --locale=es_VE

# 2. Seed it from the closest shipped one, keeping the rows you already translated.
#    Copy into your own theme or module, never into the vendor package.
#    (es_ES.csv and es_VE.csv carry the same phrases; only the translations differ.)

# 3. Adjust the wording, then deploy that locale
bin/magento setup:static-content:deploy es_VE

Because the collector never overwrites an existing translation, you can copy a dictionary across, edit the rows that need regional wording, and re-run the command to pick up new phrases without losing the edits. An override placed in your own theme wins over the one the package ships, the same way any theme file does.

Key Notes

  • $t works in any island component out of the box — the plugin is already installed by the island runtime.
  • The dictionary is Magento's native js-translation.json; no separate i18n backend.
  • If the config global is absent (e.g. no islands on the page), the layer degrades to passthrough — phrases render as written.
  • Always run mage-obsidian:i18n:collect after adding new $t('…') or __('…') phrases so they reach the CSV and, after deploy, the dictionary.
  • The default theme ships en_US and es_ES. Any other locale, regional Spanish included, needs its own dictionary.

Next Steps