Structure

Multiple JSON files

Transloco, i18next and vue-i18n all split a locale across several files — common.json, errors.json, one per area — so a screen loads only the strings it needs. Mergua keeps that split: the files go in as they are, one editor works across them, and they come back in the same shape. The CLI, the API and the database call one of these files a namespace; everything you see in the app calls it a JSON file.

Looking for the directory layout the CLI reads, the layouts it does not, or --namespace? That is the CLI contract, and it lives on its own page.

Namespaces in the CLI →

Files per language

A project says once, in Settings → Files per language, whether it holds one JSON file per language or several. The setting is the project owner's to change, and it is what every other view switches on — not which files happen to exist. One mistyped import can no longer turn a project into a multi-file project for good.

One file

Every key lives in one JSON file per language. The name of an imported file is not consulted at all — common.json and de.json both land in the same place. The file picker in the editor and in the download dialog stays off.

Several files

Each imported file name becomes a file of its own, and the name can be overridden per import. Keys are unique per file, so common.json and errors.json can both hold a title.

Switching back needs the other files emptied

Going from one file to several is always allowed. Going back is refused while keys still sit in a file other than the default one, and the message names those files: the editor's filter, the grouped key tree and the download's picker all go off with the setting, so those keys would stay in the project with nothing left to show or download them. Manage files, directly under the switch, is how you empty the last one.

A file that exists only on an open branch counts here too, and holds the switch shut the same way.

The setting is enforced on every write path, not just in the dialog. A one-file project that is asked for another file — by the API, by the sync script, by --namespace — is answered with 400 rather than quietly writing the keys somewhere else.

Importing several files

Drop or pick as many .json and .arb files as the language has. They belong to one language per import — the locale is chosen once, in the second step.

Where each file lands

The name is the file name without its extension. A name that is nothing but a locale code is the language's own file rather than a name someone chose, so it goes to the default file:

SettingFileLands in
One filecommon.jsondefault
One filede.jsondefault
Several filescommon.jsoncommon
Several filesde.jsondefault
Several filesde-final.jsonde-final

de.json, de-DE.arb and pt_BR.json are all read as locale codes. de-final.json is not one, so it is taken as a name.

Correcting the guess

In multi-file mode the derived name is an editable field on every row, so a guess can be corrected before the import rather than undone after it. Names take letters, digits, dot, hyphen and underscore, up to 255 characters — checked in the dialog, because the import sends one request per file and a name the server rejects would otherwise fail one file after the others had already landed.

Two files that resolve to the same name are refused before anything is written, and both file names are shown. In one-file mode there is no column and no choice: one import writes one file of one language, so picking more than one file is refused outright.

One preview per file

The second step previews each file on its own — new keys, conflicts with the values already there, keys the import will skip, the unchanged count and any ARB metadata changes. Per file rather than per import, because every file has its own keys and therefore its own conflicts.

Conflict handling, ARB metadata and the round-trip of context notes are the same for one file as for twenty — Import & Export covers them.

Working across files

One editor covers every file. The file controls follow the project's setting rather than the files it happens to hold, so a project set to one file per language never sees them.

The file filter

The filter sits in the editor's menu and offers All files or one file, each with its key count; the menu carries a dot while a single file is selected. With All files selected the key tree is grouped into one collapsible section per file, so the files stay visible instead of being flattened into one list.

The badge

A key outside the default file carries that file's name beside it. Two keys can share a leaf name and differ only by the file they live in — that is the whole reason the badge exists. Inside a grouped section the heading already names the file, so the badge is not repeated on every row.

Creating a key

Start typing a key and Mergua suggests the files whose name the first segment matches. Type a segment that matches no existing file and it says so before you commit — the key would create a new JSON file.

Downloading

The download dialog adds a JSON file picker for multi-file projects: All files or one of them, with the key count of each. Formats are the same as everywhere else — nested JSON, flat JSON, ARB or XLIFF.

What you get

DownloadFileResult
One languagedefaultfr.json
One languagecommoncommon.json
One languageAll filesfr.zip, holding fr/common.json
All languagesone filethe project archive, holding fr.json
All languagesAll filesthe project archive, holding fr/common.json

The archive keeps the layout your app already reads — one folder per locale, one file per name — so it unpacks straight over your i18n directory.

XLIFF keeps the files inside one document

The table above is the JSON and ARB story. An XLIFF download of All files is not an archive: a file is a <file original="common.json"> element inside the one document, in the order the picker lists them. So one language hands over as one fr.xlf however many files it is split across, and a translator opens one thing.

Coming back the other way it is one file per upload, and this is the one place the two directions differ. An import writes one file, so a document holding several <file> elements is refused by name rather than merged into one or fanned out across them — including the All-files document Mergua wrote. Hand that one over; bring the translations back a file at a time, picking each in the download dialog. A single-file document names its own: original is read and proposed, so you do not pick it twice.

Branches bring their own files

The list is the selected target's, not the project's: a branch can hold a file that exists nowhere on main, and main's key counts say nothing about that branch. Switching the target reloads the list, and a file the previous target had but the new one does not falls back rather than downloading nothing.

Over the API

The download endpoints take one file at a time through ?namespace=, defaulting to default. There is no "all files" over the API — the archive is assembled in the browser, so ask for the files you want one request each, or let the CLI walk them for you.

curl -H "X-API-Key: $MERGUA_API_KEY" \
  "https://api.mergua.com/v1/$PROJECT_ID/translations/fr?namespace=common"

Every parameter these endpoints take is listed under API access.

Renaming, merging and deleting a file

Manage files sits directly under the Files-per-language switch, because the refusal to switch back is what sends people looking for it. It lists every file the project holds outside the default one, with its live key count; a file only an open branch created is marked branch only, since it holds the switch shut while appearing on no download list.

Rename

common becomes shared and every key keeps its identity — its translations, its history, its comments and any branch work on it come along untouched. Nothing is created and nothing is copied, so branches need no reconciling: the keys are the same keys, under a different file.

A name already in use is refused rather than absorbed — a rename that quietly became a merge would settle same-named keys without anyone deciding. Merge instead. A name held only by deleted keys is refused too, and says so: delete that file first to free it.

Merge

Moves the keys of one file into another. Keys only one file holds simply change file. Keys both hold are conflicts, and the merge does not run until you choose:

  • Keep the version in the target — the source's key is deleted and the target's value stands.
  • Use the version from the source — its values are written onto the target's key, recorded in that key's history the way any edit is, and the source's key is then deleted.

Either answer keeps the target's key, so its history and comments stay attached to the key that survives. The dialog shows how many keys move and which ones clash before you confirm.

A merge is refused while an open branch still holds pending changes on one of the clashing keys — merge or discard that branch first. The message names it.

Delete

Only a file that holds no keys any more, which is what a merge leaves behind. Deleting it frees the name and removes the file's already-deleted keys for good — they are what was still holding the name. A file that still holds keys says how many, and points at merge.

The default file is neither renamed nor deleted: it is where a one-file project keeps everything, and where a locale-named import lands.

In the pipeline

The CLI reads the same files off disk and needs no flag to find them: it works out from your directory structure whether the project is one file per language or several. Point it at your i18n directory and it pushes and pulls every file; name one with --namespace to limit it to that file.

curl -sf https://api.mergua.com/v1/mergua.sh | sh -s -- sync

Which directory layouts it reads, which it refuses, and what every flag does is the CLI's contract, and it is written up once. Worth reading first if you use Transloco scopes: they arrive on disk with the two path segments the other way round, and the contract page carries the two-line loader that swaps them.

Namespaces in the Mergua CLI — the locale/name.json layout, auto-detection, the single-file fallback and the flags.

Set a project to one file per language and a repo laid out as several is refused rather than flattened, so the setting and the directory have to agree.