112 lines
3.1 KiB
JavaScript
112 lines
3.1 KiB
JavaScript
// Shared localization for the service panel and progress window.
|
|
(function () {
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const LANGUAGES = ['en', 'zh_CN', 'zh_TW', 'ja_JP', 'ko_KR', 'ru_RU', 'es_ES', 'de_DE'];
|
|
const FALLBACK = 'zh_CN';
|
|
|
|
function normalizeLocale(raw) {
|
|
if (!raw) return null;
|
|
const tag = String(raw).trim().toLowerCase().replace(/-/g, '_');
|
|
if (tag === 'zh_tw' || tag === 'zh_hk' || tag === 'zh_hant' || tag === 'zh_mo') return 'zh_TW';
|
|
for (const code of LANGUAGES) {
|
|
if (code.toLowerCase() === tag) return code;
|
|
}
|
|
const base = tag.split('_')[0];
|
|
if (base === 'zh') return 'zh_CN';
|
|
for (const code of LANGUAGES) {
|
|
if (code.toLowerCase().split('_')[0] === base) return code;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function requestedLocale() {
|
|
try {
|
|
const fromUrl = new URLSearchParams(location.search).get('lang');
|
|
if (fromUrl) return fromUrl;
|
|
} catch (error) { /* No browser location. */ }
|
|
try {
|
|
if (typeof eagle !== 'undefined' && eagle.app && eagle.app.locale) return eagle.app.locale;
|
|
} catch (error) { /* Eagle API is injected asynchronously. */ }
|
|
try {
|
|
return navigator.language;
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function pluginRoot() {
|
|
if (window.SpinePluginPath) return window.SpinePluginPath;
|
|
if (window.SpinePluginRoot) return window.SpinePluginRoot;
|
|
return typeof __dirname !== 'undefined' ? __dirname : null;
|
|
}
|
|
|
|
function readLocale(code) {
|
|
try {
|
|
const root = pluginRoot();
|
|
if (!root) return {};
|
|
return JSON.parse(fs.readFileSync(path.join(root, '_locales', code + '.json'), 'utf8'));
|
|
} catch (error) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function lookup(table, key) {
|
|
let node = table;
|
|
for (const part of String(key).split('.')) {
|
|
if (!node || typeof node !== 'object') return undefined;
|
|
node = node[part];
|
|
}
|
|
return typeof node === 'string' ? node : undefined;
|
|
}
|
|
|
|
function apply(root) {
|
|
const scope = root || document;
|
|
for (const el of scope.querySelectorAll('[data-i18n]')) el.textContent = t(el.dataset.i18n);
|
|
for (const el of scope.querySelectorAll('[data-i18n-title]')) el.title = t(el.dataset.i18nTitle);
|
|
for (const el of scope.querySelectorAll('[data-i18n-aria-label]')) el.setAttribute('aria-label', t(el.dataset.i18nAriaLabel));
|
|
}
|
|
|
|
let fallbackMessages = readLocale(FALLBACK);
|
|
let locale = FALLBACK;
|
|
let messages = fallbackMessages;
|
|
|
|
function t(key, vars) {
|
|
let text = lookup(messages, key);
|
|
if (text === undefined) text = lookup(fallbackMessages, key);
|
|
if (text === undefined) return key;
|
|
if (vars) {
|
|
text = text.replace(/\{(\w+)\}/g, (whole, name) =>
|
|
Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : whole);
|
|
}
|
|
return text;
|
|
}
|
|
|
|
function setLocale(raw) {
|
|
locale = normalizeLocale(raw) || FALLBACK;
|
|
fallbackMessages = readLocale(FALLBACK);
|
|
messages = locale === FALLBACK ? fallbackMessages : readLocale(locale);
|
|
document.documentElement.lang = locale.replace('_', '-');
|
|
apply(document);
|
|
return locale;
|
|
}
|
|
|
|
const api = {
|
|
languages: LANGUAGES,
|
|
fallback: FALLBACK,
|
|
get locale() { return locale; },
|
|
normalizeLocale,
|
|
requestedLocale,
|
|
setLocale,
|
|
t,
|
|
apply,
|
|
};
|
|
|
|
window.SpineThumbI18n = api;
|
|
setLocale(requestedLocale());
|
|
|
|
})();
|