253 lines
8.5 KiB
JavaScript
253 lines
8.5 KiB
JavaScript
(function () {
|
|
var STORAGE_KEY = 'hyapp_h5_lang';
|
|
var DEFAULT_LANG = 'en';
|
|
var SUPPORTED = ['en', 'ar', 'tr', 'es', 'zh', 'id'];
|
|
var LANG_QUERY_KEYS = [
|
|
'language',
|
|
'lang',
|
|
'locale',
|
|
'app_lang',
|
|
'appLanguage',
|
|
];
|
|
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(/_/g, '-');
|
|
if (lang.indexOf('en') === 0) lang = 'en';
|
|
else 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();
|
|
for (var i = 0; i < LANG_QUERY_KEYS.length; i += 1) {
|
|
var value = params.get(LANG_QUERY_KEYS[i]);
|
|
if (value) return value;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function normalizeLang(value) {
|
|
var lang = String(value || '')
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/_/g, '-');
|
|
if (!lang) return '';
|
|
if (lang.indexOf(',') >= 0) lang = lang.split(',')[0];
|
|
if (lang.indexOf(';') >= 0) lang = lang.split(';')[0];
|
|
lang = lang.trim();
|
|
if (lang.indexOf('en') === 0) lang = 'en';
|
|
else 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() {
|
|
var requestedLang = queryLang();
|
|
if (requestedLang) return normalizeLang(requestedLang) || DEFAULT_LANG;
|
|
return 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();
|
|
}
|
|
})();
|