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
@@ -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 @@
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 fromthe 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();