# Локализация и перевод Сайт локализован **build-time**: для каждого языка собирается отдельная папка/URL (`/` — русский, `/en/` — английский). Клиентского переключателя нет — это сделано ради SEO, корректной генерации PDF (Puppeteer) и совместимости с content-edit-mode. ## Архитектура - `LOCALES = ["ru", "en"]`, `DEFAULT_LOCALE = "ru"` — задаётся в `scripts/build-pages.mjs`. - Дефолтная локаль собирается в корень `public/`, остальные — в `public//` (`public/en/`). - Переключатель языка: `src/partials/lang-switch.html` (показывает RU/EN, `aria-current` на активном). ## Как работает сборка (`build-pages.mjs`) 1. **UI-строки** (навигация, футер, кнопки) вынесены в словари `src/i18n/{ru,en}.json` и вставляются токеном `{{i18n:key}}`. Для не-дефолтной локали значение берётся из `en.json`, при отсутствии ключа — фоллбэк на `ru.json`. Неразрезолвенный токен остаётся как `{{i18n:key}}` (признак пропущенного ключа). 2. **Тело страницы** (главная, cookie-policy, кейсы) живёт в per-locale исходниках: - `src/pages/.html` — русский (дефолт). - `src/pages/.en.html` — английский override. Если файла нет, берётся русский. - В сборке базовый walk **исключает** `*.en.html`, чтобы они не собирались как отдельные страницы. 3. **Partials**: для не-дефолтной локали инклуд `{{> name}}` сначала ищет `src/partials/name.en.html`, иначе `name.html`. Так переводятся частично захардкоженные partials (например, `projects-section.en.html`). 4. **`assetPrefix`**: относительный префикс к ассетам. Для `en` к значению из `baseVars` добавляется один уровень `../`, потому что страницы лежат в подпапке `public/en/`. Используй только для путей к ассетам (css/js/изображения). 5. **`pagePrefix`**: префикс до **корня локали** (`/en/` для en), без en-сдвига. Используй его для ВСЕХ навигационных ссылок (лого, «назад», футер, якоря `#section`, ссылки на другие страницы), чтобы они вели в ту же локаль, а не в корень сайта. `assetPrefix` предназначен только для путей к ассетам — для навигации он «перепрыгивает» через `/en/` и ведёт на ru, поэтому в навигационных `href` его НЕ применяй. 6. **Статик-ассеты** (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. 7. `` подменяется на текущую локаль (``). > Видимый контент страниц переводится через отдельные `*.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:…}}` и содержимое `` (через HTML-комментарии, восстанавливаются после). HTML-режим авто-включается, если во входе есть `<…>`. ```bash 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 (токены и защищены) echo '

{{assetPrefix}} siteThemeV1

' | 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. ## Воркфлоу: перевести страницу 1. Создать `src/pages/.en.html` (копия `.html`). 2. Прогнать через скрипт: ```bash node scripts/translate.mjs --target EN --source RU --file src/pages/.en.html --out src/pages/.en.html ``` Скрипт переведёт видимый текст, оставив `{{…}}` и `` нетронутыми. 3. Если страница инклудит partials с захардкоженным русским (`projects-section`, `contact-section`, `cookie-notice`, `fab-stack`, `mini-projects-section`), создать их `.en.html` и перевести так же — сборка подхватит `.en`-вариант для `en`. 4. UI-строки, оставшиеся как `{{i18n:key}}`, добавить в `src/i18n/en.json`. 5. `npm run build:pages`. 6. Проверить: ```bash curl -s http://localhost:4175/en// | grep -o '' curl -s http://localhost:4175/en// | grep -cE 'остался русский текст' # ожидаем 0 ``` ## Добавить новый язык 1. Добавить код в массив `LOCALES` в `scripts/build-pages.mjs`. 2. Создать `src/i18n/.json` (UI-строки). 3. Создавать `..html` / `..html` override для нужных страниц/партилов (или полагаться на фоллбэк ru). 4. Пересобрать. URL новой локали — `//`. ## Где что лежит | Файл | Назначение | |---|---| | `scripts/build-pages.mjs` | Сборка локалей, резолв `{{i18n:}}`, override страниц/партилов, `assetPrefix`, `lang` | | `src/i18n/ru.json`, `src/i18n/en.json` | Словари UI-строк | | `src/pages/.en.html` | Переведённое тело страницы (override) | | `src/partials/.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 (не коммитить) |