Языки и переводы страниц
Многоязычность в OnPress — это Polylang. У каждой страницы есть язык, а переводы одной страницы объединены в группу: из группы строятся hreflang в <head> и переключатель языков onpress/lang-switcher. Языки самого сайта (какие есть, какой основной, как попадают в адрес) описаны на странице языки сайта; здесь — всё, что касается страниц.
Язык страницы и адрес
Язык задаётся полем lang при публикации. Без него страница попадает в язык сайта по умолчанию, и ответ предупреждает об этом.
Адрес страницы не основного языка начинается с префикса языка: русская страница /o-nas/ на сайте с основным английским открывается по /ru/o-nas/. Основной язык обычно без префикса (настройка hide_default). В ответах API это разведено по двум полям: url — путь в дереве без префикса (/o-nas/), link — настоящий адрес (https://site/ru/o-nas/). В GET /pages и в системных источниках url уже с префиксом — это адрес, по которому страницу отдаёт сайт.
Одинаковый слаг в разных языках — норма. /o-nas/ и /ru/o-nas/ — две разные страницы с одним слагом. Поэтому всё, что ищет страницу по слагу, делает это с учётом языка: публикация ищет существующую страницу своего языка, GET /pages/{слаг}?lang=ru — русскую. Без lang голый слаг означает страницу основного языка.
Опубликовать перевод
Возьмите оригинал в формате заготовки под перевод:
curl -s -H "Authorization: Bearer $OP_TOKEN" \
"$API/site/$SITE/pages/o-nas/markdown?as=translation_source" | jq -r .markdown > o-nas.ru.md
Вместо id во frontmatter будет translation_of с id оригинала, status не будет. Поменяйте lang, name, текст и, если нужно, slug и url:
---
name: "О компании"
slug: "o-nas"
url: "/o-nas/"
lang: "ru"
translation_of: 6703
frame: "article"
---
Текст на русском.
Опубликуйте обычным POST /pages/markdown. Страница создаётся и вступает в группу оригинала в одной транзакции: либо и то и другое, либо ничего.
{
"success": true,
"updated": false,
"id": 6711,
"lang": "ru",
"url": "/o-nas/",
"link": "https://example.onpress.pro/ru/o-nas/",
"translations": { "ru": 6711, "en": 6703 },
"translation_group": { "origin": { "id": 6703, "lang": "en" }, "term_id": 170, "created": true, "changed": true, "unlinked": [] }
}
Правила translation_of:
- значение — id, слаг или путь оригинала; путь может быть с языковым префиксом;
- обязателен
lang, и он должен отличаться от языка оригинала; - группа дополняется, а не заменяется: третий язык встаёт рядом с первыми двумя;
- если в группе уже была страница этого языка, она выходит из группы, и ответ предупреждает;
- повторная публикация того же перевода даёт
translation_group.changed: false.
Не копируйте id оригинала в перевод. Выгрузка обычного вида кладёт во frontmatter id; перевод с забытым id перезаписал бы оригинал. Такой запрос отклоняется 409 lang_mismatch — поэтому для переводов и существует ?as=translation_source.
Когда перевод не привязывается
| Код | Когда | Что делать |
|---|---|---|
422 translation_link_failed | ссылка не сработала; причина в reason: polylang_missing (на сайте нет языков), lang_required (нет lang), origin_not_found, origin_has_no_language, same_language | исправить ссылку; или link_soft: true, чтобы опубликовать без привязки — причина придёт в translation_link_error |
409 translation_link_failed | ссылка верна, но в группе есть страницы, чей язык не совпадает с ключом; список в rejected | привести группу в порядок ручкой PUT /pages/{ref}/translations |
409 translation_group_conflict | публикация нашла существующую страницу по слагу, а та уже состоит в другой группе с другими страницами | перенос между группами — отдельная операция: DELETE /pages/{ref}/translations, затем привязать |
409 front_page_group_conflict | то же, и страница — главная своего языка | главные меняются только через PUT /site/front-page |
Управлять группой напрямую
Ручки под /pages/{ref}/translations. {ref} — id, слаг или путь с тильдами (ru~o-nas для /ru/o-nas/).
Посмотреть группу:
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/pages/6444/translations" | jq '{translations, missing_languages}'
{ "translations": { "en": 6444, "ru": 6445 }, "missing_languages": [] }
Положить группу целиком — карта «язык → страница»; кого нет в карте, тот выходит из группы:
curl -s -X PUT "$API/site/$SITE/pages/6444/translations" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"translations": {"en": 6444, "ru": "/ru/o-nas/"}}' | jq '{changed, group, unlinked}'
{ "changed": true, "group": { "term_id": 172, "created": true }, "unlinked": [] }
Повтор той же карты — changed: false, ничего не пишется, поэтому миграция может присылать группы на каждом прогоне.
Добавить один перевод, сохранив остальную группу:
curl -s -X POST "$API/site/$SITE/pages/6444/translations/ru" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"page": 6445}' | jq '{linked, translations}'
Вывести страницу из группы (она остаётся опубликованной со своим языком):
curl -s -X DELETE "$API/site/$SITE/pages/6445/translations" -H "Authorization: Bearer $OP_TOKEN" | jq '{was_in, remaining, group_deleted}'
Все проверки выполняются до записи, и неудачная не пишет ничего: 409 translations_rejected (неизвестный язык, нет страницы, язык страницы не совпадает с ключом, одна страница дважды), 409 page_in_other_group (страница из другой группы — сначала выведите её), 409 same_language, 409 page_language_mismatch, 409 page_has_no_language. После каждой изменившей группу записи все затронутые страницы, включая выпавшие, получают событие обновления: пересобираются hreflang и сбрасывается их кэш.
Что строится из группы
hreflangв<head>каждой страницы группы — собирает SEO-плагин по группе.- Переключатель
onpress/lang-switcherво фрейме показывает только языки, на которых страница существует и опубликована (scope: "page", по умолчанию). У страницы без переводов он не выводится вовсе.scope: "site"показывает все языки сайта: где перевод есть, ссылка ведёт на него, где нет — на главную этого языка. - Главные страницы языков: главная сайта хранит страницу основного языка, остальные языки получают главной её переводы из той же группы.
Остальное с языковым измерением
Язык есть не только у страниц. Меню стоят в области темы отдельно на каждый язык (меню). Набор данных может иметь вариант на язык: faq и faq@ru (наборы данных). Сайдбар документации строится по языку страницы (сайдбар). Фреймы общие для всех языков, а всё языкозависимое в них (меню, подписи вставок, повторители) собирается на языке страницы.
Страницы без языка
Страница без терма языка на многоязычном сайте не открывается: WordPress уводит её редиректом саму на себя. Сейчас API ставит язык всегда, но старые страницы без языка чинит отдельная ручка:
curl -s -X POST "$API/site/$SITE/pages/language" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"missing": true, "lang": "ru", "dry_run": true}' | jq '{total, changed, pages}'
missing: true берёт все опубликованные страницы без языка (statuses расширяет выборку), pages: [ … ] — конкретные страницы, lang — какой язык ставить (без него — основной). Уберите dry_run, чтобы записать. Ручка ставит только язык и в группы переводов не лезет.