Skip to content

Commit c65a026

Browse files
committed
Publish translated docs (zh-CN, ja, ko, pt-BR) and the tool that maintains them
Adds machine-translated documentation sites for Simplified Chinese, Japanese, Korean and Brazilian Portuguese under /zh-CN/, /ja/, /ko/ and /pt-BR/, generated from the English pages, which stay the only source. scripts/docs/translations.py has three commands: status, translate and stage. Pages are split at their sections and only sections whose English changed are re-translated; the rest is carried forward byte-for-byte. Heading ids come from the site renderer and are pinned into the translation, code blocks are re-imposed from the English, and each generated page records the section hashes it reflects in its own front matter. Per-language inputs live in i18n/<lang>/instructions.md and glossary.json; corrections go there, never into the generated pages. The build stages each language (English overlaid with its translations plus a short notice after each page title), takes sidebar titles from the translated pages, links the single English API reference, and builds language sites non-strictly so English-only changes never fail on translations. A manually dispatched workflow refreshes a language and opens a draft PR for its reviewers.
1 parent a4f4ccd commit c65a026

234 files changed

Lines changed: 31709 additions & 49 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: 🌐 Translation problem
2+
description: Report a wrong, awkward, or misleading passage in a translated docs page
3+
labels: ["translation"]
4+
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
The translated docs are machine-generated from the English pages; https://py.sdk.modelcontextprotocol.io/translations/ explains how.
10+
Fixes never go into the translated text directly. They go into that language's glossary or style guide under `i18n/`, so you can also open a PR there instead of an issue.
11+
12+
- type: input
13+
id: language
14+
attributes:
15+
label: Language
16+
description: The language of the translated site, as it appears in the URL.
17+
placeholder: pt-BR
18+
validations:
19+
required: true
20+
21+
- type: input
22+
id: page
23+
attributes:
24+
label: Page URL
25+
description: The translated page where you found the problem.
26+
placeholder: https://py.sdk.modelcontextprotocol.io/pt-BR/servers/tools/
27+
validations:
28+
required: true
29+
30+
- type: textarea
31+
id: passage
32+
attributes:
33+
label: The passage
34+
description: Quote the translated text that's wrong, and the English it corresponds to if you have it.
35+
validations:
36+
required: true
37+
38+
- type: textarea
39+
id: problem
40+
attributes:
41+
label: What's wrong, or how it should read
42+
description: A wrong term, awkward phrasing, meaning that drifted from the English, tone that's off. If you know the better rendering, give it.
43+
validations:
44+
required: true
45+
46+
- type: dropdown
47+
id: native-speaker
48+
attributes:
49+
label: Are you a native or fluent speaker of this language?
50+
options:
51+
- "Yes"
52+
- "No"
53+
validations:
54+
required: true

.github/workflows/deploy-docs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ on:
1212
# docs pages include their code blocks from these files via `--8<--`, so a
1313
# change here changes the rendered site even when no .md file moves.
1414
- docs_src/**
15+
# translated pages and the language registry feed the site/<code>/ sites
16+
- i18n/**
1517
- mkdocs.yml
1618
- src/mcp/**
1719
- src/mcp-types/**

.github/workflows/docs-preview.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ on:
2121
paths:
2222
- docs/**
2323
- docs_src/**
24+
- i18n/**
2425
- mkdocs.yml
2526
- scripts/docs/**
2627
- pyproject.toml

.github/workflows/translate.yml

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
name: Translate Docs
2+
3+
# Refreshes one language's machine-translated docs (i18n/<lang>/) and opens or
4+
# updates a draft PR with the result for that language's reviewers. See
5+
# docs/translations.md and i18n/README.md.
6+
#
7+
# Manual dispatch only. To refresh on a cadence, add a `schedule:` trigger
8+
# (e.g. `- cron: "0 5 1,15 * *"`) with a matrix over i18n/languages.yml once
9+
# someone owns the API budget.
10+
#
11+
# Required configuration:
12+
# - secrets.ANTHROPIC_API_KEY
13+
# - Settings → Actions → General → "Allow GitHub Actions to create and
14+
# approve pull requests"
15+
#
16+
# The PR is opened with GITHUB_TOKEN, so it does not trigger CI by itself;
17+
# close and reopen it (or push to its branch) to run the checks, or pass
18+
# create-pull-request a bot/App token via `token:`. Every refresh puts the PR
19+
# back in draft, since its content changed under any earlier review.
20+
21+
on:
22+
workflow_dispatch:
23+
inputs:
24+
lang:
25+
description: Language code from i18n/languages.yml (e.g. pt-BR)
26+
required: true
27+
type: string
28+
pages:
29+
description: Re-translate exactly these pages in full (space-separated paths under docs/, e.g. "index.md servers/tools.md"); not capped by limit
30+
required: false
31+
type: string
32+
grep:
33+
description: Revise only current pages (and sections) whose English matches this regex, e.g. after a glossary change
34+
required: false
35+
type: string
36+
limit:
37+
description: Maximum number of pages to translate in this run
38+
required: false
39+
default: 15
40+
type: number
41+
42+
permissions: {}
43+
44+
concurrency:
45+
# One run per language at a time; a second dispatch queues rather than
46+
# cancelling a run that is already spending API budget.
47+
group: translate-${{ inputs.lang }}
48+
cancel-in-progress: false
49+
50+
jobs:
51+
translate:
52+
runs-on: ubuntu-latest
53+
permissions:
54+
contents: write # push the translate/<lang> branch
55+
pull-requests: write # open or update the draft PR
56+
steps:
57+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
58+
with:
59+
persist-credentials: false
60+
61+
- name: Install uv
62+
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
63+
with:
64+
enable-cache: true
65+
version: 0.9.5
66+
67+
- name: Install dependencies
68+
run: uv sync --frozen --group translate
69+
70+
# Exit code 1 means some pages failed while the rest were written: those
71+
# still ship in the PR (the body says so) and the run is failed at the
72+
# end. Any other non-zero code (2: configuration or credentials, 3: the
73+
# tool crashed) stops here, before anything is built or proposed.
74+
- name: Translate
75+
id: translate
76+
env:
77+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
78+
LANG_CODE: ${{ inputs.lang }}
79+
PAGES: ${{ inputs.pages }}
80+
GREP: ${{ inputs.grep }}
81+
LIMIT: ${{ inputs.limit }}
82+
# Per-page progress lines reach the log as they happen, not at exit.
83+
PYTHONUNBUFFERED: "1"
84+
run: |
85+
args=(--lang "$LANG_CODE" --limit "$LIMIT")
86+
if [[ -n "$PAGES" ]]; then
87+
read -r -a pages <<< "$PAGES"
88+
args+=(--pages "${pages[@]}")
89+
fi
90+
if [[ -n "$GREP" ]]; then
91+
args+=(--grep "$GREP")
92+
fi
93+
set +e
94+
uv run --frozen --no-sync python scripts/docs/translations.py translate "${args[@]}" 2>&1 \
95+
| tee "$RUNNER_TEMP/translate.log"
96+
exit_code=${PIPESTATUS[0]}
97+
set -e
98+
echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"
99+
case $exit_code in
100+
0 | 1) ;;
101+
*) exit "$exit_code" ;;
102+
esac
103+
104+
# build.sh builds English first, then this language with --strict so a
105+
# dead link or anchor introduced by this refresh fails here, in the run
106+
# that produced it, rather than as a warning in everyone else's builds.
107+
# The translations are already paid for, so a failed build still opens
108+
# the PR (flagged in its body) and fails the run at the end.
109+
- name: Build the language site strictly
110+
id: build
111+
continue-on-error: true
112+
env:
113+
DOCS_LANGUAGES: ${{ inputs.lang }}
114+
DOCS_STRICT_LANGUAGES: "1"
115+
run: bash scripts/docs/build.sh
116+
117+
- name: Compose the pull request
118+
id: compose
119+
env:
120+
LANG_CODE: ${{ inputs.lang }}
121+
TRANSLATE_EXIT_CODE: ${{ steps.translate.outputs.exit_code }}
122+
BUILD_OUTCOME: ${{ steps.build.outcome }}
123+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
124+
run: |
125+
reviewers="$(PYTHONPATH=scripts/docs uv run --frozen --no-sync python -c '
126+
import os, build_config
127+
code = os.environ["LANG_CODE"]
128+
language = next(lang for lang in build_config.load_registry().languages if lang.code == code)
129+
print(",".join(language.reviewers))
130+
')"
131+
echo "reviewers=$reviewers" >> "$GITHUB_OUTPUT"
132+
{
133+
echo "Machine translation refresh for \`$LANG_CODE\`, generated by [this workflow run]($RUN_URL)."
134+
echo
135+
echo "Corrections go in \`i18n/$LANG_CODE/instructions.md\` or \`i18n/$LANG_CODE/glossary.json\`, not in this diff: the pages are regenerated from those inputs (see \`i18n/README.md\`)."
136+
echo
137+
if [[ "$BUILD_OUTCOME" == "failure" ]]; then
138+
echo "> [!CAUTION]"
139+
echo "> The strict build of this language site failed (a dead link or anchor, most likely); see the run log before merging."
140+
echo
141+
fi
142+
if [[ "$TRANSLATE_EXIT_CODE" != "0" ]]; then
143+
echo "> [!WARNING]"
144+
echo "> Some pages failed to translate and keep their previous version:"
145+
echo
146+
echo '```text'
147+
grep '^error:' "$RUNNER_TEMP/translate.log" || echo "(see the run log)"
148+
echo '```'
149+
echo
150+
fi
151+
echo "### Status"
152+
echo
153+
echo '```text'
154+
uv run --frozen --no-sync python scripts/docs/translations.py status --lang "$LANG_CODE"
155+
echo '```'
156+
echo
157+
echo "### Usage"
158+
echo
159+
echo '```text'
160+
grep -i '^usage' "$RUNNER_TEMP/translate.log" || echo "(no usage reported)"
161+
echo '```'
162+
} > "$RUNNER_TEMP/pr-body.md"
163+
164+
- name: Open or update the draft PR
165+
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
166+
with:
167+
branch: translate/${{ inputs.lang }}
168+
delete-branch: true
169+
add-paths: i18n/${{ inputs.lang }}/
170+
commit-message: "i18n(${{ inputs.lang }}): refresh translations"
171+
title: "i18n(${{ inputs.lang }}): refresh translations"
172+
body-path: ${{ runner.temp }}/pr-body.md
173+
labels: translation
174+
reviewers: ${{ steps.compose.outputs.reviewers }}
175+
draft: always-true
176+
177+
- name: Fail the run if some pages failed or the site did not build
178+
if: steps.translate.outputs.exit_code != '0' || steps.build.outcome == 'failure'
179+
env:
180+
TRANSLATE_EXIT_CODE: ${{ steps.translate.outputs.exit_code }}
181+
BUILD_OUTCOME: ${{ steps.build.outcome }}
182+
run: |
183+
echo "::error::translate exit code ${TRANSLATE_EXIT_CODE}, strict build ${BUILD_OUTCOME}; the PR carries what succeeded and says so."
184+
exit 1

.gitignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,13 @@ venv.bak/
144144
# documentation
145145
/site
146146
/.worktrees/
147-
# Generated at build time by scripts/docs/ (the API reference tree and the
148-
# concrete Zensical config spliced from mkdocs.yml).
147+
# Generated at build time by scripts/docs/ (the API reference tree, the
148+
# concrete Zensical configs spliced from mkdocs.yml, and the staged docs tree
149+
# of each translated site).
149150
/docs/api/
150151
/mkdocs.gen.yml
152+
/mkdocs.*.gen.yml
153+
/.build/
151154

152155
# mypy
153156
.mypy_cache/

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,10 @@ pre-commit run --all-files
126126
- Add type hints to all functions
127127
- Include docstrings for public APIs
128128

129+
## Documentation and Translations
130+
131+
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 next translation run overwrites them. 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 the fix carries into every future run. See [`i18n/README.md`](i18n/README.md) for the details.
132+
129133
## Pull Requests
130134

131135
By the time you open a PR, the "what" and "why" should already be settled in an issue. This keeps reviews focused on implementation.

docs/translations.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Translations
2+
3+
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.
4+
5+
## What's available
6+
7+
Translated documentation is currently a **preview** in four languages: Chinese (Simplified), Japanese, Korean and Portuguese (Brazil). Pick one from the language switcher at the top of any page. More languages may follow once these have proved themselves.
8+
9+
The API reference is not translated: the translated site links to the single English one.
10+
11+
## English is the source of truth
12+
13+
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:
14+
15+
- **Machine translation** — the page was translated automatically and links to its English original.
16+
- **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.
17+
- **Shown in English** — there is no current translation of the page, so you are reading the English text.
18+
19+
## How the translations are made
20+
21+
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.
22+
23+
## Reporting a translation problem
24+
25+
Found a wrong term, an awkward sentence, or a translation that says something the English doesn't? [Open a translation issue](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=translation.yaml) with the language, the page and the passage; reports from native speakers are especially valuable, and maintainers track them with the `translation` label. 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.

i18n/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Documentation translations
2+
3+
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.
4+
5+
- `languages.yml` — the registry: one entry per translated site (served at `/<code>/`), the model id, and the nav pages that stay in English.
6+
- `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.
7+
- `<code>/instructions.md` (register, voice, humour, 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.
8+
- `<code>/pages/**` and `<code>/notices.md`**generated**, never edited by hand: the next run overwrites them. A correction goes into that language's `instructions.md` or `glossary.json` (or into the English page), and the affected pages are then re-run.
9+
10+
## The tool
11+
12+
```text
13+
uv run --frozen python scripts/docs/translations.py status [--lang CODE]
14+
uv run --frozen --group translate python scripts/docs/translations.py translate --lang CODE
15+
[--pages PATH ... | --grep REGEX] [--limit N] [--dry-run]
16+
uv run --frozen python scripts/docs/translations.py stage --lang CODE
17+
```
18+
19+
`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), and pages the build will serve in English. `translate` calls the Claude API (`ANTHROPIC_API_KEY` in the environment) for missing and outdated pages, at most `--limit` (default 15) per run; `--pages` re-translates exactly the named pages from scratch (English only, no previous translation shown), `--grep` selects current pages whose English matches and reopens only the matching sections, and `--dry-run` prints the assembled prompts without calling anything. The model is the registry's, or `DOCS_TRANSLATE_MODEL` from the environment to trial another one for a run without editing the registry. `stage` assembles the tree a language site is built from; `scripts/docs/build.sh` runs it for every language.
20+
21+
A page is outdated only when an English section it was translated from has changed; each generated page records the section hashes it reflects (plus a fingerprint of its prompt inputs, for information) in front matter that the build strips. Editing a glossary or instructions file therefore invalidates nothing: apply such a change to existing pages with `translate --grep`, which revises only the matching sections and keeps the rest byte-for-byte, or with `--pages` for a fresh translation of whole pages.
22+
23+
## Adding a language
24+
25+
Add an entry to `languages.yml`, write `<code>/instructions.md` (the six sections the pt-BR file has) and `<code>/glossary.json`, then dispatch the `.github/workflows/translate.yml` workflow for that code: it translates, builds the site strictly, and opens a draft pull request on branch `translate/<code>` with the generated pages, requesting the reviewers named in the registry. Each dispatch starts from `main`, so either pass a `limit` at least the page count `status` reports to fill a language in one run, or merge each refresh pull request before dispatching the next (a second run before the merge redoes the same pages). The workflow needs an `ANTHROPIC_API_KEY` repository secret, and the repository needs a `translation` label, which the issue form and the workflow's pull requests carry.

0 commit comments

Comments
 (0)