---
name: docs-translations
description: >-
  Explains how localisation works for the Vapor docs site (docs.vapor.codes) and
  how to replicate a docs change across every language, optionally using AI to do
  the translations. Use when adding a new page or section, adding or editing a
  part of an existing page, or adding a completely new language — and you want the
  change reflected in all supported languages rather than only English.
---

# Vapor Docs Translations

The Vapor docs are generated by [Kiln](https://github.com/brokenhandsio/kiln) (a
Swift site generator) and served at [docs.vapor.codes](https://docs.vapor.codes).
Every page can exist in multiple languages. This skill explains the moving parts
and gives step-by-step recipes — including how to safely use AI for the actual
translation — for the three common changes: **adding a page/section**, **editing
part of a page**, and **adding a new language**.

## How localisation works (the mental model)

There are three separate layers, and a full change usually touches all three:

1. **Content** — the Markdown under `docs/<version>/…` (current version is `4.0`).
   - English/default: `install/macos.md`
   - A translation: `install/macos.<lang>.md`, e.g. `install/macos.ja.md`
   - `<lang>` is the language's **code** (`de`, `es`, `fr`, `it`, `ja`, `ko`,
     `nl`, `pl`, `zh`, `ar`). It matches the `LanguageCode` in `Languages.swift`,
     **not** necessarily the OG-image name (Chinese files are `.zh.md` but its
     social card is `zh-Hans-2x.png`).
   - **Fallback is automatic:** if `foo.<lang>.md` doesn't exist, Kiln serves the
     English `foo.md` for that language. So partial translations are fine and
     nothing 404s.

2. **Navigation titles** — the sidebar tree lives in
   `Sources/VaporDocs/Version4.swift` as `Page("Title", "path.md")` and
   `Section("Title") { … }`, always with **English** titles. Each language
   translates those titles in `Sources/VaporDocs/Languages.swift` via
   `navTranslations`, keyed by the exact English title. A key that's missing falls
   back to the English title.

3. **UI chrome** — search box, prev/next, footer, language/theme pickers, error
   pages. Translated per language in `Languages.swift` through `customStrings`
   (docs-specific navbar/footer strings, `#localise("key")` in templates) and
   `localisation:` (Kiln's built-in strings). Anything unset falls back to English.

Plus two things outside the generator:

- **OG/social card** — a per-language `1200×630` (ideally 2×, `2400×1260`) PNG at
  `docs/4.0/assets/og/<code>-2x.png`, referenced by `image:` on the `Language`.
- **Language-suggestion banner** — the "this site is available in …" banner is
  driven by the `LOCALES` map in the shared design repo
  [vapor/design](https://github.com/vapor/design) (`src/js/languageSuggestion.js`).
  A new language works without it; the banner just won't proactively suggest it.

### Supported languages (as of writing)

German `de`, Spanish `es`, French `fr`, Italian `it`, Japanese `ja`, Korean `ko`,
Dutch `nl`, Polish `pl`, Chinese `zh`, Arabic `ar` (RTL). English `en` is the
default/fallback. Always re-derive the current list from the `languages` array in
`Sources/VaporDocs/Languages.swift` rather than trusting this line.

## Build & preview

Requires a Swift 6.2+ toolchain (no Python needed to build):

```sh
swift run VaporDocs                        # writes the site to ./site
python3 -m http.server --directory site    # preview at http://localhost:8000
```

Or `docker compose up` (serves on port 8000). CI builds the site on every PR with
`swift run VaporDocs`, so a change that doesn't compile (e.g. a broken
`Languages.swift`) will fail the check.

## Using AI to do the translations (rules that always apply)

When you translate a Markdown file with AI, translate the **prose** and leave the
**structure and code** byte-for-byte intact. Concretely:

- **DO translate:** paragraph text; headings; list items; table cell text; link
  text; image `alt` text; blockquote text; the `title:` and `description:` fields
  in the YAML frontmatter; and the *custom title* of an admonition
  (`!!! note "My title"` → translate `My title`).
- **DO NOT translate / must stay identical:**
  - Contents of fenced code blocks (```` ``` ````) and inline `` `code` `` —
    including comments unless they're plainly explanatory prose the reader needs.
  - The admonition/type keyword itself: `!!! note`, `!!! warning`, `!!! tip`,
    `!!! seealso` stay in English (Kiln maps the keyword; only the optional
    quoted title is translated).
  - URLs, file paths, API/type/method names, Swift keywords, package names.
  - Markdown structure: heading levels (`#` count), list markers, table pipes,
    the frontmatter `---` fences and its keys.
  - HTML tags/attributes and their `class`/`id` values (e.g. `class="kiln-center"`).
  - Anchor slugs — if a link points to `#some-heading`, keep the original English
    anchor unless you also verify the anchor generated for the translated heading.
- **Match an existing translation's voice.** Before translating for a language,
  open an existing `*.<lang>.md` file and mirror its terminology and formality.
  Reuse the term choices already in that language's `navTranslations` (e.g. don't
  invent a new word for "Routing" if the nav already picks one).
- **RTL languages (Arabic):** translate content normally — Kiln handles direction
  via `isRTL` on the `.arabic` code. Keep code blocks and inline code LTR (they
  are by default). Arabic in this repo is flagged as a first pass pending
  native-speaker review; mark AI output the same way.
- **Never fabricate.** If unsure of a term, leave the English word and flag it in
  the PR rather than guessing.

> A machine translation should be labelled as such in the PR and, ideally, get a
> native-speaker review before merge. AI gets you a complete, reviewable draft —
> it is not a substitute for the `@vapor/translators` review.

---

## Recipe A — Add a new page or a new section

Say you added `docs/4.0/basics/websockets-guide.md` (English).

1. **Register it in the nav** (`Sources/VaporDocs/Version4.swift`): add a
   `Page("WebSockets Guide", "basics/websockets-guide.md")` in the right
   `Section`, or add a whole new `Section("New Area") { Page(…) }`. Use the
   English title.
2. **Add nav title translations** (`Sources/VaporDocs/Languages.swift`): for
   **every** language, add the new English title(s) to its `navTranslations` map,
   keyed by the exact English string:
   ```swift
   "WebSockets Guide": "WebSocket-Leitfaden",   // in the German block
   ```
   Any language you skip will show the English title in its sidebar (acceptable,
   but the goal is full coverage).
3. **Translate the page** for each language into
   `basics/websockets-guide.<lang>.md`, following the AI rules above. Copy the
   English file, then translate in place so structure is preserved. Files you omit
   fall back to English automatically.
4. **Build & preview** with `swift run VaporDocs`; switch languages and confirm
   the page appears, the sidebar title is translated, and code blocks are intact.
5. **Track the work** — a docs-changing PR normally spawns a translation-needed
   issue for `@vapor/translators`. If you already included AI drafts for all
   languages, note that in the PR; tag translation-only follow-up PRs with the
   `translation-update` label so they don't spawn a new issue.

## Recipe B — Add or change part of an existing page

Say you added a section to `docs/4.0/basics/routing.md` (English).

1. **Apply the same edit to each translation** `basics/routing.<lang>.md` that
   exists. For each one, translate **only the new/changed portion** and splice it
   into the same structural location, keeping the surrounding translated text as-is.
2. **Watch for drift:** if a translated file is now shorter/older than English in
   other places, don't silently rewrite the whole file — scope your change to what
   actually changed in English, unless you're deliberately doing a full refresh.
3. **New headings?** If your edit adds a heading and something links to it via an
   anchor, verify anchors in each language (translated headings produce different
   slugs). Prefer stable link targets.
4. If a translation for that page **doesn't exist yet**, you don't have to create
   it just for this change — English fallback covers it. Creating it is a bonus
   (that's Recipe A step 3 for a single language).
5. **Build, preview each affected language, and note AI-translated files in the PR.**

## Recipe C — Add a completely new language

Example: adding Portuguese (`pt`).

1. **Add a `Language` entry** to the `languages` array in
   `Sources/VaporDocs/Languages.swift`. Copy an existing complete block (German is
   a good, fully-populated template) and translate every value:
   ```swift
   Language(
       .portuguese,                 // built-in LanguageCode, OR .custom(code: "xx", name: "…")
       siteName: "Documentação do Vapor",
       description: "Documentação do Vapor (framework web para Swift).",
       navTranslations: [
           // every English nav title from Version4.swift → Portuguese
           "Welcome": "Bem-vindo",
           "Install": "Instalação",
           // …
       ],
       customStrings: [
           // copy the keys from the English block at the top of the file;
           // any you leave out fall back to English
           "tagline": "…",
           "skipToContent": "Pular para o conteúdo",
           // …
       ],
       image: "assets/og/pt-2x.png",
       localisation: .init(
           searchPlaceholder: "Buscar",
           // … other built-in UI strings; unset ones fall back to English
       )
   )
   ```
   - Use a built-in `LanguageCode` if one exists; otherwise
     `.custom(code: "pt", name: "Português")`. The `code` becomes the Markdown file
     suffix (`*.pt.md`).
   - For an RTL language, a built-in code like `.arabic` sets `isRTL`
     automatically; for a custom RTL code, check Kiln's `LanguageCode` API.
2. **Add the OG card:** drop a `1200×630` (ideally `2400×1260`) PNG at
   `docs/4.0/assets/og/pt-2x.png` and point `image:` at it. Omit it and the
   language falls back to the site-wide card.
3. **Translate content.** You don't need every page at once — fallback handles
   gaps — but at minimum do the high-traffic pages (`index.md`, `install/*`,
   `getting-started/*`). Create `<path>.pt.md` next to each English file, following
   the AI translation rules. A good AI-assisted flow: iterate over the English
   `.md` files (those without a language suffix), translate each into `.pt.md`,
   and skip ones you're intentionally deferring.
4. **Register in the translation issue template:** add the language to
   `.github/translation_needed.description.leaf` (the `Languages:` checklist) so
   future doc changes flag it for translation.
5. **Enable the suggestion banner (optional but recommended):** add the locale and
   native name to the `LOCALES` map in
   [vapor/design](https://github.com/vapor/design) → `src/js/languageSuggestion.js`.
   Without this the language is fully usable; it just won't be proactively
   suggested to visitors in that locale.
6. **Build & preview:** `swift run VaporDocs`, then check the language appears in
   the picker, pages render, untranslated pages fall back cleanly, and (for RTL)
   layout direction is correct.

---

## Quick checklist for "I changed the docs — cover all languages"

- [ ] English content added/edited under `docs/4.0/…`
- [ ] New nav entries added to `Version4.swift` (English titles)
- [ ] `navTranslations` updated for **every** language in `Languages.swift`
- [ ] Translated `*.<lang>.md` created/updated (AI drafts OK, labelled as such)
- [ ] Code blocks, links, HTML, admonition keywords left intact in translations
- [ ] Frontmatter `title`/`description` translated
- [ ] (New language only) `Language` entry, OG card, issue template, design-repo `LOCALES`
- [ ] `swift run VaporDocs` builds; previewed the affected languages
- [ ] PR notes which files are machine-translated; translation-only PRs tagged `translation-update`

## Key files reference

| What | Where |
|------|-------|
| English/source content | `docs/4.0/<path>.md` |
| A translation | `docs/4.0/<path>.<lang>.md` |
| Nav tree (English titles) | `Sources/VaporDocs/Version4.swift` |
| Per-language nav titles, UI strings, OG image | `Sources/VaporDocs/Languages.swift` |
| Social/OG cards | `docs/4.0/assets/og/<code>-2x.png` |
| Translation issue template | `.github/translation_needed.description.leaf` |
| Language-suggestion banner | `vapor/design` → `src/js/languageSuggestion.js` |
| Build command | `swift run VaporDocs` → `./site` |
