Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 pageFontsLanguages
windows-1252_westfra, ger, ita, spa
windows-1250_centpol
windows-1251unprefixedeng, 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>
OptionRequiredDefaultDescription
-p, --path <path>...yesRoot holding translation sources, or one source file. Repeat to layer roots, highest priority first
--source <source>containing-installationHow 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>yesDirectory the built string tables are written to
-l, --language <language>allLanguage to build, or all of them
--no-sortPreserve source order instead of sorting dynamic translation files
--refuse-linksRefuse to write through links or junctions already below the destination, instead of following them
-s, --silentTurn off logging
-v, --verboseTurn on verbose logging
--jsonWrite 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>...
OptionRequiredDefaultDescription
-p, --path <path>...yesPaths to json translation sources or folders holding them
-c, --checkRun 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, --silentTurn off logging
-v, --verboseTurn on verbose logging
--jsonWrite 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>
OptionRequiredDefaultDescription
-p, --path <path>yesPath to translation folder
-s, --silentTurn off logging
-v, --verboseTurn on verbose logging
--jsonWrite 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>
OptionRequiredDefaultDescription
-p, --path <path>...yesRoot holding raw xml translations. Repeat to layer roots, highest priority first
--source <source>containing-installationHow 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>yesLanguage every entry read by this run is filed under. Raw xml carries no language, so it is declared rather than guessed
-d, --dest <dest>yesDirectory the json sources are written to, merging with any already there
--file <file>Restrict the run to one string table, by file name
-f, --forceReplace existing text that differs, instead of keeping what is already there
--dry-runReport what would be written without writing it
--strictAnswer with a check failure when anything was unreadable or off schema
--refuse-linksRefuse to write through links or junctions already below the destination, instead of following them
-s, --silentTurn off logging
-v, --verboseTurn on verbose logging
--jsonWrite 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>...
OptionRequiredDefaultDescription
-p, --path <path>...yesRoot holding translation sources, or one source file. Repeat to layer roots, highest priority first
--source <source>containing-installationHow 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>allLanguage to verify, or all of them
--strictAlso answer with a check failure when any translation is missing
-s, --silentTurn off logging
-v, --verboseTurn on verbose logging
--jsonWrite the run’s JSON report to stdout, moving human output to stderr
--report <PATH>Write the run’s JSON report to a file