Как перевести языковой файл JSON, не нарушив ключи и плейсхолдеры
Узнайте, какие значения в файле en.json переводятся, что переписывает переводчик файлов (комментарии, порядок ключей, длинные идентификаторы) и как проверить плейсхолдеры, ICU-множественные формы и ключи с помощью бесплатного скрипта и тестового файла.

Чтобы перевести языковой файл JSON, например en.json, передайте весь файл переводчику, который понимает JSON. Вставлять необработанный текст в окно чата или веб-переводчик — рискованный способ. Переводчик, учитывающий JSON, читает файл, переводит только текстовые значения и записывает те же ключи обратно, поэтому ваше приложение по-прежнему сможет загрузить файл. Копирование и вставка могут перевести ключ, изменить кавычку или потерять фигурную скобку. Любое из этого нарушает файл.
Сохранить ключи — простая часть. Основные проблемы возникают после: плейсхолдер {name} возвращается как {nom}, ICU-множественная форма теряет ветку other, URL отправляется на перевод как обычный текст, и переводчик вносит несколько изменений в сам файл. Именно это и рассматривается в данном руководстве.
Мы создали тестовый файл, содержащий все эти случаи, и написали бесплатную программу проверки, которая сравнивает исходный файл с его переводом. Мы также прогнали тестовый файл через локальную копию кода разбора и записи JSON, который использует BookTranslator, с маркером вместо модели, чтобы вы могли точно увидеть, какие значения отправляются и как файл возвращается.
Краткий ответ: что происходит с каждой частью файла
| Часть файла | Пример из нашего теста | Что делает BookTranslator | Что проверять |
|---|---|---|---|
| Ключи | "greeting", "error" | Остаются как есть | Ничего |
| Простые текстовые значения | "Reading list" | Переводятся | Смысл, длина на экране |
| Значения с плейсхолдерами | "Welcome back, {name}!", "Saved %s of %d chapters" | Переводятся, плейсхолдеры включаются в отправляемый текст | Имена плейсхолдеров и их количество (это делает программа проверки) |
| ICU-сообщения множественного числа | "{count, plural, one {# book} other {# books}}" | Отправляются как одна строка | Ветка other и категории множественного числа для целевого языка |
| Встроенный HTML | "accept the <b>Terms</b>." | Переводится; количество тегов проверяется после перевода | Имена тегов и их количество (это делает скрипт проверки) |
| URL-адреса, хранящиеся как текст | "https://example.com/help" | Отправляются на перевод | Что они вернулись без изменений |
| Значения без букв | "2.4.1", "—", "" | Оставлены как были | Ничего |
Числа, true/false, null | 20, true | Оставлены как были | Очень большие ID (см. ниже) |
| Массивы строк | ["Open", "Save", "Export as EPUB"] | Переведены в переведённом файле; оставлены без перевода в двуязычном файле | Двуязычный файл, если вы его используете |
| Комментарии (JSONC) | // Shown on the home screen | Удалены. Если значение также содержит //, файл может не загрузиться | Удалите комментарии перед загрузкой |
Остальная часть этого руководства показывает, откуда берётся каждая строка и что с ней делать.
Шаг 1: Проверьте исходный файл перед загрузкой
Запустите скрипт проверки на файле, который планируете переводить. Для него нужен только Python 3, и больше ничего:
python3 check_i18n_json.py en.json
На нашем тестовом файле он вывел:
WARN limits.projectId: 9007199254740993 is above 2^53 and can change in a JavaScript round trip; store IDs as strings
WARN error.network: a flat key with dots and a nested key resolve to the same dot path; tools that address strings by dot path can mix them up
INFO (root): 3 keys look like integers; JavaScript sorts those first and in numeric order when it rewrites the object
INFO source: 17 strings, 2 numbers
Два предупреждения и примечание о ключах, похожих на целые числа, каждое соответствует изменению, которое мы наблюдали, когда наш тестовый файл проходил через переводчик. Если в вашем файле не появится ни одного из них, можете перейти к шагу 3.
Скрипт проверки также отклоняет файл, который не является строгим JSON, например, с комментариями или висячими запятыми, потому что это следующая проблема.
Что вернулось из нашего тестового файла
Тест проводился 2 ноября 2026 года на текущей обработке JSON в бэкенде BookTranslator. Вместо вызова модели тест заменял каждое значение, которое должно было быть отправлено на перевод, на [ZH] плюс исходный текст. Это делает результат предсказуемым: всё, что с [ZH] в начале, было отправлено, всё без него — нет. Оба результата опубликованы: переведённый файл и двуязычный файл.
Переведённый файл
Все ключи вернулись без изменений, и файл по-прежнему успешно парсился. Были отправлены значения, содержащие буквы, включая {name}, %s, сообщение ICU, тег <b> и URL справки. Строка версии "2.4.1", тире и пустая строка не отправлялись, так как в них нет букв. Числа и логические значения остались нетронутыми.
Изменилось ещё четыре вещи:
- Одна строка попала не в тот ключ. В нашем тесте в файле был плоский ключ
"error.network"с текстом "Check your connection.", а также вложенный"error": { "network": "Network error" }. Переводчик идентифицирует каждую строку по её пути с точками между частями, поэтому обе строки имели одинаковый адрес —error.network. В выводе оба ключа содержали перевод "Network error". "Check your connection." исчезло. Для этого нужны оба стиля в одном файле, что редкость, но ошибка происходит молча. Проверяющий предупреждает об этом до загрузки. - Ключи, похожие на целые числа, были переупорядочены.
"steps": { "10": ..., "2": ..., "1": ... }вернулся как"1","2","10". JavaScript всегда перечисляет ключи, похожие на целые числа, первыми, в числовом порядке, когда пересобирает объект. Ключи и значения по-прежнему совпадают, так что ваше приложение в порядке, но сравнение с исходным файлом будет шумным. - Большой ID потерял точность.
9007199254740993вернулся как9007199254740992. JavaScript хранит числа как 64-битные числа с плавающей запятой, и целые числа выше 2^53 не все могут быть представлены точно (MDN оNumber.MAX_SAFE_INTEGER). Храните длинные ID как строки или держите их вне языкового файла. - Форматирование было переписано. В выводе используются отступы в два пробела и завершающий перевод строки. Если в исходнике использовались табы или четыре пробела, ожидайте изменений пробельных символов в сравнении.
В нашем тестовом файле не было дублирующихся ключей, но о них стоит знать: парсер JSON в JavaScript сохраняет только последнее значение для повторяющегося ключа, поэтому более ранний текст никогда не переводится. Ваше приложение загрузило бы то же последнее значение, так что ничего видимого не ломается, но проверяющий перечисляет дублирующиеся ключи, чтобы вы могли их очистить.
Двуязычный файл
Двуязычный файл сохраняет каждое исходное значение и добавляет соседний ключ с _translated в конце:
"greeting": "Welcome back, {name}!",
"greeting_translated": "[ZH] Welcome back, {name}!",
Это хорошо работает для проверки, потому что исходник и перевод находятся рядом. Это не файл, который может загрузить ваше приложение, потому что теперь у каждого ключа есть двойник. Используйте переведённый файл в вашем приложении.
Один пробел: строки внутри массивов не получают соседний _translated, потому что элементы массива не имеют ключа, к которому можно было бы его добавить. В нашем тесте "menu": ["Open", "Save", "Export as EPUB"] вернулся на английском в двуязычном файле, но переведённым в переведённом файле. Если вы проверяете по двуязычному файлу, проверьте массивы в переведённом файле.
Файлы с комментариями
Многие проекты хранят комментарии в своих языковых файлах (JSONC), что строгий JSON не допускает. Переводчик сначала пытается использовать строгий JSON. Если это не удаётся, он удаляет комментарии и висячие запятые и пытается снова. Удаление комментариев означает удаление всего после // в строке, и это включает // в https://.
Наш файл с комментарием и URL вообще не загрузился; парсинг не удался на первом комментарии. Файл с комментариями, но без // внутри каких-либо значений загрузился нормально, и комментарии были удалены из вывода.
Итак: если в вашем файле есть комментарии, удалите их перед загрузкой или сохраните копию с комментариями и примите, что в переводе их не будет.
Шаг 2: Подготовьте файл
Вы можете исправить большинство этих проблем перед переводом:
- Сделайте его строгим JSON. Удалите комментарии и висячие запятые. Если ваш инструмент сборки читает JSONC, сохраните файл с комментариями как исходный и экспортируйте чистую копию для перевода.
- Используйте один стиль ключей. Либо вкладывайте ключи, либо уплощайте их с точками, но не оба в одном файле. Если вы используете i18next, его опция
keySeparatorуправляет тем, как читаются точки в ключах, поэтому проверьте эту настройку, прежде чем что-либо уплощать. - Вынесите нетекстовые данные. ID, URL, флаги функций и лимиты принадлежат конфигурации, а не языковому файлу. Если URL должен остаться, оставьте его, но ожидайте, что его нужно будет проверить после перевода. Наше руководство о том, что не переводить, рассказывает, как решить, что ещё должно остаться на исходном языке.
- Пишите заполнители одинаково везде. Один стиль, например
{name}, легче проверить, чем смесь{name},{{name}}и%s.
Шаг 3: Переведите весь файл
Загрузите очищенный файл в переводчик документов BookTranslator, который перечисляет .json среди других форматов локализации и структурированного текста, которые он принимает. Цена отображается до оформления заказа. Когда задача завершится, страница истории предложит переведённый файл и двуязычный файл в виде отдельных загрузок.
На результат влияют две особенности перевода текста:
- Модель не видит ваши ключи. Каждое значение отправляется как пронумерованный сегмент без имени ключа. Если
"button.open"и"status.open"оба содержат «Open», модель не может знать, что в одном случае это глагол, а в другом — прилагательное. Короткие неоднозначные строки стоит проверять вручную. - Каждое значение переводится отдельно. Ничто не связывает два ключа, поэтому одно и то же слово в двух местах может вернуться в двух разных вариантах, а слово, которому нужны два разных перевода, может вернуться одинаково. Необязательный автословарь BookTranslator собирает имена, названия мест и специальные термины и обеспечивает их единообразие, но вы не можете задать в нём собственные фиксированные переводы. Для терминов, которые должны переводиться строго определённым образом, ведите список терминов и проверяйте эти строки после перевода; наше руководство по словарю объясняет, как его спланировать.
Встроенные HTML-теги подсчитываются после перевода, и сегмент, теги которого не совпадают, отправляется на повторную обработку. Плейсхолдеры в фигурных скобках, плейсхолдеры в стиле %s и синтаксис ICU отправляются как часть текста, но не проверяются таким же образом. Выполняйте шаг 4 для каждого перевода.
Если вы предпочитаете переводить с помощью чат-модели или локальной модели, отправляйте только значения, никогда не сырой файл, и возвращайте их на место по ключам с помощью скрипта. Проверки ниже применимы в любом случае.
Шаг 4: Проверка переведённого файла
Сравните перевод с исходником:
python3 check_i18n_json.py en.json fr.json
Чтобы показать, что он выявляет, мы создали французский файл с намеренными ошибками. Он сделан вручную, а не сгенерирован моделью. После тех же предупреждений об исходнике, что и на шаге 1, программа проверки вывела:
ERROR app.greeting: placeholders differ; lost ['name'] extra ['nom']
ERROR app.help_url: urls differ; lost ['https://example.com/help'] extra ['https://example.com/aide']
ERROR app.items: ICU message has no 'other' case
ERROR app.legacy: placeholders differ; lost ['%d'] extra []
ERROR limits.maxFiles: type changed from int to str
ERROR limits.projectId: non-text value changed: 9007199254740993 -> 9007199254740992
INFO menu[1]: identical to source: 'Save'
INFO compared 20 source values, 1 unchanged strings
Каждая из этих ошибок проявилась бы в приложении: пустое имя, сломанная ссылка, сбой или сырое сообщение при отображении множественного числа, отсутствующее количество глав, лимит, сравниваемый как текст. Строка INFO перечисляет строки, вернувшиеся без изменений. Некоторые из них в порядке, например названия брендов; «Save» во французском меню — нет.
Программа проверки сравнивает ключи, типы значений, нетекстовые значения, плейсхолдеры ({name}, {{name}}, %s, %1$s, %@), имена аргументов ICU, HTML-теги и URL-адреса. Сам перевод она не оценивает.
Формы множественного числа должны меняться
Множественное число ICU содержит по одному сообщению для каждой категории множественного числа. В английском две категории — one и other, но во многих языках набор другой. Хороший русский перевод нашего сообщения items обычно использует one, few, many и other, а японский — только other. Поэтому несовпадение здесь не всегда ошибка.
Категории берутся из правил множественного числа Unicode CLDR, и other есть в каждом языке. Вот количественные категории, которые возвращает Intl.PluralRules в Node.js с CLDR 48:
| Язык | Категории множественного числа |
|---|---|
| Английский, немецкий | one, other |
| Французский, испанский, португальский | one, many, other |
| Русский, польский | one, few, many, other |
| Арабский | zero, one, two, few, many, other |
| Японский, китайский, корейский | other |
Французский, испанский и португальский используют many только для очень больших круглых чисел, например миллиона, поэтому сообщение для небольших чисел обычно может обойтись без него. Проверка сообщает об отсутствии other как об ошибке, а любое другое изменение категорий — как о предупреждении, которое нужно сравнить с этой таблицей. Вы можете вывести список для любого языка самостоятельно:
node -e 'console.log(new Intl.PluralRules("ru").resolvedOptions().pluralCategories)'
Переводчику передаются английские формы, поэтому проверьте, что вернулось. Если в русском или арабском сообщении по-прежнему есть только one и other, добавьте недостающие формы вручную или поручите это переводчику.
Затем загрузите его в приложение
Проверка доказывает, что файл безопасно загружать, а не то, что он хорошо читается. Переключите приложение на новый язык и посмотрите на экраны: длинные слова в немецком или финском, выходящие за пределы кнопки, текст справа налево в арабском или иврите, а также даты или числа, которые ваш код форматирует отдельно от языкового файла. Наше руководство по переводу и локализации рассказывает, что ещё меняется между рынками помимо слов.
Частые проблемы
Переведённый файл не загружается в моё приложение. Запустите на нём проверку. Если проверка не может его разобрать, значит файл был отредактирован после перевода или изначально был невалидным. Если он разбирается, ищите строки ERROR о ключах или типах.
Строка отображается как сырое ICU-сообщение, например {count, plural, ...}. Синтаксис ICU сломался при переводе, обычно из-за пропущенной фигурной скобки или отсутствующей формы other. Скопируйте структуру из исходника и переведите только слова внутри каждой формы.
Мой файл — это YAML, файл .po или Markdown. BookTranslator принимает и их, но у каждого формата свои правила. Для документации смотрите наше руководство по переводу Markdown; для веб-страниц — руководство по HTML-файлам.
Можно ли перевести только новые ключи? Не в рамках одной загрузки; переводится весь файл. Поместите новые ключи в отдельный файл, переведите его и объедините результат по ключам. Проверка подтвердит, что в объединённом файле есть все ключи.
Стоит ли переводить ключи? Нет. Ключи — это идентификаторы, которые ищет ваш код. Только значения — это текст для чтения людьми.
Похожие статьи





