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.
| Library | ICU plugin | Wiring |
|---|---|---|
@jsverse/transloco | @jsverse/transloco-messageformat | provideTranslocoMessageformat() beside provideTransloco() |
@ngx-translate/core | ngx-translate-messageformat-compiler and @messageformat/core | provideTranslateCompiler(TranslateMessageFormatCompiler) |
i18next | i18next-icu and intl-messageformat | i18next.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:
sourceLayout | Files that belong to the base-language catalog |
|---|---|
locale-files | Files 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-tree | Every 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:
sync_previewuploads the base file as a dry run. It reports the keys the sync would create and the estimated credits.- You approve the spend.
syncuploads the base file and queues the translation. fill_statuspolls until the translation is complete.- You approve the publish.
pullwrites one file per target language intooutput, for examplepublic/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_localebefore 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
setupprompt. Extractingt('...')calls from the source is on the roadmap. - Angular's built-in
$localizeand its XLIFF files are not supported yet.