trans is a Rust CLI for managing application translations stored as JSON files.
It supports both:
react-intlstyle flat message filesnext-intlstyle nested message files
The tool covers interactive editing, non-interactive batch updates, verification, import/export, mode migration, language management, and optional AI-assisted translation workflows.
- Interactive setup with
trans init - Interactive add/edit/delete flow when running
transwith no subcommand - Support for
.trans.config.jsonand.trans.config.yaml react-intlandnext-intlstorage modes- Add, update, delete, show, rename, sort, sync, import, export, and verify commands
- CSV and Excel export
- CSV and Excel import
- Add and remove language files
- AI suggestions for interactive translation, verification fixes, import fixes, and auto-translation
- Verification before and after mutations, with rollback on failure
- Stable sorted output via
BTreeMap-backed JSON writes
Install with Homebrew:
brew tap mbuvarp/trans
brew install transBuild or run from source:
cargo run -- --helpInstall locally:
cargo install --path .Then use:
trans --helpInitialize config in the project root:
trans initRun the interactive flow:
transOpen the interactive flow for a specific message ID:
trans app.header.titleAdd a translation non-interactively:
trans add --id app.header.title --values en:Hello,nb:HalloVerify all language files:
trans verifytrans looks for exactly one config file in the current directory, then walks up parent directories until it finds one:
.trans.config.json.trans.config.yaml
If neither file exists, most commands fail and tell you to run trans init.
Paths in the config, such as languageFilesPath, are resolved relative to the directory containing the discovered config file.
Example JSON config:
{
"mode": "react-intl",
"languageFilesPath": "messages",
"availableLanguages": ["en", "nb", "de"],
"requiredLanguages": ["en", "nb"],
"primaryLanguage": "en",
"defaultUntranslatedValue": "",
"newlineAtEndOfFile": false,
"defaultExportFormat": "excel",
"excelPassword": "unlock",
"runUpdateCheck": false,
"ai": {
"enabled": true,
"model": "gpt-5-mini",
"apiKeyEnv": "OPENAI_API_KEY",
"maxOutputTokens": 128,
"concurrency": 2
}
}Key fields:
mode:react-intlornext-intllanguageFilesPath: directory containing<lang>.jsonavailableLanguages: all managed localesrequiredLanguages: languages prompted for during add/update flowsprimaryLanguage: source language and reference set for verificationdefaultUntranslatedValue: value written for non-required or missing translationsnewlineAtEndOfFile: iftrue, saved translation JSON files end with a newline; defaults tofalsedefaultExportFormat:csvorexcelexcelPassword: password used for Excel sheet protectionrunUpdateCheck: iftrue, successful commands may prompt forbrew upgrade transai: optional AI configuration
Use trans config --help for interactive config editing and format conversion between JSON and YAML.
react-intl mode uses flat keys:
{
"app.header.title": "Hello"
}next-intl mode stores the same message as nested JSON:
{
"app": {
"header": {
"title": "Hello"
}
}
}In both modes, the CLI works with dotted message IDs such as app.header.title.
Message IDs must include at least one namespace segment. title is invalid, while app.title is valid.
Main commands:
trans: interactive add/edit/delete flowtrans init: create config interactivelytrans list-required-languages: print required languagestrans add --id <id> --values <lang:value,...>: add a new messagetrans update --id <id> --values <lang:value,...>: update an existing messagetrans delete --id <id>: remove a message from all languagestrans show --id <id> [--lang <lang>]: show one message in all or one languagetrans change-id <old_id> <new_id>: rename a message across all languagestrans verify [--ai]: check for key mismatches and format issuestrans sort: sort all configured translation files by keytrans sync: add missing IDs from the primary language into other languagestrans export [--format csv|excel]: export all translationstrans import <file>: import translations from CSV or Exceltrans migrate <mode>: convert betweenreact-intlandnext-intltrans auto: fill missing translations with AItrans add-lang <lang>: add a new language filetrans del-lang <lang>: remove a language filetrans config: inspect or edit config values
Run trans --help or trans <command> --help for the full option set.
Global options:
-C, --cwd <DIR>: run as iftranswas started inDIR; config discovery starts there, and relative project paths still resolve from the discovered config directory.
Running trans without a subcommand starts an interactive workflow:
- prompts for a message ID unless one was passed positionally
- shows the existing primary-language value if the ID already exists
- lets you update or delete an existing message
- prompts for translations in required languages
- supports
--allto prompt for every available language
Examples:
trans
trans --all
trans app.header.titleDuring interactive translation prompts, you can type /ai instead of a translation to ask for an AI suggestion.
This applies to:
transtrans <message_id>trans add --id <id> --alltrans update --id <id> --all
You can also provide guidance inline:
/ai
/ai keep the tone formal
/ai use the word "shipment" instead of "delivery"
Behavior:
- the primary language must be entered manually first
/aiis only available for non-primary languages- if AI is configured,
transasks the model for a suggestion using the primary-language text as source - existing translations in other languages can be used as extra reference context
- after a suggestion is returned, you can accept it, ask the AI for another version or instruction, or write a custom translation manually
If AI is not configured, the prompt falls back to manual input.
Export:
trans export --format csv
trans export --format excel --output translations-review
trans export --lang nb,de --missingNotes:
export --langalways includes the primary language automatically--missingexports only rows containing untranslated values- Excel exports are protected by default; use
--no-lockto disable protection
Import:
trans import translations.csv
trans import translations.xlsx --lang nb,de
trans import translations.xlsx --extra-langs create --trim
trans import translations.csv --aiThe import/export tabular format uses id as the first column and one column per language, for example:
id,en,nb
app.header.title,Hello,HalloMutation commands such as add, update, delete, and change-id verify the language files before making changes.
They also verify again after writing files. If post-write verification fails, the operation is rolled back to the previous snapshot.
Other safety-related behavior:
- JSON output is written in sorted key order
sortrewrites every language inavailableLanguagesusing the configured storage modeverifyreports missing keys, extra keys, invalid JSON, and message format problemssyncfills missing IDs in non-primary languages usingdefaultUntranslatedValuedel-langrefuses to delete the primary languagenext-intlmode requires string leaf values
AI support is optional and configured under the ai section in the config file.
Supported AI-assisted flows:
- interactive translation suggestions
trans verify --aitrans import --aitrans auto
The API key is read from the environment variable named by ai.apiKeyEnv. .env in the project root is loaded automatically.
Example:
OPENAI_API_KEY=your-key-hereConvert translation files between storage modes:
trans migrate next-intl
trans migrate react-intl --check
trans migrate next-intl --backup
trans migrate next-intl --out-dir converted/messagesUseful flags:
--check: validate compatibility without writing files--backup: create<languageFilesPath>__backupbefore migration--out-dir: write converted files to a different directory--no-update-language-files-path: keep the existing config path when using--out-dir
Migration to next-intl fails if dotted keys would conflict with nested object paths.
Run tests:
cargo testThe repository includes unit tests and CLI integration tests under tests/cli.rs.