eloqnt/cli
eloqnt crowdin
Sync messages with an existing Crowdin project.
When localization is outgrowing your repo, a translation management system like Crowdin adds helpful features that enable non-developers to participate in the workflow:
- Integration with 3rd-party services (Figma, CMS, …)
- Collaboration with team members
- Reviews of translations
- Project-wide glossaries
- Screenshots
- … and more
Note that if you offload the translation step to Crowdin, you can still benefit from eloqnt lint and eloqnt review before translation work begins.
crowdin.yml
The sync is driven by a crowdin.yml file at your project root, which maps your messages files onto a file-based Crowdin project.
To authenticate the CLI, create a personal access token in your Crowdin account settings, make it available as CROWDIN_PERSONAL_TOKEN in your environment.
Then, map your messages to the Crowdin project:
crowdin.yml
project_id: '123456'files:- source: /messages/en.jsontranslation: /messages/%two_letters_code%.json
The file follows the Crowdin configuration and supports a subset of its options. Unsupported options will either warn or error based on the severity.
project_id
The numeric ID of your Crowdin project as a string:
crowdin.yml
project_id: '123456'
Alternatively, you can set project_id_env to the name of an environment parameter that holds the value (e.g. CROWDIN_PROJECT_ID).
api_token
A personal access token with manager access:
crowdin.yml
api_token: '<your token here>'
Alternatively, you can set api_token_env to the name of an environment parameter that holds the value (e.g. CROWDIN_PERSONAL_TOKEN).
base_path
The directory every pattern resolves against (defaults to the enclosing folder of crowdin.yml):
crowdin.yml
base_path: './apps/site'
base_url
The Crowdin API to talk to (for Crowdin Enterprise):
crowdin.yml
base_url: 'https://acme.api.crowdin.com'
preserve_hierarchy
By default, Crowdin strips any enclosing folders of your messages. With preserve_hierarchy, you can rebuild the same structure in your Crowdin project:
crowdin.yml
preserve_hierarchy: true
files
One entry per set of source files, mapping them to where their translations live:
crowdin.yml
files:- source: /messages/en.jsontranslation: /messages/%two_letters_code%.json
files[].source
A glob matching the files to upload, where a leading slash is relative to base_path:
crowdin.yml
source: /messages/en.json
files[].translation
Where each translated file lands, built from placeholders:
crowdin.yml
translation: /messages/%two_letters_code%.json
Placeholders for the target language:
%language%for the name (French)%language_id%for the Crowdin id (fr)%two_letters_code%for the ISO 639-1 code (fr)%three_letters_code%for the ISO 639-2/T code (fra)%locale%for the locale (fr-FR)%locale_with_underscore%for the locale with an underscore (fr_FR)%android_code%for the Android code (fr-rFR)%osx_code%for the macOS code (fr.lproj)%osx_locale%for the macOS locale (fr)
Placeholders carried over from the source file:
%original_file_name%for the file name (en.json)%file_name%for the name without its extension (en)%file_extension%for the extension (json)%original_path%for the parent folders on the Crowdin project**for whatever**matched insource
files[].dest
Where the file lands on the Crowdin project, when that should differ from its path in your repo:
crowdin.yml
preserve_hierarchy: truefiles:- source: /packages/admin/messages/en.jsontranslation: /packages/admin/messages/%locale%.jsondest: /admin/%original_file_name%
The pattern takes the placeholders carried over from the source file, all resolved against the file in your repo. Crowdin requires preserve_hierarchy: true alongside dest, and a source with a glob needs a dest that includes a placeholder, so each match keeps its own path.
files[].type
The Crowdin file format, needed when the extension doesn't imply it:
crowdin.yml
source: /messages/en.jsontype: chrome
files[].ignore
Globs to exclude from source, written the same way:
crowdin.yml
ignore:- /messages/internal/**/*.json
files[].languages_mapping
Overrides the code a placeholder substitutes:
crowdin.yml
languages_mapping:two_letters_code:pt-BR: pt-brzh-CN: zh-cnzh-TW: zh-tw
files[].content_segmentation
Set to 0 to stop Crowdin splitting text into sentence-level strings, which it does for document formats like Markdown and HTML:
crowdin.yml
content_segmentation: 0
files[].update_option
Either update_as_unapproved or update_without_changes depending on what should happen to existing translations when a source string changes:
crowdin.yml
update_option: update_as_unapproved
files[].skip_untranslated_strings
Leaves untranslated strings out of a download instead of defaulting them to source text:
crowdin.yml
skip_untranslated_strings: true
Upload sources
After adding or changing source strings, push your source files to the Crowdin project.
Files are created on the first upload and updated in place afterwards:
Flags
| --branch | string | Sync with this branch of the Crowdin project (created when missing) |
| --config | string | Path to the eloqnt config file (defaults to .eloqnt/config.{ts,mts,js,mjs}) |
| --json | boolean | Output the result as JSON |
Upload translations
Seed the project with translations that already exist locally.
They arrive unapproved, so reviewers in Crowdin stay in charge:
Flags
| --branch | string | Sync with this branch of the Crowdin project |
| --locale | string | Restrict to these target languages (comma-separated) |
| --config | string | Path to the eloqnt config file (defaults to .eloqnt/config.{ts,mts,js,mjs}) |
| --json | boolean | Output the result as JSON |
Download translations
Once translations are ready in Crowdin, pull them into your repo.
Downloading overwrites local translation files with Crowdin's version:
Flags
| --branch | string | Sync with this branch of the Crowdin project |
| --locale | string | Restrict to these target languages (comma-separated) |
| --config | string | Path to the eloqnt config file (defaults to .eloqnt/config.{ts,mts,js,mjs}) |
| --json | boolean | Output the result as JSON |