@kununu/phraseapp-cli
v7.0.0
Published
CLI helper for downloading PhraseApp locale files
Downloads
10,928
Maintainers
Keywords
Readme
@kununu/phraseapp-cli
CLI for syncing translations between your project and Phrase. It downloads locale JSON files from a Phrase project, and can also check your source for missing or unused translation keys.
Two bash scripts for syncing translations with the PhraseApp API:
update-translations.sh (download locale files) and check-translations.sh
(report missing/unused keys).
Requirements
bash3.2+curl— forupdate-translations.shjq— for both scripts- standard
grep/sed/find— forcheck-translations.sh(no other tools)
Installation
Install as a dependency and call the scripts from node_modules/vendor, or
vendor the .sh files directly if you don't want the dependency.
npm (FE):
npm install --save @kununu/phraseapp-cliComposer (BE): add the VCS repository and require the package:
"repositories": [
{ "type": "vcs", "url": "https://github.com/kununu/kununu-phraseapp-cli.git", "no-api": true }
],
"require": {
"kununu/kununu-phraseapp-cli": "^7.0"
}composer update kununu/kununu-phraseapp-cliThen add shortcuts to your task runner / package.json / composer.json
scripts.
FE example (package.json scripts):
"update-translations": "bash ./node_modules/@kununu/phraseapp-cli/update-translations.sh",
"check-translations": "SOURCE_DIR=src bash ./node_modules/@kununu/phraseapp-cli/check-translations.sh"To check against a locale other than the default de_AT, override REFERENCE_LOCALE:
"check-translations": "SOURCE_DIR=src REFERENCE_LOCALE=de_DE bash ./node_modules/@kununu/phraseapp-cli/check-translations.sh"BE example (composer.json script):
"update-translations": "vendor/kununu/kununu-phraseapp-cli/update-translations.sh"Env vars
PHRASEAPP_PROJECT_ID and PHRASEAPP_ACCESS_TOKEN override the values in the
config file. A local .env file (if present) is sourced automatically. Keep
the access token out of version control.
| Variable | Description |
|---|---|
| PHRASEAPP_PROJECT_ID | Your Phrase project ID |
| PHRASEAPP_ACCESS_TOKEN | Phrase API access token |
You have to add the .phraseapp.json file to the directory you run the script from.
3. Add the configuration file
Create a .phraseapp.json in your project root:
{
"path": "YOUR_TRANSLATIONS_PATH/",
"locales": [
{
"locale_id": "de_DE",
"tags": ["TAG1", "TAG2", "TAG3"]
},
{
"locale_id": "de_AT",
"tags": [
"TAG1"
],
"fallback_locale_id": "de_DE"
}
]
}Supported keys:
path— output directory (the locale file name is appended to it, so end it with/)branch— optional PhraseApp branchfile_format— optional PhraseApp download format (defaultreact_simple_json). Any*json*format is validated and compacted withjq; any*yml*/*yaml*format is written as-is with its leading---document marker stripped (for Symfony'syml_symfony2); other formats are written unchanged.file_name_template— optional output file-name template. Supports the placeholders{locale}and{prefix}(e.g."messages.{locale}.yml"→messages.de_AT.yml). When omitted, files are named<locale>.jsonor<filePrefix>-<locale>.json.locales[]—locale_id, optionaltags,fallback_locale_id, andfilePrefix(multi-page configs — locales sharing an id/fallback with differenttagsper page — are split locally after download; this requires a JSONfile_format)
Example: Symfony / YAML consumer
Apps that consume Symfony YAML (rather than React JSON) set the format and a matching file-name template:
{
"path": "translations/",
"file_format": "yml_symfony2",
"file_name_template": "messages.{locale}.yml",
"locales": [
{ "locale_id": "en_US", "tags": ["inquiries", "review2", "kununu_jobs"] },
{ "locale_id": "de_AT", "tags": ["inquiries", "review2", "kununu_jobs"] }
]
}Usage
./update-translations.shLocales are downloaded 4 at a time, respecting PhraseApp's
concurrency limit,
with retry/backoff and Retry-After-aware handling of rate limits (429).
Checking translations
check-translations.sh finds the i18n keys used in your source and compares
them against the downloaded de_AT.json, reporting missing and unused
keys. It scans .js/.jsx/.ts/.tsx files (ignoring *.spec.*) for
formatMessage({id: '...'}) and <FormattedMessage id="..." /> with
best-effort grep/sed regex (newlines are flattened first, so multi-line
calls are matched).
SOURCE_DIR=src ./check-translations.shSOURCE_DIR(env, required) — directory to scan, relative to the cwdREFERENCE_LOCALE(env, optional) — locale to compare against, defaultde_AT- Reads
.pathand optional.dynamicKeysfrom.phraseapp.json - Compares against every reference-locale file (one per configured
filePrefix, so multi-page configs like app-mykununu/app-profiles are covered) - Writes reports under
report-check-translations/:missing_keys.json,not_used_keys.json,possible_dynamic_keys.json - Exits
1when missing keys are found (suitable for CI)
Keys built dynamically (formatMessage({id: someVar}), <FormattedMessage id={x} />,
template literals, or id={'x'}) cannot be resolved statically; the files
containing them are listed in possible_dynamic_keys.json. Register such keys
under dynamicKeys in .phraseapp.json so they are not reported as missing/unused.
Note: extraction is regex-based, not a full AST parse, so it is best-effort — unusually formatted calls, or a
formatMessage/FormattedMessagewith noidat all, may be missed. For anything the regex can't resolve statically, register the key underdynamicKeysto keep reports accurate.
Documentation
- CONTRIBUTING.md — dev setup, releases, and PR process
- AGENTS.md — entry points, conventions, and constraints (for both humans and AI agents)
License
Apache-2.0 © kununu
