BookTranslator
BookTranslator

Как перевести HTML-файл, не сломав теги, атрибуты и ссылки

Что переводить в HTML-файле, что оставить без изменений и как это проверить. С примером страницы, бесплатной проверкой и тестом локальной LLM из 9 запусков.

Чтобы перевести HTML-файл, переведите текст, который читают люди, и атрибуты, содержащие обращённые к читателю слова, такие как alt, title, placeholder, aria-label и мета-описание страницы. Разметку оставьте без изменений: имена тегов, href, src, id, class, скрипты, стили и всё, что помечено translate="no". Затем измените одно значение, которое легко забыть, — атрибут lang у элемента <html>, чтобы браузеры, скринридеры и поисковые системы знали, на каком языке теперь страница.

Сохранение тегов в целости оказалось простой частью. Мы девять раз перевели 59-строчную HTML-страницу справки на упрощённый китайский с помощью локальной модели, используя три метода, и проверили каждый результат скриптом. В каждом запуске сохранились все теги, ссылки и ID. Проблемы были тише: модель перевела строку внутри блока кода <pre> во всех шести запусках, где отправлялся весь файл, оставила aria-label навигации на английском во всех шести, а метод, который по своей сути безопаснее всего сохраняет разметку, — перевод текстовых фрагментов по одному, — во всех трёх своих запусках выдал предложение, которое советовало читателю сделать неверную вещь.

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

Часть файлаПример из нашей выборкиПереводить?
Видимый текстзаголовки, абзацы, ячейки таблиц, текст ссылок, <label>Да
<title> и мета-описание<title>Export Your Library | Shelfmark Help</title>Да. Оба отображаются в результатах поиска и на вкладках браузера.
og:title и другие социальные мета-теги<meta property="og:title" content="Export Your Library">Да, только значение content
alt, title, placeholder, aria-labelalt="The Export dialog with EPUB selected", aria-label="Breadcrumb"Да
lang у <html><html lang="en">Измените его на целевой язык, например zh-Hans
Имена тегов и атрибутов<a>, <img>, width, сам aria-labelНет
href, src, id, class, for, name, type, data-*href="#choose-format", id="q"Нет
<code>, <pre>, <script>, <style>shelfmark export --format epubНет. Отдельно решите насчёт комментариев в коде и примера вывода.
Текст, задаваемый через CSS.note::before { content: "Note: "; }Да, но это находится в таблице стилей, поэтому учитывайте это
Структурированные данные JSON-LD"name": "Export your Shelfmark library"Переведите значения name и description; ключи и JSON должны остаться валидными
Элементы с translate="no"<span translate="no">Shelfmark</span>Нет
HTML-комментарии<!-- Generated by the help-center build... -->Необязательно. Читатели их никогда не видят.
Символьные ссылки&lt;library&gt;, &nbsp;, &copy;Оставляйте экранированные последовательности экранированными. &lt; не должен превратиться в настоящий <.

Этот список не наше изобретение. В разделе стандарта HTML об атрибуте translate определены «переводимые атрибуты»: title у любого элемента, alt у изображений, placeholder у полей ввода, content у элементов meta, имя которых известно как переводимое, и lang, который, как сказано, должен изменяться в соответствии с новым языком. Там также aria-label из ARIA назван примером атрибута, который могут добавлять другие спецификации, и сказано, что в элементе с translate="no" текст и значения атрибутов «должны оставаться как есть при локализации страницы».

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

Мы написали sample-source.html — страницу справочного центра для того же вымышленного приложения для чтения Shelfmark, которое использовали в тесте перевода Markdown. В ней есть по одному примеру для каждой строки таблицы выше, а также несколько ловушек:

  • предложение, разделённое встроенными тегами: «Before you export, back up your notes. An export copies your books, but it does not delete anything from Shelfmark», где название продукта находится внутри translate="no";
  • предложение с двумя встроенными элементами <code>, порядок которых важен: «Type the path to the <library> folder after --out»;
  • блок <pre><code> с командой и одной строкой примера вывода: # Export finished: 128 books;
  • правило CSS, которое вставляет слово «Note:» перед абзацем;
  • внутренняя ссылка на #choose-format, ссылка mailto:, форма поиска и блок JSON-LD.

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

  1. Весь файл. Вставить весь файл и попросить вернуть переведённый файл.
  2. С инструкциями. То же самое плюс восемь правил: сохранять все имена тегов и атрибутов, не изменять значения href, src, id, class и подобные, переводить перечисленные текстовые атрибуты и метатеги, не трогать <code>, <pre>, <script> и <style>, кроме name и description в JSON-LD, соблюдать translate="no", сохранять символьные ссылки и комментарии и установить lang в zh-Hans.
  3. Сегменты. Подход инструментов, извлекающих текст из HTML: скрипт разбивает файл на теги и текст, пропускает элементы <code>, <pre>, <script>, <style> и translate="no", отправляет оставшиеся текстовые фрагменты и текстовые атрибуты модели в виде нумерованного списка JSON и записывает переводы обратно в нетронутую разметку. Он также сам устанавливает lang="zh-Hans".

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

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

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

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

ПроверкаВесь файл (3 запуска)С инструкцией (3 запуска)Сегменты (3 запуска)
Те же 58 тегов в том же порядке; все href, src, id, class без изменений3 из 33 из 33 из 3
Текст с translate="no" сохранён, внутренняя ссылка работает, &lt;library&gt; остаётся экранированным3 из 33 из 33 из 3
Строка примера вывода внутри <pre> переведена3 из 33 из 30 из 3 (пропущено намеренно)
CSS content: "Note: " переведён3 из 30 из 30 из 3 (пропущено намеренно)
JSON-LD name и description переведены3 из 33 из 30 из 3 (пропущено намеренно)
aria-label="Breadcrumb" переведён0 из 30 из 33 из 3
Предложение с двумя встроенными элементами <code> передаёт нужный смысл3 из 33 из 30 из 3
Пробелы между китайским текстом вокруг встроенного теганет0, 5 и 46 в каждом прогоне
lang на <html>zh-CN (не запрашивался)zh-Hanszh-Hans (установлен скриптом)

Каждый из девяти прогонов сохранил те же 58 тегов в том же порядке, каждый href, src, id и class, цель внутристраничной ссылки, комментарий, экранированный &lt;library&gt; и название продукта внутри translate="no". Если вы опасались, что модель испортит разметку простой страницы, этого не случилось. Проблемы были исключительно в том, что переводить.

1. Пример вывода внутри <pre> был переведён

Блок кода в нашем примере заканчивается строкой вывода программы:

<pre><code>shelfmark export --format epub --covers --out ~/Books/export
# Export finished: 128 books</code></pre>

Во всех шести прогонах с целым файлом и с инструкциями модель возвращала # 导出完成:128 本书 в этой строке, включая три прогона, где в промпте было сказано не менять ничего внутри <pre>. Сама команда осталась нетронутой. Модель восприняла строку как комментарий, а перевод комментариев — разумный выбор в учебнике. Но эта строка — то, что выводит программа, и читатель, сравнивающий её со своим терминалом, увидит английский текст. Решите до перевода, переводятся ли пример вывода и комментарии к коду, и сообщите инструменту, или полностью защитите блоки <pre>.

Прогоны с целым файлом также перевели CSS content: "Note: " в блоке <style>, тогда как прогоны с инструкциями, которым было сказано не трогать <style>, оставили его на английском. Само по себе ни то, ни другое не ошибочно. Переводить таблицу стилей правильно для страницы, где CSS встроен, и неправильно для сайта, где одна общая таблица стилей обслуживает все языки. В любом случае это решение, которое должен принять человек, а не деталь, которую модель должна угадывать.

2. Метки доступности были пропущены

Навигационная цепочка — это <nav aria-label="Breadcrumb">. Скринридер объявляет эту метку; зрячие читатели её никогда не видят. Во всех шести прогонах с целым файлом и с инструкциями она осталась на английском, хотя в промпте с инструкциями aria-label был указан среди атрибутов для перевода. В одном прогоне с инструкциями aria-label="Search" кнопки поиска тоже остался на английском.

Сегментный метод переводил её каждый раз, потому что его скрипт собирал все значения aria-label, alt, title и placeholder и запрашивал каждое явно. Это самый сильный аргумент в пользу целенаправленного извлечения текстовых атрибутов, а не надежды на то, что модель их заметит.

3. Перевод по фрагментам сохранил теги, но сломал предложение

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

Английское предложение: "Type the path to the <library> folder after --out." Встроенный <code> разбивает его на три части: "Type the path to the", "folder after" и точку. В китайском эти слова идут в другом порядке, поэтому переводчику нужно переместить два элемента кода. Сегментный метод не может их переместить. Все три прогона перевернули инструкцию. Прогон 2 дал такой результат (в прогонах 1 и 3 стоит 后 вместо 之后, смысл тот же):

在 <code>&lt;library&gt;</code> 之后输入 <code>--out</code>文件夹的路径。

Это читается как "после <library> введите путь к папке --out", то есть наоборот. Все шесть прогонов с целым файлом и с инструкциями переместили элементы кода и справились правильно:

在 <code>--out</code> 后面输入 <code>&lt;library&gt;</code> 文件夹的路径。

То же разбиение оставило лишние пробелы там, где тег соприкасается с китайским текстом, например 前往 <b>设置</b> и <b>书库</b> ,, во всех трёх сегментных прогонах, по шесть мест в каждом. Браузер показывает их как видимые промежутки, в том числе перед китайской пунктуацией. В прогонах с целым файлом их не было; в двух из трёх прогонов с инструкциями было четыре или пять.

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

Менее значительные различия

  • lang: прогоны с целым файлом сами изменили его на zh-CN. Прогоны с инструкциями использовали zh-Hans, как и было указано. Оба варианта — допустимые языковые теги; zh-Hans обозначает письменность (упрощённый китайский), zh-CN обозначает регион. Рекомендации W3C по объявлению языка советуют объявлять язык на элементе <html>, а не с помощью метатега Content-Language.
  • JSON-LD: прогоны с целым файлом и с инструкциями перевели значения name и description и сохранили JSON валидным. Наш сегментный скрипт пропускал содержимое <script>, поэтому его структурированные данные остались на английском — так же поведёт себя любой инструмент, игнорирующий скрипты.
  • Символьные ссылки: прогоны с целым файлом и с инструкциями сохранили &rsaquo;, &nbsp;, &copy; и ссылку на эмодзи как есть. Пять из этих шести прогонов переписали диапазон 2&ndash;5 китайскими словами (2 到 5 или 2 至 5); один прогон с инструкциями написал обычное тире вместо ссылки, хотя в его запросе было сказано сохранять ссылки. Наш сегментный скрипт раскодировал их все и записал символы обратно, что отображается так же. Единственное, что важно, — &lt;library&gt; внутри <code> — осталось экранированным во всех девяти прогонах.

Перед переводом: подготовьте файл

  1. Отметьте то, что не должно меняться. Поставьте translate="no" на названия продуктов, номера моделей, строки, похожие на код, в обычном тексте и на всё остальное, что должно остаться как есть. Это часть HTML, ничего не стоит, и модель соблюдала это во всех шести прогонах, где получала целый файл. Сегментные инструменты, которые это читают, как наш скрипт, полностью пропускают такие элементы. В нашем руководстве о том, что не переводить, рассказано, как решить, что попадёт в этот список.
  2. Определите политику для кода. Переводятся ли комментарии в коде? А примеры вывода? Общий ли стиль для всех языков? Запишите ответ вместе с названиями, которые не должны меняться, в глоссарий перевода и передайте его тому, кто или что будет переводить.
  3. Оставляйте ID на исходном языке. Ссылки внутри страницы указывают на значения id. Если кто-то переведёт id, все ссылки на него сломаются. Ни один из наших прогонов так не сделал, но переводчик, который «усердно» локализует якоря, сделает.
  4. Соберите скрытый текст. Перечислите атрибуты, содержащие слова: alt, title, placeholder, aria-label, value у кнопок, метатеги и JSON-LD, — чтобы их проверили и не забыли.
  5. Не переводите строки JavaScript на месте. Сообщения, задаваемые скриптом, например alert или подпись кнопки, создаваемая в коде, требуют отдельного шага локализации в коде. Ни один из шести прогонов с целым файлом или с инструкциями не изменил наш блок скрипта, и это правильно, но значит, что эти строки остаются на английском.

После перевода: проверьте его

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

    python3 check_html_translation.py source.html translated.html --lang zh-Hans --keep Shelfmark
    

    Она сравнивает теги и их порядок, каждый атрибут, не являющийся текстом для читателя, содержимое <pre>, <script> и <style>, встроенный код, ключи JSON-LD, текст с translate="no", комментарии и ссылки внутри страницы. Она предупреждает о текстовых атрибутах, всё ещё на английском, о значении lang, отличающемся от ожидаемого, о видимом тексте, который выглядит непереведённым, о встроенном коде, изменившем порядок, и о пробелах между китайским, японским или корейским текстом по обе стороны встроенного тега. Она не оценивает качество перевода.

  2. Прочитайте каждое предложение, содержащее встроенный тег. Именно здесь в нашем тесте сломался смысл. Не полагайтесь на предупреждение чекера «встроенный код переупорядочен», чтобы найти эти предложения: в нашем тесте неправильными оказались те переводы, которые сохранили исходный порядок.

  3. Откройте страницу в браузере. Пощёлкайте ссылки внутри страницы, пройдите форму с помощью скринридера, если можете, и поищите промежутки вокруг жирного и связанного текста.

  4. Для языков с письмом справа налево также задайте dir="rtl" на элементе <html>. В нашем руководстве по переводу книг с RTL рассказано, что ещё меняется.

  5. Для XHTML, например файлов глав внутри EPUB, также убедитесь, что файл остаётся корректным XML, например с помощью xmllint --noout chapter.xhtml. Браузер прощает незакрытый тег; электронная читалка может не простить. Наш контрольный список проверки перевода EPUB охватывает остальное для электронной книги.

Место BookTranslator

На странице загрузки BookTranslator принимает файлы .html, .htm и .xhtml наряду с книгами и документами, поэтому вы можете загрузить сохранённую страницу или статическую справочную статью как единый файл. Мы не прогоняли этот образец через BookTranslator для данной статьи и не проверяли, как он обрабатывает порядок встроенного кода, CSS-контент или JSON-LD, поэтому проверяйте его вывод так же, как проверяли бы вывод любого другого инструмента. Вы видите цену до оплаты.

Что бы вы ни использовали, выполните подготовительные шаги до перевода и проверки после. Страница со всеми целыми тегами всё равно может сказать читателю ввести неверные данные. Если ваш HTML на самом деле является документацией, написанной на Markdown, наше руководство по переводу Markdown описывает исходный формат.

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

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

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