Translate documentation
Create, update, and review translated M-Press Flavoured Markdown without changing code or document structure.
M-Press treats translation as part of the content workflow. It can create translated M-Press Flavoured Markdown with an OpenRouter, OpenAI, or OpenAI-compatible model. The static site does not need the provider or an API key.
M-Press does not charge for the translation workflow, language routing, freshness tracking, glossary checks, or review state. The selected model provider may charge for API usage.
The reader interface is included in the CLI for English, French, German, Portuguese, Russian, Japanese, Korean, Indonesian, Simplified Chinese, and Traditional Chinese. Search, accessibility settings, contribution dialogs, and navigation controls use the page language without a network request. Custom labels remain as written when they do not match a built-in message.
Links in a translated page that point to default-language documentation are rendered as links to the corresponding translated page when it exists. Links to headings use the matching translated heading. Keep heading order and levels aligned with the source so these links remain correct. External links, code examples, and explicit links to another language retain their destinations. The edit-page link opens the source file for the current language.
D2 translation state identifies labels by their node or edge, so adding explicit
labels cannot mix them up during review. If you translated diagrams with M-Press
v1.0.4–v1.0.6, run mpress translate --lang <language> --scope stale after
upgrading. This regenerates diagram labels recorded with the old positional
identifiers. Review the resulting diagrams before publishing.
Numbers immediately before units, including decimals and ranges such as 0.5s,
2-3s, and 150MB, are protected during translation. To regenerate a machine
translation made before v1.0.8, select its page with
mpress translate --lang <language> --file <page.mpd> --scope all and review the
result. A translated paragraph that starts with an @mention remains ordinary
text rather than becoming an MPD directive.
Add target languages
Add every published language to mpress.yaml:
site: defaultLanguage: en languages: [en, fr, ja] languageLabels: en: English fr: Français ja: 日本語 defaultLanguageAtRoot: true missingTranslation: link-to-default1Put language settings in the site section of the mpress.yaml file.
2List the language codes that M-Press must publish. The first build can use only the default language. Add another code when its translated content is ready.
3Set the names that readers see in the language selector.
4Send readers to the default-language page when the selected translation does not exist. Set this value to omit to hide unavailable languages instead.
The default language lives directly in the content directory. M-Press writes other languages to a language directory:
content/├── index.md├── installation.md├── _nav.yaml├── fr/│ ├── index.md│ ├── installation.md│ └── _nav.yaml└── ja/ ├── index.md └── _nav.yaml1The content directory is the default location. Change build.contentDir when the project uses another location.
2Keep default-language pages and navigation directly in the content directory.
3Give each translated language its own directory. Use the same relative paths as the default language so M-Press can match the pages.
4A language directory can contain fewer pages. Here, Japanese does not have installation.md, so the configured missing-translation policy controls what the reader sees.
Missing pages are not copied into the generated site. link-to-default sends a
visitor to the default-language page and marks the translation as unavailable.
omit removes that language from the page selector.
Configure a provider
This example uses OpenRouter:
translation:
provider: openrouter
model: openai/gpt-5.4-mini
baseURL: https://openrouter.ai/api/v1
apiKeyEnv: OPENROUTER_API_KEY
sourceLanguage: en
glossary: glossary.yaml
styleGuide: docs/writing-standard.md
stateDir: .mpress/translations
dataCollection: deny
requireParameters: trueSet the key in the process environment. Do not put it in mpress.yaml:
export OPENROUTER_API_KEY="your-key"
For OpenAI, use provider: openai, https://api.openai.com/v1, and
OPENAI_API_KEY. For another compatible provider, use
provider: openai-compatible and set its base URL and environment-variable
name.
For local-first translation, use an installed coding agent instead of an API key:
translation:
provider: codex # or claude
model: local
command: codex # optional; defaults to codex or claudeM-Press sends the structured request to the command on standard input. The
command must return a JSON object with a translations array containing the
same id values. This works with an authenticated local Codex CLI or Claude
Code installation and keeps documentation content and credentials on the
machine. A custom wrapper command is supported when the local CLI uses a
different invocation.
Override the configured provider for one run when you do not want to edit the project configuration:
mpress translate --lang ja --harness codex
mpress translate --lang ja --harness claudecode
The second command invokes the claude executable used by Claude Code.
Harness selection is temporary. It does not modify mpress.yaml, provider
credentials, or model settings stored in the project.
Select an exact model for one run with --model:
mpress translate --lang ja --harness codex --model gpt-5.6-terra
mpress translate --lang cy --harness claudecode --model claude-opus-5
Codex receives the model through codex exec --model. Claude Code receives it
through claude --model. If --model is absent, the harness uses its own
configured default. Claude Code also accepts aliases such as sonnet and
opus, but a complete model ID gives reproducible translation state.
requireParameters tells OpenRouter to select only a backend that supports the
structured response. dataCollection: deny excludes providers that may store
the supplied documentation.
Model quality varies by language and documentation set. In the development
tool, use the model comparison workflow to translate the same protected sample
with two candidates and choose the better result without seeing their names.
For French and other European languages, start by comparing
mistralai/mistral-small-2603, openai/gpt-5.4-mini, and
google/gemini-3.5-flash-lite. Treat these as candidates, not a permanent
ranking.
Check before you translate
Run a status check. This command does not contact the provider:
mpress translate status --lang fr
mpress translate status --lang fr --file installation.md --json
Estimate the paid work before you start it:
mpress translate --estimate --lang fr
mpress translate --estimate --lang fr --file installation.md --json
The estimate reports the planned page targets, segments, provider requests, and approximate input and output tokens. It exits without contacting the provider and without writing configuration, translated pages, or state.
To include an approximate USD total, record the configured model’s current price per million tokens:
translation:
inputPricePerMillion: 0.25
outputPricePerMillion: 2.00Prices are not built into M-Press because providers can change them. Update these values when you change the model. The estimate is a planning ceiling, not an invoice. Retries and provider-specific tokenisation can change the final charge.
Then translate missing and stale text:
mpress translate --lang fr
mpress translate --lang fr --file installation.md
For a new language, add it to the site configuration and translate the complete site with one command:
mpress translate --lang fr --add-language --label "Français"
This writes the language and reader-facing label to mpress.yaml, translates
every default-language Markdown page plus navigation, and writes the
result under the language directory. The operation is resumable. Repeating the
command translates only missing or stale segments.
M-Press uses four concurrent provider requests by default. For a provider with
higher rate limits, use --workers with a value from 1 to 16:
mpress translate --lang fr --workers 8
Use a lower value when the provider returns rate-limit errors. Each page is validated completely before M-Press replaces its target file and state.
If the project is not checked out on this machine, let M-Press create a local checkout first:
mpress translate \
--repo [email protected]:example/docs.git \
--checkout ../docs-translation \
--branch main \
--lang fr
M-Press uses the existing Git credential helper or gh authentication. It does
not copy a GitHub token into the project configuration. The destination must be
empty, and the cloned repository must contain mpress.yaml.
Use --scope missing to fill gaps only. Use --scope all --force only when you
intend to replace existing machine text and manual conflicts.
If a translated file exists but its translation sidecar is missing or empty,
status reports it as untracked. Updates stop instead of overwriting the
existing text. If the sidecar is in Git history, restore it. If translations
were committed as plain content and never tracked, adopt them as described in
Adopt existing translations. If you deliberately
want a new machine translation, select that single page with --file and use
--scope all --force. Keep a copy of any human corrections for review. A deleted
target file is reported as missing even when its old sidecar still exists.
Contributors can open the local translation workflow with
mpress contribute <page-url> --goal translate. The generated contributor
scripts accept the same option; see Enable site contributions.
The contributor workflow keeps the project’s document format. Translating a Markdown page does not convert the source tree to MPD.
Markdown and MPD pages with local heading links can be updated and refined without treating
translated anchors as manual edits. If the heading structure changes and those
anchors cannot be aligned safely, preserve any manual corrections, regenerate
that page with --file path/to/page.mpd --scope all --force, and review it again.
Audits report each damaged inline segment as protected-content,
including changed quantities, code, or formatting. Targeted refinement can
rebuild those segments from the protected source while retaining the other
translations. A refinement that still damages protected content is rejected
before the target file is replaced.
You can run the same workflow from the Translations item in the development bar. Start with the outcome you need: add a language, translate the current page, update a complete language, or mark the current page as reviewed. M-Press then shows the target, scope, affected pages, and conflict policy before it writes content. Remote writes require the development server authoring token.
What M-Press protects
Markdown translation includes component titles and labels, structured component fields, visible text and accessibility attributes in HTML blocks, nested hero/banner metadata, and D2 labels. Functional identifiers such as input and variant names stay protected. Custom components and labels embedded inside inline HTML still need a manual coverage review.
M-Press parses M-Press Flavoured Markdown into a structured document. It sends exact prose ranges to the provider and patches the returned text into the original bytes. It does not regenerate the source document.
The translator protects:
- fenced and inline code;
- link destinations and automatic links;
- HTML and component declarations;
- component directives, identifiers, escaped characters, metadata references, and unknown emoji shortcodes;
- visible component attributes such as titles, labels, descriptions, and alternative text are translated without exposing their JSON syntax;
- configuration placeholders such as
{name}and${HOME}; - block structure and component field boundaries, while allowing prose paragraphs to reflow and inline placeholders to move;
- navigation links and YAML nesting.
D2 diagram labels are translated using D2’s editing API. Shape identifiers, connections, styles, imports, and code-shaped labels remain protected. Visible text and accessibility labels inside raw HTML are translated; scripts, styles, code, and tag structure remain unchanged. Nested hero and banner metadata is included, so landing-page text is covered too. Navigation labels are safely quoted as YAML strings and can use natural target-language punctuation.
Requests use structured output keyed by a stable segment ID. Long pages are split into bounded requests with the page title, outline, nearby text, style guide, and glossary as context. M-Press rejects incomplete responses and any result that changes protected structure.
Each heading, paragraph, and table cell is one translation unit.
Inline code and other immutable constructs become placeholders inside that
unit. The model can therefore move an inline command to the position required
by the target language without separating it from the sentence. In a
@filetree, paths stay protected and human-readable descriptions are
translated.
Migrate existing translation state
The current sidecar schema is version 2. It identifies the extractor used for
its segment hashes. Old Markdown sidecars and sidecars renamed from .mpd to
.md must be migrated before provider updates. Native MPD and navigation
sidecars with compatible extraction remain readable.
Keep a project snapshot containing the original source files, translated files, configuration, and sidecars together. From the current project, preview migration:
mpress translate migrate-state --from ../original-project --lang fr --json
mpress translate migrate-state --from ../original-project --lang fr --write
Add --file path/to/current.md to select one page. Omit --lang to include
all configured target languages. The default run writes nothing and contacts
no provider. --write changes sidecars only; repeating a completed migration
leaves them unchanged. Do not use a snapshot whose sidecars were already
mechanically renamed: restore the original triplets from Git first.
Migration preserves approvals and provider/model provenance only when the original source and target evidence can be verified. The report distinguishes pre-existing layout differences, stale or missing historical state, unmapped content, and changes since the snapshot. Sidecars retain the original segment records as historical evidence.
A single added standalone Markdown paragraph can retain the surrounding
translations’ history when removing that paragraph restores both the original
source and translated documents exactly. The new paragraph remains
migration-review with reason inserted-content. Duplicate or otherwise
ambiguous matches, multiple insertions, and other layout changes still require
review; ordinal segment IDs alone do not establish identity.
A page containing migration-review segments is blocked from provider updates
and refinement. Compare the page with its source, correct any content or
structure differences, then record your review:
mpress translate review --lang fr --file path/to/current.md
The explicit mpress convert --to markdown --replace and
mpress convert --to mpd --replace commands plan sidecar migration before
writing converted documents or removing their originals. Review the reported
migration requirements before continuing translation work.
translate audit and translate check inspect document content independently
of sidecar freshness. Their success does not approve migrated state.
Adopt existing translations
Translations committed as plain content have no sidecar, so status reports
every segment untracked and M-Press cannot update them incrementally.
mpress translate adopt writes the missing sidecars without touching a single
translated byte:
mpress translate adopt --since 0795c4709 --dry-run
mpress translate adopt --since 0795c4709
mpress translate status
--since is the Git revision the translations were made from (the last commit
before the source changed that they do not reflect). Adopt reads the source page
at that revision, pairs each translated segment with the source segment it came
from, and records the result as the sidecar would have looked then. Segments
whose source is unchanged since the revision become adopted. Segments whose
source changed are recorded against their old text, so status shows them
stale and the next mpress translate run retranslates only those. Paragraphs
inserted after the revision are missing.
Adoption uses the same extraction and structure checks as translate review.
A page whose headings or protected structure do not align with the source at the
revision is refused, listed in the output, and left untracked, and the
command exits with an error. Fix or retranslate those pages; adopt does not
guess. Pages that already have a sidecar are never overwritten, so repeating
the command is safe. Use --lang and --file to narrow the run, and --json
for machine-readable output.
Adopted segments record provider: adopted and the revision in adoptedFrom.
They are not marked reviewed. Default runs and --scope all without --force
leave them alone; only --force regenerates them, and a later person edit
turns them into manual.
Without a known revision, --accept-drift adopts against the current source.
Any source change made after the translation is then accepted silently and will
never be reported stale. Prefer --since.
Commit .mpress/translations/ after adopting. status and build warn when
.gitignore ignores the state directory, because sidecars that are never
committed leave every fresh clone untracked.
Track freshness and human edits
Commit .mpress/translations/ to the repository. Each sidecar file records
source and target hashes, extractor identity, provider, model, prompt version, and review state. It
does not contain an API key.
M-Press reports these states:
| State | Meaning |
|---|---|
missing |
No target text exists. |
untracked |
A target file exists without translation state; its freshness cannot be established. |
migration-required |
The sidecar uses a different extractor; update it before translating. |
migration-review |
Migration could not verify a segment; review the page before updating it. |
machine-translated |
The target matches the last provider output. |
adopted |
The target existed before tracking and was adopted with mpress translate adopt; no one has reviewed it. |
stale |
The source changed after machine translation. |
manual |
A person edited the target while the source stayed unchanged. |
conflict |
Both the source and the manually edited target changed. |
reviewed or final |
A review tool recorded an approved state. |
Normal translation runs preserve manual text and stop short of conflicts. Use
--force only after review.
Mark a reviewed page from the development tool or the CLI:
mpress translate review --lang fr --file installation.md
mpress translate review --lang fr --file installation.md --status final
If the source or target changes later, M-Press replaces the approval with the correct stale, manual, or conflict state.
Add a permanent translationKey to frontmatter when a page may move or change
its filename:
---
title: Install M-Press
translationKey: installation
---M-Press uses this key to move the target and its state without sending unchanged text to the provider again. Keys must be unique across the site.
Use a glossary
Create a YAML glossary when a product or technical term must be consistent:
terms:
- source: M-Press
translations:
fr: M-Press
ja: M-Press
note: Product name. Do not translate it.
- source: development server
translations:
fr: serveur de développementM-Press includes applicable terms in each request. It also rejects a result when a required translation is missing.
Check translations before publishing
Run the local publication gate to require translations of every source page and navigation file in all configured target languages:
mpress translate check --json
Missing, empty, and orphaned translations fail, as do local audit findings,
including warnings. This command calls the Go audit engine directly and does
not invoke a provider or write files. Use --lang fr to check one language.
For reviewed linguistic false positives, pass an exception file with
--exceptions translation/audit-exceptions.json. Exceptions are tied to the
exact source and translated file hashes. See the command-line reference
for the exception format and explicit legacy audit exclusions.
After building, check the rendered pages too:
mpress build --strict
mpress check site --json
Add --cloudflare-pages to enforce the Pages upload size and file-count limits.
Review generated files
Audit a translated language before human review:
mpress translate audit --lang fr
mpress translate audit --lang fr --file installation.md
The audit is local and does not contact a provider. It checks that translated files and segments exist, validates document structure and glossary terms, finds unchanged source prose, and warns when French requirement language may have lost its force. An error gives the command a non-zero exit status, so the same check can run in CI.
Use a separate coding-agent model for an independent semantic review:
mpress translate audit \
--lang fr \
--harness claudecode \
--model claude-opus-5
The reviewer receives paired source and target segments. It cannot change the
files. M-Press accepts findings only when they refer to a real file and segment,
then labels them with an ai- code. This stage can incur model usage charges.
Use a different model family from the translator when you want an independent
assessment.
Translated pages are normal Markdown files. Review and edit them with the same tools as the source pages. Then run:
mpress build --strict
mpress check
The local tool provides translation, state, glossary enforcement, and validation. A hosted product can add shared translation memory, assignments, budgets, pull requests, vendor workflows, and organisation audit history.
Correct links without retranslating prose
A URL-only English correction is not automatically propagated to existing
translations. The normal incremental translator hashes complete segments:
changing a protected URL can make a segment stale, invoke the configured
provider, or conflict with a human translation. --scope missing is not a
URL synchronization command and can reject a reused segment whose URL differs.
Do not use --force to fix links in human translations.
For a revision that changes only simple inline Markdown HTTP(S) link destinations, provide the original English file explicitly:
# Save the English source from before the URL correction.
git show BASE_REVISION:docs/guide.md > /tmp/guide-before.md
# Update the English inline links, then preview one existing translation.
mpress translate sync-links --lang fr --file guide.md \
--previous-source /tmp/guide-before.md
# Apply after inspecting the English diff and the selected destinations.
mpress translate sync-links --lang fr --file guide.md \
--previous-source /tmp/guide-before.md --write
Repeat for each language and page. The command is local and provider-free. It requires the entire English revision to reconstruct exactly from supported link changes. It validates the translation against the old source, applies only the matching destinations, and validates the result against the new source before writing. It preserves translated labels, prose and formatting byte for byte.
It refuses changed prose/layout, ambiguous replacements, human-edited URLs that no longer match the old source, damaged structure and unsupported link syntax. Reference links, autolinks, HTML, code/clone commands and MPD files are outside this narrow helper’s contract and need manual review. Link targets are not checked over the network by this command.
Translation sidecars, manual/reviewed/final statuses, provider provenance and
hash-pinned audit exceptions remain unchanged. The files may consequently be
reported as stale/manual/conflicting; this command does not claim a fresh human
review. Run translate audit/translate check, inspect the diff and handle
remaining findings through the existing review/migration process. Never bulk
refresh exception hashes just to obtain a clean check.