docs: document i18n localization and apinex translation workflow
- docs/translation.md: build-time ru/en architecture, build-pages mechanics, apinex translator API + scripts/translate.mjs usage, page translation workflow, adding a new language - AGENTS.md: link docs/translation.md in key-files table
This commit is contained in:
1 parent
86ea6731fa
commit
1d25dcee47
2 files changed
+104
No files matched your search
@@ -80,3 +80,4 @@
|
||||
| `src/pages/cases/mini-projects-showcase.html` | Карточки активности (mp-card) |
|
||||
| `docs/case-page-blocks.md` | Инструкция по блокам лонгридов |
|
||||
| `docs/resume-pdf.md` | Генерация PDF резюме (Puppeteer) |
|
||||
| `docs/translation.md` | Локализация (ru/en) и перевод через apinex |
|
||||
@@ -0,0 +1,103 @@
|
||||
# Локализация и перевод
|
||||
|
||||
Сайт локализован **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`)
|
||||
|
||||
1. **UI-строки** (навигация, футер, кнопки) вынесены в словари `src/i18n/{ru,en}.json` и вставляются токеном `{{i18n:key}}`. Для не-дефолтной локали значение берётся из `en.json`, при отсутствии ключа — фоллбэк на `ru.json`. Неразрезолвенный токен остаётся как `{{i18n:key}}` (признак пропущенного ключа).
|
||||
2. **Тело страницы** (главная, cookie-policy, кейсы) живёт в per-locale исходниках:
|
||||
- `src/pages/<page>.html` — русский (дефолт).
|
||||
- `src/pages/<page>.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/`. Навигационные ссылки `{{casesPrefix}}cases/...` остаются относительными и для `en` корректно ведут в `/en/cases/...`.
|
||||
5. **Статик-ассеты** (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.
|
||||
6. `<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-режим авто-включается, если во входе есть `<…>`.
|
||||
|
||||
```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 (токены и <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.
|
||||
|
||||
## Воркфлоу: перевести страницу
|
||||
|
||||
1. Создать `src/pages/<page>.en.html` (копия `<page>.html`).
|
||||
2. Прогнать через скрипт:
|
||||
```bash
|
||||
node scripts/translate.mjs --target EN --source RU --file src/pages/<page>.en.html --out src/pages/<page>.en.html
|
||||
```
|
||||
Скрипт переведёт видимый текст, оставив `{{…}}` и `<code>` нетронутыми.
|
||||
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/<page>/ | grep -o '<html lang="en">'
|
||||
curl -s http://localhost:4175/en/<page>/ | grep -cE 'остался русский текст' # ожидаем 0
|
||||
```
|
||||
|
||||
## Добавить новый язык
|
||||
|
||||
1. Добавить код в массив `LOCALES` в `scripts/build-pages.mjs`.
|
||||
2. Создать `src/i18n/<code>.json` (UI-строки).
|
||||
3. Создавать `<page>.<code>.html` / `<name>.<code>.html` override для нужных страниц/партилов (или полагаться на фоллбэк ru).
|
||||
4. Пересобрать. 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 (не коммитить) |
|
||||
Reference in new issue
Block a user