Hand off to translators
kapi extract and kapi merge read the recipe's content globs, target
locales and target files and use the project's content memory, so for the XLIFF
or PO hand-off start with kapi init and run them inside the project. The .kpz workspace runs ad hoc
(kapi extract src/*.json -o work.kpz), and ad-hoc one-way format
conversion is plain kapi run -i <file>.
When a person should translate (a professional translator, a native-speaking colleague, an outside agency), you hand the work out and take it back. kapi writes a file the translator opens in the tool they already use, accepts the returned version, and rebuilds your original files byte for byte. The corrections that come back go into the content memory, so the next run reuses them. For the memory model behind it (matching, fuzzy thresholds, pre-fill/absorb), see Content memory.
Two carriers, chosen by recipient. The native interchange format is the
lossless bilingual .kpz (kapi extract --format kpz), for a recipient
working in kapi or the neokapi review tool; it carries inline codes, memory
matches, and term context in one integrity-verified, diffable parcel. For a
third-party CAT tool, kapi extract emits XLIFF 2.x / PO (the default), the
formats the rest of this page uses. Both go out and come back through the same
extract / merge verbs; see KBF vs XLIFF. (Memory
and terminology have their own interchange: kapi memory import/export for TMX,
kapi terms import/export for TBX.)
The narration uses terminology that has since been retired. The interface and the behaviour shown are unchanged.
Content memory in the loop
The content memory participates on both sides of the round-trip:
- Memory pre-fill on extract: every segment is looked up against the
project's memory first. Exact matches land pre-filled as
state="translated"; fuzzy matches above the recipe'smemory.fuzzy_thresholdland as fuzzy. - Memory absorb on merge: every accepted target becomes an entry in the
project's memory, with provenance (batch id, source path, content hash,
filename) that
kapi memory audit --batch <id>can trace back later.
Both are on by default; --no-memory and --no-memory-update disable them. Absorb is
recycle leverage and never a review decision: whether a reviewer approved a
target is a separate, content-hash-bound fact recorded in the project
state store by a decide operation through kapi apply; the
content memory holds none of it.
Run the engine behind the round-trip yourself: pick the bilingual XLIFF sample,
run it through, and compare source with output; the same reader and writer back
kapi extract and kapi merge:
The kapi.yaml recipe
# kapi.yaml
version: v1
name: My App
defaults:
source_language: en-US
target_languages: [fr-FR, de-DE, es-ES]
merge:
conflict_policy: translator-wins # | existing-wins | newest-wins
memory:
fuzzy_threshold: 75 # percent; 0..100
collections:
- path: src/locales/en/*.json
target: src/locales/{lang}
The merge, memory, and (opt-in) segmentation sections are optional; details on
the project file reference. Seed the memory from an
existing corpus if you have one: kapi memory import ./corporate-en-fr.memory.json.
Extract
From inside the project (auto-discovered from cwd, git-style), kapi extract
writes one XLIFF 2.2 per source → target pair under out/ and records a
manifest of the batch in the project's gitignored cache, which a later extract
uses to reuse the work for unchanged sources. Each file carries a
<note category="kapi"> with the batch id, source file and source hash, and
each block carries, as its <mda:meta> metadata of category kapi, the
revision of its translation as the project held it (if-match, absent where
there was none) and the revision of its source (basis):
kapi extract
Extracting batch 6f2e8a1c (format=xliff2, targets=fr-FR, de-DE, es-ES, sources=1)
LOCALE FILES BLOCKS EXACT FUZZY NEW
fr-FR 1 412 108 67 237
de-DE 1 412 0 0 412
es-ES 1 412 0 0 412
Batch 6f2e8a1c complete. Manifest: .kapi/work/cache/extractions/6f2e8a1c/manifest.yaml
Aggregate content-memory leverage: exact=108 fuzzy=67 new=1061 (total=1236)
Multi-target in one pass is the default. Common options: --target-lang fr,de
(subset), --only <collection>, --pattern <glob>, --xliff-version 2.0,
--no-memory, --out-dir <dir>, and --redact-rules: redacted extraction ships
placeholders only and kapi merge restores the originals from the local vault
(see Redaction).
Any bilingual tool that reads XLIFF 2.x works for the translator, and kapi takes no part until the file returns.
Merge the return
kapi merge -i vendor-return/app.en-US-to-fr-FR.xliff
kapi merge -i vendor-return/ # every .xliff in the directory; -i repeats
Merge reads the source file and the language each returned file names, and
what each block was extracted against travels in the file, so a renamed file
merges, and so does one whose extraction batch is gone from the cache. Each
returned translation is written to the file the collection's target names,
and the translated file follows its source's structure. A translation the file
held for a block the return does not carry stays where the file names its blocks
by key, as JSON keys and PO messages do. A paragraph of a Markdown or HTML file
is named by its position in its section, so after the source gains or loses a
paragraph, return every block of that file. A PO catalog that holds every
message of its source keeps its own header and comments; once the source gains
or loses a message, the catalog is written from the source, header included.
A collection with no target is source-only, and merging into it fails.
Each input is applied independently: a failure on one (a parse error, a source
with no translation file) doesn't abort the others; per-file outcomes are
reported and the exit code reflects any failure.
- Stale blocks. A block whose source changed since the extraction (someone edited the file while it was out), or whose source text was changed in the returned file, is reported stale and left out, neither applied nor absorbed into memory. Re-extract and re-translate the changed portion.
- Conflicts. When the project's translation of a block changed since the
extraction (someone translated or edited it while the file was out),
merge.conflict_policydecides:translator-wins(default) replaces it with the returned one,existing-winskeeps it and skips the returned one, andnewest-winstakes the returned one when the returned file is newer than the translation's file. A block whose translation is the one it was extracted against takes the returned translation under every policy. Merge never prompts. - Refusals. A returned translation that drops an inline code its source protects, or that breaks a rule the project holds it to (a required term, say), is refused and reported on stderr; the rest of the file still lands. Fix the translation, or mark the rule advisory, and merge again.
- Plurals and selects. kapi writes each XLIFF
<unit>or PO entry as one string, and a message with a plural or select, such as an ICU plural in an ARB catalog, has a branch per form.kapi extractleaves such a message out of the file and names it, andkapi mergerefuses a returned translation that would write one form in place of several. Translate those messages withkapi translate, which translates each branch. - Audit.
kapi memory audit --batch <merge-batch-id>lists every entry the merge wrote, with provenance.
After a merge, the gates say where you stand: kapi status re-derives coverage
from the merged targets, and kapi check --ship enforces the ship gate before a
release; see the kapi loop.
PO (gettext) alternative
Same flow, different exchange format: kapi extract --format po, then
kapi merge -i …/*.po: one entry per block, kapi's bookkeeping as extracted
comments (#. kapi-batch:, #. kapi-block:, and each entry's revisions as
#. kapi-if-match: and #. kapi-basis:), the language in the header's
Language field, and #, fuzzy on fuzzy memory pre-fills. If an editor
rewrites the header to a language the recipe does not list, merge takes the
language from the extraction that wrote the file, and fails for that file when
the extraction is gone from the cache.
A single merge invocation can mix XLIFF and PO from the same batch. PO cannot
carry sentence-level segmentation; projects with segmentation.source: true
must emit XLIFF (the combination errors early).
What stays in the project
The bilingual file carries one chosen target per language, the source runs and inline codes, and the revisions each block was extracted against. Merge rebuilds each translated file from the source in the project, byte-for-byte outside the translated text. Everything else stays in project state: the content memory, the terms store, and the stand-off overlays, variants, and per-target status the content model records. The bilingual file is the lean, tool-neutral handoff. The project holds the full record, so merge needs the same project the extract ran in.
Related
- KBF vs XLIFF: when to use each interchange format.
- Pre-translate from memory: leverage existing translations before sending anything out.
- Command reference: every command and flag.