- Static HTML hotel website with Bootstrap 5 - i18next-based client-side internationalization - Language switcher (DE/EN) in navbar - German and English translation JSON files - Translated: navigation, content, forms, FAQ, legal pages (AGB) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
135 lines
3.8 KiB
JavaScript
135 lines
3.8 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
function detectLanguage() {
|
|
var stored = localStorage.getItem('i18n-lang');
|
|
if (stored) return stored;
|
|
|
|
var browserLang = navigator.language || navigator.userLanguage || '';
|
|
var short = browserLang.split('-')[0].toLowerCase();
|
|
if (short === 'en') return 'en';
|
|
return 'de';
|
|
}
|
|
|
|
function interpolate(str, options) {
|
|
if (!str || !options) return str;
|
|
return str.replace(/\{\{(\w+)\}\}/g, function (match, key) {
|
|
return options[key] !== undefined ? options[key] : match;
|
|
});
|
|
}
|
|
|
|
function translatePage() {
|
|
var elements = document.querySelectorAll('[data-i18n]');
|
|
elements.forEach(function (el) {
|
|
var rawKey = el.getAttribute('data-i18n');
|
|
if (!rawKey) return;
|
|
|
|
var optionsAttr = el.getAttribute('data-i18n-options');
|
|
var options = optionsAttr ? JSON.parse(optionsAttr) : null;
|
|
|
|
// Handle attribute prefixes like [value], [placeholder]
|
|
var attrMatch = rawKey.match(/^\[(\w+)\](.+)$/);
|
|
if (attrMatch && attrMatch[1] !== 'html') {
|
|
var attr = attrMatch[1];
|
|
var tKey = attrMatch[2];
|
|
var val = i18next.t(tKey, options || {});
|
|
val = interpolate(val, options);
|
|
el.setAttribute(attr, val);
|
|
return;
|
|
}
|
|
|
|
// Handle [html] prefix for innerHTML
|
|
var isHtml = false;
|
|
var lookupKey = rawKey;
|
|
|
|
if (rawKey.indexOf('[html]') === 0) {
|
|
isHtml = true;
|
|
lookupKey = rawKey.substring(6);
|
|
}
|
|
|
|
var translation = i18next.t(lookupKey, options || {});
|
|
translation = interpolate(translation, options);
|
|
|
|
if (isHtml) {
|
|
el.innerHTML = translation;
|
|
} else {
|
|
el.textContent = translation;
|
|
}
|
|
});
|
|
|
|
// Update document lang attribute
|
|
document.documentElement.lang = i18next.language;
|
|
|
|
// Update document title
|
|
var pageKey = detectPageKey();
|
|
if (pageKey) {
|
|
var titleTranslation = i18next.t(pageKey + '.page_title');
|
|
if (titleTranslation && titleTranslation !== pageKey + '.page_title') {
|
|
document.title = titleTranslation;
|
|
}
|
|
}
|
|
|
|
// Update active state on language switcher
|
|
updateSwitcherActive();
|
|
}
|
|
|
|
function detectPageKey() {
|
|
var path = window.location.pathname;
|
|
var filename = path.substring(path.lastIndexOf('/') + 1).replace('.html', '');
|
|
if (!filename || filename === '' || filename === 'index') return 'index';
|
|
return filename;
|
|
}
|
|
|
|
function updateSwitcherActive() {
|
|
var lang = i18next.language;
|
|
var options = document.querySelectorAll('.lang-option');
|
|
options.forEach(function (el) {
|
|
if (el.getAttribute('data-lang') === lang) {
|
|
el.classList.add('active');
|
|
el.setAttribute('aria-current', 'true');
|
|
} else {
|
|
el.classList.remove('active');
|
|
el.removeAttribute('aria-current');
|
|
}
|
|
});
|
|
}
|
|
|
|
function initSwitcher() {
|
|
document.addEventListener('click', function (e) {
|
|
var target = e.target.closest('.lang-option');
|
|
if (!target) return;
|
|
e.preventDefault();
|
|
var lang = target.getAttribute('data-lang');
|
|
if (lang && lang !== i18next.language) {
|
|
i18next.changeLanguage(lang, function () {
|
|
localStorage.setItem('i18n-lang', lang);
|
|
translatePage();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
var lng = detectLanguage();
|
|
|
|
i18next
|
|
.use(i18nextHttpBackend)
|
|
.init({
|
|
lng: lng,
|
|
fallbackLng: 'de',
|
|
backend: {
|
|
loadPath: './locales/{{lng}}.json'
|
|
},
|
|
interpolation: {
|
|
escapeValue: false
|
|
}
|
|
}, function (err) {
|
|
if (err) console.error('i18next init error:', err);
|
|
translatePage();
|
|
initSwitcher();
|
|
|
|
// Signal that i18n is ready (used by preloader)
|
|
window.i18nReady = true;
|
|
document.dispatchEvent(new CustomEvent('i18nReady'));
|
|
});
|
|
|
|
})();
|