Посібник з i18n для проєкту
mysimulator.uk постачає кожну статтю, туторіал і довідник у двох
локалях — англійській (en) та українській
(uk) — у вигляді повністю дзеркальних статичних
HTML-файлів, а не через рантайм-шар перекладу. Цей посібник описує
конвенцію каталогів, контракт URL і метаданих, якому має
відповідати кожна сторінка, та чек-лист для додавання нової
локалізованої сторінки без поломки перемикача мов.
1. Дзеркальна архітектура каталогів
Сайт не має етапу білду і не використовує рантайм-фреймворк i18n —
жодного i18next, жодного узгодження локалі на боці
сервера, жодних клієнтських таблиць рядків для контенту сторінок.
Натомість англійська версія є джерелом істини в
корені сайту, а українська — паралельним деревом, коренем якого є
/uk/, і яке точно повторює ту саму структуру шляхів:
mysimulator.uk/
├── content/
│ ├── articles/backpropagation.html ← EN
│ ├── tutorials/build-physics-engine.html ← EN
│ └── references/i18n-guide.html ← EN (ця сторінка)
├── uk/
│ └── content/
│ ├── articles/backpropagation.html ← дзеркало UK
│ ├── tutorials/build-physics-engine.html ← дзеркало UK
│ └── references/i18n-guide.html ← дзеркало UK
└── shared/
├── theme.css
├── components.css
└── components.js ← навбар/футер вставляються на кожній сторінці
Кожен EN-файл за шляхом content/<type>/<slug>.html
має структурно ідентичного «близнюка» за адресою
uk/content/<type>/<slug>.html. Slug ніколи не
змінюється між локалями — лише текст і префікс шляху
uk/. Це робить відповідність між локалями суто
механічною: додати або прибрати uk/
зі шляху.
2. Структура URL і правила canonical
Оскільки обидві локалі — це реальні, незалежно доступні HTML-файли,
кожна з них потребує власного тега
<link rel="canonical">, що вказує на саму
себе, а не на EN-версію. Це відрізняється від i18n на основі
параметрів, де для всіх локалей існує лише один канонічний URL.
| Локаль | URL | Ціль canonical |
|---|---|---|
| en | /content/references/i18n-guide.html |
сама на себе |
| uk | /uk/content/references/i18n-guide.html |
сама на себе |
Обидва теги canonical використовують абсолютну
форму https://www.mysimulator.uk/… — відносні
canonical неоднозначні, щойно сторінка дзеркалюється на двох різних
рівнях глибини шляху, і деякі краулери взагалі ігнорують відносні
canonical URL.
3. Контракт глибини data-base
Навбар і футер не дублюються на кожній сторінці — вони вставляються
під час рантайму скриптом shared/components.js у два
елементи-заповнювачі, #navbar-root і
#footer-root. Оскільки заповнювачі завантажують
часткові файли та ресурси за відносними шляхами, кожна
сторінка повинна повідомити скрипту, скільки рівнів каталогів
відділяють її від кореня сайту, через атрибут
data-base:
// shared/components.js вирішує кожен ресурс/посилання відносно data-base
function renderNavbar(rootEl) {
const base = rootEl.getAttribute('data-base') || './';
const locale = detectLocale(); // 'en' | 'uk', з URL-шляху
const homeHref = locale === 'uk' ? base + 'uk/' : base;
rootEl.innerHTML = buildNavHTML(base, homeHref, locale);
}
Неправильне значення data-base — найпоширеніша
i18n-помилка на цьому сайті: вона мовчки завантажує CSS/JS навбару
з неправильної глибини, що або спричиняє 404 для стилів, або —
гірше — рендерить застарілий нестилізований навбар, який усе одно
виглядає правдоподібно при швидкій візуальній перевірці.
content/references/ —
це 2 папки від /. uk/content/references/
— це 3 папки, тому що сам uk/ рахується як один
рівень.
4. Підключення hreflang
Кожна сторінка — в обох локалях — містить однакові три теги
<link rel="alternate" hreflang="…">, що вказують
на ті самі два абсолютні URL. Це повідомляє пошуковим системам, що
два файли є перекладами однієї логічної сторінки, а не дубльованим
чи непов'язаним контентом:
<link rel="alternate" hreflang="en"
href="https://www.mysimulator.uk/content/references/i18n-guide.html" />
<link rel="alternate" hreflang="uk"
href="https://www.mysimulator.uk/uk/content/references/i18n-guide.html" />
<link rel="alternate" hreflang="x-default"
href="https://www.mysimulator.uk/content/references/i18n-guide.html" />
x-default завжди вказує на EN-URL,
оскільки англійська — це резервний варіант для будь-якої локалі,
яку сайт ще не підтримує. Набір із трьох тегів побайтово
ідентичний між EN-файлом та його UK-дзеркалом — відрізняється лише
власний тег canonical сторінки.
5. Структуровані дані для кожної локалі
Блок JSON-LD TechArticle/Article у
<head> кожної сторінки повинен встановлювати
inLanguage відповідно до локалі самого файлу, а
Open Graph-тег og:locale підпорядковується тому ж
правилу, використовуючи повний код локалі:
| Поле | EN-файл | UK-файл |
|---|---|---|
html lang |
en |
uk |
og:locale |
en_US |
uk_UA |
JSON-LD inLanguage |
en |
uk |
JSON-LD headline/description |
Англійський текст | Український текст |
datePublished лишається однаковим в обох файлах — він
описує, коли логічна сторінка була опублікована, а не коли був
зроблений кожен переклад. Заголовки, meta-описи, ключові слова та
headline/description у JSON-LD мають бути справді перекладені, а
не скопійовані, оскільки саме вони з'являються безпосередньо в
локалізованих результатах пошуку.
6. Перемикач мов і 404-безпека
Перемикач мов у навбарі не просто додає чи прибирає
/uk/ з location.pathname — ранні версії
сайту робили саме так, і це генерувало 404, коли сторінка існувала
лише в одній локалі. Поточна реалізація перевіряє маніфест відомих
локалізованих slug'ів перед переписуванням URL і в іншому випадку
повертається на головну сторінку відповідної локалі:
function switchLocale(targetLocale) {
const path = location.pathname;
const isUk = path.startsWith('/uk/');
const currentLocale = isUk ? 'uk' : 'en';
if (currentLocale === targetLocale) return;
const enPath = isUk ? path.replace(/^\/uk\//, '/') : path;
const candidate = targetLocale === 'uk' ? '/uk' + enPath : enPath;
// KNOWN_SLUGS генерується під час збору контенту (articles.json тощо)
if (KNOWN_SLUGS.has(candidate)) {
location.href = candidate;
} else {
// безпечний фолбек: головна сторінка локалі, ніколи не «вгаданий» URL
location.href = targetLocale === 'uk' ? '/uk/' : '/';
}
}
fetch(candidate, {method:'HEAD'}))
спрацювала б, але додає мережевий раунд-тріп до кожного кліку
перемикача і не може виконатися до першого рендеру. Статичний
маніфест, зібраний під час збору контенту, спрацьовує миттєво і
відповідає тому, як решта сайту уникає клієнтських запитів даних
для навігації.
8. Додавання нової локалізованої сторінки
-
Напишіть EN-сторінку за шляхом
content/<type>/<slug>.html, скопіювавши head/структуру наявної сторінки того ж типу (стаття, туторіал чи довідник), щоб класи CSS, форма JSON-LD і глибинаdata-baseвже були правильними. -
Встановіть
lang="en", EN-canonical, усі три посиланняhreflang(ціль EN, ціль UK, x-default → EN),og:locale=en_USіinLanguage: "en"у блоці JSON-LD. -
Скопіюйте готовий EN-файл до
uk/content/<type>/<slug>.html— той самий slug, на один рівень каталогу більше. -
У UK-копії: встановіть
lang="uk", замінітьcanonicalна UK-URL, збільштеdata-baseна один додатковий../, встановітьog:locale=uk_UAтаinLanguage: "uk", а потім перекладіть кожен видимий рядок — заголовок, meta-опис, ключові слова, headline/lead, текст секцій, комірки таблиць та коментарі в коді, де це доречно. Формули, синтаксис коду й назви змінних лишаються незмінними. -
Залиште
datePublishedідентичним в обох файлах. -
Зареєструйте новий slug у маніфесті контенту сайту
(
articles.jsonчи еквіваленті), щоб набірKNOWN_SLUGSперемикача мов і sitemap підхопили його — це окремий крок від написання самого HTML.
9. Типові граблі
-
Неправильна глибина data-base: забути, що
uk/додає один рівень, ламає всі відносні ресурси та внутрішні посилання на сторінці. -
Відносні canonical URL: завжди
використовуйте повну форму
https://www.mysimulator.uk/…; відносні canonical стирають різницю між EN і UK. -
Скопійований JSON-LD: залишити
inLanguage: "en"чи англійський headline у UK-файлі після перекладу видимого тексту. - Вгадані URL перемикача: будувати URL цільової локалі підстановкою рядка без перевірки, чи він справді існує — завжди проходьте через маніфест відомих slug'ів.
- Невідредагований машинний переклад: UK-дзеркало має читатися як природна українська мова, а не буквальний дослівний прохід — лише формули та ідентифікатори мають лишатися незмінними.