hyapp-h5/common/i18n.js
2026-06-11 01:01:40 +08:00

247 lines
8.2 KiB
JavaScript

(function () {
var STORAGE_KEY = 'hyapp_h5_lang';
var DEFAULT_LANG = 'en';
var SUPPORTED = ['en', 'ar', 'tr', 'es', 'zh', 'id'];
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 scopedSupported() {
var scoped =
window.HyAppI18nSupported || window.HyAppSupportedLanguages || null;
if (!Array.isArray(scoped)) return SUPPORTED;
var normalized = [];
scoped.forEach(function (value) {
var lang = String(value || '')
.trim()
.toLowerCase()
.replace('_', '-');
if (lang.indexOf('ar') === 0) lang = 'ar';
else if (lang.indexOf('tr') === 0) lang = 'tr';
else if (lang.indexOf('es') === 0) lang = 'es';
else if (lang.indexOf('zh') === 0) lang = 'zh';
else if (lang.indexOf('id') === 0) lang = 'id';
if (SUPPORTED.indexOf(lang) >= 0 && normalized.indexOf(lang) < 0) {
normalized.push(lang);
}
});
return normalized.length ? normalized : SUPPORTED;
}
function storageGet(key) {
try {
return window.localStorage ? window.localStorage.getItem(key) : '';
} catch (error) {
return '';
}
}
function storageSet(key, value) {
try {
if (window.localStorage) {
window.localStorage.setItem(key, value);
}
} catch (error) {
// Some in-app browser contexts disable localStorage. Language still works for the current page session.
}
}
function currentParams() {
var parsed =
window.HyAppParams &&
window.HyAppParams.current &&
window.HyAppParams.current.raw;
if (parsed && parsed.get) return parsed;
var params = new URLSearchParams(window.location.search);
var hash = window.location.hash || '';
if (hash.indexOf('?') >= 0) {
new URLSearchParams(hash.split('?').slice(1).join('?')).forEach(
function (value, key) {
if (!params.has(key)) params.set(key, value);
}
);
}
return params;
}
function queryLang() {
var params = currentParams();
return (
params.get('lang') ||
params.get('language') ||
params.get('locale') ||
params.get('app_lang') ||
params.get('appLanguage') ||
''
);
}
function normalizeLang(value) {
var lang = String(value || '')
.trim()
.toLowerCase()
.replace('_', '-');
if (!lang) return '';
if (lang.indexOf('ar') === 0) lang = 'ar';
else if (lang.indexOf('tr') === 0) lang = 'tr';
else if (lang.indexOf('es') === 0) lang = 'es';
else if (lang.indexOf('zh') === 0) lang = 'zh';
else if (lang.indexOf('id') === 0) lang = 'id';
return scopedSupported().indexOf(lang) >= 0 ? lang : '';
}
function resolveLang() {
return (
normalizeLang(queryLang()) ||
normalizeLang(storageGet(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;
storageSet(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: scopedSupported(),
};
function init() {
initLanguageMenu();
load(resolveLang());
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();