Skip to main content

Web apps with JSON catalogs

easyi18n translates a JavaScript or TypeScript app that keeps one JSON file per locale: Angular with Transloco or ngx-translate, and React, Vue or Node apps that use i18next. The base-language file is the source of truth. You add strings to it, and easyi18n writes the file of every other language.

The web adapter ships in CLI 0.13.0, which is not released yet.

What init sets up​

init with stack: auto selects the web stack when package.json depends on one of these libraries. easyi18n init --stack web names it explicitly.

LibraryICU pluginWiring
@jsverse/transloco@jsverse/transloco-messageformatprovideTranslocoMessageformat() beside provideTransloco()
@ngx-translate/corengx-translate-messageformat-compiler and @messageformat/coreprovideTranslateCompiler(TranslateMessageFormatCompiler)
i18nexti18next-icu and intl-messageformati18next.use(ICU)

easyi18n writes placeholders as {name} and plurals as ICU, for example {count, plural, one {# item} other {# items}}. Each library reads that syntax through its plugin. init names the plugin and prints the wiring. It does not edit package.json: install the plugin with your package manager. doctor fails until the plugin is in package.json and wired in the app.

init writes an easyi18n.yaml like this one:

workspace: acme
project: web-app
mode: a
format: json
output: public/i18n
source: public/i18n/en.json
sourceRoot: public/i18n
sourceLayout: locale-files

source is the base-language file that sync uploads. output is the directory where the files of the other languages are written. format is json for a flat file and nested_json when the base file nests objects.

sourceRoot and sourceLayout are required for source: sync. Paths are relative to easyi18n.yaml; the root must contain the physical source file. The project base language is read from /meta before any catalog upload, including a preview. Choose the layout that describes your input catalog:

sourceLayoutFiles that belong to the base-language catalog
locale-filesFiles named for the base locale, such as en.json and admin/en.json. Locale names ignore case and treat _ and - alike. es.json is a different locale even under a directory named en.
base-treeEvery JSON under sourceRoot, including en.json, es.json, app.json and nested files. Keep target-language directories outside this root.

For a namespace layout, declare the base-language tree explicitly:

source: locales/en/app.json
sourceRoot: locales/en
sourceLayout: base-tree

A single app.json can be synced. A second physical JSON in that tree is refused with several_files_per_locale, naming both files. Merging namespaces is not supported. In locale-files, the source must have a base-language filename in the input inventory, either directly or through a file alias. Aliases and hardlinks of the same file do not count as additional files. Directory names outside the root, YAML location and the working directory do not change the declared layout.

If generated files live inside the input tree, declare their directories separately, for example sourceExclude: [public/i18n/dist]. These paths are also relative to the YAML and must name existing directories. Changing output does not exclude inputs. Directory aliases are resolved before exclusions; an exclusion cannot contain the source file. Hidden directories are skipped unless they contain the configured source. Unreadable directories, unresolved links and invalid input boundaries stop sync before upload.

Migrate an existing configuration​

Add both settings to every configuration that uses source:. For one file per locale, use the locale directory and locale-files; for namespace files, use the base-language directory and base-tree. An absent or invalid declaration produces catalog_scope_unknown; sync does not guess a layout or edit the YAML. init writes locale-files for its supported web layout and keeps an existing configuration unchanged.

This contract is unreleased. It requires a CLI build containing the final redesign from PR #2098, until a release assigns its minimum version. Pin that build for CLI and MCP; an older CLI may ignore sourceLayout and use the previous selector.

init takes the directory from rootTranslationsPath when transloco.config.ts declares it. Otherwise it looks in public/i18n, src/assets/i18n and locales, and creates an empty base file when it finds none.

Translate and publish​

The agent uses the file-first tools, the same ones an Xcode project uses:

  1. sync_preview uploads the base file as a dry run. It reports the keys the sync would create and the estimated credits.
  2. You approve the spend. sync uploads the base file and queues the translation.
  3. fill_status polls until the translation is complete.
  4. You approve the publish.
  5. pull writes one file per target language into output, for example public/i18n/es.json.

From a terminal or CI, easyi18n sync runs those steps in one command, and easyi18n pull downloads the published files.

Neither command writes the file source names. Edit the base file yourself and review the other files in the git diff. Do not edit a target file: the next pull or sync stops on it with edited_targets until the change is in the dashboard or discarded. Reload the app to load the new files.

Limits​

  • One file per locale. A project that splits a language into several files is refused with several_files_per_locale before anything is uploaded. i18next namespaces (locales/en/common.json) and Transloco scopes (public/i18n/admin/en.json) are such layouts. A sync is the whole catalog of the project, so uploading one of those files would disable the keys of the others.
  • The agent moves your literals into the base file by hand, following the setup prompt. Extracting t('...') calls from the source is on the roadmap.
  • Angular's built-in $localize and its XLIFF files are not supported yet.