Skip to main content

Translation pipeline notes

How the 12 target locales are produced, and the one rule that has to live outside this repository.

Where translation is configured

That last row is the important one. npx lingo.dev@latest run with LINGO_API_KEY set routes every string through our localization engine on the Lingo.dev platform. The engine applies its layers in order:
  1. Glossary (highest): exact term mappings
  2. Rules: locale-scoped linguistic conventions
  3. Brand voice: tone, formality, register
  4. Model chain: which model handles each locale pair, with fallbacks
  5. AI reviewers: asynchronous quality checks
None of these are expressible in i18n.json. The config schema (version 1.10) has no field for instructions, glossaries or brand voice; its only prompt-ish field is provider.prompt, which applies solely to a bring-your-own-key provider (openai, anthropic, google, ollama, openrouter, mistral). Adding a provider block would take us off the engine and lose the glossary, brand voices and translation memory along with it. Do not do that to get at the prompt.

The no-dashes rule

House style bans em dashes (U+2014) and en dashes (U+2013) everywhere. English is clean, but the engine reintroduces dashes while translating: as of 2026-09-16 a sweep removed 1543 of them across 545 files in all 12 locales. Sweeping the locale files fixes today’s output only. The locale directories are generated, so the next time the English source of a string changes, the engine retranslates it and can put the dash straight back. The durable fix is a rule on the engine. Add it as a rule (the “locale-scoped linguistic conventions” layer), applied to every target locale, not as a glossary entry, because a glossary maps terms and this is a punctuation convention. Suggested wording:
Never use an em dash (U+2014, —) or an en dash (U+2013, –) in any output. This applies to body text, headings, link text, image alt text, table cells and YAML frontmatter values alike. Where the source or a natural translation would use one, rephrase using the target language’s own conventions: a comma, a colon, parentheses, a conjunction, or two sentences. Never substitute an ASCII hyphen (-) for a dash. Numeric and date ranges are spelled out with the target language’s word for “to” rather than a dash.

How to apply it

The engine is configured through the Lingo.dev MCP server, which can create and update rules, glossaries and brand voices conversationally, or through the Lingo.dev dashboard. The MCP server is not wired into this repository’s tooling, so this is a one-time manual step by someone with access to the Scribe organization on Lingo.dev. Ask the assistant to “add a rule to every locale on the Scribe engine: never emit em dashes or en dashes, rephrase instead”, and paste the wording above. Caveat worth keeping in mind: an engine rule steers a language model, it does not hard-guarantee a character never appears. That is why the repo also keeps a checker.

The guard

scripts/check-dashes.mjs runs last in translate.yml, with if: always().
  • en/** and the other hand-written pages: hard failure. English is authored by people, so a dash there is a real style bug with an owner.
  • The 12 locale directories: report only. Blocking would be a deadlock. The commit step in translate.yml only runs when the preceding steps succeed, so failing on a generated file means the refreshed translations are never committed, and since the dashes come back from the engine, that failure repeats every run and freezes all 12 locales on an older build. Locale hits become GitHub warning annotations instead.
Run node scripts/check-dashes.mjs --strict locally to fail on locales too, which is what you want when doing a deliberate sweep. scripts/check-mdx.mjs additionally parses each page’s YAML frontmatter. The MDX compiler treats the --- block as opaque, so a page with unreadable frontmatter compiles fine here and then fails the Mintlify build. The specific trap when removing dashes: replacing a dash with ": " inside an unquoted plain scalar turns description: Install in one click: do this into a mapping value nested in a mapping value, which is a YAML error. Use a comma, or quote the whole scalar.

Before committing anything that touches translations

Mintlify rejects an entire deploy on one bad page, and a failed build strands that commit’s files permanently, because later builds only carry their own diff.