BookTranslator
BookTranslator

Как переводить файлы Markdown, не ломая код, ссылки и front matter

Что переводить в файле Markdown или MDX, что оставить без изменений и как проверить результат. С примером файла, бесплатной проверкой и тестом локальной LLM из 9 запусков.

Чтобы перевести файл Markdown, переведите текст и оставьте нетронутой всю техническую часть: ключи front matter, код, URL, подписи ссылок, теги компонентов и метки сносок должны вернуться байт в байт. Затем проверьте три вещи, которые ломаются даже при аккуратном переводе: ссылки на заголовки, комментарии в коде и, в китайском, японском или корейском, пробелы вокруг полужирного текста и диапазонов чисел.

Мы девять раз перевели страницу MDX из 73 строк на упрощённый китайский с помощью локальной модели, используя три разных промпта, и проверили каждый результат скриптом. То, о чём обычно беспокоятся, сохранилось во всех девяти запусках: код, встроенный код, URL, таблица, JSX-компонент и список задач. Три другие вещи пошли не так. Ссылка на заголовок сломалась во всех девяти запусках. Комментарии в коде были переведены во всех шести запусках, где отправлялся весь файл, включая три запуска, где в промпте было сказано не трогать код. А промпт, который скрывал синтаксис за плейсхолдерами и звучит как самый безопасный вариант, сломал front matter во всех трёх своих запусках.

Краткий ответ: что переводить, а что оставить без изменений

Часть файлаПример из нашего образцаПереводить?
Ключи front matterslug:, sidebar_position:, tags:Нет
Значения front mattertitle: "Export Your Library"Только те, что видны читателю, например title и description
Импорты и экспорты (MDX)import Callout from '@site/src/components/Callout';Нет
Блоки кодаshelfmark export --format epubНет. Отдельно решите насчёт комментариев и строк, видимых пользователю.
Встроенный кодlibrary.json, --coversНет
Адреса ссылок и изображений(./export-options.md), (https://www.w3.org/TR/epub-33/ "EPUB 3.3")Нет, кроме необязательного заголовка в кавычках
Текст ссылок и alt-текст изображений[glossary section], ![Folder layout after an export]Да
Метки и определения ссылок[project glossary][glossary], [glossary]: ./glossary.md#termsОставьте метку [glossary] и URL; переведите видимый текст
Заголовки## Keep one glossary for the whole bookДа, но сначала задайте им фиксированные ID (см. ниже)
JSX-компоненты<Callout type="warning" title="Back up first">Сохраняйте имена тегов и атрибутов; переводите значения атрибутов, видимые читателю, например title
Маркеры admonition:::tip и :::Нет
Метки сносок[^sync]Нет
Комментарии MDX{/* Translator note: ... */}Необязательно. Читатели их никогда не видят.

Всё остальное — обычный текст: абзацы, элементы списков, ячейки таблиц и цитаты.

Что мы тестировали

Мы написали sample-source.mdx — страницу документации для вымышленного приложения для чтения под названием Shelfmark. Она написана в стиле MDX, который используется в Docusaurus и похожих сайтах документации, и содержит по одному примеру каждой конструкции из таблицы выше, а также жёсткий перенос строки из двух пробелов, жирную ссылку, жирную фразу, заканчивающуюся скобкой, два диапазона чисел, записанных через среднее тире, и внутристраничную ссылку на заголовок «Keep one glossary for the whole book».

Затем мы попросили локальную модель перевести его на упрощённый китайский тремя способами:

  1. Весь файл. Вставить весь файл и попросить вернуть переведённый файл.
  2. С инструкциями. То же самое плюс шесть правил: сохранять ключи front matter, ничего не менять внутри кода, сохранять URL и определения ссылок, сохранять импорты, теги и комментарии, сохранять метки сносок и маркеры admonition, а также переводить заголовки, текст ссылок, альтернативный текст и атрибут title.
  3. С защитой. Заменить код, встроенный код, URL, теги, комментарии, метки сносок, маркеры admonition и строки front matter нумерованными заполнителями вроде ⟦12⟧, перевести остальное, а затем вернуть оригиналы на место. Значения title и description остались открытыми для перевода, и, как выяснилось, строки --- во front matter и закрывающие кавычки тоже.

Моделью была Qwen 3.6 27B (Q4_K_M) в LM Studio с отключённым режимом размышления и температурой 0.3, на Apple M4 Max с 64 ГБ памяти. Мы запускали каждое условие три раза, всего девять запусков, 25 октября 2026 года. Скрипт — run_llm_markdown_test.py, и все девять результатов опубликованы без редактирования; в README перечислены все файлы. Если вы никогда не настраивали локальную модель, наше руководство по переводу с помощью локальной LLM описывает настройки.

Это был небольшой тест одной модели, одной языковой пары и одной страницы. Он показывает, какие проблемы возникают и как их обнаружить. Он не ранжирует модели или промпты.

Результат: синтаксис выжил; ссылки и решения — нет

Мы проверили каждый результат с помощью check_markdown_translation.py и отрендерили каждый из них с помощью remark и компилятора MDX. Полный вывод проверки находится в check-results.txt, а в results.csv по одной строке на каждый запуск.

ПроверкаВесь файлС инструкциейЗащищено
Компилируется как MDX3 из 33 из 33 из 3
Код, встроенный код, URL-адреса, таблица, теги, список задач без изменений3 из 33 из 33 из 3
Жирный шрифт и курсив по-прежнему отображаются3 из 33 из 33 из 3
Front matter без изменений3 из 33 из 30 из 3
Внутренняя ссылка по-прежнему указывает на свой заголовок0 из 30 из 30 из 3
Комментарии в коде оставлены как есть0 из 30 из 33 из 3
Заголовок title в блоке-выноске переведён3 из 33 из 30 из 3

Первые строки — хорошие новости. Эта модель вернула синтаксис Markdown без изменений, когда мы вставили весь файл; мы не нашли ничего, что нужно исправить в коде, URL или таблицах. Последние четыре строки — это то, над чем нужно работать, и ни одна из них не проявляется как ошибка при сборке сайта.

1. Переведённые заголовки ломают ссылки на них

В примере есть ссылка на раздел глоссария: [glossary section](#keep-one-glossary-for-the-whole-book). Каждый запуск переводил заголовок, как и должен, и каждый запуск сохранял цель ссылки, как и должен. В результате получается ссылка на ID, которого больше не существует.

Большинство инструментов Markdown создают ID заголовков из текста заголовка. Документация Pandoc описывает идентификатор как «основанный на тексте заголовка», а Docusaurus предупреждает, что вы можете захотеть изменить или перевести заголовок, «не обновляя существующий ID, чтобы избежать поломки ссылок». Как только заголовок читается как 为整本书保留一个术语表, его сгенерированный ID становится китайским, и #keep-one-glossary-for-the-whole-book никуда не ведёт. Страница всё равно собирается, так что ничто вам об этом не сообщает.

Вы не можете исправить это, отредактировав ссылку после перевода. За девять запусков модель написала один и тот же заголовок четырьмя разными способами, поэтому сгенерированный ID снова изменился бы при каждом повторном переводе. Исправление должно быть до перевода: дайте каждому заголовку, на который есть ссылка, фиксированный ID.

  • В Docusaurus MDX используйте ## Keep one glossary for the whole book {/* #keep-one-glossary */}. Docusaurus отмечает, что старая форма {#id} «больше не является допустимым синтаксисом MDX», а его команда write-heading-ids добавляет ID в каждый документ за вас.
  • В Pandoc и в процессорах Markdown, следующих PHP Markdown Extra, используйте {#keep-one-glossary} в конце заголовка.

Тогда ID остаётся одинаковым на всех языках, и проверка считает его целью ссылки.

2. Комментарии в коде переводятся, а видимая строка — нет

В примере есть комментарий shell и комментарий YAML:

library:
  name: "My books"   # shown in the sidebar
  sort: title

Все шесть запусков, отправивших весь файл, перевели оба комментария, включая три, где в подсказке было сказано «Не меняйте ничего внутри блоков кода в ограждении». Сам код остался нетронутым во всех девяти запусках. Тем временем "My books", которое, согласно комментарию, отображается в боковой панели, осталось на английском во всех девяти запусках. Модель сделала противоположное тому, что нужно локализованной странице: она перевела примечание для разработчиков и оставила метку, которую увидят читатели.

Ни один из вариантов не является неправильным во всех случаях. Для руководства, читатели которого будут копировать код в свой проект, переведённые комментарии помогают. Для справочной документации, код которой должен соответствовать репозиторию, они заставляют страницу расходиться с источником. Примите решение один раз для проекта и просматривайте строковые значения в примерах конфигурации вручную, потому что ни один инструмент не отличит отображаемую метку от идентификатора. По этой причине проверка сообщает об изменениях только в комментариях как о предупреждениях.

3. Безопасный на вид метод сломал front matter

Защищённое условие сделало то, что обещало: все 32 заполнителя вернулись во всех трёх запусках, поэтому код, URL и теги были восстановлены идеально. Но наш скрипт оставил две мелочи вне заполнителей: строку ---, открывающую front matter, и закрывающую кавычку после переведённых заголовка и описания. Модель отбросила их все в трёх запусках. Файл начинался так:

title: "导出您的书库
description: "在更换设备之前,请备份您的笔记和书籍。
slug: export-library

Без открывающей --- парсер Markdown больше не видит front matter. Он читает шесть строк YAML плюс закрывающую --- как заголовок, поэтому страница открывалась бы с большим заголовком, содержащим YAML. Она всё равно компилировалась как MDX. Вернуть --- тоже недостаточно: парсер YAML отклоняет две незакрытые кавычки.

Урок не в том, чтобы избегать заполнителей. А в том, что всё, что вы оставляете открытым, может вернуться изменённым, и что front matter вообще не следует отправлять модели как часть страницы. Разберите его, отправьте только обращённые к читателю значения (title, description) как отдельные строки и запишите переводы обратно в исходную структуру. Это несколько строк кода, и они устраняют целый класс проблем.

Жирный текст и диапазоны в китайском, японском и корейском

Ещё две проблемы ни разу не появились в наших девяти запусках, но их стоит проверять, потому что они ломают страницы молча, и обе затронули собственный блог BookTranslator. Блог написан на MDX и машинно переводится на 27 языков, и в одном запланированном запуске перевода две корейские статьи три раза подряд не прошли нашу проверку структуры. Причинами были два шаблона ниже.

Жирный текст, заканчивающийся знаком препинания. CommonMark решает, может ли ** открывать или закрывать жирный шрифт, глядя на символы по обе стороны от него. Закрывающий **, идущий сразу после знака препинания, должен сопровождаться пробелом, знаком препинания или концом строки; это правило правого фланга. В английском там всегда есть пробел. В китайском, японском и корейском его часто нет. Поэтому 打开**设置 → 导出(测试版)**并选择 показывает буквальные звёздочки, так как жирный шрифт заканчивается полноширинной скобкой, а сразу за ней идёт 并. Та же строка без скобки отображается жирным, как и строка с пробелом после **. У жирной ссылки та же проблема, потому что ссылка заканчивается на ): 点击**[导出](./export-options.md)**打开 не срабатывает, как и корейское **[API](./api.md)**는.

Наша модель случайно ставила пробел с обеих сторон каждой жирной фразы, поэтому её вывод отображался правильно. Редактор, который позже удалит эти пробелы, следуя китайским типографским правилам, вернёт ошибку. Вы можете попробовать все семь вариантов в render-cases.md.

Числовые диапазоны с тильдой. В китайском и японском тексте диапазоны часто записываются как 3~5. Сайты на GitHub Flavored Markdown, построенные на remark, по умолчанию превращают одиночные тильды в зачёркивание: опция singleTilde в remark-gfm равна true, если вы её не измените. Два диапазона в одном абзаце, например 需要 3~5 分钟,生成 10~20 MB 的文件夹, отображаются с зачёркиванием текста между ними. В нашем образце использовались средние тире, и восемь прогонов сохранили их, а девятый написал 3 到 5. Просите средние тире или слова в диапазонах, либо установите singleTilde: false в вашей конфигурации remark.

Структурная проверка выявляет оба шаблона. Чтобы убедиться, что на самом деле отображается, render_check.mjs подсчитывает жирный шрифт, курсив, зачёркивание и оставшиеся звёздочки в исходнике и переводе, а также пытается скомпилировать MDX:

npm install @mdx-js/mdx@3 unified@11 remark-parse@11 remark-gfm@4
node render_check.mjs source.mdx translated.mdx

Меньше узлов жирного шрифта или больше буквальных звёздочек в переводе означает, что какое-то выделение перестало закрываться.

Рабочий процесс, который выдерживает нагрузку

  1. Сначала исправьте ID заголовков. Добавьте явные ID каждому заголовку, на который что-то ссылается, в синтаксисе, поддерживаемом вашим генератором сайта. Сделайте это на исходном языке, один раз, и зафиксируйте в репозитории до того, как что-либо будет переведено.

  2. Вынесите front matter из страницы. Переводите только обращённые к читателю значения, такие как title и description, как отдельные строки, и записывайте их обратно в исходный YAML.

  3. Определите политику для кода. Переводятся ли комментарии; переводятся ли отображаемые строки в примерах конфигурации. Запишите это в вашем глоссарии перевода рядом с названиями продуктов, которые не должны меняться, например Shelfmark в нашем образце.

  4. Переведите тело. Используйте инструмент, который анализирует Markdown, или отправьте всю страницу с явными правилами. В нашем тесте отправка всей страницы сохранила синтаксис нетронутым; если вы используете заполнители, защищайте целые конструкции и не оставляйте ничего наполовину открытым.

  5. Запустите структурную проверку.

    python3 check_markdown_translation.py source.mdx translated.mdx --keep Shelfmark
    

    Она сравнивает ключи и значения front matter, импорты, блоки кода, встроенный код, цели ссылок, определения ссылок, уровни заголовков, цели ссылок внутри страницы, теги и атрибуты JSX, комментарии MDX, маркеры admonition, метки сносок, форму таблиц, флажки списков задач, жёсткие переносы строк, выделение, которое не может закрыться, диапазоны с тильдой и термины, которые вы просите сохранить. Она не оценивает качество перевода.

  6. Проверьте отображение для CJK-целей с помощью render_check.mjs и добавьте пробел после любого закрывающего **, следующего за знаком препинания.

  7. Соберите сайт и пройдите по ссылкам. Откройте страницу на каждом языке, нажмите на каждую ссылку внутри страницы и прочитайте примеры конфигурации. Успешная сборка доказывает только то, что файл компилируется.

Для документации, где важна точность, шаги по терминологии и проверке из нашего рабочего процесса технического перевода применяются поверх этого.

Место BookTranslator

Переводчик TXT от BookTranslator принимает файлы .txt, .md и .mdx. Согласно странице продукта, он переводит их, сохраняя абзацы, переносы строк, специальные символы и структуру текста, и разбивает большие файлы на сегменты для контекстно-зависимого перевода, так что вы можете загрузить целое руководство или книгу, написанную в Markdown, вместо того чтобы вставлять её по частям.

На странице продукта не описана специальная обработка front matter, блоков кода или компонентов MDX, и мы не прогоняли этот образец через BookTranslator для данной статьи. Какой бы инструмент вы ни использовали, выполните шаги с 1 по 3 до перевода и шаги с 5 по 7 после. Заголовок без фиксированного ID всё равно теряет свои ссылки, а диапазон, записанный с тильдами, всё равно превращается в зачёркивание. Если вы публикуете книгу на нескольких языках, наше руководство по самостоятельной публикации на нескольких языках охватывает остальную часть этой работы.

Получите тестовые файлы

Всё из этого теста можно свободно использовать повторно:

Похожие статьи