Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ on:
# docs pages include their code blocks from these files via `--8<--`, so a
# change here changes the rendered site even when no .md file moves.
- docs_src/**
# translated pages and the language registry feed the site/<code>/ sites
- i18n/**
- mkdocs.yml
- src/mcp/**
- src/mcp-types/**
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docs-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ on:
paths:
- docs/**
- docs_src/**
- i18n/**
- mkdocs.yml
- scripts/docs/**
- pyproject.toml
Expand Down
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,13 @@ venv.bak/
# documentation
/site
/.worktrees/
# Generated at build time by scripts/docs/ (the API reference tree and the
# concrete Zensical config spliced from mkdocs.yml).
# Generated at build time by scripts/docs/ (the API reference tree, the
# concrete Zensical configs spliced from mkdocs.yml, and the staged docs tree
# of each translated site).
/docs/api/
/mkdocs.gen.yml
/mkdocs.*.gen.yml
/.build/

# mypy
.mypy_cache/
Expand Down
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ pre-commit run --all-files
- Add type hints to all functions
- Include docstrings for public APIs

## Documentation and Translations

Documentation contributions are English only: the pages under `docs/` are the source of truth, and the translated documentation sites are generated from them, guided by the per-language style guides and glossaries under `i18n/<lang>/`. Never edit the generated pages under `i18n/<lang>/pages/`—the tool can't tell a hand edit from its own output, so the edit persists unchecked, is carried forward into future runs, and hides the real fix. To fix a translation, change that language's `instructions.md` or `glossary.json` (or the English page, if that's where the problem is) and re-run `translate --pages` for the affected pages; the fix then carries into every future run. See [`i18n/README.md`](i18n/README.md) for the details.
Comment thread
maxisbey marked this conversation as resolved.
Resolved

## Pull Requests

By the time you open a PR, the "what" and "why" should already be settled in an issue. This keeps reviews focused on implementation.
Expand Down
31 changes: 31 additions & 0 deletions docs/js/language-switch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// The theme links each language-switcher entry to that language's home page.
// Point the entries at the current page on each language's site instead: every
// prose page exists at the same path on all of them. The API reference is
// English-only, so from there the entries keep pointing at the site roots.
// Instant navigation swaps the page but keeps the header, so re-run on every
// page the theme loads (`document$`) rather than once.
const base = JSON.parse(document.getElementById("__config").textContent).base;
// The site root as a directory path; `base` lacks the trailing slash on 404 pages.
const site = new URL(base.replace(/\/?$/, "/"), location).pathname;
const entries = ".md-select__link[hreflang]";

function samePage(entry) {
const page = location.pathname.slice(site.length);
return entry.dataset.site + (page.startsWith("api/") ? "" : page);
}

document$.subscribe(() => {
for (const entry of document.querySelectorAll(entries)) {
entry.dataset.site ??= entry.getAttribute("href"); // the language root the theme rendered
entry.href = samePage(entry);
}
});
Comment thread
maxisbey marked this conversation as resolved.
Resolved
Comment thread
maxisbey marked this conversation as resolved.
Resolved

// Headings carry the same ids on every site, so the reader's place carries over
// too: query and fragment as they are when the switch happens, not at page load.
function aim(event) {
const entry = event.target instanceof Element ? event.target.closest(entries) : null;
if (entry?.dataset.site && (event.type !== "keydown" || event.key === "Enter"))
entry.href = samePage(entry) + location.search + location.hash;
}
for (const type of ["click", "auxclick", "keydown"]) document.addEventListener(type, aim, true);

Check warning on line 31 in docs/js/language-switch.js

View check run for this annotation

Claude / Claude Code Review

Language switcher carries the API page fragment/query onto the language-site home page

The click-time href enrichment in aim() appends location.search + location.hash unconditionally, but samePage() deliberately maps api/ pages to the bare language root (the API reference is English-only) — so switching language from an API deep link like /api/mcp/index/#mcp.ClientSession sends the reader to /de/#mcp.ClientSession, the language home page with a dangling fragment (and any ?h= highlight query carried along). Fix: skip the append when the current page is under api/, e.g. guard on !lo
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
25 changes: 25 additions & 0 deletions docs/translations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Translations

This documentation is written in English. To make it useful to more people, we also publish machine-translated editions of it, and this page explains what that means for you and how to help improve them.

## What's available

Translated documentation is currently a **preview** in twelve languages: Deutsch, español, français, हिन्दी, 日本語, 한국어, português (Brasil), русский язык, Türkçe, українська мова, 简体中文 and 繁體中文. Pick one from the language switcher at the top of any page. More languages may follow once these have proved themselves.

The API reference is not translated: the translated site links to the single English one.

## English is the source of truth

If a translated page and its English original disagree, the English page is correct. Every page of a translated site opens with one of three notes saying where it stands:

- **Machine translation** — the page was translated automatically and links to its English original.
- **Translation behind the English page** — the English original changed after the page was translated, so parts of it may be out of date until the translation catches up.
Comment thread
maxisbey marked this conversation as resolved.
Resolved
- **Shown in English** — there is no current translation of the page, so you are reading the English text.
Comment thread
maxisbey marked this conversation as resolved.
Resolved

## How the translations are made

Translated pages are machine-generated by a tool in this repository from the English pages under `docs/`, guided by two human-written inputs per language: a style guide (register, tone, typography, how to handle jokes and idioms) and a glossary (which terms stay in English, and the required and forbidden renderings for the rest). The generated text is never edited by hand. Every improvement goes into those inputs instead, so it survives the next time the pages are regenerated.
Comment thread
maxisbey marked this conversation as resolved.
Resolved

## Reporting a translation problem

Found a wrong term, an awkward sentence, or a translation that says something the English doesn't? [Open an issue](https://github.com/modelcontextprotocol/python-sdk/issues) with the language, the page and the passage; reports from native speakers are especially valuable. If you know the fix, propose it directly as a pull request against that language's style guide (`instructions.md`) or glossary (`glossary.json`) under [`i18n/`](https://github.com/modelcontextprotocol/python-sdk/tree/main/i18n) — the correction then reaches every affected page the next time the translations are regenerated. Problems with the English text itself are fixed in the pages under `docs/`, like any other documentation change.
20 changes: 20 additions & 0 deletions i18n/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Documentation translations

The English pages under `docs/` are the source. This directory holds what steers their machine translation and the generated result; [`docs/translations.md`](../docs/translations.md) is the reader-facing explanation.

- `languages.yml` — the registry: one entry per translated site (served at `/<code>/`), the model id, and the nav pages that stay in English.
Comment thread
maxisbey marked this conversation as resolved.
Resolved
- `general-prompt.md` — translation rules shared by every language. `notices.md` — English source of the three notes staged onto the pages of a translated site.
Comment thread
maxisbey marked this conversation as resolved.
Resolved
- `<code>/instructions.md` (register, voice, typography, terminology) and `<code>/glossary.json` (`keep`: terms that stay in English; `terms`: required renderings, each with an optional `note` and banned `avoid` renderings, which are checked) — human-authored, sent with every request.

Check warning on line 7 in i18n/README.md

View check run for this annotation

Claude / Claude Code Review

load_glossary accepts a bare string for avoid/keep, exploding it into per-character banned renderings that fail every page of a paid run

load_glossary silently accepts a bare string for a term's `avoid` (or the top-level `keep`) — e.g. `"avoid": "Gastgeber"` instead of `["Gastgeber"]` — because a str satisfies `Sequence[str]` by iterating its characters, so every letter becomes a banned rendering and every page of the next paid translate run fails validation with baffling per-letter findings. Add an isinstance shape check in load_glossary so this raises ConfigError (exit 2, before any API call), as its docstring already promises.
Comment thread
claude[bot] marked this conversation as resolved.
Resolved
- `<code>/pages/**` and `<code>/notices.md` — **generated**, never edited by hand: a correction goes into that language's `instructions.md` or `glossary.json` (or the English page), and the affected pages are re-run.
Comment thread
maxisbey marked this conversation as resolved.
Resolved

## The tool

```text
uv run --frozen python scripts/docs/translations.py status [--lang CODE]
uv run --frozen --group translate python scripts/docs/translations.py translate --lang CODE [--pages PATH ...]
Comment thread
maxisbey marked this conversation as resolved.
Resolved
uv run --frozen python scripts/docs/translations.py stage [--lang CODE]
```

Check failure on line 16 in i18n/README.md

View check run for this annotation

Claude / Claude Code Review

serve() still cross-wires anchors/fences when an English change both edits and reorders sections

The 00e9565 fix for reorder cross-wiring covers only serve()'s changed==[] branch (scripts/docs/translations.py:944-948): when one English commit both edits a section and reorders others, the changed-non-empty branch still lays the stored translation out in its old positional order, so reimpose() pins today's heading ids (and copies fences) onto the wrong sections, and the staged site serves cross-wired anchors on all 12 language sites until someone runs translate. Fix: in the changed-non-empty
Comment thread
maxisbey marked this conversation as resolved.
Resolved
Comment thread
claude[bot] marked this conversation as resolved.
Resolved

`status` is offline: per language it lists missing, outdated (with the sections that changed), current and removable pages (translations whose English page is gone — `git rm` them). `translate` calls the Claude API (`ANTHROPIC_API_KEY` in the environment; the registry's model, or `DOCS_TRANSLATE_MODEL` to trial another) for the missing and outdated pages, retranslating only the English sections that changed and keeping the rest byte for byte; `--pages` instead re-translates exactly the named pages from scratch, which is also how a glossary or instructions change reaches existing pages (each generated page records the English section hashes it reflects, so editing those inputs invalidates nothing). `stage` assembles the tree each language site is built from (every language's, or one with `--lang`); `scripts/docs/build.sh` runs it before building them. Commit the generated pages in an ordinary pull request.

Check warning on line 18 in i18n/README.md

View check run for this annotation

Claude / Claude Code Review

Surplus trailing newlines in a model reply are baked in and surface as mid-page blank-line drift

unwrap() in scripts/docs/translations.py (lines 682-689) appends a missing trailing newline to a model reply but never trims a surplus one, and no later validator inspects blank-line structure, so a reply ending with an extra newline is written and recorded as current under the English section hashes. When a later English edit appends or reorders sections, carry_forward()/serve() splice the stored last section into a middle position and its surplus newline becomes a doubled blank line mid-page,
Comment thread
claude[bot] marked this conversation as resolved.
Resolved

To add a language, add an entry to `languages.yml`, write `<code>/instructions.md` (the sections the `pt` file has) and `<code>/glossary.json`, then run `translate --lang <code>`.
Loading
Loading