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¶
- PHP publishes the runtime config on the page as
window.__MAGE_OBSIDIAN_I18N__ = { locale, dictionaryUrl }(emitted once by theI18nRuntimeblock). - The browser fetches the dictionary from
dictionaryUrl— Magento's nativejs-translation.jsonfor the active locale — once, shared across every mounted app, held reactively so$tre-renders as soon as it resolves. - 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¶
$tworks 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:collectafter adding new$t('…')or__('…')phrases so they reach the CSV and, after deploy, the dictionary. - The default theme ships
en_USandes_ES. Any other locale, regional Spanish included, needs its own dictionary.
Next Steps¶
- Vue Islands — the runtime that wires the i18n plugin.
- Vue Components — authoring components.