Translation CLI
Translation commands import X-Ray XML string tables, maintain XRF JSON translation sources, and compile them back to gamedata. Start with Parse for an existing game or mod; use Format, Verify, and Build for an existing JSON project.
Examples run from a working directory containing a translations source folder. Import examples additionally assume a
stalker-anomaly installation beside it.
Initialize
Add missing language keys as null placeholders in the JSON sources:
xrf-cli translation initialize --path ./translations
This updates files in place. Running it again on the same initialized sources makes no further change. A null marks a
missing translation; it does not supply fallback text.
Format
Normalize source layout, then use check mode in automation:
xrf-cli translation format --path ./translations
xrf-cli translation format --path ./translations --check
Example output — check unformatted JSON sources:
Checking 2 translation source(s)
Not formatted: ./translations\st_items.json
Not formatted: ./translations\st_ui.json
Stderr:
Format issues with 2/2 translation source(s) in 2 ms
Check failed: 2 finding(s)
Exit code: 3.
The formatter sorts ids and language keys naturally, uses two-space indentation, and adds a trailing newline. Natural
order places st_thanks2 before st_thanks10, and ammo-5.45x39-ap before ammo-11.43x23-fmj. Check mode writes
nothing and exits 3 when selected files need formatting.
Repeat --path for multiple inputs. Directories select JSON files recursively; an explicitly named file is accepted
regardless of its extension.
By default, each file uses its dominant LF or CRLF line ending; a tie or a file without line breaks selects LF. Mixed
line endings are normalized to that choice. Check mode ignores line-ending differences unless --line-endings lf or
--line-endings crlf explicitly requires one convention.
What it does not touch
Formatting preserves JSON values and their representation as strings or arrays. A one-element ["text"] stays an array.
The build joins array elements with the literal \n sequence used by string values, so formatting does not choose
between these authoring forms.
It does not add null placeholders; use initialize or parse for that. Files already matching the selected format
are not rewritten, preserving their timestamps.
When it refuses
Selecting no sources or encountering an unparseable source exits 1. A parse failure stops the run at that file; files formatted earlier remain changed. Each replacement is staged as a whole file.
Build
Compile JSON sources into one XML string table per source and selected language, using that language’s code page:
xrf-cli translation build --path ./translations --dest ./gamedata/configs/text --language ukr
Example output — build Ukrainian string tables:
Building translations in ./translations (containing-installation), language - ukr, sorted - true
Building 2 translation source(s)
Built translation files in 2 ms
Exit code: 0.
A missing translation compiles to its id. The report summarizes tables written and ids compiled per language.
Each table lands at <dest>/<language>/<source name>.xml, whatever directory its source sits in. The game opens every
table by its bare name in the language directory, so a table one level deeper would stop it with “Can’t find specified
xml file”. Two sources with one name, in any directories, are refused before anything is written.
Each language is written in the code page the game’s fonts draw it with. The game never decodes a table; the font maps each byte to a glyph.
| Code page | Fonts | Languages |
|---|---|---|
| windows-1252 | _west | fra, ger, ita, spa |
| windows-1250 | _cent | pol |
| windows-1251 | unprefixed | eng, rus, ukr |
A character the language’s code page cannot hold fails the build with the entry named, because the game would show another letter in its place. So does an entry whose line is longer than 4094 bytes. The game’s XML reader takes lines of at most 4095 bytes in xray-16 and 4094 in Anomaly’s engine, and a longer line stops it loading the table.
Build and verify accept a single source file or roots read through the virtual file system. Layered roots resolve
winning files by priority, including files from mounted archives. The --dest directory is a plain directory and must
be outside every source root.
Verify
Check completeness for a language:
xrf-cli translation verify --path ./translations --language ukr --strict `
--report ./translation-report.json
Example output — find missing Ukrainian text:
Verifying translations in ./translations (containing-installation), language - ukr
Verifying 2 translation source(s)
Verified translation files in 0 ms, 4 checked, 2 missing, 0 the game would refuse or mangle
Stderr:
Translation key missing: st_medkit_name ukr in st_items.json
Translation key missing: st_ui_quit ukr in st_ui.json
Check 'translations' is failed with 2 finding(s)
Check failed: 2 finding(s)
Exit code: 3. Both the absent key and the explicit null are reported as missing.
Both an absent language key and an explicit null count as missing. Without --strict, missing translations are
reported while a completed check succeeds, since the game shows each one as its id. With --strict, those gaps produce
exit 3.
Verify also checks what the game would refuse or show wrong, and fails with exit 3 on it with or without --strict:
translations.unencodable: a character the language’s code page cannot hold.translations.overlong: a table line longer than the game’s XML reader takes.translations.duplicate-across-files: an id two sources define. The game shows whichever loads last, and xray-16 loads tables in parallel.translations.collision: two sources that build to one table.
The report’s result holds checkedTranslations, missingTranslations, engineProblems, languages, status, and
checks. The translations check carries one translations.missing finding per missing id. The translations.engine
check carries the game problems. languages holds a summary row per file and language. status is failed whenever a
translation is missing or a problem is found; only --strict makes a missing translation exit 3. Use the summary rows
to review large imports, then inspect findings for the files being translated.
An unreadable source is an execution failure, exit 1. A malformed source fails verification with exit 3 regardless of
--strict; the report then carries the parse error as a single translations.source finding, with nothing else
checked.
Parse
Import XML tables once per language into a shared JSON output directory:
xrf-cli translation parse --path ./stalker-anomaly --language eng --dest ./translations
xrf-cli translation parse --path ./stalker-anomaly --language ukr --dest ./translations
XML tables do not declare their language. --language labels the imported text, so select the language that the input
actually contains. Installations with tables in db/configs archives are read through the same virtual file system as
loose trees.
What it writes
Each table becomes a JSON source under --dest with its subdirectory path preserved. Imports into the same destination
merge languages. Ids and language keys use the same canonical order as Format, independently of import order.
A record missing one of the languages represented in its file receives an explicit null.
Existing text that differs from the import is preserved and counted as a conflict. Add -f/--force to replace it.
Reimporting unchanged tables into an unchanged output is idempotent.
Finding the tables
--path names the input root. The importer looks under configs/text when present, then selects the directory named
for the requested language. Use --prefix for a different layout. A selected scope that still contains another
language’s directory is refused to prevent labeling its strings with the wrong language.
Before writing anything
Add --dry-run to inspect the proposed import without writing. Use --file to select one table. Unreadable tables are
reported; --strict makes those findings fail the run.
Notes
In the engine repository, npm run cli -- verify translations wraps verification, and the translations build target
wraps compilation. Call xrf-cli directly for formatting, initialization, and imports.
Command reference
xrf-cli translation build
Build translation files into gamedata
xrf-cli translation build [OPTIONS] --path <path>... --dest <dest>
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path>... | yes | Root holding translation sources, or one source file. Repeat to layer roots, highest priority first | |
--source <source> | containing-installation | How to read the path: auto treats it as an installation only when it declares one, directory ignores any declaration, volumes mounts every archive volume beneath it, installation requires one, containing-installation searches parent directories for one. Possible values: auto, directory, volumes, installation, containing-installation. | |
--prefix <prefix> | Limit to one logical subtree, such as translations | ||
-d, --dest <dest> | yes | Directory the built string tables are written to | |
-l, --language <language> | all | Language to build, or all of them | |
--no-sort | Preserve source order instead of sorting dynamic translation files | ||
--refuse-links | Refuse to write through links or junctions already below the destination, instead of following them | ||
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |
xrf-cli translation format
Normalize JSON translation sources
xrf-cli translation format [OPTIONS] --path <path>...
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path>... | yes | Paths to json translation sources or folders holding them | |
-c, --check | Run formatter in check mode | ||
--line-endings <line-endings> | Write these line endings instead of preserving each file’s own, and judge them in check mode. Possible values: lf, crlf. | ||
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |
xrf-cli translation initialize
Initialize translation files
xrf-cli translation initialize [OPTIONS] --path <path>
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path> | yes | Path to translation folder | |
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |
xrf-cli translation parse
Parse XML translations into JSON sources
xrf-cli translation parse [OPTIONS] --path <path>... --language <language> --dest <dest>
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path>... | yes | Root holding raw xml translations. Repeat to layer roots, highest priority first | |
--source <source> | containing-installation | How to read the path: auto treats it as an installation only when it declares one, directory ignores any declaration, volumes mounts every archive volume beneath it, installation requires one, containing-installation searches parent directories for one. Possible values: auto, directory, volumes, installation, containing-installation. | |
--prefix <prefix> | Limit to one logical subtree, such as configs\text\eng | ||
-l, --language <language> | yes | Language every entry read by this run is filed under. Raw xml carries no language, so it is declared rather than guessed | |
-d, --dest <dest> | yes | Directory the json sources are written to, merging with any already there | |
--file <file> | Restrict the run to one string table, by file name | ||
-f, --force | Replace existing text that differs, instead of keeping what is already there | ||
--dry-run | Report what would be written without writing it | ||
--strict | Answer with a check failure when anything was unreadable or off schema | ||
--refuse-links | Refuse to write through links or junctions already below the destination, instead of following them | ||
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |
xrf-cli translation verify
Verify that translation sources build to tables the game loads as written, and are translated into every language
xrf-cli translation verify [OPTIONS] --path <path>...
| Option | Required | Default | Description |
|---|---|---|---|
-p, --path <path>... | yes | Root holding translation sources, or one source file. Repeat to layer roots, highest priority first | |
--source <source> | containing-installation | How to read the path: auto treats it as an installation only when it declares one, directory ignores any declaration, volumes mounts every archive volume beneath it, installation requires one, containing-installation searches parent directories for one. Possible values: auto, directory, volumes, installation, containing-installation. | |
--prefix <prefix> | Limit to one logical subtree, such as translations | ||
-l, --language <language> | all | Language to verify, or all of them | |
--strict | Also answer with a check failure when any translation is missing | ||
-s, --silent | Turn off logging | ||
-v, --verbose | Turn on verbose logging | ||
--json | Write the run’s JSON report to stdout, moving human output to stderr | ||
--report <PATH> | Write the run’s JSON report to a file |