XLIFF Workflow
Localization source files use XLIFF 2.0 — one file per locale. Lexical scopes (knots/stitches/root) map to <file> elements within the XLIFF document. brink-specific metadata (content hashes for change tracking) uses XLIFF’s custom namespace extension (brink:, see BRINK_NS in brink-intl), which conforming tools preserve across round-trips.
The workflow is shipped end-to-end: the brink CLI exposes export-xliff, compile-locale, regenerate-xliff, and migrate-xliff, and brink-intl exposes the same operations as a library (generate_locale, compile_locale_xliff, regenerate_locale, migrate_unit_ids).
<unit id> is keyed on the scope’s DefinitionId (e.g. 0x0100000000000001:0), not its display name — this is a canonical, NMTOKEN-safe identifier decoupled from the mutable, non-unique-across-scopes display name, matching the format brink:scope-id and IntlError::InvalidUnitId already documented. Unit ids are not literally stable across renames: a DefinitionId is itself a hash of the scope’s (qualified) name/path, so renaming or moving a knot/stitch assigns it a new DefinitionId and every unit id beneath it changes. The human-readable scope name still rides along as the name attribute on <unit> ({scope_name}:{line_index}) and as the id attribute on the containing <file>, for translator context.
That churn no longer costs you translations, as long as the rename is declared. Annotate it with #@was(old_name) and both regenerate-xliff and compile-locale follow the compiled alias table to rebind the moved scope onto its old translations, instead of treating it as a brand-new scope (or, for compile-locale, failing outright). You do not need to run migrate-xliff after a rename — rebinding is automatic, and migrate-xliff exists only for the one-off migration of .xlf files exported before unit ids moved off display names.
#@was on a knot or stitch records an alias for the whole renamed subtree, not just the declaration itself: every stitch and label re-keyed only because its parent’s name changed (its qualified name contains the parent’s name) gets its own compiled alias entry too, so its translations rebind right alongside the renamed container’s. See the scope-matching rules in docs/intl-spec.md for the full set.
Why XLIFF
Every major translation management platform (Lokalise, Crowdin, etc.) natively imports/exports XLIFF, and the spec requires tools to preserve unknown extensions — brink-specific metadata survives round-trips through external tooling.
Workflow
The translation pipeline is .ink → compile → .inkb → export-xliff → .xlf.
(Always start from a compiled .inkb; never feed inklecate’s .ink.json into
the intl tooling.)
-
Export: extract every translatable line from a compiled story into an XLIFF file, organized by scope with context for translators.
brink export-xliff story.inkb --src-lang en --trg-lang es -o story.es.xlf -
Translate: work in the
.xlfdirectly or import it into a TMS (Lokalise, Crowdin, …). Translation state rides XLIFF’sstateattribute (initial/translated/reviewed/final). -
Compile: turn the translated XLIFF into a binary
.inkloverlay.brink compile-locale --base story.inkb --xliff story.es.xlf --locale es -o story.es.inkl -
Regenerate: after the source changes and you recompile, diff the new
.inkbagainst the existing XLIFF — preserving human translations while updating machine-managed fields (original text, context). Content-hash changes flag entries whose source moved.brink regenerate-xliff --base story.inkb --existing story.es.xlf -o story.es.xlf
Load the resulting .inkl at runtime with brink_runtime::apply_locale, or in
Bevy via the locale-switching API (see the Bevy Integration section).
Migrating archived .xlf files
.xlf files exported before the scope-id-based unit id scheme landed carry
display-name-based unit ids (e.g. intro:0 instead of
0x0100000000000001:0). brink regenerate-xliff already re-keys them for
free the next time you recompile (it rebuilds the document from the fresh
export and overlays translations by content hash, never by unit id). If you
need to re-key an archived .xlf without recompiling — for example to push
it back through a TMS that indexes on unit id before your next source
change — use migrate-xliff:
brink migrate-xliff story.es.xlf -o story.es.xlf
This only rewrites the id attribute on each <unit>; <source>,
<target>, state, and every brink:* extension attribute are left
untouched, so no translation is lost. It’s idempotent — running it on a file
that’s already on the new scheme is a no-op.