hfs
Version:
HTTP File Server
127 lines (126 loc) • 4.98 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
Object.defineProperty(exports, "__esModule", { value: true });
exports.i18nFromTranslations = i18nFromTranslations;
const valtio_1 = require("valtio");
const utils_1 = require("valtio/utils");
function i18nFromTranslations(translations, embedded = 'en') {
const state = (0, valtio_1.proxy)({
embedded,
disabled: false,
translations, // all dictionaries
});
const searchLangs = [];
(0, utils_1.watch)(get => {
const snapshot = get(state);
searchLangs.splice(0, Infinity, 'all', ...Object.keys(snapshot.translations), snapshot.embedded); // replace completely
});
const warns = new Set(); // avoid duplicates
function getLangs() {
return Object.keys(state.translations);
}
// If one of the keys is an "id", that should be the first. If one of the keys should work as a fallback, that should be the last. Use 'fallback' parameter if you don't want the fallback to work as a key.
function t(keyOrTpl, params, fallback) {
var _a, _b;
if (!keyOrTpl)
return '';
// memoize?
const keys = isTemplateStringsArray(keyOrTpl) ? [(fallback !== null && fallback !== void 0 ? fallback : (fallback = keyOrTpl[0]))]
: Array.isArray(keyOrTpl) ? keyOrTpl : [keyOrTpl];
if (typeof params === 'string' && !fallback) {
fallback = params;
params = null;
}
let found;
let selectedLang = ''; // keep track of where we find the translation
const { embedded } = state;
const langs = Object.keys(state.translations);
if (!state.disabled)
for (const key of keys) {
for (const lang of searchLangs)
if (found = (_b = (_a = state.translations[selectedLang = lang]) === null || _a === void 0 ? void 0 : _a.translate) === null || _b === void 0 ? void 0 : _b[key])
break;
if (found)
break;
if (!warns.has(key) && langs.length && langs[0] !== embedded) {
warns.add(key);
console.debug("miss i18n:", key);
}
}
if (!found) {
found = fallback || keys[keys.length - 1];
selectedLang = embedded;
}
return Array.from(tokenizer(found)).map(([s, inside]) => {
if (!inside)
return s;
const [k, cmd, rest] = s.split(',').map(x => x.trim());
if (!params)
throw "missing params on " + keys[0];
const v = k && params[k];
if (cmd === 'plural' && rest)
return plural(v, rest);
return v || v === 0 ? v : '';
}).join('');
function plural(v, rest) {
const plural = !Intl.PluralRules ? 'other'
: new Intl.PluralRules(selectedLang || embedded).select(Number(v));
let other = '';
let pickNext = false;
let collectOther = false;
for (const [s, inside] of tokenizer(rest)) {
if (pickNext)
return pick(s);
if (collectOther) {
other = s;
collectOther = false;
}
if (inside)
continue;
const selectors = s.trim().split(/\s+/);
pickNext = selectors.some(sel => sel[0] === '=' && v === Number(sel.slice(1))
|| sel === plural);
collectOther = !pickNext && selectors.includes('other');
}
return pick(other);
function pick(s) {
return s.replace('#', String(v));
}
}
}
function* tokenizer(s) {
let ofs = 0;
while (1) {
const open = s.indexOf('{', ofs);
if (open < 0)
break;
yield [s.slice(ofs, open), false];
let stack = 1;
ofs = open + 1;
while (stack && ofs < s.length) {
if (s[ofs] === '{')
stack++;
else if (s[ofs] === '}')
stack--;
ofs++;
}
if (stack)
return console.debug('tokenizer: unclosed'); // invalid, abort
yield [s.slice(open + 1, ofs - 1), true];
}
yield [s.slice(ofs), false];
}
function isTemplateStringsArray(x) {
return (x === null || x === void 0 ? void 0 : x.raw) && Array.isArray(x);
}
return {
t,
state,
getLangs,
i18nWrapperProps: () => ({ lang: getLangs()[0] || state.embedded }),
useI18N() {
(0, valtio_1.useSnapshot)(state);
return { t };
}
};
}
;