fix(i18n): keep en navigation within /en/ via pagePrefix

- 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
This commit is contained in:
root committed 2026-09-15 23:00:32 +02:00
1 parent 1d25dcee47
commit e02ce1c629
11 files changed
+20 -16

No files matched your search

+4 -3
View File
@@ -16,9 +16,10 @@
- `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">`).
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. `<html lang="ru">` подменяется на текущую локаль (`<html lang="en">`).
> Видимый контент страниц переводится через отдельные `*.en.html` исходники, а НЕ тысячами `{{i18n:}}`-токенов — так проще править большие тексты.
+3
View File
@@ -165,6 +165,9 @@ function build() {
const source = readUtf8(sourcePath);
const vars = baseVars(relNorm, depth);
// Префикс до КОРНЯ ЛОКАЛИ (без en-сдвига) — для навигационных ссылок,
// чтобы они вели в ту же локаль (/en/), а не в корень сайта (/).
vars.pagePrefix = vars.assetPrefix;
// Для не-дефолтной локали всё содержимое уходит на уровень глубже (public/<lang>/),
// поэтому к assetPrefix добавляем один ../, а пустой casesPrefix — в ../cases/.
if (!isDefault) {
+2 -2
View File
@@ -667,9 +667,9 @@
</ul>
<p class="section__prose">
Навыки: мозаика на широком экране — см.
<a href="{{assetPrefix}}#skills">главную, #skills</a>. Портфолио: карусель на
<a href="{{pagePrefix}}#skills">главную, #skills</a>. Портфолио: карусель на
<code>100vw</code> с <code>--projects-card-width</code> и JS-прокруткой — на главной
<a href="{{assetPrefix}}#projects">#projects</a>.
<a href="{{pagePrefix}}#projects">#projects</a>.
</p>
<h3>Wide в кейсах</h3>
+2 -2
View File
@@ -70,7 +70,7 @@
<strong>Nikolai Mamadaev</strong>
is the operator of the Site. For questions regarding data processing, please use the contact information in the “Contacts” section on
<a href="{{assetPrefix}}#contact">the home page</a>
<a href="{{pagePrefix}}#contact">the home page</a>
(Telegram, VKontakte, Yandex, and email by copying the address using the button).
</p>
@@ -126,7 +126,7 @@
<h2 id="changes">Changes</h2>
<p class="section__prose">
The text of the policy may be updated as technologies on the site change. The current version is published on the “Cookie Policy” page at <a href="{{assetPrefix}}cookie-policy/">/cookie-policy.html</a> relative to the site root.
The text of the policy may be updated as technologies on the site change. The current version is published on the “Cookie Policy” page at <a href="{{pagePrefix}}cookie-policy/">/cookie-policy.html</a> relative to the site root.
</p>
</div>
</main>
+2 -2
View File
@@ -68,7 +68,7 @@
<h2 id="operator">Кто отвечает за сайт</h2>
<p class="section__prose">
Оператором сайта является <strong>Николай Мамадаев</strong>. По вопросам обработки данных используйте контакты в блоке «Контакты» на
<a href="{{assetPrefix}}#contact">главной странице</a>
<a href="{{pagePrefix}}#contact">главной странице</a>
(Telegram, ВКонтакте, Яндекс и почта через копирование адреса кнопкой).
</p>
@@ -124,7 +124,7 @@
<h2 id="changes">Изменения</h2>
<p class="section__prose">
Текст политики может обновляться при изменении технологий на сайте. Актуальная версия публикуется на странице «Политика cookie» по адресу <a href="{{assetPrefix}}cookie-policy/">/cookie-policy.html</a> относительно корня сайта.
Текст политики может обновляться при изменении технологий на сайте. Актуальная версия публикуется на странице «Политика cookie» по адресу <a href="{{pagePrefix}}cookie-policy/">/cookie-policy.html</a> относительно корня сайта.
</p>
</div>
</main>
+1 -1
View File
@@ -1,7 +1,7 @@
<aside class="cookie-notice glass-neutral" hidden data-cookie-notice>
<p class="cookie-notice__text">
This site uses cookies for a comfortable experience. Read more in
<a href="{{assetPrefix}}cookie-policy/" data-cookie-policy-link>cookie policy</a>.
<a href="{{pagePrefix}}cookie-policy/" data-cookie-policy-link>cookie policy</a>.
</p>
<button type="button" class="btn btn--secondary btn--flat cookie-notice__accept" data-cookie-accept>
<span>Accept</span>
+1 -1
View File
@@ -1,7 +1,7 @@
<aside class="cookie-notice glass-neutral" hidden data-cookie-notice>
<p class="cookie-notice__text">
Сайт использует cookie для комфортной работы. Подробнее — в
<a href="{{assetPrefix}}cookie-policy/" data-cookie-policy-link>политике cookie</a>.
<a href="{{pagePrefix}}cookie-policy/" data-cookie-policy-link>cookie policy</a>.
</p>
<button type="button" class="btn btn--secondary btn--flat cookie-notice__accept" data-cookie-accept>
<span>Принять</span>
+1 -1
View File
@@ -55,7 +55,7 @@
</div>
</div>
</div>
<a class="fab fab--primary" href="{{assetPrefix}}#contact" aria-label="Связаться">
<a class="fab fab--primary" href="{{pagePrefix}}#contact" aria-label="Связаться">
<span class="fab-primary__ico" aria-hidden="true"></span>
</a>
</div>
+1 -1
View File
@@ -2,6 +2,6 @@
<div class="footer__inner">
<span class="footer__chunk footer__chunk--date">© 2015–2026<span class="footer__sep" aria-hidden="true"> · </span></span>
<span class="footer__chunk footer__chunk--main">{{i18n:footer.credit}}</span>
<span class="footer__chunk footer__chunk--meta footer__attr"><span class="footer__sep" aria-hidden="true"> · </span><a href="{{assetPrefix}}cases/design-system/">{{i18n:footer.designSystem}}</a><span class="footer__sep" aria-hidden="true"> · </span><a href="{{assetPrefix}}cookie-policy/">{{i18n:footer.cookiePolicy}}</a></span>
<span class="footer__chunk footer__chunk--meta footer__attr"><span class="footer__sep" aria-hidden="true"> · </span><a href="{{pagePrefix}}cases/design-system/">{{i18n:footer.designSystem}}</a><span class="footer__sep" aria-hidden="true"> · </span><a href="{{pagePrefix}}cookie-policy/">{{i18n:footer.cookiePolicy}}</a></span>
</div>
</footer>
+1 -1
View File
@@ -1,7 +1,7 @@
<a class="skip-link" href="#case-main">{{i18n:skip.case}}</a>
<header class="header" data-theme-picker>
<div class="header__inner">
<a class="logo" href="{{assetPrefix}}" aria-label="{{i18n:logo.aria}}">НМ</a>
<a class="logo" href="{{pagePrefix}}" aria-label="{{i18n:logo.aria}}">НМ</a>
<div class="header__cluster">
<nav class="nav" aria-label="{{i18n:nav.aria}}">
<a href="#contact">{{i18n:nav.contact}}</a>
+2 -2
View File
@@ -2,10 +2,10 @@
<header class="header" data-theme-picker>
<div class="header__inner">
<a class="logo" href="{{assetPrefix}}" aria-label="{{i18n:logo.aria}}">НМ</a>
<a class="logo" href="{{pagePrefix}}" aria-label="{{i18n:logo.aria}}">НМ</a>
<div class="header__cluster">
<nav class="nav" aria-label="{{i18n:nav.aria}}">
<a href="{{assetPrefix}}">{{i18n:back.home}}</a>
<a href="{{pagePrefix}}">{{i18n:back.home}}</a>
</nav>
{{> lang-switch}}
</div>