From 37554119542a665eafdc611091cd886f31a059dc Mon Sep 17 00:00:00 2001 From: SashegDev Date: Thu, 20 Aug 2026 09:31:16 +0000 Subject: [PATCH] site: add RU/EN language toggle with full i18n on landing --- server/site/css/style.css | 25 ++++ server/site/index.html | 138 +++++++++--------- server/site/js/main.js | 289 ++++++++++++++++++++++++++++++++++---- 3 files changed, 359 insertions(+), 93 deletions(-) diff --git a/server/site/css/style.css b/server/site/css/style.css index d934e63..42df3df 100644 --- a/server/site/css/style.css +++ b/server/site/css/style.css @@ -104,6 +104,30 @@ ul { list-style: none; } .nav-cta:hover { background: var(--accent-soft); transform: translateY(-1px); } +.nav-right { + display: flex; + align-items: center; + gap: 16px; +} + +.lang-toggle { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 8px; + background: transparent; + color: var(--text-secondary); + font-size: 13px; + font-weight: 600; + letter-spacing: 0.4px; + cursor: pointer; + transition: color .2s ease, border-color .2s ease, background .2s ease; +} + +.lang-toggle:hover { color: var(--text); border-color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.04); } + /* ---------- Hero ---------- */ .hero { @@ -804,6 +828,7 @@ ul { list-style: none; } @media (max-width: 720px) { .nav-links { display: none; } + .nav-cta { display: none; } .section { padding: 90px 20px; } .features-grid, .news-grid, .theme-grid { grid-template-columns: 1fr; } .server-stats { grid-template-columns: 1fr; } diff --git a/server/site/index.html b/server/site/index.html index edabaa9..932a1b5 100644 --- a/server/site/index.html +++ b/server/site/index.html @@ -1,5 +1,5 @@ - + @@ -20,14 +20,20 @@ Zern - Play now + @@ -40,21 +46,21 @@
-

CUSTOM MINECRAFT LAUNCHER

-

Awaken from
the cold.

-

Zern brings your favourite modpacks to life. One launcher, every loader, no hassle — built for the ZernMC server and beyond.

+

КАСТОМНЫЙ MINECRAFT ЛАУНЧЕР

+

Очнись от
холода.

+

Zern оживляет твои любимые модпаки. Один лаунчер, любой загрузчик, без лишних хлопот — создан для сервера ZernMC и не только.

v · · Windows

- Scroll to explore + Листай вниз
@@ -64,24 +70,24 @@
01 / 06 -

The server is alive.

-

ZernMC is a custom Minecraft server with its own lore, hand-written plugins and a living community. This is what's happening right now.

+

Сервер жив.

+

ZernMC — это кастомный Minecraft-сервер со своим лором, самописными плагинами и живым комьюнити. Вот что происходит прямо сейчас.

-
Status
-
Checking…
-
1.20.1 / OBТ
+
Статус
+
Проверяем…
+
1.20.1 / ОБТ
-
Players online
+
Игроков онлайн
— / —
mc.zernmc.ru
-
Mode
-
Survival · Season Prologue
+
Режим
+
Survival · Сезон Prologue
1.21.1 NeoForge · Create: Aeronautics
@@ -90,14 +96,14 @@
02 / 06 -

Server highlights.

-

Moments captured on the ZernMC server — wars, builds and chaos.

+

Хайлайты сервера.

+

Моменты, запечатлённые на сервере ZernMC — войны, постройки и хаос.

@@ -105,8 +111,8 @@
03 / 06 -

Everything you need.

-

A launcher that does the heavy lifting, so you can just play.

+

Всё, что тебе нужно.

+

Лаунчер, который делает всю тяжёлую работу, чтобы ты просто играл.

@@ -115,8 +121,8 @@ -

Forge, NeoForge, Fabric

-

Every major mod loader handled automatically. Libraries are prefetched and verified, so installs just work — even on slow networks.

+

Forge, NeoForge, Fabric

+

Все основные модлоадеры подхватываются автоматически. Библиотеки предзагружаются и проверяются, так что установка работает — даже на медленном интернете.

@@ -124,8 +130,8 @@ -

Dark, fast, native UI

-

A lightweight JavaFX interface with a clean dark theme. Starts quick, responds instantly, and stays out of your way.

+

Тёмный, быстрый нативный UI

+

Лёгкий интерфейс на JavaFX с чистым тёмным оформлением. Быстро запускается, мгновенно реагирует и не мешает.

@@ -133,8 +139,8 @@ -

Smart mirrors & proxy

-

Downloads route through fast mirrors with automatic failover. Blocked or throttled? The proxy takes over so you never stall at 80%.

+

Умные зеркала и прокси

+

Загрузки идут через быстрые зеркала с автоматическим фолбэком. Заблокировали или зарезали скорость? Прокси подхватит — ты больше не зависнешь на 80%.

@@ -142,8 +148,8 @@ -

News & server status

-

Changelogs, events and updates stream straight into the launcher. You always know what's new before you hit play.

+

Новости и статус сервера

+

Чейнджлоги, ивенты и обновления приходят прямо в лаунчер. Ты всегда знаешь, что нового, ещё до нажатия «Играть».

@@ -151,8 +157,8 @@ -

One-click pack install

-

Pick a pack, hit install. Version manifests, vanilla assets and libraries are pulled and verified automatically. Simple.

+

Установка сборки в один клик

+

Выбери сборку и жми «Установить». Манифесты версий, ванильные ассеты и библиотеки подтягиваются и проверяются автоматически. Просто.

@@ -160,40 +166,40 @@
04 / 06 -

What's inside the season.

-

Prologue — a modded season built around Create: Aeronautics, war and survival. OBT is the testing ground for the whole idea.

+

Что внутри сезона.

+

Prologue — модовый сезон на базе Create: Aeronautics, война и выживание. ОБТ — полигон, где всё это обкатывается.

- OBT · 1.20.1 Forge -

War in Zarya

-

Two-day event set in the city of Zarya: the Fenix group attacks, Lastochka defends, and civilians pick a side — or rise up. Military rules apply.

+ ОБТ · 1.20.1 Forge +

Война в Заре

+

Двухдневный ивент в городе Заря: группировка «Феникс» атакует, «Ласточка» обороняется, а жители выбирают сторону — или поднимают бунт. Военные правила.

- OBT · test stage -

Open beta server

-

The beta is an event server: city battles, an atomic-reactor city-life scenario and rule sets that will shape the real season.

+ ОБТ · тестовая стадия +

Открытый бета-сервер

+

Бета — это ивентовый сервер: городские бои, сценарий атомного реактора и правила, которые лягут в основу настоящего сезона.

- Season · 1.21.1 NeoForge -

Ships in the sky

-

Create: Aeronautics gives you airships, submarines and flying contraptions. Trains, diesel engines, big cannons and a nuclear reactor to master.

+ Сезон · 1.21.1 NeoForge +

Корабли в небе

+

Create: Aeronautics даёт дирижабли, субмарины и летающие механизмы. Поезда, дизели, большие пушки и атомный реактор — осваивай.

- Season · Prologue -

A year-long run

-

Each season is a marathon, not a sprint. Start this chapter from day one and shape it with the community.

+ Сезон · Prologue +

Марафон на год

+

Сезон — это марафон, а не спринт. Начни эту главу с первого дня и строй её вместе с комьюнити.

- Server -

Own lore & plugins

-

Hand-written plugins, events written for the server story, and systems the launcher integrates with out of the box.

+ Сервер +

Свой лор и плагины

+

Самописные плагины, ивенты под историю сервера и системы, которые из коробки работают с лаунчером.

- Community -

Voice & friends

-

Simple Voice Chat in-game, a friends system in the launcher and a Telegram community that runs the show — decisions are voted on by players.

+ Комьюнити +

Голос и друзья

+

Simple Voice Chat в игре, система друзей в лаунчере и Telegram-комьюнити, которое реально рулит — решения принимаются голосованием игроков.

@@ -202,8 +208,8 @@
05 / 06 -

Get Zern.

-

Download the launcher for Windows and join the ZernMC server.

+

Скачать Zern.

+

Скачай лаунчер для Windows и заходи на сервер ZernMC.

@@ -216,22 +222,22 @@
- Download latest + Скачать актуальную версию
- Mirrors + Зеркала
-

No paywalls, no ads. Just a launcher. System requirements: Windows 10+, 4 GB RAM.

+

Без платных подписок и рекламы. Просто лаунчер. Системные требования: Windows 10+, 4 ГБ ОЗУ.

06 / 06 -

What's new.

-

Latest changelogs and updates from the Zern team.

+

Что нового.

+

Последние чейнджлоги и обновления команды Zern.

@@ -249,7 +255,7 @@ Zern ZernMC
- + diff --git a/server/site/js/main.js b/server/site/js/main.js index 6298715..b1f4325 100644 --- a/server/site/js/main.js +++ b/server/site/js/main.js @@ -3,6 +3,220 @@ const API = ''; // same origin (root -> launcher server) + const I18N = { + ru: { + 'nav.server': 'Сервер', + 'nav.highlights': 'Хайлайты', + 'nav.season': 'Сезон', + 'nav.features': 'Возможности', + 'nav.download': 'Скачать', + 'nav.news': 'Новости', + 'nav.play': 'Играть сейчас', + 'hero.kicker': 'КАСТОМНЫЙ MINECRAFT ЛАУНЧЕР', + 'hero.title': 'Очнись от
холода.', + 'hero.sub': 'Zern оживляет твои любимые модпаки. Один лаунчер, любой загрузчик, без лишних хлопот — создан для сервера ZernMC и не только.', + 'hero.download': 'Скачать лаунчер', + 'hero.explore': 'Обзор', + 'hero.scroll': 'Листай вниз', + 'server.title': 'Сервер жив.', + 'server.sub': 'ZernMC — это кастомный Minecraft-сервер со своим лором, самописными плагинами и живым комьюнити. Вот что происходит прямо сейчас.', + 'server.status': 'Статус', + 'server.checking': 'Проверяем…', + 'server.players': 'Игроков онлайн', + 'server.mode': 'Режим', + 'server.season': 'Сезон Prologue', + 'server.online': 'Онлайн', + 'server.offline': 'Оффлайн', + 'server.unavailable': 'Недоступно', + 'highlights.title': 'Хайлайты сервера.', + 'highlights.sub': 'Моменты, запечатлённые на сервере ZernMC — войны, постройки и хаос.', + 'carousel.prev': 'Назад', + 'carousel.next': 'Вперёд', + 'carousel.cap1': 'Zern', + 'carousel.cap3': 'Это честно не я', + 'carousel.cap5': 'Спавн ТЦ (старое)', + 'carousel.cap6': 'Спавн склад (старое)', + 'carousel.alt': 'Хайлайт Zern', + 'features.title': 'Всё, что тебе нужно.', + 'features.sub': 'Лаунчер, который делает всю тяжёлую работу, чтобы ты просто играл.', + 'features.forge.title': 'Forge, NeoForge, Fabric', + 'features.forge.sub': 'Все основные модлоадеры подхватываются автоматически. Библиотеки предзагружаются и проверяются, так что установка работает — даже на медленном интернете.', + 'features.ui.title': 'Тёмный, быстрый нативный UI', + 'features.ui.sub': 'Лёгкий интерфейс на JavaFX с чистым тёмным оформлением. Быстро запускается, мгновенно реагирует и не мешает.', + 'features.proxy.title': 'Умные зеркала и прокси', + 'features.proxy.sub': 'Загрузки идут через быстрые зеркала с автоматическим фолбэком. Заблокировали или зарезали скорость? Прокси подхватит — ты больше не зависнешь на 80%.', + 'features.news.title': 'Новости и статус сервера', + 'features.news.sub': 'Чейнджлоги, ивенты и обновления приходят прямо в лаунчер. Ты всегда знаешь, что нового, ещё до нажатия «Играть».', + 'features.pack.title': 'Установка сборки в один клик', + 'features.pack.sub': 'Выбери сборку и жми «Установить». Манифесты версий, ванильные ассеты и библиотеки подтягиваются и проверяются автоматически. Просто.', + 'theme.title': 'Что внутри сезона.', + 'theme.sub': 'Prologue — модовый сезон на базе Create: Aeronautics, война и выживание. ОБТ — полигон, где всё это обкатывается.', + 'theme.zarya.tag': 'ОБТ · 1.20.1 Forge', + 'theme.zarya.title': 'Война в Заре', + 'theme.zarya.sub': 'Двухдневный ивент в городе Заря: группировка «Феникс» атакует, «Ласточка» обороняется, а жители выбирают сторону — или поднимают бунт. Военные правила.', + 'theme.obt.tag': 'ОБТ · тестовая стадия', + 'theme.obt.title': 'Открытый бета-сервер', + 'theme.obt.sub': 'Бета — это ивентовый сервер: городские бои, сценарий атомного реактора и правила, которые лягут в основу настоящего сезона.', + 'theme.air.tag': 'Сезон · 1.21.1 NeoForge', + 'theme.air.title': 'Корабли в небе', + 'theme.air.sub': 'Create: Aeronautics даёт дирижабли, субмарины и летающие механизмы. Поезда, дизели, большие пушки и атомный реактор — осваивай.', + 'theme.year.tag': 'Сезон · Prologue', + 'theme.year.title': 'Марафон на год', + 'theme.year.sub': 'Сезон — это марафон, а не спринт. Начни эту главу с первого дня и строй её вместе с комьюнити.', + 'theme.lore.tag': 'Сервер', + 'theme.lore.title': 'Свой лор и плагины', + 'theme.lore.sub': 'Самописные плагины, ивенты под историю сервера и системы, которые из коробки работают с лаунчером.', + 'theme.community.tag': 'Комьюнити', + 'theme.community.title': 'Голос и друзья', + 'theme.community.sub': 'Simple Voice Chat в игре, система друзей в лаунчере и Telegram-комьюнити, которое реально рулит — решения принимаются голосованием игроков.', + 'download.title': 'Скачать Zern.', + 'download.sub': 'Скачай лаунчер для Windows и заходи на сервер ZernMC.', + 'download.latest': 'Скачать актуальную версию', + 'download.mirrors': 'Зеркала', + 'download.note': 'Без платных подписок и рекламы. Просто лаунчер. Системные требования: Windows 10+, 4 ГБ ОЗУ.', + 'news.title': 'Что нового.', + 'news.sub': 'Последние чейнджлоги и обновления команды Zern.', + 'news.empty': 'Новостей пока нет.', + 'news.error': 'Новости временно недоступны.', + 'news.update': 'Обновление', + 'footer.note': 'Не аффилирован с Mojang или Microsoft. Minecraft — торговая марка Mojang AB.' + }, + en: { + 'nav.server': 'Server', + 'nav.highlights': 'Highlights', + 'nav.season': 'Season', + 'nav.features': 'Features', + 'nav.download': 'Download', + 'nav.news': 'News', + 'nav.play': 'Play now', + 'hero.kicker': 'CUSTOM MINECRAFT LAUNCHER', + 'hero.title': 'Awaken from
the cold.', + 'hero.sub': 'Zern brings your favourite modpacks to life. One launcher, every loader, no hassle — built for the ZernMC server and beyond.', + 'hero.download': 'Download launcher', + 'hero.explore': 'Explore', + 'hero.scroll': 'Scroll to explore', + 'server.title': 'The server is alive.', + 'server.sub': 'ZernMC is a custom Minecraft server with its own lore, hand-written plugins and a living community. This is what\'s happening right now.', + 'server.status': 'Status', + 'server.checking': 'Checking…', + 'server.players': 'Players online', + 'server.mode': 'Mode', + 'server.season': 'Season Prologue', + 'server.online': 'Online', + 'server.offline': 'Offline', + 'server.unavailable': 'Unavailable', + 'highlights.title': 'Server highlights.', + 'highlights.sub': 'Moments captured on the ZernMC server — wars, builds and chaos.', + 'carousel.prev': 'Previous', + 'carousel.next': 'Next', + 'carousel.cap1': 'Zern', + 'carousel.cap3': 'This is honestly not me', + 'carousel.cap5': 'Spawn mall (old)', + 'carousel.cap6': 'Spawn storage (old)', + 'carousel.alt': 'Zern highlight', + 'features.title': 'Everything you need.', + 'features.sub': 'A launcher that does the heavy lifting, so you can just play.', + 'features.forge.title': 'Forge, NeoForge, Fabric', + 'features.forge.sub': 'Every major mod loader handled automatically. Libraries are prefetched and verified, so installs just work — even on slow networks.', + 'features.ui.title': 'Dark, fast, native UI', + 'features.ui.sub': 'A lightweight JavaFX interface with a clean dark theme. Starts quick, responds instantly, and stays out of your way.', + 'features.proxy.title': 'Smart mirrors & proxy', + 'features.proxy.sub': 'Downloads route through fast mirrors with automatic failover. Blocked or throttled? The proxy takes over so you never stall at 80%.', + 'features.news.title': 'News & server status', + 'features.news.sub': 'Changelogs, events and updates stream straight into the launcher. You always know what\'s new before you hit play.', + 'features.pack.title': 'One-click pack install', + 'features.pack.sub': 'Pick a pack, hit install. Version manifests, vanilla assets and libraries are pulled and verified automatically. Simple.', + 'theme.title': 'What\'s inside the season.', + 'theme.sub': 'Prologue — a modded season built around Create: Aeronautics, war and survival. OBT is the testing ground for the whole idea.', + 'theme.zarya.tag': 'OBT · 1.20.1 Forge', + 'theme.zarya.title': 'War in Zarya', + 'theme.zarya.sub': 'Two-day event set in the city of Zarya: the Fenix group attacks, Lastochka defends, and civilians pick a side — or rise up. Military rules apply.', + 'theme.obt.tag': 'OBT · test stage', + 'theme.obt.title': 'Open beta server', + 'theme.obt.sub': 'The beta is an event server: city battles, an atomic-reactor city-life scenario and rule sets that will shape the real season.', + 'theme.air.tag': 'Season · 1.21.1 NeoForge', + 'theme.air.title': 'Ships in the sky', + 'theme.air.sub': 'Create: Aeronautics gives you airships, submarines and flying contraptions. Trains, diesel engines, big cannons and a nuclear reactor to master.', + 'theme.year.tag': 'Season · Prologue', + 'theme.year.title': 'A year-long run', + 'theme.year.sub': 'Each season is a marathon, not a sprint. Start this chapter from day one and shape it with the community.', + 'theme.lore.tag': 'Server', + 'theme.lore.title': 'Own lore & plugins', + 'theme.lore.sub': 'Hand-written plugins, events written for the server story, and systems the launcher integrates with out of the box.', + 'theme.community.tag': 'Community', + 'theme.community.title': 'Voice & friends', + 'theme.community.sub': 'Simple Voice Chat in-game, a friends system in the launcher and a Telegram community that runs the show — decisions are voted on by players.', + 'download.title': 'Get Zern.', + 'download.sub': 'Download the launcher for Windows and join the ZernMC server.', + 'download.latest': 'Download latest', + 'download.mirrors': 'Mirrors', + 'download.note': 'No paywalls, no ads. Just a launcher. System requirements: Windows 10+, 4 GB RAM.', + 'news.title': 'What\'s new.', + 'news.sub': 'Latest changelogs and updates from the Zern team.', + 'news.empty': 'No news yet.', + 'news.error': 'News is temporarily unavailable.', + 'news.update': 'Update', + 'footer.note': 'Not affiliated with Mojang or Microsoft. Minecraft is a trademark of Mojang AB.' + } + }; + + const STORAGE_KEY = 'zern_lang'; + + function getLang() { + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved === 'ru' || saved === 'en') return saved; + } catch (e) { /* ignore */ } + return 'ru'; // default: Russian + } + + let currentLang = getLang(); + + function tr(key) { + const dict = I18N[currentLang] || I18N.ru; + return dict[key] != null ? dict[key] : key; + } + + function applyI18n() { + document.documentElement.lang = currentLang; + + const toggle = document.getElementById('langToggleLabel'); + if (toggle) toggle.textContent = currentLang === 'ru' ? 'EN' : 'RU'; + + document.querySelectorAll('[data-i18n]').forEach((el) => { + el.textContent = tr(el.dataset.i18n); + }); + document.querySelectorAll('[data-i18n-html]').forEach((el) => { + el.innerHTML = tr(el.dataset.i18nHtml); + }); + document.querySelectorAll('[data-i18n-aria]').forEach((el) => { + el.setAttribute('aria-label', tr(el.dataset.i18nAria)); + }); + + // Carousel captions & alts are created dynamically with tr() at init + document.querySelectorAll('.carousel-caption[data-caption-key]').forEach((el) => { + el.textContent = tr(el.dataset.captionKey); + }); + document.querySelectorAll('.carousel-slide img').forEach((img) => { + const slide = img.closest('.carousel-slide'); + const cap = slide && slide.querySelector('.carousel-caption[data-caption-key]'); + if (cap) img.alt = tr(cap.dataset.captionKey); + else img.alt = tr('carousel.alt'); + }); + + // Live-update dynamic strings + const statusValue = document.getElementById('srv-status'); + if (statusValue && statusValue.dataset.state) { + updateStatusUI(statusValue.dataset.state); + } + } + + function toggleLang() { + currentLang = currentLang === 'ru' ? 'en' : 'ru'; + try { localStorage.setItem(STORAGE_KEY, currentLang); } catch (e) { /* ignore */ } + applyI18n(); + } + function fmtSize(bytes) { if (!bytes) return '—'; const units = ['B', 'KB', 'MB', 'GB']; @@ -67,7 +281,6 @@ const statusEl = document.getElementById('srv-status'); const playersEl = document.getElementById('srv-players'); const versionEl = document.getElementById('srv-version'); - const dot = document.querySelector('#srv-status .status-dot'); if (!statusEl) return; try { @@ -75,37 +288,53 @@ const data = await res.json(); if (data.online) { - if (dot) dot.className = 'status-dot status-dot--online'; - statusEl.innerHTML = 'Online'; + statusEl.dataset.state = 'online'; if (playersEl) playersEl.textContent = `${data.players_online} / ${data.players_max}`; if (versionEl && data.version) versionEl.textContent = data.version; } else { - if (dot) dot.className = 'status-dot status-dot--offline'; - statusEl.innerHTML = 'Offline'; + statusEl.dataset.state = 'offline'; if (playersEl) playersEl.textContent = '— / —'; } } catch (e) { + statusEl.dataset.state = 'unavailable'; + if (playersEl) playersEl.textContent = '— / —'; + } + updateStatusUI(statusEl.dataset.state); + } + + function updateStatusUI(state) { + const statusEl = document.getElementById('srv-status'); + const dot = document.querySelector('#srv-status .status-dot'); + if (!statusEl) return; + + if (state === 'online') { + if (dot) dot.className = 'status-dot status-dot--online'; + statusEl.innerHTML = '' + tr('server.online') + ''; + } else if (state === 'offline') { if (dot) dot.className = 'status-dot status-dot--offline'; - statusEl.innerHTML = 'Unavailable'; + statusEl.innerHTML = '' + tr('server.offline') + ''; + } else { + if (dot) dot.className = 'status-dot status-dot--offline'; + statusEl.innerHTML = '' + tr('server.unavailable') + ''; } } const HIGHLIGHTS = [ - { file: '1.jpg', type: 'image', caption: 'Zern' }, - { file: '2.mp4', type: 'video', caption: null }, - { file: '3.jpg', type: 'image', caption: 'Это честно не я' }, - { file: '4.mp4', type: 'video', caption: null }, - { file: '5.jpg', type: 'image', caption: 'Спавн ТЦ (старое)' }, - { file: '6.jpg', type: 'image', caption: 'Спавн склад (старое)' }, - { file: '7.jpg', type: 'image', caption: null }, - { file: '8.jpg', type: 'image', caption: null }, - { file: '9.jpg', type: 'image', caption: null }, - { file: '10.jpg', type: 'image', caption: null }, - { file: '11.jpg', type: 'image', caption: null }, - { file: '12.jpg', type: 'image', caption: null }, - { file: '13.jpg', type: 'image', caption: null }, - { file: '14.jpg', type: 'image', caption: null }, - { file: '15.jpg', type: 'image', caption: null }, + { file: '1.jpg', type: 'image', captionKey: 'carousel.cap1' }, + { file: '2.mp4', type: 'video', captionKey: null }, + { file: '3.jpg', type: 'image', captionKey: 'carousel.cap3' }, + { file: '4.mp4', type: 'video', captionKey: null }, + { file: '5.jpg', type: 'image', captionKey: 'carousel.cap5' }, + { file: '6.jpg', type: 'image', captionKey: 'carousel.cap6' }, + { file: '7.jpg', type: 'image', captionKey: null }, + { file: '8.jpg', type: 'image', captionKey: null }, + { file: '9.jpg', type: 'image', captionKey: null }, + { file: '10.jpg', type: 'image', captionKey: null }, + { file: '11.jpg', type: 'image', captionKey: null }, + { file: '12.jpg', type: 'image', captionKey: null }, + { file: '13.jpg', type: 'image', captionKey: null }, + { file: '14.jpg', type: 'image', captionKey: null }, + { file: '15.jpg', type: 'image', captionKey: null }, ]; function initCarousel() { @@ -131,15 +360,16 @@ } else { const img = document.createElement('img'); img.src = '/highlights/' + h.file; - img.alt = h.caption || 'Zern highlight'; + img.alt = h.captionKey ? tr(h.captionKey) : tr('carousel.alt'); img.loading = 'lazy'; slide.appendChild(img); } - if (h.caption) { + if (h.captionKey) { const cap = document.createElement('span'); cap.className = 'carousel-caption'; - cap.textContent = h.caption; + cap.dataset.captionKey = h.captionKey; + cap.textContent = tr(h.captionKey); slide.appendChild(cap); } @@ -202,7 +432,7 @@ if (!res.ok) throw new Error('news fetch failed'); const data = await res.json(); const news = (data.news || []).slice(0, 6); - if (!news.length) { grid.innerHTML = '

No news yet.

'; return; } + if (!news.length) { grid.innerHTML = '

' + tr('news.empty') + '

'; return; } grid.innerHTML = ''; news.forEach((n) => { @@ -225,7 +455,7 @@ } const title = document.createElement('h3'); - title.textContent = n.title || 'Update'; + title.textContent = n.title || tr('news.update'); const body = document.createElement('p'); const text = stripMarkup(n.body); @@ -238,7 +468,7 @@ }); } catch (e) { console.warn('[site] failed to load news', e); - grid.innerHTML = '

News is temporarily unavailable.

'; + grid.innerHTML = '

' + tr('news.error') + '

'; } } @@ -290,6 +520,11 @@ } document.addEventListener('DOMContentLoaded', () => { + applyI18n(); + + const toggle = document.getElementById('langToggle'); + if (toggle) toggle.addEventListener('click', toggleLang); + loadLauncherInfo(); loadMirrors(); loadNews();