2026-11-06-crowdin-translation-step-2-full

Crowdin translation, step 2 (full translation): implementation plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Translate all 109 pages of the main documentation and all interface text into German, Arabic, Norwegian, French and Italian, with the check script green on every page.

Architecture: The pilot (step 1) gave the site config, the check script, the glossary and 5 pages in each language. This plan first makes the check script strict (a full-coverage mode, a JSON check, a list of out-of-date pages). Then it translates one language for each merge request. Inside one language, subagents translate by topic folder, and each subagent runs the check after each page.

Tech Stack: Docusaurus 3.9.2 with the faster bundler, Node 20 with node:test, the check script docusaurus-site/scripts/i18n/check-translations.mjs, GitLab CI.

Spec: docs/superpowers/specs/2026-11-04-crowdin-translation-design.md, section 8, step 2. Sections 6.1, 6.3.6 and 6.4 apply.

Global Constraints

  • Locales: de, ar, no, fr, it. Translated page path: docusaurus-site/i18n/<locale>/docusaurus-plugin-content-docs/current/<same path as docs/>.
  • The rules for the translator are in docusaurus-site/scripts/i18n/TRANSLATING.md. Every subagent reads them before its first page.
  • Do not translate the terms of scripts/i18n/glossary.json, code, URLs, file and image paths, frontmatter keys, slug, id, sidebar_position, admonition keywords.
  • Keep every link target unchanged. The API reference has its own path in each language (/<locale>/api-reference/hawkeye), since !20(knowledge-base).
  • One merge request for each language into v1.0. Conventional commits, each with the trailer Co-Authored-By: Claude Opus 5.5 <[email protected]>.
  • No Crowdin call in this step. Do not change docusaurus-site/crowdin.yml (step 3 does it).
  • Translation has no fee: Claude Code sessions only. No paid engine, no agency, no Crowdin AI credits.
  • Never search the whole disk. Search only the repo.

Facts on 2026-11-06

ItemValue
English pages109 (about 49,000 words)
Translated pages5 in each language (the pilot)
Pages to translate104 in each language, 520 in total
Largest foldercityscope-previous-platform/: 41 pages, about 20,500 words (42% of the work)
Live languagesall 5 are live on help.staging.vianova.dev since !21(knowledge-base) (user's choice, for a demo)

Rulings (decisions that differ from the spec)

  1. The live site changes. The spec says "the live site shows no change" for step 2. All 5 languages are live on staging since !21(knowledge-base), so each merge request of this plan shows new translated pages on staging. The done condition becomes: the Chrome check passes on staging after the deploy. Cost if wrong: a reader on staging sees a translation that no native speaker has read. The site is staging only, so the risk is low.
  2. Folder cityscope-previous-platform/ goes last in each language. It documents the old platform, and it is 42% of the work. The user can remove it from the scope before Task 5 starts, and no translated work is lost. Open question for the user, below.

Open fields (the user fills them)

FieldValueNeeded by
Translate cityscope-previous-platform/ (41 pages)?open, default: yesTask 5
Native reader, Germanopennot this plan (step 4)
Native reader, Arabicopennot this plan (step 4)
Native reader, Norwegianopennot this plan (step 4)
Native reader, Frenchopennot this plan (step 4)
Native reader, Italianopennot this plan (step 4)

Review Focus

  1. An English page changes on v1.0 while its translation is in work. Expected: the out-of-date list shows the page, and the translator updates it before the merge request merges. Test in Task 1 (staleTranslations).
  2. A page that uses a relative link (../maps/satellite-view.md) or an image with a relative path. Expected: the translated page keeps the same target, and the built page shows the image. Checked by the build-locales job and the Chrome check in Task 4.
  3. A JSON file of i18n/<locale>/ that misses a key that English has (for example a new sidebar category). Expected: the check fails with rule json-missing. Test in Task 1.
  4. A page with a table that has inline code or a link in a cell, in Arabic. Expected: the table reads right to left, and the code stays left to right. Chrome check in Task 4.
  5. A subagent that stops in the middle of a folder. Expected: the coverage report shows the missing pages, and the next run translates only those pages. Test in Task 1 (coverage).

File map

FileResponsibility
docusaurus-site/scripts/i18n/check-translations.mjsAdds --require-all, the JSON check and the coverage line
docusaurus-site/scripts/i18n/stale-translations.mjsLists translated pages whose English page changed after the translation
docusaurus-site/scripts/i18n/*.test.mjsTests of the two scripts
docusaurus-site/scripts/i18n/TRANSLATING.mdAdds the batch method and the out-of-date check
docusaurus-site/i18n/<locale>/docusaurus-plugin-content-docs/current/**The 104 translated pages of each language
docusaurus-site/i18n/<locale>/**/*.jsonInterface text of each language, including the API reference sidebar labels

Task 1: A stricter check script

Files:

  • Modify: docusaurus-site/scripts/i18n/check-translations.mjs
  • Create: docusaurus-site/scripts/i18n/stale-translations.mjs, docusaurus-site/scripts/i18n/stale-translations.test.mjs
  • Modify: docusaurus-site/scripts/i18n/check-translations.test.mjs, docusaurus-site/package.json (script "i18n:stale": "node scripts/i18n/stale-translations.mjs")

Interfaces:

  • Consumes: checkSite(siteDir, locales, glossary) and listPages(dir) of check-translations.mjs.

  • Produces:

    • coverage(siteDir: string, locales: string[]): Promise<Record<string, {translated: number, total: number, missing: string[]}>>.
    • checkJson(siteDir: string, locales: string[]): Promise<Problem[]>. For each JSON file of i18n/en/ (recursive), the file of i18n/<locale>/ must exist and have every key of the English file. Rules: json-missing-file, json-missing. Extra keys pass (Docusaurus ignores them).
    • CLI: node scripts/i18n/check-translations.mjs [--require-all] [--skip-folder <folder>] [locale...]. It always prints one line COVERAGE <locale> <translated>/<total>. With --require-all, each missing page is a problem with rule missing, and the checkJson problems count. Without --require-all, checkJson problems print as WARN lines and do not change the exit code. So the CI job check-translations (no flag, all 5 languages) does not fail on a language that is not started yet. --skip-folder removes one top folder of docs/ from total and from missing (for the open field about cityscope-previous-platform/).
    • staleTranslations(repoDir: string, siteDir: string, locales: string[]): Array<{locale: string, file: string, englishCommit: string}>. A page is out of date when git log -1 --format=%ct -- docs/<file> is newer than the same for the translated file. Use execFileSync('git', [...]), never a shell string.
    • CLI: npm run i18n:stale [locale...] prints <locale> <file> <englishCommit> for each page, or OK no out-of-date page.
  • Step 1: Write the failing tests.

    • coverage counts missing pages: temp dir with docs/a.md, docs/b.md and only a.md in de → {de: {translated: 1, total: 2, missing: ['b.md']}}.
    • require-all reports a missing page: the same dir, CLI with --require-all de → exit code 1, and the output has de b.md missing.
    • json with all keys passes: i18n/en/code.json {"x": {"message": "Hi"}} and i18n/de/code.json {"x": {"message": "Hallo"}} → [].
    • json missing key fails: de/code.json is {} → one problem, rule json-missing, detail has x.
    • json missing file fails: no de/code.json → rule json-missing-file.
    • json gaps only warn without require-all: CLI with de on the dir of the last test → exit code 0, and the output has a WARN line.
    • skip-folder reduces the total: docs/a.md, docs/old/b.md, de/a.md, CLI --require-all --skip-folder old de → COVERAGE de 1/1, exit code 0.
    • stale page is listed: temp git repo (git init, user.email [email protected]), commit docs/a.md and its de copy, then commit a change to docs/a.md with a later date (GIT_COMMITTER_DATE) → one entry, file === 'a.md'.
    • fresh page is not listed: the translated file is committed after the English file → [].
  • Step 2: Run cd docusaurus-site && npm run i18n:test. Expected: the 9 new tests fail (functions not defined).

  • Step 3: Implement the functions and the CLI flags above. The CI job check-translations keeps npm run i18n:check with no --require-all, so a missing page still shows English.

  • Step 4: Run npm run i18n:test. Expected: all tests pass. Run npm run i18n:check. Expected: COVERAGE de 5/109 (and the same for the other 4 languages), then WARN lines for the JSON files that the pilot did not translate (for example the API reference sidebar labels), and OK. Task 3 Step 2 fixes the warnings of each language.

  • Step 5: Run npm run i18n:stale. Expected: a list (maybe empty) of pilot pages whose English page changed after 2026-11-04. Task 3 updates them.

  • Step 6: Commit feat(i18n): report translation coverage, JSON keys and out-of-date pages. Push, open a merge request into v1.0 with /mr-describe, run /review-board, merge through the normal merge path.

Task 2: Translator method for large batches

Files:

  • Modify: docusaurus-site/scripts/i18n/TRANSLATING.md

  • Step 1: Add a section "Large batches" in strict ASD-STE100:

    1. Before a batch, run npm run i18n:stale <locale> and update each listed page first.
    2. Translate one topic folder at a time. After each page, run npm run i18n:check <locale>.
    3. Keep the words of the earlier pages of the same language: read 2 translated pages of the language before the first page of a new folder.
    4. At the end of the language, run npm run i18n:check -- --require-all <locale>. The result must be OK.
    5. Write npx docusaurus write-translations --locale <locale> before the JSON work, so that every new key is in the file. Then translate each message. This covers the sidebar labels of docs, recipes, API reference and changelog (spec 6.1.3).
  • Step 2: Commit docs(i18n): add the method for large translation batches, in the same merge request as Task 1.

Task 3: One language (do this task 5 times)

Order of languages: de, no, ar, fr, it (the clients in Germany and in the Nordic countries wait first, and Arabic needs the most layout checks). One branch and one merge request for each language: feat/i18n-full-<locale>(knowledge-base).

Files:

  • Create: docusaurus-site/i18n/<locale>/docusaurus-plugin-content-docs/current/** (104 pages).
  • Modify: every JSON file of docusaurus-site/i18n/<locale>/.

Interfaces:

  • Consumes: the CLI flags of Task 1, the method of Task 2.

  • Step 1: Run npm run i18n:stale <locale>, and update each listed pilot page.

  • Step 2: Run npx docusaurus write-translations --locale <locale>. Translate every message of every JSON file. Run npm run i18n:check <locale>. Expected: no json- problem.

  • Step 3: Translate the topic folders, in this order: get-started, maps, zones, data-layers, analysis-and-dashboards, export-and-share, regulations-and-reports, shared-mobility, citizen-reporting-app, administration, developers, tools, data-feeds-and-quality, data-products. One subagent for each group of about 10,000 words is allowed. Each subagent gets: the folder list, TRANSLATING.md, the glossary, and 2 translated pages of the language as a word reference. Commit after each folder: feat(i18n): translate <folder> into <language>.

  • Step 4: Folder cityscope-previous-platform/, only when the open field "Translate cityscope-previous-platform/" is yes. Same method.

  • Step 5: Verify. Run npm run i18n:check -- --require-all <locale>. Expected: COVERAGE <locale> 109/109 and OK 109 files checked. Without the old-platform folder, add --skip-folder cityscope-previous-platform: expected COVERAGE <locale> 68/68. Then DOCUSAURUS_NO_PERSISTENT_CACHE=1 npx docusaurus build --locale <locale> --out-dir build. Expected: [SUCCESS], and no new broken-link warning for /<locale>/ compared with the English build.

  • Step 6: Local Chrome check on npm run serve -- --port 3077. Open 5 pages of the language: one from each of get-started, maps, data-products, developers, data-feeds-and-quality. Check: text in the language, images show, tables, code blocks, the sidebar labels, the search box finds a word of the language. For ar, also do the Arabic list of spec section 8. Save a screenshot of each page.

  • Step 7: Push. Open the merge request with /mr-describe; put the coverage line, the check output and the screenshots in it. Wait on CI with GLW_REPO=vianova/knowledge-base gl-wait <pipeline> check-translations build build-locales build-all-locales. Run /review-board, then the normal merge path.

  • Step 8: Staging check after the v1.0 deploy: the same 5 pages on https://help.staging.vianova.dev/<locale>/... return 200 and show the language.

Task 4: All languages done

  • Step 1: Run npm run i18n:check -- --require-all. Expected: 5 COVERAGE lines at 109/109, then OK 545 files checked. Without the old-platform folder: npm run i18n:check -- --require-all --skip-folder cityscope-previous-platform, expected 68/68 and OK 340 files checked.
  • Step 2: Run npm run i18n:stale. Expected: OK no out-of-date page.
  • Step 3: Update the focus with focus park "step 3: Crowdin connection waits for the Crowdin admin".

Done condition (spec section 8, step 2, with ruling 1)

The check script is green with --require-all on all 5 languages, every language merge request is merged, and the staging check of Task 3 Step 8 passed for each language.

Not in this plan

  • The Crowdin project, crowdin.yml, the Crowdin CI jobs and the one-writer check (step 3).
  • The native review (step 4). The 5 reader names stay open until then.

Did this page help you?