showcase · tier 3 · content
Localization factory
glob finds every doc, two fan-outs read + translate in parallel, jq zips the mirror tree back.
- 6tasksthe plan falls out of the bindings
- 6wavesparallel by construction
- 2verbs exercisedinvoke · infer
- T3the tiercontent
The whole file
6 tasks · 6 wavesThe plan falls out of the bindings, nothing is scheduled by hand.
localization-factory.nika.yamlsource
nika: v1workflow: id: localization-factory description: "glob docs → parallel read → parallel translate → mirror tree"model: ollama/qwen3.5:4b # local default · swap for mistral/mistral-large (EU model for EU locales)const: lang: "fr" # The committed rehearsal tree. Point this at ./docs (or wherever yours # lives) and change `permits.fs.read` to match, in the same edit. source_root: "./examples/fixtures/docs"permits: tools: ["nika:glob", "nika:jq", "nika:read", "nika:write"] fs: # `nika:glob` opens the ROOT of its pattern, so the bound has to cover # the directory AND everything under it — `**` does both (it matches the # dir itself and any descendant at any depth). A bound naming only the # `.md` files refuses the glob before it lists anything: # `NIKA-SEC-004 · ./examples/…/docs resolves outside permits.fs.read`. read: ["./examples/fixtures/docs/**"] # The mirror is as deep as the source tree, so this one earns its `**`: # every locale lands under ./i18n/ and nothing lands outside it. Naming # the locale (`./i18n/fr/**`) would be tighter but would have to be # re-edited for every value of `const.lang` — the bound is the tree the # factory owns. write: ["./i18n/**"]tasks: # ── the collection is discovered, never declared ──────────────────── files: invoke: tool: "nika:glob" args: pattern: "${{ const.source_root }}/**/*.md" exclude: ["**/node_modules/**"] # ── stage 1 · read every file, 8 at a time ───────────────────────── texts: with: files: ${{ tasks.files.output }} for_each: ${{ with.files }} max_parallel: 8 # local disk · the only cost is file handles invoke: tool: "nika:read" args: { path: "${{ item }}" } # ── the zip · path + text, and the source root comes off ─────────── # `texts` is an array in the SAME ORDER as `files`, so transpose pairs # them index-for-index. `ltrimstr` makes each path relative to the source # root — that stripped path is what the mirror tree is built from. pairs: with: files: ${{ tasks.files.output }} texts: ${{ tasks.texts.output }} invoke: tool: "nika:jq" args: input: ["${{ with.files }}", "${{ with.texts }}"] expression: >- transpose | map({ path: (.[0] | ltrimstr("${{ const.source_root }}/")), text: .[1] }) # ── stage 2 · translate every file, 3 at a time ──────────────────── translated: with: pairs: ${{ tasks.pairs.output }} for_each: ${{ with.pairs }} max_parallel: 3 # rate-limit the provider, not the disk fail_fast: false # finish the batch · one bad file is not a batch failure on_error: recover: null # null holds the index open so the zip below stays aligned infer: max_tokens: 4000 # a doc page, not a book · the cost ceiling is a real number prompt: | Translate to ${{ const.lang }} · keep the markdown structure, leave code blocks untouched, and keep the original tone · ${{ item.text }} # ── the fan-in · drop the files that failed, keep their paths ────── # Second transpose, same law: `translated` is index-aligned with `pairs` # because `recover: null` filled the failures in place. `select(.[1] != # null)` is where a failed translation leaves the batch — by VALUE, after # the fact, not by aborting the fan-out. bundle: with: pairs: ${{ tasks.pairs.output }} translated: ${{ tasks.translated.output }} invoke: tool: "nika:jq" args: input: ["${{ with.pairs }}", "${{ with.translated }}"] expression: >- transpose | map(select(.[1] != null)) | map({ path: .[0].path, text: .[1] }) # ── stage 3 · write the mirror ───────────────────────────────────── mirror: with: bundle: ${{ tasks.bundle.output }} for_each: ${{ with.bundle }} max_parallel: 8 invoke: tool: "nika:write" args: path: "./i18n/${{ const.lang }}/${{ item.path }}" content: "${{ item.text }}" create_dirs: true # the mirror's subdirectories do not exist yetoutputs: translated: value: ${{ tasks.bundle.output }} description: "Every file that made it through, as {path, text} · the mirror's manifest"nika inspect · engine 0.106.1 · vendored graph, never re-derived
7 declared edges · 6 waves · the plan falls out of the bindings