| 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 (a
Swift site generator) and served at 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:
-
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.
-
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.
-
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 (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):
swift run VaporDocs
python3 -m http.server --directory site
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).
- 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.
- 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:
"WebSockets Guide": "WebSocket-Leitfaden",
Any language you skip will show the English title in its sidebar (acceptable,
but the goal is full coverage).
- 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.
- Build & preview with
swift run VaporDocs; switch languages and confirm
the page appears, the sidebar title is translated, and code blocks are intact.
- 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).
- 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.
- 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.
- 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.
- 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).
- Build, preview each affected language, and note AI-translated files in the PR.
Recipe C — Add a completely new language
Example: adding Portuguese (pt).
- 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:
Language(
.portuguese,
siteName: "Documentação do Vapor",
description: "Documentação do Vapor (framework web para Swift).",
navTranslations: [
"Welcome": "Bem-vindo",
"Install": "Instalação",
],
customStrings: [
"tagline": "…",
"skipToContent": "Pular para o conteúdo",
],
image: "assets/og/pt-2x.png",
localisation: .init(
searchPlaceholder: "Buscar",
)
)
- 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.
- 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.
- 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.
- 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.
- Enable the suggestion banner (optional but recommended): add the locale and
native name to the
LOCALES map in
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.
- 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"
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 |