hyapp-h5/common/i18n.js
2026-05-29 19:55:17 +08:00

173 lines
5.8 KiB
JavaScript

(function () {
var STORAGE_KEY = 'hyapp_h5_lang';
var DEFAULT_LANG = 'en';
var SUPPORTED = ['en', 'ar', 'tr', 'es'];
var scriptURL =
document.currentScript && document.currentScript.src
? document.currentScript.src
: '../common/i18n.js';
var localeBaseURL = new URL('locales/', scriptURL).toString();
var messages = {};
var currentLang = '';
function queryLang() {
return new URLSearchParams(window.location.search).get('lang') || '';
}
function normalizeLang(value) {
var lang = String(value || '')
.trim()
.toLowerCase()
.replace('_', '-');
if (!lang) return '';
if (lang.indexOf('ar') === 0) return 'ar';
if (lang.indexOf('tr') === 0) return 'tr';
if (lang.indexOf('es') === 0) return 'es';
return SUPPORTED.indexOf(lang) >= 0 ? lang : '';
}
function resolveLang() {
return (
normalizeLang(queryLang()) ||
normalizeLang(window.localStorage.getItem(STORAGE_KEY)) ||
normalizeLang(window.navigator.language) ||
DEFAULT_LANG
);
}
function load(lang) {
var normalized = normalizeLang(lang) || DEFAULT_LANG;
return window
.fetch(localeBaseURL + normalized + '.json', {
cache: 'no-store',
})
.then(function (response) {
if (!response.ok) throw new Error('locale_not_found');
return response.json();
})
.then(function (json) {
messages = json || {};
currentLang = normalized;
window.localStorage.setItem(STORAGE_KEY, currentLang);
apply();
return messages;
})
.catch(function () {
if (normalized === DEFAULT_LANG) {
messages = {};
currentLang = DEFAULT_LANG;
apply();
return messages;
}
return load(DEFAULT_LANG);
});
}
function t(key, fallback) {
return Object.prototype.hasOwnProperty.call(messages, key)
? messages[key]
: fallback || key;
}
function apply(root) {
var scope = root || document;
document.documentElement.lang = currentLang || DEFAULT_LANG;
document.documentElement.dir = currentLang === 'ar' ? 'rtl' : 'ltr';
scope.querySelectorAll('[data-i18n]').forEach(function (node) {
node.textContent = t(
node.getAttribute('data-i18n'),
node.textContent
);
});
scope
.querySelectorAll('[data-i18n-placeholder]')
.forEach(function (node) {
node.setAttribute(
'placeholder',
t(
node.getAttribute('data-i18n-placeholder'),
node.getAttribute('placeholder') || ''
)
);
});
scope.querySelectorAll('[data-i18n-aria]').forEach(function (node) {
node.setAttribute(
'aria-label',
t(
node.getAttribute('data-i18n-aria'),
node.getAttribute('aria-label') || ''
)
);
});
scope.querySelectorAll('[data-lang-option]').forEach(function (node) {
node.classList.toggle(
'is-active',
node.getAttribute('data-lang-option') === currentLang
);
});
scope.querySelectorAll('[data-current-lang]').forEach(function (node) {
node.textContent = (currentLang || DEFAULT_LANG).toUpperCase();
});
window.dispatchEvent(
new CustomEvent('hyapp:i18n-ready', {
detail: { lang: currentLang, messages: messages },
})
);
}
function initLanguageMenu() {
document.addEventListener('click', function (event) {
var toggle = closest(event.target, '[data-language-toggle]');
var menu = document.querySelector('[data-language-menu]');
if (toggle && menu) {
var expanded = toggle.getAttribute('aria-expanded') === 'true';
toggle.setAttribute(
'aria-expanded',
expanded ? 'false' : 'true'
);
menu.hidden = expanded;
return;
}
var option = closest(event.target, '[data-lang-option]');
if (option) {
if (menu) menu.hidden = true;
var button = document.querySelector('[data-language-toggle]');
if (button) button.setAttribute('aria-expanded', 'false');
load(option.getAttribute('data-lang-option'));
return;
}
if (menu && !closest(event.target, '.language-switcher')) {
menu.hidden = true;
var languageButton = document.querySelector(
'[data-language-toggle]'
);
if (languageButton)
languageButton.setAttribute('aria-expanded', 'false');
}
});
}
function closest(node, selector) {
while (node && node !== document) {
if (node.matches && node.matches(selector)) return node;
node = node.parentNode;
}
return null;
}
window.HyAppI18n = {
load: load,
apply: apply,
t: t,
lang: function () {
return currentLang || DEFAULT_LANG;
},
supported: SUPPORTED.slice(),
};
document.addEventListener('DOMContentLoaded', function () {
initLanguageMenu();
load(resolveLang());
});
})();