Vue · vue-i18n

The JSON your build imports, reviewed and merged like code.

vue-i18n takes a plain object of messages per locale, which in practice is a JSON file you import — or several of them, once one file has more owners than it can hold. Mergua reads and writes those files in place, so your app keeps the setup it has and gains a branch, a review step and a pipeline that fills them in.

Vue + vue-i18n

/for/vue-i18n

Supported

locale files

src/locales

file format

nested

01

The files you already have

Mergua reads what is in your repo. Nothing is renamed, nothing moves, and no package is added to your project — the sync script is fetched with curl when the pipeline runs.

src/locales/
├── en.json      ← source locale
├── de.json
└── fr.json
src/locales/en.json
{
  "cart": {
    "summary": {
      "title": "Your cart",
      "items": "{count} items",
      "checkout": "Checkout"
    }
  }
}
02

How vue-i18n reads them

This is vue-i18n's own setup, not Mergua's — if you already have it, skip the panel. It is here so the page is a working path from empty project to translated screen rather than a page about a product.

src/cart/CartSummary.vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n';

defineProps<{ count: number }>();

const { t } = useI18n();
</script>

<template>
  <section>
    <h1>{{ t('cart.summary.title') }}</h1>
    <p>{{ t('cart.summary.items', { count }) }}</p>
  </section>
</template>

src/i18n.ts

import { createI18n } from 'vue-i18n';

import en from './locales/en.json';
import de from './locales/de.json';
import fr from './locales/fr.json';

export const i18n = createI18n({
  legacy: false,
  locale: 'en',
  fallbackLocale: 'en',
  messages: { en, de, fr },
});
03

Back into the build

One job, and the locale files in src/locales are the ones your build imports. Keys you added in code go up, finished translations come back down as a commit on the branch the job is running on.

The job runs on the branch it is already on, so a feature branch syncs against the matching Mergua branch and main against main. That is the part no other translation tool does.

And you do not have to run it on a timer: a project can POST to a URL of yours when translations change or a branch is merged, so the job above starts on the change. The delivery is signed, it is filtered to the branch you name, and an import of four hundred keys arrives as one call.

# --path, because the CLI defaults to Angular's src/assets/i18n.
# --format stays nested, which is the shape createI18n expects.
curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync --path src/locales
04

Placeholders

vue-i18n writes a variable as {count}. Mergua treats it as part of the string: if a translation comes back without it, the validator says so on the way out, and the AI translator is told to carry it through.

the source string
"items": "{count} items"
what fills it in
t('cart.summary.items', { count: 3 })
05

Between the push and the pull

The job above is both ends of one loop, and what happens in between is people. A translation gets written, somebody else approves it, and sometimes a whole language leaves for an agency and comes back — none of which touches a file in your repo until the next pull.

Approved before it is bundled

createI18n takes the messages object up front, so a wrong string is compiled into the bundle rather than fetched at runtime — which makes the review the last place anyone catches it. A reviewer approves or rejects one key at a time with the reason attached to the key itself, a review request mails everyone who could answer it, and a branch cannot be merged while anything on it is unreviewed: what main bundles has been read twice.

How a key gets from draft to reviewed

One document per language, out and back

A translation agency wants XLIFF rather than a folder of imports, so a language leaves as one XLIFF 2.0 document and comes back as one — each of the files you compose into the messages object sitting inside it as a file element that names itself, checkout.json. vue-i18n never reads it and the sync script does not carry it: the document is the handoff, and src/locales stays the JSON your build imports.

What travels in the document and what does not

Worth knowing on Vue

Single braces, and that matters

vue-i18n names a variable {count}. Transloco and i18next both write {{count}}. A file copied from one to the other renders the braces as text, which is exactly the class of mistake Mergua's placeholder check catches on the way out.

Bundle the messages, do not fetch them

createI18n takes the messages up front, so the JSON is imported and bundled rather than requested at runtime. Nothing in the sync changes — the CLI writes the files before the build reads them, which is why the job belongs in an early stage.

Several files per language, whenever you want them

A Mergua project holds more than one file per language, and vue-i18n has no opinion about it — you build the messages object, so a second file is a second import. It reads a folder per language with the files inside, src/locales/en/checkout.json, and the file name is what the keys are filed under: checkout.json becomes checkout, while a plain en.json is read as the language. The "split" tab above is that setup.

Splitting a language across several files

Vue I18n messages can be functions — those do not sync

A message compiled to a function in a .ts file is code, not data, and Mergua reads JSON. Keep the strings that need translating in the locale files and reserve functions for formatting you do not want a translator editing.

A different stack?

Mergua writes plain locale files, so anything that loads one works. These stacks have a page of their own as well.

Run it on one branch.

Upload the src/locales/en.json you already have and see the whole loop on the free tier.

Create your account