35 lines
2.4 KiB
JavaScript
35 lines
2.4 KiB
JavaScript
// The complete, versioned catalog is embedded before this script by the server.
|
|||
|
|
// Only textContent/Alpine x-text is used: parameters are never interpreted as HTML.
|
||
|
|
const I18n = (() => {
|
||
|
|
const supported=['en','ru','fr'];
|
||
|
|
const detect=(languages)=>{for(const raw of languages || []){const base=String(raw).toLowerCase().split(/[-_]/)[0];if(supported.includes(base))return base;}return 'en';};
|
||
|
|
const browser=detect(navigator.languages?.length?navigator.languages:[navigator.language]);
|
||
|
|
const locale=()=>globalThis.Alpine?.store('locale')?.language || browser;
|
||
|
|
const translate=(key,params={},language=locale())=>{
|
||
|
|
const entry=MEGURI_MESSAGES[key];
|
||
|
|
if(!entry)throw new Error('Missing localization key: '+key);
|
||
|
|
const pattern=entry[language] || entry.en;
|
||
|
|
return pattern.replace(/\{([\w]+)\}/g,(match,name)=>Object.hasOwn(params,name)?String(params[name]):match);
|
||
|
|
};
|
||
|
|
const exact=new Map();
|
||
|
|
for(const [key,entry]of Object.entries(MEGURI_MESSAGES))for(const text of Object.values(entry))if(text)exact.set(text,key);
|
||
|
|
const patterns=[];
|
||
|
|
for(const [key,entry]of Object.entries(MEGURI_MESSAGES)){
|
||
|
|
if(!entry.en.includes('{'))continue;
|
||
|
|
const names=[];let from=0,expression='^';
|
||
|
|
for(const match of entry.en.matchAll(/\{(\w+)\}/g)){expression+=entry.en.slice(from,match.index).replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'([\\s\\S]*?)';names.push(match[1]);from=match.index+match[0].length;}
|
||
|
|
if(names.length){expression+=entry.en.slice(from).replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'$';patterns.push({key,names,re:new RegExp(expression)});}
|
||
|
|
}
|
||
|
|
const diagnostic=(text)=>{
|
||
|
|
if(text==null)return '';
|
||
|
|
text=String(text);const key=exact.get(text);if(key)return translate(key);
|
||
|
|
for(const item of patterns){const match=item.re.exec(text);if(match)return translate(item.key,Object.fromEntries(item.names.map((name,i)=>[name,match[i+1]])));}
|
||
|
|
return text; // User values and third-party diagnostics are opaque, never guessed or rewritten.
|
||
|
|
};
|
||
|
|
document.addEventListener('alpine:init',()=>{
|
||
|
|
Alpine.store('locale',{language:browser});
|
||
|
|
Alpine.effect(()=>{const language=locale();document.documentElement.lang=language;const title=document.querySelector('[data-title-key]');if(title)document.title=translate(title.dataset.titleKey);});
|
||
|
|
});
|
||
|
|
return Object.freeze({supported,browser,detect,locale,translate,diagnostic});
|
||
|
|
})();
|
||
|
|
function tr(key,params){return I18n.translate(key,params);}
|