2026-11-04-crowdin-translation-design
Crowdin translation of the Help Center: design
Date: 2026-11-04 · Status: approved by the user, after a builder check and an adversarial review
1. Why
Local client staff (cities and operators) read the Help Center. Many of them do not read English well. The clients that need translations now are in Germany, in the Nordic countries and in Arabic-speaking countries. Today the site is English only. French and Italian are in scope too (user's decision); the site config already declares them.
2. Goals
- The main documentation is available in 5 languages: German (
de), Arabic (ar), Norwegian (no), French (fr), Italian (it). - Arabic pages show a right-to-left layout that reads correctly.
- A reader selects the language in the language menu of the top bar. Each language has its own URL path, for example
/de/docs/.... - The translations are stored in Crowdin, so that Vianova local teams can correct them in the Crowdin web page, without git.
- The cost stays as low as possible (user's decision).
3. Non-goals
- The changelog (
blog/), the recipes (recipes/), the custom pages and the API reference stay in English (user's decision: option "main documentation only"). - Swedish, Danish and Finnish are out of scope. "Nordic" means Norwegian, the same as in the frontend.
- No paid machine translation engine, no translation agency and no Crowdin AI credits (user's decision: "you translate, we add in Crowdin").
- No change to how editors write: editors write English Markdown only.
- The old root folder
docs/(ReadMe export copy) is not translated. The site builds fromdocusaurus-site/docs/.
4. Anti-goals (the work fails if one of these occurs)
- A translated page has broken Markdown, broken links, missing images or changed code blocks.
- Product names are translated (for example "Cityscope", "VIP", "Streams" become local words).
- Crowdin overwrites the agent translations with empty or English text.
- The English deploy becomes more than 2 minutes slower, or the English deploy fails because of a translation error.
- An Arabic page shows a broken layout (mixed direction in the menus, tables or code blocks).
5. Facts about the current state
| Item | State on 2026-11-04 |
|---|---|
| Pages to translate | 109 files in docusaurus-site/docs/ (108 .md, 1 .mdx). 2 pages contain JSX. No page uses custom heading ids ({#id}) |
| English words | about 49,000 |
Locales in docusaurus.config.ts | en, fr, de, it (no ar, no no) |
| Translated pages | 0. i18n/fr/ holds only JSON files, and these are English copies with old link descriptions |
docusaurus-site/crowdin.yml | Exists. It sends docs, recipes, blog, pages and all JSON. It matches *.md only, not *.mdx |
| CI build | npm run build -- --locale en in job build; job deploy-staging syncs to S3 with --delete --exclude "v2/*" |
| API reference | A third docs instance (api-reference), about 580 generated pages with the interactive @theme/ApiItem |
| Local search | @easyops-cn/docusaurus-search-local with language: ['en'] |
| Crowdin login for the agent | None in the Keychain |
| Crowdin at Vianova | The frontend repo vianova-frontend-v2 uses the Crowdin GitLab integration. Its locales include ar and no |
6. How it works
6.1 First translation
- The agent (Claude Code) translates the 109 pages into the 5 languages. Translation is in Claude Code sessions, so it has no translation fee.
- The agent writes the files to
docusaurus-site/i18n/<locale>/docusaurus-plugin-content-docs/current/<same path>. - The agent translates every JSON file of
i18n/<locale>/(navbar, footer,code.json, sidebar category labels of docs, recipes, API reference and changelog). It first makes them again withnpx docusaurus write-translations --locale <locale>, because the currentfrfiles are English copies. - The agent uses one glossary file for all languages (6.4).
6.2 Crowdin keeps the translations: Crowdin CLI in GitLab CI (user's decision)
The user chose the Crowdin CLI in CI over the Crowdin GitLab integration and over "translations only in Crowdin". This follows the Docusaurus Crowdin guide.
- A Vianova Crowdin admin creates one Crowdin project for the KB, with the target languages
de,ar,no,fr,it. The admin creates a personal token restricted to that project, with the smallest scopes and an expiry date. - The token and the project id go in GitLab CI variables (
CROWDIN_PERSONAL_TOKEN, masked and protected;CROWDIN_PROJECT_ID). The agent gets a copy in the Keychain ascrowdin-token, throughkc-put, and runs the CLI fromdocusaurus-site/withwith-secret kc:crowdin-token CROWDIN_PERSONAL_TOKEN -- npx crowdin .... - First import. The agent runs
crowdin upload sources, thencrowdin upload translations. The translations are imported as not approved, so that the native speaker approves them (6.7). - Job
crowdin-upload. It runs after each merge tov1.0that changes a source file. It runswrite-translations --locale en, thencrowdin upload sources --delete-obsolete. - Job
crowdin-download. It runs once a week (scheduled pipeline) or by hand. It runscrowdin downloadand opens or updates one merge request "Crowdin translations" intov1.0, with a GitLab project access token. - Export setting. Crowdin exports all translations, approved or not. "Export only approved" is off, because it replaces unapproved text with English. The go-live gate is
live-locales.json(6.7). - One writer. After the first import, translation files in
i18n/<locale>/(noten) change in git only through thecrowdin-downloadmerge request. A CI check fails any other merge request that changes these files. Local teams and the agent correct text in Crowdin only. crowdin.ymlsends only/docs/**/*.md,/docs/**/*.mdx(file typemdx_v2, with a fixed parser version) and an explicit list of the JSON files ofi18n/en/. It has no entries for recipes, blog or pages. This change merges before the first import. Before the first import,crowdin upload sources --dryrunproves that each output path is a folder that the build reads, including the Norwegian folder.
6.3 Updates after an English change
- An editor merges an English change. The job
crowdin-uploadsends it to Crowdin. Crowdin marks the changed sentences as not translated. - The agent translates the untranslated sentences in a session: it downloads them with the CLI, translates them, and uploads them as not approved.
- The next
crowdin-downloadmerge request brings the text to the repo. Until a translation exists, the changed sentence shows English text in that language. - A deleted or renamed English page:
--delete-obsoleteremoves the old file in Crowdin, and the next download removes the translated file. - A red
crowdin-downloadmerge request: the agent fixes the text in Crowdin and runs the job again. - Before the Crowdin connection (steps 1 and 2 of section 8), the agent finds English changes with
git diffsince the commit of its translation, and updates the translated files in git.
6.4 Glossary and rules for the translator
- Do not translate: product and brand names (Vianova, Cityscope, VIP, Streams, Hawkeye, and similar), API field names, code, URLs, file paths, image paths, frontmatter keys,
slugandidvalues, admonition keywords (:::note,:::tip). - Translate: frontmatter
title,sidebar_label,description, body text, imagealttext. - Keep the same heading structure. A link to a heading anchor must resolve in the translated page: add an explicit heading id with the English anchor, or update the link.
- Keep the glossary as a file in the repo, so that every session uses the same words. The check script reads it (6.6).
6.5 Site config
- All 6 locales stay in
i18n.localesat all times:en,de,ar,no,fr,it. So every locale can be built and previewed on this machine and in CI. arhasdirection: 'rtl'. Labels: English, Deutsch, العربية, Norsk, Français, Italiano.nois a placeholder code. The builder uses the code that both Docusaurus and Crowdin accept for the same folder (noornb).- The homepage: the agent puts the visible text of
src/pages/index.tsxin<Translate>tags, then runswrite-translations --locale enagain, so that the text goes intocode.json. This is the only code change outside the config and CI. - The translated locales do not build the API reference again. Every API reference link (menu, footer, homepage) goes to
/api-reference/hawkeye. In a translated locale, a small redirect page at/<locale>/api-reference/hawkeyesends the reader to the English page. (Apathname://link does not work: Docusaurus adds the locale prefix to it.) - The local search indexes each language. Each locale searches its own pages.
6.6 Build, deploy and checks
- Job
build(English). It buildsenonly, as today.deploy-stagingneeds this job. - Job
build-locales. It builds the locales listed indocusaurus-site/i18n/live-locales.json.deploy-stagingadds its output to the English build. If it fails, English still deploys, and the pipeline shows the failure. - Job
build-all-locales(merge requests only). It builds all 6 locales and is not deployed. It proves that every locale builds before go-live, and it reports the build time. - Check script. It runs in CI on merge requests only, never on
v1.0. It blocks a merge request that changes translated files or the check; on other merge requests it only warns, because an English edit can make a translation out of date. The English build does not wait for it. For each translated file, it checks against the English file: the file compiles as MDX; same number of headings, code blocks, links and images; same image paths and code text; sameslugandid; every do-not-translate glossary term of the English file is also in the translated file; each link target exists. A missing translated file passes (English shows). A translated file with no English file fails. - Branch rule.
v1.0requires a green pipeline before a merge, so a red Crowdin merge request cannot merge. onBrokenLinksstayswarn. The deploy job keepsaws s3 sync --delete --exclude "v2/*". Each locale is a sub-folder (/de/,/ar/), so no new routing rule is necessary.
6.7 A native speaker reads each language before it goes live (user's decision)
- A language goes on the site only after a Vianova person who speaks it has read and approved the text. The user chose this over "live after the automatic checks".
- The person reads and approves the text in Crowdin, with the Crowdin proofread step.
- A language goes live only when the native speaker has approved 100% of its text in Crowdin.
docusaurus-site/i18n/live-locales.jsonlists the live languages. The language menu shows onlyenand the live languages. A merge request that adds a language to this file is the go-live step.- Before the go-live, the agent checks the language on a local build, in Chrome.
- Rollback. A merge request that removes the language from
live-locales.json. The next deploy deletes/<locale>/from S3, because the sync uses--delete. The user decides a rollback. - The site in this repo is
help.staging.vianova.dev. The CI has no other environment.
7. Cost
| Item | Cost |
|---|---|
| Translation | No fee. Agent session time only: about 49,000 words × 5 languages |
| Crowdin | Hosted words: about 49,000 source words added to the Vianova account. The admin checks the plan limit before the first import |
| CI | 1 extra build job on each merge request (all locales), and 1 extra build job before the deploy (live locales) |
8. Order of work and done conditions
| Step | Work | Done when |
|---|---|---|
| 1. Pilot | Site config for the 6 locales, live-locales.json empty, the 3 build jobs, the check script, the glossary, the homepage <Translate> tags, and 5 pages in each language (one page with tables, images and code; the JSX page) | Check script green. build-all-locales green, and its time is known. The English deploy is at most 2 minutes slower. Chrome check of the pilot pages on a local build, and for Arabic the list below. The live site shows no change |
| 2. Full translation | The other pages, one merge request for each language | Check script green on all 109 pages × 5 languages. The live site shows no change |
| 3. Crowdin connection | The admin creates the project and the token. The agent changes crowdin.yml, adds the jobs crowdin-upload and crowdin-download and the one-writer check, and runs the first import | Dry run paths are correct. An edit in Crowdin comes back in the crowdin-download merge request. No translated file goes back to English or becomes empty |
| 4. Native review | A native speaker proofreads each language in Crowdin | 100% of the language is approved in Crowdin, and the crowdin-download merge request with that text is merged |
| 5. Go-live | One merge request for each language adds it to live-locales.json. Before the first go-live: (a) measure the deploy time with the live languages, and if the English deploy is more than 2 minutes slower, deploy English first and the languages in a second job; (b) make a failed build-locales job on v1.0 visible (a notification), and do not show a language in the menu before its first build passed | The language shows in the menu of help.staging.vianova.dev, and the Chrome check passes on the live site |
| 6. Update flow | A runbook for editors and the agent | One English page changes, the flow runs, and the crowdin-download merge request has the new translation and a green check |
Arabic Chrome check list: top bar and language menu, left sidebar, table of contents on the right, a page with a table, a page with a code block, admonitions, images with captions, the search box and its results, the homepage, a recipe page (English text in the Arabic layout), and the link to the English API reference.
9. Left to the builder
- How to split the translation work between agents (by language or by topic folder).
- How the check script parses MDX and counts the elements.
- How
deploy-stagingmerges the outputs ofbuildandbuild-locales.
10. Open items for the user or a Crowdin admin
- Who is the Crowdin admin who creates the KB project and the token, and does the Crowdin plan have space for about 49,000 more words. Step 3 needs this.
- Who is the native speaker for each language (German, Arabic, Norwegian, French, Italian). Step 4 needs these names.
11. Sources
- Docusaurus, "i18n - Using Crowdin": https://docusaurus.io/docs/i18n/crowdin
- Crowdin, "Export settings": https://support.crowdin.com/project-settings/export/
- Crowdin, "Uploading existing translations": https://support.crowdin.com/uploading-translations/
- Crowdin store, "MDX": https://store.crowdin.com/mdx
Updated about 23 hours ago
