Язык, локаль и постоянный адрес
У русской учебной библиотеки уже есть ссылки из поиска, закладок и других сайтов. Добавление английского языка не должно превращать эти адреса в недействительные. В этой серии мы создадим самостоятельную библиотеку «Параллель» с русской и английской версиями двух статей. Русский материал сохранит прежний адрес, а перевод получит свой. На опыте ProfessorWeb особенно хорошо видна причина такого решения: язык и структура каталога меняются быстрее, чем внешние ссылки на уроки.
Нам понадобятся основы HTML и JavaScript. Позднее небольшой сборщик на Python превратит подготовленный Markdown в страницы. Сейчас определим модель данных: какие свойства описывают содержание, какие отвечают за представление и какие становятся частью постоянного адреса. Этот договор позволит добавлять языки без массового переименования материалов.
Язык текста и локаль интерфейса
Язык отвечает на вопрос, на каком языке написана статья. Локаль уточняет правила представления чисел, дат и других значений. Страница на английском языке не обязательно предназначена только для США: читатель из Великобритании тоже может понимать её, но ожидать другое представление даты. Поэтому значение en для текста и en-GB для форматирования могут сосуществовать.
У «Параллели» будет два опубликованных языка: ru и en. Для русского интерфейса используем локаль ru-RU, для английского — en-GB. Это редакционное решение учебного примера, а не попытка определить гражданство посетителя. В дальнейшем можно добавить региональную версию, если у неё действительно появятся отличия содержания, условий или числовых представлений.
Запишем полный исходный файл site.json:
{
"origin": "https://parallel.example",
"default_language": "ru",
"languages": {
"ru": {"locale": "ru-RU", "label": "Русский", "dir": "ltr"},
"en": {"locale": "en-GB", "label": "English", "dir": "ltr"}
}
}
Домен .example здесь учебный; он не обозначает опубликованную библиотеку. Сборщик будет использовать его для абсолютных метаданных. Перед настоящим выпуском origin требуется заменить в одном месте. Языки перечислены явно: произвольная строка из параметра запроса не становится новым каталогом, словарём или адресом файла.
Атрибут lang в HTML описывает язык документа и помогает средствам чтения правильно произносить текст. Он не задаёт регион форматирования автоматически. На русской странице будем выводить <html lang="ru">, а значение ru-RU отдельно передавать в Intl. Эти две операции решают разные задачи. Правила объявления языка подробно описаны в рекомендациях W3C.
Идентификатор статьи переживает перевод
У каждой статьи будет неизменный ID. Он связывает версии, но не является заголовком и не обязан совпадать со slug. Возьмём материал stable-urls: русское название — «Постоянные адреса», английское — «Stable URLs». Названия можно исправлять, не меняя ID и опубликованные пути.
Фрагмент реестра articles.json показывает одну статью:
[
{
"id": "stable-urls",
"source_revision": 3,
"variants": {
"ru": {
"title": "Постоянные адреса",
"url": "/articles/stable-urls.html",
"file": "content/ru/stable-urls.md",
"status": "published",
"translated_from": 3
},
"en": {
"title": "Stable URLs",
"url": "/en/articles/stable-urls.html",
"file": "content/en/stable-urls.md",
"status": "published",
"translated_from": 3
}
}
}
]
Русский путь остаётся без /ru/. Такое несимметричное дерево нормально: сохранение уже известного адреса важнее визуальной симметрии каталогов. Для совершенно нового проекта можно выбрать языковые префиксы у всех версий, но это другое исходное условие. Не стоит сначала переносить действующий сайт, а потом объяснять поисковым системам переезд, который не был необходим.
Поле file относится к исходнику, url — к выдаваемой странице. Между ними нет обязательной связи один к одному по имени. Если файл переименовали при редакционной работе, адрес остаётся прежним. Когда перевод переименовали с stable-urls.md на более удобное внутреннее имя, достаточно изменить file. Внешний посетитель этого изменения не заметит.
Доступная версия и ещё не готовый перевод
Статус принадлежит конкретной языковой версии. Русская статья может быть опубликована, пока английская находится в черновике. Наличие объекта в реестре не означает, что его можно включить в навигацию и карту сайта. Введём простую функцию выбора, которая понадобится сборщику:
def published_variants(article):
return {
language: variant
for language, variant in article["variants"].items()
if variant["status"] == "published"
}
Если у английской версии изменить статус на draft, результат функции будет содержать только ru. Ни переключатель языка, ни hreflang не должны ссылаться на ещё не существующую публичную страницу. Редактор продолжит видеть черновик в исходниках; посетитель получит только доступные версии. Ожидаемое поведение здесь следует из условий выборки, а не из выполненной сборки.
Неправильное решение — создавать английский адрес с русским текстом только ради заполнения каталога. Пользователь выбирает English и ожидает перевод, а получает тот же материал. Вместо этого оставим понятную ссылку на русский оригинал и сообщим, что перевода пока нет. Сам перевод появится по своему окончательному адресу после редакционной проверки.
Что мы будем сохранять при расширении
Наш договор включает три устойчивых свойства: ID материала, опубликованный URL каждой версии и явную связь переводов. Локаль, название и состояние работы могут меняться независимо. Это позволяет построить отчёт по отсутствующим переводам, не сравнивая похожие заголовки и не угадывая соответствия по путям.
Рассмотрите вторую статью reading-list, у которой пока есть только русский текст. Добавьте её запись в реестр с адресом /articles/reading-list.html. Английский путь можно записать заранее со статусом draft, но не выдавать как готовую страницу. После этого у библиотеки будет два русских материала и один английский: нормальное, явно описанное промежуточное состояние.
Один адрес и предсказуемое содержание
Представим, что русский URL начинает выдавать английское тело в зависимости от сохранённой настройки браузера. Закладка перестаёт однозначно обозначать документ: у двух людей по одной ссылке разный язык, а кэш может сохранить ответ для одного посетителя и отдать другому. Такой вариант потребовал бы другого договора HTTP и персонализации. Для статической учебной библиотеки отдельные адреса проще объяснить, проверить и передать.
Сохранённое предпочтение может помочь предложить перевод, но не переписывает реестр. Если человек копирует английскую ссылку коллеге, тот получает английскую статью независимо от прежних действий на сайте. По этому же принципу автор может дать точную ссылку на русское объяснение. Идентичность материала объединяет версии, а адрес определяет конкретный документ, который сейчас читают.
В следующих уроках перенесём подписи интерфейса в словари и научимся формировать целые фразы с параметрами. Реестр переводов статьи останется отдельным: перевод кнопки и перевод учебного текста имеют разные объёмы, сроки и критерии готовности.