- build-pages.mjs: add pagePrefix (locale-root-relative, no en offset) - use pagePrefix for nav links: logo, back-home, footer design-system/ cookie-policy/, FAB #contact, cookie-notice link, cookie-policy body link, case-page #skills/#projects anchors (assetPrefix pointed to site root and leaked en pages to ru) - docs/translation.md: document assetPrefix vs pagePrefix
9.0 KiB
Локализация и перевод
Сайт локализован build-time: для каждого языка собирается отдельная папка/URL (/ — русский, /en/ — английский). Клиентского переключателя нет — это сделано ради SEO, корректной генерации PDF (Puppeteer) и совместимости с content-edit-mode.
Архитектура
LOCALES = ["ru", "en"],DEFAULT_LOCALE = "ru"— задаётся вscripts/build-pages.mjs.- Дефолтная локаль собирается в корень
public/, остальные — вpublic/<lang>/(public/en/). - Переключатель языка:
src/partials/lang-switch.html(показывает RU/EN,aria-currentна активном).
Как работает сборка (build-pages.mjs)
- UI-строки (навигация, футер, кнопки) вынесены в словари
src/i18n/{ru,en}.jsonи вставляются токеном{{i18n:key}}. Для не-дефолтной локали значение берётся изen.json, при отсутствии ключа — фоллбэк наru.json. Неразрезолвенный токен остаётся как{{i18n:key}}(признак пропущенного ключа). - Тело страницы (главная, cookie-policy, кейсы) живёт в per-locale исходниках:
src/pages/<page>.html— русский (дефолт).src/pages/<page>.en.html— английский override. Если файла нет, берётся русский.- В сборке базовый walk исключает
*.en.html, чтобы они не собирались как отдельные страницы.
- Partials: для не-дефолтной локали инклуд
{{> name}}сначала ищетsrc/partials/name.en.html, иначеname.html. Так переводятся частично захардкоженные partials (например,projects-section.en.html). assetPrefix: относительный префикс к ассетам. Дляenк значению изbaseVarsдобавляется один уровень../, потому что страницы лежат в подпапкеpublic/en/. Используй только для путей к ассетам (css/js/изображения).pagePrefix: префикс до корня локали (/en/для en), без en-сдвига. Используй его для ВСЕХ навигационных ссылок (лого, «назад», футер, якоря#section, ссылки на другие страницы), чтобы они вели в ту же локаль, а не в корень сайта.assetPrefixпредназначен только для путей к ассетам — для навигации он «перепрыгивает» через/en/и ведёт на ru, поэтому в навигационныхhrefего НЕ применяй.- Статик-ассеты (css/js/vendor/favicon) в partials (
head-links.html,head-scripts.html,theme-init-script.html,head-plyr.html,page-scripts.html) прописаны root-absolute (/css/...,/js/...,/vendor/...,/assets/shared/...). Причина: dev-сервер (serve-dev.mjs) отдаётindex.html(200) для отсутствующих файлов, маскируя 404, поэтому относительные{{assetPrefix}}cssна/en/ломались бы. Держи статик-ссылки root-absolute. <html lang="ru">подменяется на текущую локаль (<html lang="en">).
Видимый контент страниц переводится через отдельные
*.en.htmlисходники, а НЕ тысячами{{i18n:}}-токенов — так проще править большие тексты.
Перевод через apinex
Бесплатный инструмент перевода: POST https://api.apinex.bond/v1/tools/translator (дока: https://apinex.bond/developers/tools/translator).
- Ключ — в
/root/.hermes/.env, переменнаяHERMES_CUSTOM_APINEX_API_KEY(илиAPINEX_API_KEY). Никогда не выводи значение ключа в чат/логи. - Заголовок:
Authorization: Bearer $KEY. - Тело:
{ "text": "строка|массив", "target_lang": "EN", "source_lang": "RU", "tag_handling": "html", "preserve_formatting": true }. Батч до 50 строк / 100k символов, лимит 60 запросов/мин.
Скрипт scripts/translate.mjs
Обёртка над API (Node, без зависимостей). Автоматически защищает от перевода шаблонные токены {{assetPrefix}}, {{> partial}}, {{i18n:…}} и содержимое <code> (через HTML-комментарии, восстанавливаются после). HTML-режим авто-включается, если во входе есть <…>.
cd /root/.hermes/projects/site-portfolio
# ключ в окружение (без печати значения)
export HERMES_CUSTOM_APINEX_API_KEY="$(grep -iE 'HERMES_CUSTOM_APINEX_API_KEY' /root/.hermes/.env | head -1 | cut -d= -f2- | sed 's/^["'\''"]//;s/["'\''"]$//')"
# список поддерживаемых языков
node scripts/translate.mjs --list-languages
# одна строка
node scripts/translate.mjs --target EN --source RU --text "Политика cookie"
# HTML из stdin (токены и <code> защищены)
echo '<p>{{assetPrefix}} <code>siteThemeV1</code></p>' | node scripts/translate.mjs --target EN --html
# файл страницы -> per-locale override
node scripts/translate.mjs --target EN --source RU --file src/pages/foo.html --out src/pages/foo.en.html
# пакет: одна строка на line
printf 'строка1\nстрока2\n' | node scripts/translate.mjs --target EN --batch
# npm-алиас
npm run translate -- --target EN --text "..."
--targetобязателен;--sourceопционален (авто-детект).--html/--no-html— принудительно вкл/выкл HTML-режим (иначе авто).--outпишет в файл, иначе вывод в stdout.
Скилл translate-apinex
Загружается агентом при запросе перевода: содержит API, где ключ, защиту токенов и воркфлоу. Используй его вместо ручного копирования curl.
Воркфлоу: перевести страницу
- Создать
src/pages/<page>.en.html(копия<page>.html). - Прогнать через скрипт:
Скрипт переведёт видимый текст, оставив
node scripts/translate.mjs --target EN --source RU --file src/pages/<page>.en.html --out src/pages/<page>.en.html{{…}}и<code>нетронутыми. - Если страница инклудит partials с захардкоженным русским (
projects-section,contact-section,cookie-notice,fab-stack,mini-projects-section), создать их.en.htmlи перевести так же — сборка подхватит.en-вариант дляen. - UI-строки, оставшиеся как
{{i18n:key}}, добавить вsrc/i18n/en.json. npm run build:pages.- Проверить:
curl -s http://localhost:4175/en/<page>/ | grep -o '<html lang="en">' curl -s http://localhost:4175/en/<page>/ | grep -cE 'остался русский текст' # ожидаем 0
Добавить новый язык
- Добавить код в массив
LOCALESвscripts/build-pages.mjs. - Создать
src/i18n/<code>.json(UI-строки). - Создавать
<page>.<code>.html/<name>.<code>.htmloverride для нужных страниц/партилов (или полагаться на фоллбэк ru). - Пересобрать. URL новой локали —
/<code>/.
Где что лежит
| Файл | Назначение |
|---|---|
scripts/build-pages.mjs |
Сборка локалей, резолв {{i18n:}}, override страниц/партилов, assetPrefix, lang |
src/i18n/ru.json, src/i18n/en.json |
Словари UI-строк |
src/pages/<page>.en.html |
Переведённое тело страницы (override) |
src/partials/<name>.en.html |
Переведённый partial (override) |
src/partials/lang-switch.html |
Переключатель RU/EN |
scripts/translate.mjs |
CLI-обёртка над apinex translator |
.env (/root/.hermes/.env) |
HERMES_CUSTOM_APINEX_API_KEY — ключ apinex (не коммитить) |