# General Translation Knowledge Base: 如何在不破坏语法的前提下翻译 Markdown 和 MDX？
URL: https://generaltranslation.com/zh/kb/translate-markdown-files.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: 翻译正文、图片 alt 文本、frontmatter 中的标题和描述，以及用于显示文本的组件属性。代码块、内联代码、导入语句、JSX 组件名称、frontmatter 键和链接 URL 应保持不变，除非区域设置映射会改写它们。为翻译后的 YAML 值加上引号，然后运行站点构建。
Category: 翻译
Last updated: 2026-10-20
Authors: Kevin Wu, Jackie Chen
Published: 2026-10-20

Markdown 文件中既有正文，也有技术语法。MDX 还通过 `import` 语句、JSX 组件和 `{expressions}` 引入了 JavaScript，这些内容在翻译后仍须能被编译器正确解析。翻译前，先确定哪些字段包含显示文本：组件的 `title` 可能需要翻译，而其 `id` 则必须保持不变。

## Markdown 经过通用翻译器后会出现哪些问题

如果翻译器把文件视为纯文本，就会把其中所有内容都当作自然语言来翻译。需要排查的问题如下：

* **代码块和内联代码。** 命令和标识符可能被翻译，例如 `npm install` 可能变成 `npm installer`。
* **链接 URL 和锚点。** 链接中的 slug 或 `#fragment` 可能被翻译，导致链接失效。翻译后的标题还会生成新的 ID，导致所有指向这些标题的 `#anchor` 链接失效；正因如此，Docusaurus 建议使用[显式标题 ID](https://docusaurus.io/docs/i18n/tutorial#translate-markdown-files)。
* **Frontmatter。** `title` 等键可能被翻译，`slug` 的值可能被改动，翻译后的值也可能破坏 YAML 格式。法语习惯在冒号前加空格，因此 `title: Installer la CLI : guide rapide` 是无效的 YAML (`yaml` 解析器会将其报告为嵌套映射) 。这个值需要加引号。
* **HTML 属性。** `alt` 和 `title` 的文本需要翻译。`class` 及其他标识符应保持不变；`src` 或 `href` 只能通过显式的本地化资源或 URL 映射来更改。
* **MDX 导入和 JSX。** 如果 `import Tabs from '@theme/Tabs'` 被当作句子翻译，或者 `<Tabs>` 变成了 `<Onglets>`，构建就会失败。[Docusaurus Crowdin 指南](https://docusaurus.io/docs/i18n/crowdin#mdx)提醒，把 JSX 当作 HTML 解析的工具可能生成无效的 JSX，其中值为对象或数组的属性最容易出错。
* **提示块 (Admonitions) 。** Docusaurus 只将 `:::note`、`:::tip`、`:::info`、`:::warning` 和 `:::danger` 识别为[关键字](https://docusaurus.io/docs/markdown-features/admonitions)。翻译后的 `:::astuce` 不在其列，因此提示块会被渲染为纯文本。
* **表格。** GFM 表格只有在表头行与分隔行的单元格数量一致时才会被识别，且单元格内的字面竖线必须转义为 `\|` ([GFM 规范](https://github.github.com/gfm/#tables-extension-)) 。如果翻译器在单元格内加入了竖线或换行，该行就会被破坏。

下面是一个源文件，以及一个集齐上述所有问题的输出示例。该示例只是把这些问题集中展示，并非某个特定工具的实际输出。

```mdx
---
title: Install the CLI
slug: /install
---

import Tabs from '@theme/Tabs';

## Install the CLI {/* #install */}

Run `npm install my-cli`, then read the [setup guide](/docs/setup#api-keys).

:::tip
Use <Tabs groupId="pm">a package manager</Tabs> you already have.
:::
```

```mdx
---
titre: Installer la CLI : guide rapide
slug: /installer
---

importer Tabs depuis '@theme/Tabs';

## Installer la CLI {/* #installer */}

Exécutez `npm installer my-cli`, puis lisez le [guide](/docs/configuration#cles-api).

:::astuce
Utilisez <Onglets groupId="gp">un gestionnaire de paquets</Onglets> que vous avez déjà.
:::
```

第二个文件中出现了一系列问题：frontmatter 键被翻译了、YAML 格式无效、slug 被改动、导入语句无法被 MDX 解析、标题 ID 变了、命令被破坏、链接失效、提示块类型无法识别，还引用了一个根本不存在的组件。

## 翻译 Markdown 和 MDX 的方法

### a) Google 或 DeepL 的文档翻译

[Google Cloud Translation](https://docs.cloud.google.com/translate/docs/supported-formats) 的文档翻译功能 (Advanced 版) 支持 Word、PDF、PowerPoint 和 Excel 文件，但不支持 Markdown；其文本翻译功能则支持纯文本或 HTML。[DeepL API](https://developers.deepl.com/docs/api-reference/document) 将 Markdown (`.md`) 文档翻译标注为 beta 功能，且并未列出 MDX。无论选用哪一种，都应先用自己的文件测试输出效果，确认可靠后再交给它处理代码块和 frontmatter。

### b) 使用 AST 将正文与语法分离

[remark](https://github.com/remarkjs/remark) 会将 Markdown 解析为 [mdast](https://github.com/syntax-tree/mdast) 树。下面的提取示例会遍历 `text` 节点，同时跳过代码、属性和 frontmatter。它可以作为检查内容的起点，但并不是一条完整的翻译流水线：

```js
// extract.mjs: npm i unified remark-parse remark-stringify remark-mdx
//   remark-frontmatter remark-gfm remark-directive unist-util-visit
import { readFileSync } from 'node:fs';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';
import remarkMdx from 'remark-mdx';
import remarkDirective from 'remark-directive';
import remarkStringify from 'remark-stringify';
import { visit } from 'unist-util-visit';

const processor = unified()
  .use(remarkParse)
  .use(remarkFrontmatter, ['yaml']) // frontmatter 会被解析为单个 `yaml` 节点
  .use(remarkGfm)                   // 表格
  .use(remarkMdx)                   // import/export 和 JSX
  .use(remarkDirective)             // :::tip 提示框
  .use(remarkStringify);

const tree = processor.parse(readFileSync(process.argv[2], 'utf8'));

// 此演示仅提取 text 节点。在生产环境的翻译器中，属性和
// frontmatter 中的显示文本需要单独处理。
const segments = [];
visit(tree, 'text', (node) => {
  if (node.value.trim()) segments.push(node);
});

console.log(segments.map((n) => JSON.stringify(n.value)).join('\n'));

// 模拟翻译调用：原地替换每个片段。
for (const node of segments) node.value = node.value.toUpperCase();
console.log('\n' + processor.stringify(tree));
```

以上面的源文件为输入，并额外加入一个围栏 `bash` 代码块和一个两列表格，运行 `node extract.mjs guide.mdx` 后输出了以下片段 (节选) ：

```text
"Install the CLI "
"Run "
", then read the "
"setup guide"
"."
"Use "
"a package manager"
" you already have."
```

重新序列化后的文件保留了 frontmatter 内容、import 语句、标题 ID、内联代码、链接目标、提示块名称、JSX 属性和代码块内容，同时将文本节点转换成了大写。不过，它并没有逐字节保留所有格式。我们发现了三个局限：

* **句子被拆得支离破碎。** &quot;Run … then read the … setup guide&quot; 被拆成了五个片段，译者因此无法围绕代码和链接调整语序。生产环境中的脚本会以整段为单位翻译，并用占位符替代内联节点。
* **部分正文不在 `text` 节点中。** 图片的 `alt` 文本、JSX 属性值 (如 `title` 属性) 以及 frontmatter 中 `title` 等字段的值，都需要单独处理。
* **输出会被重新格式化。** `remark-stringify` 重新对齐了表格列宽，因此差异中会出现你并未改动的行。

解析还能尽早暴露语法错误。Mintlify 风格的 `{#install}` 标题后缀会导致 `remark-mdx` 报错 &quot;Could not parse expression with acorn&quot;，因为在 MDX 中，花括号标志着 JavaScript 表达式的开始。Docusaurus 也支持注释形式 `{/* #install */}`，可以通过 `docusaurus write-heading-ids --syntax mdx-comment` 自动写入。

大写替换只能体现解析器选中了哪些文本，并不能衡量翻译质量。在启用翻译流水线之前，请将受保护字段与源文件逐一比对，验证 YAML 和 MDX 语法，并实际运行一次站点构建，以排查失效的导入、链接或框架专用组件。

### c) 翻译管理系统

翻译管理系统 (TMS) 会替你解析文件。Crowdin 原生支持 [Markdown](https://store.crowdin.com/md) 和 [MDX](https://store.crowdin.com/mdx)，其[解析器配置](https://support.crowdin.com/parsers-configuration/)可以排除代码块和指定的 frontmatter 元素；MDX 文件默认按 MDX v1 上传，如需使用 v2，可选择 `mdx_v2` 类型。不过，你仍需映射源路径和输出路径、在 CI 中同步文件，并自行处理被解析器误读的 JSX。

### d) 通过规则提示 LLM

将整个文件交给模型，可以保留段落上下文。请明确需要翻译的显示文本字段，例如正文、图片 alt 文本、frontmatter 中的 `title` 和 `description`，以及指定的组件属性。代码、结构性键、导入语句、JSX 组件名称和 `:::` 关键字须保持不变；URL 只能按照你的区域设置映射进行修改，且 YAML 引号必须合法有效。提示中的规则无法强制执行，因此请解析输出结果、编译 MDX，并将受保护的内容与源文件逐一比对。

### 对比

| 方法                      | 理解 Markdown 结构                        | MDX 和 JSX              | Frontmatter        | 保持译文同步          | 所需投入              |
| ----------------------- | ------------------------------------- | ---------------------- | ------------------ | --------------- | ----------------- |
| Google 或 DeepL 文档翻译     | DeepL 的 `.md` 支持处于 beta 阶段；Google 不支持 | 未说明                    | 未经测试，需自行验证         | 逐个文件重新运行        | 低，另需检查            |
| remark 脚本               | 支持，由你决定处理哪些节点                         | 支持，需配合 `remark-mdx`    | 需自行处理              | 需自行编写变更检测       | 高                 |
| TMS (Crowdin)           | 支持                                    | 原生支持，但官方文档注明了 JSX 相关限制 | 可排除指定的键            | 在 CI 中通过 CLI 同步 | 中                 |
| 基于规则的 LLM               | 仅靠指令约束                                | 仅靠指令约束                 | 仅靠指令约束             | 需自行编写脚本         | 中，另需检查            |
| General Translation CLI | 专门处理 Markdown                         | 专门处理 MDX               | 翻译 Mintlify 的标题和描述 | 重复运行时复用匹配的译文    | 配置源路径、审阅输出、运行站点构建 |

如果只是一次性翻译某个 `.md` 文件并手动审阅，可以使用 DeepL 的 beta 版 Markdown 支持。如果需要完全掌控一小批稳定的文件，可以编写 remark 脚本。如果译者在 TMS 的编辑器中工作，就选用 TMS。如果只需快速生成草稿，再自行比对差异，可以使用 LLM 提示词。对于随版本发布持续变化的文档，请选择 General Translation：它负责生成和复用译文，翻译后的文件则始终保留在你的代码仓库中。

## 如何使用 General Translation 翻译 Markdown 和 MDX

如果 Markdown 和 MDX 文件存放在频繁变更的代码仓库中，可以借助 General Translation 的 CLI `gt` 在每次运行时自动翻译。它能够翻译 [MDX 和 Markdown 文件](/docs/cli/reference/formats/mdx-md-files)，同时保留原有的语法和格式。按照 [CLI 快速入门](/docs/cli/quickstart)运行 [`npx gt init`](/docs/cli/reference/commands/init) 后，在 `gt.config.json` 的 `files` 下添加 `mdx` 或 `md` 条目：

```json title="gt.config.json"
{
  "defaultLocale": "en",
  "locales": ["fr", "ja"],
  "files": {
    "mdx": { "include": ["docs/[locale]/**/*.mdx"] },
    "md": { "include": ["docs/[locale]/**/*.md"] }
  }
}
```

然后运行 [`npx gt translate`](/docs/cli/reference/commands/translate)。CLI 会从 `docs/en/` 读取源文件，并将译文写入 `docs/fr/` 和 `docs/ja/`。对于文档，以下几点行为值得注意：

* **标题 ID。** Mintlify 风格的 `{#id}` 后缀会保留在对应的标题上 (需要 `gt` 2.17.3 或更高版本) 。
* **Frontmatter。** 对于 Mintlify 站点，frontmatter 中的标题和描述也会被翻译。
* **增量运行。** 先前运行中匹配的译文会被复用，只有新增或更改的内容才会被翻译并计费。`--force` 会重新翻译全部内容，并覆盖本地修改。
* **手动修改。** 将修改同步回平台需要主动启用，可使用 [`npx gt translate --save-local`](/docs/cli/reference/commands/translate) 或 [`npx gt save-local`](/docs/cli/reference/commands/save-local)。
* **链接与导入。** 按区域设置重写 URL、导入语句和资源路径目前仍处于实验阶段，需通过 `--experimental-localize-static-urls` 等选项启用。

建议在构建之前[在 CI 中](/docs/cli/guides/generating-translations)运行该命令，将产品术语维护在与应用共享的[词汇表](/docs/platform/dashboard/guides/defining-context-for-translations)中，并在 [Dashboard 编辑器](/docs/platform/dashboard/guides/reviewing-translations)中审校措辞。发布前，请针对每个区域设置运行构建和链接检查。 (如需了解更全面的取舍，请参阅 [General Translation 与 Google Translate 对比](/kb/general-translation-vs-google-translate)以及 [General Translation 与 Crowdin 对比](/kb/general-translation-vs-crowdin)。)

## 要点总结

* 翻译完整的句子以及选定的显示文本字段，包括 alt 文本、frontmatter 值和相关属性。保留技术语法和标识符；URL 仅通过已配置的映射进行重写。
* 法语中的 `" : "` 标点可能会破坏未加引号的 YAML frontmatter，而 `{#id}` 标题后缀会导致普通 MDX 解析器解析失败。
* Google 的文档翻译不支持 Markdown；DeepL 将 `.md` 标为测试版支持，且未列出 MDX。
* remark 示例虽然能暴露文本节点，但完整的流水线还必须保留句子上下文，并处理这些节点之外的显示文本。
* General Translation 的 CLI 在翻译 `md` 和 `mdx` 文件时会保留语法，并在后续运行中复用匹配的译文。

## Related questions

- [如何翻译 i18n JSON 文件？](https://generaltranslation.com/zh/kb/translate-json-files.mdx): 只翻译 JSON 区域设置文件中的字符串值。非复数键、{{name}} 之类的占位符以及标签都应保持不变，但复数键会因语言而异：例如波兰语需要 inbox_few 和 inbox_many。请使用能识别文件格式的工具或 API，然后先解析并验证输出，再进行审校。
- [如何让网站支持多语言？](https://generaltranslation.com/zh/kb/make-website-multilingual.mdx): 先确定译文放在哪里：代理、CMS 插件、建站工具，还是你自己的代码；然后按照 Google 的建议，为每种语言分配独立的 URL。完整翻译页面以及标题和元描述，用 hreflang 关联各语言版本，并提供语言切换器，而不是自动重定向访问者。
- [Google Translate 替代方案：General Translation 与 Google Translate 对比](https://generaltranslation.com/zh/kb/general-translation-vs-google-translate.mdx): Google Translate 适合快速翻译一次性文本，但要以另一种语言发布产品，还需要结合上下文翻译每个字符串、保持术语一致、正确处理变量和复数形式、进行审校，并建立发布流程。General Translation 能理解代码中每个字符串的上下文，将从代码、审校到每一次发布的整个本地化工作流全面自动化。
- [Crowdin 替代方案：General Translation 与 Crowdin 对比](https://generaltranslation.com/zh/kb/general-translation-vs-crowdin.mdx): 如果你希望将文案保留在组件中，并随构建生成译文，请选择 General Translation。Crowdin 围绕资源文件、译者和审校工作流程来管理翻译项目，需要工程师将其接入应用，并随产品迭代持续维护。对于开源社区翻译和由供应商主导的翻译项目，Crowdin 依然是很好的选择。

## Previous and next questions

- Previous question: [如何在不破坏时间轴的情况下翻译 SRT 字幕？](https://generaltranslation.com/zh/kb/translate-srt-subtitles.mdx)
- Next question: [如何找出未翻译的硬编码字符串？](https://generaltranslation.com/zh/kb/find-hardcoded-strings.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/zh/kb/sitemap.md) for every article.
