qwik-speak
Version:
Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps
598 lines (597 loc) • 19.5 kB
JavaScript
import { readdir as ee, readFile as te, writeFile as R } from "fs/promises";
import { existsSync as J, createWriteStream as ne, mkdirSync as se } from "fs";
import { normalize as k, extname as re } from "path";
import ie from "crypto";
/**
* @license
* Qwik Speak
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/robisim74/qwik-speak/blob/main/LICENSE
*/
function le(e, t = 0) {
const n = [], r = [], l = [];
let u = "", i = t;
const o = (p) => (t = i, { type: p, value: g(), position: { start: t, end: i } }), s = (p) => {
p.value = g(), p.position.start = t, p.position.end = i;
}, a = () => e[++i], f = () => i < e.length - 1 ? e[i + 1] : "", y = () => i > 0 ? e[i - 1] : "", g = () => e.substring(t, i + 1), d = (p) => /["'`0-9+-]/.test(p), b = (p) => /[a-zA-Z_$]/.test(p), $ = (p) => /[(){},:;.[\]?!=<>]/.test(p), w = (p) => /\//.test(p) && /\*/.test(f()), P = (p, h) => {
if (!h)
h = o("Literal"), n.push(h);
else {
if (l.length === 0 && !/[0-9.]/.test(p))
return I(p);
s(h);
}
return l.length === 0 && /["'`]/.test(p) ? l.push(p) : l.length > 0 && l[l.length - 1] === p && (/\\/.test(y()) || l.pop()), P(a(), h);
}, c = (p, h) => {
if (!h)
u = p, h = o("Identifier"), n.push(h);
else if (/[0-9a-zA-Z_$. ]/.test(p))
u += p, (u == "null" || u == "undefined" || u == "true" || u == "false" || u == "void 0") && (h.type = "Literal"), s(h);
else
return I(p);
return c(a(), h);
}, m = (p, h) => (h = o("Punctuator"), n.push(h), /\(/.test(p) ? r.push(p) : /\)/.test(p) && r.pop(), I(a())), v = (p) => /\//.test(p) && /\*/.test(y()) ? I(p) : v(a()), Y = () => n[n.length - 1]?.type === "Punctuator" && r.length === 0 || i === e.length, I = (p = e[i]) => Y() ? n : d(p) ? P(p) : b(p) ? c(p) : $(p) ? m(p) : w(p) ? v(p) : I(a());
return I();
}
function ae(e, t, n) {
let r, l = 0;
const u = [], i = () => l < e.length - 1 ? e[l + 1] : { type: "", value: "", position: { start: 0, end: 0 } }, o = () => e[++l], s = () => e[e.length - 1], a = (c) => c.replace(/^["'`]|["'`]$/g, ""), f = (c) => (r.arguments.push({ type: "Literal", value: a(c.value) }), w(o())), y = (c) => (r.arguments.push({ type: "Identifier", value: c.value }), w(o())), g = (c, m, v) => (v || (v = {
type: "Property",
key: { type: "Identifier", value: c.value },
value: { type: "Literal", value: "" }
}, m.push(v)), !/^:$/.test(c.value) && !/^:$/.test(i().value) ? c.type === "Literal" ? (v.value.value = a(c.value), b(o(), m)) : (v.value.type = "Expression", d(c, m, v)) : g(o(), m, v)), d = (c, m, v) => (v.value.value += c.value, /^[({]$/.test(c.value) ? u.push(c.value) : /^[)}]$/.test(c.value) && u.pop(), /^[,}]$/.test(i().value) && u.length === 0 ? b(o(), m) : d(o(), m, v)), b = (c, m) => (m || (m = [], r.arguments.push({ type: "ObjectExpression", properties: m })), /^}$/.test(c.value) ? w(o()) : /^:$/.test(i().value) ? g(c, m) : b(o(), m)), $ = (c, m) => (m || (m = [], r.arguments.push({ type: "ArrayExpression", elements: m })), /^]$/.test(c.value) ? w(o()) : (c.type === "Literal" && m.push({ type: "Literal", value: a(c.value) }), c.type === "Identifier" && m.push({ type: "Identifier", value: c.value }), $(o(), m))), w = (c) => Object.is(c, s()) ? r : c.type === "Literal" ? f(c) : c.type === "Identifier" && /^\($/.test(i().value) ? P(c) : c.type === "Identifier" ? y(c) : /^{$/.test(c.value) ? b(c) : /^\[$/.test(c.value) ? $(c) : w(o()), P = (c, m, v = c.position.start) => m ? /^\)$/.test(c.value) ? (m.value = t.substring(v, c.position.end + 1), w(o())) : P(o(), m, v) : new RegExp(n).test(c.value) ? (r = {
type: "CallExpression",
value: t.substring(v, s().position.end + 1),
arguments: []
}, w(o())) : (m = { type: "CallExpression", value: "" }, r.arguments.push(m), P(o(), m, v));
return P(e[l]);
}
function ue(e, t) {
const n = new RegExp(`${t}\\(`, "gs"), r = [];
let l;
for (; (l = n.exec(e)) !== null; )
r.push(l.index);
return r;
}
function E(e, t) {
const n = [], r = ue(e, t);
let l = [];
for (const u of r) {
try {
l = le(e, u);
} catch (i) {
console.error(i), console.error(
`
\x1B[31mQwik Speak Tokenizer error\x1B[0m
%s`,
e.substring(u, u + 100) + ` [...]
`
);
}
if (l.length > 0)
try {
const i = ae(l, e, t);
i && n.push(i);
} catch (i) {
console.error(i), console.error(
`
\x1B[31mQwik Speak Parser error\x1B[0m
%s`,
e.substring(u, l[l.length - 1].position.end) + `
`
);
}
}
return n;
}
function N(e) {
return /inlineTranslate/.test(e);
}
function F(e) {
return /inlinePlural/.test(e);
}
function Q(e) {
let t = e.match(/(?<=\bconst\s).*?(?=\s?=\s?inlineTranslate\(\);?)/)?.[0]?.trim();
return t ? (t.startsWith("$") || (t = `\\b${t}`), t = t.replace(/\$/g, "\\$"), t) : null;
}
function z(e) {
let t = e.match(/(?<=\bconst\s).*?(?=\s?=\s?inlinePlural\(\);?)/)?.[0]?.trim();
return t ? (t.startsWith("$") || (t = `\\b${t}`), t = t.replace(/\$/g, "\\$"), t) : null;
}
function oe(e) {
return JSON.parse(e, (t, n) => n === null ? void 0 : n);
}
function fe(e, t) {
const n = /* @__PURE__ */ new Set();
for (let r = 0; r < 20; r++) {
const l = new Intl.PluralRules(e, t).select(r);
n.add(l);
}
return Array.from(n);
}
function ce(e) {
let t;
if (e) {
t = {};
for (const n of e)
t = { ...t, [n.key.value]: n.value.value };
}
return t;
}
function pe(e, t) {
return e = { ...e, ...t }, e;
}
function W(e) {
return Object.keys(e).sort().reduce(
(t, n) => (e[n] !== null && typeof e[n] == "object" && !Array.isArray(e[n]) ? t[n] = W(e[n]) : t[n] = e[n], t),
{}
);
}
function G(e) {
let t = "";
switch (typeof e) {
case "string":
t = e;
break;
case "object":
t = JSON.stringify(W(e));
break;
case "number":
t = e.toString();
break;
}
return `autoKey_${ye(t)}`;
}
function D(e, t, n) {
if (!e)
return !1;
const r = t.split(n);
let l = !1;
if (e instanceof Map)
for (const u of e.values()) {
let i = u;
for (const o of r) {
if (i[o] !== void 0)
l = !0;
else {
l = !1;
break;
}
i = i[o];
}
if (!l)
break;
}
else if (typeof e == "object") {
let u = e;
for (const i of r) {
if (u[i] !== void 0)
l = !0;
else {
l = !1;
break;
}
u = u[i];
}
}
return l;
}
function ye(e) {
return ie.createHash("md5").update(e).digest("hex");
}
const j = "__qsInlineTranslate", T = "__qsInlinePlural", O = [], V = [], me = (e, t) => `${e} - ${t}`, ge = (e, t) => `dynamic ${t}: ${e}`;
let S, A, L;
function Ke(e) {
let t = "";
const n = async (s) => {
const a = k(`${t}/${s}`);
let f = {};
if (J(a)) {
const y = await ee(a);
if (y.length > 0) {
const g = re(y[0]), d = y.map(($) => te(`${a}/${$}`, "utf8")), b = await Promise.all(d);
for (const $ of b)
if ($) {
let w = {};
switch (g) {
case ".json":
w = oe($);
break;
}
f = pe(f, w);
}
}
}
return f;
}, r = {
...e,
basePath: e.basePath ?? "./",
assetsPath: e.assetsPath ?? "i18n",
loadAssets: e.loadAssets ?? n,
outDir: e.outDir ?? "dist",
keySeparator: e.keySeparator ?? ".",
keyValueSeparator: e.keyValueSeparator ?? "@@",
autoKeys: e.autoKeys ?? !1
};
t = `${r.basePath}/${r.assetsPath}`;
const l = Object.fromEntries(r.supportedLangs.map((s) => [s, {}]));
let u;
const i = /* @__PURE__ */ new Set();
return {
name: "vite-plugin-qwik-speak-inline",
enforce: "post",
apply: void 0,
// both
async configResolved(s) {
s.build?.ssr || s.mode === "ssr" ? S = "ssr" : s.mode === "lib" ? S = "lib" : s.mode === "test" ? S = "test" : S = "client", A = s.isProduction || s.mode === "production" ? "prod" : "dev";
const a = s.build?.rollupOptions?.input;
a && (Array.isArray(a) ? L = a[0] : typeof a == "string" && (L = a)), L = L?.split("/")?.pop(), await Promise.all(r.supportedLangs.map(async (f) => {
const y = await r.loadAssets(f);
Object.assign(l[f], y);
}));
},
configureServer(s) {
A === "dev" && s.ws.on("qwik-speak:lang", (a) => {
if (u && u !== a.msg) {
for (const f of i) {
const y = s.moduleGraph.getModuleById(f);
y && s.moduleGraph.invalidateModule(y);
}
i.clear();
}
u = a.msg;
});
},
async handleHotUpdate({ file: s, server: a }) {
if (new RegExp(r.assetsPath).test(s) && /\.(json)$/.test(s)) {
for (const f of r.supportedLangs)
new RegExp(f).test(s) && await n(f);
for (const f of i) {
const y = a.moduleGraph.getModuleById(f);
y && a.moduleGraph.invalidateModule(y);
}
i.clear();
}
},
/**
* Transform functions
* Prefer transform hook because unused imports will be removed, unlike renderChunk
*/
async transform(s, a, f) {
return r.autoKeys && /\/src\//.test(a) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(a) && (F(s) && (s = ve(s, r, l)), N(s) && (s = $e(s, r, l))), (S === "client" || S === "ssr" && f?.ssr === !1) && /\/src\//.test(a) && /\.(js|cjs|mjs|jsx|ts|tsx)$/.test(a) && (F(s) && (s = xe(s)), N(s) && (s = he(s)), A === "dev" && (s.includes(j) || s.includes(T)) && (s = U(s, u, r, l), i.add(a))), S === "ssr" && (a.endsWith("entry.ssr.tsx") || a.endsWith("entry.ssr.jsx")) && (/(?<!\/\/\s*)base:\s*extractBase/.test(s) || (console.log(
`
\x1B[31mQwik Speak Inline error\x1B[0m
%s`,
"Missing 'base' option in 'entry.ssr.tsx' file: see https://robisim74.gitbook.io/qwik-speak/tools/setup"
), process.exit(1))), s;
},
/**
* Split chunks by lang
*/
async writeBundle(s, a) {
if (S === "client") {
const f = s.dir ? s.dir : k(`${r.basePath}/${r.outDir}`), y = Object.values(a), g = r.supportedLangs.map((d) => de(d, y, f, l, r));
await Promise.all(g);
}
},
async closeBundle() {
if (S === "client") {
const s = ne("./qwik-speak-inline.log", { flags: "w" });
s.write(`${S}: ` + (L ?? "-") + `
`), O.length > 0 && (s.write(`
Missing value for keys:
`), O.forEach((a) => s.write(a + `
`))), V.length > 0 && (s.write(`
Make sure the keys are in 'runtimeAssets':
`), V.forEach((a) => s.write(a + `
`))), s.write(`
Qwik Speak Inline: build ends at ${(/* @__PURE__ */ new Date()).toLocaleString()}
`), (O.length > 0 || V.length > 0) && console.log(
`
\x1B[33mQwik Speak Inline warn\x1B[0m
%s`,
"There are missing values or dynamic keys: see ./qwik-speak-inline.log"
);
}
}
};
}
async function de(e, t, n, r, l) {
const u = k(`${n}/build/${e}`);
J(u) || se(u, { recursive: !0 });
for (const i of t) {
const o = [];
if (i.type === "chunk" && "code" in i && /build\//.test(i.fileName)) {
const s = k(`${u}/${i.fileName.split("/")[1]}`);
let a = i.code;
if (a = U(a, e, l, r), o.push(R(s, a)), e === l.defaultLang) {
const f = k(`${n}/build`), y = k(`${f}/${i.fileName.split("/")[1]}`);
o.push(R(y, a));
}
} else if (i.type === "asset" && "source" in i && /build\//.test(i.fileName) && i.fileName.includes("q-bundle-graph")) {
const s = k(`${u}/${i.fileName.split("/")[1]}`);
o.push(R(s, i.source));
}
await Promise.all(o);
}
}
function $e(e, t, n) {
const r = Q(e);
if (!r)
return e;
const l = E(e, r);
if (l.length === 0)
return e;
for (const u of l) {
const i = u.value, o = u.arguments;
if (o?.length > 0) {
if (H(o, i))
continue;
if (o[0].value) {
const s = o[0].value, [a] = B(s, t.keyValueSeparator), f = G(s);
if (!D(n[t.defaultLang], a, t.keySeparator) && D(n[t.defaultLang], f, t.keySeparator)) {
const y = i.replace(s, `${f}${t.keyValueSeparator}${s}`);
e = e.replace(i, y);
}
}
}
}
return e;
}
function ve(e, t, n) {
const r = z(e);
if (!r)
return e;
const l = E(e, r);
if (l.length === 0)
return e;
for (const u of l) {
const i = u.value, o = u.arguments;
if (o?.length > 0) {
if (Z(o, i))
continue;
if (o[1]?.value) {
const s = (d) => !d || d === "undefined" || d === "null", a = o[1].value;
let f, y;
a && ([f, y] = B(a, t.keyValueSeparator), !y && /^{.*}$/.test(f) && (y = f, f = void 0));
const g = y ? JSON.parse(y) : void 0;
if (s(f) && g) {
const d = G(g);
if (D(n[t.defaultLang], d, t.keySeparator)) {
const b = i.replace(a, `${d}${t.keyValueSeparator}${y}`);
e = e.replace(i, b);
}
}
}
}
}
return e;
}
function he(e) {
const t = Q(e);
if (!t)
return e;
let n = !1;
const r = E(e, t);
if (r.length === 0)
return e;
for (const l of r) {
const u = l.value, i = l.arguments;
if (i?.length > 0) {
if (H(i, u)) {
n = !0;
continue;
}
const o = u.replace(new RegExp(`${t}\\(`), `${j}(`);
e = e.replace(u, o);
}
}
return n || (e = Ae(e, t)), e;
}
function xe(e) {
const t = z(e);
if (!t)
return e;
let n = !1;
const r = E(e, t);
if (r.length === 0)
return e;
for (const l of r) {
const u = l.value, i = l.arguments;
if (i?.length > 0) {
if (Z(i, u)) {
n = !0;
continue;
}
const o = u.replace(new RegExp(`${t}\\(`), `${T}(`);
e = e.replace(u, o);
}
}
return n || (e = qe(e, t)), e;
}
function U(e, t, n, r) {
return e.includes(T) && (e = we(e, T, j, t, n)), e.includes(j) && (e = be(e, r, j, t, n)), e;
}
function be(e, t, n, r, l) {
const u = E(e, n);
if (u.length === 0)
return e;
for (const i of u) {
const o = i.value, s = i.arguments;
if (s?.length > 0) {
const a = X(r, s[2], l);
let f = x("");
if (s[0].type === "ArrayExpression") {
const g = Ie(s[0]), d = [];
for (const b of g) {
const $ = M(
b,
t[a],
s[1],
l.keySeparator,
l.keyValueSeparator,
a
);
d.push($);
}
f = d;
} else if (s?.[0]?.value) {
const g = ke(s[0]);
f = M(
g,
t[a],
s[1],
l.keySeparator,
l.keyValueSeparator,
a
);
}
const y = Pe(f);
e = e.replace(o, y);
}
}
return e;
}
function we(e, t, n, r, l) {
const u = E(e, t);
if (u.length === 0)
return e;
for (const i of u) {
const o = i.value, s = i.arguments;
if (s?.length > 0) {
const a = X(r, s[4], l), f = ce(s[3]?.properties), y = fe(a, f), g = Se(y, a, n, s, l);
e = e.replace(o, g);
}
}
return e;
}
function Pe(e) {
return typeof e == "object" ? `${je(e)}` : e;
}
function Se(e, t, n, r, l) {
let u = "";
return u += ((o) => {
let s = "(";
for (const a of e) {
let f = r[1]?.value, y;
f && ([f, y] = B(f, l.keyValueSeparator), !y && /^{.*}$/.test(f) && (y = f, f = void 0)), f = f ? `${f}${l.keySeparator}${a}` : a;
const g = y ? JSON.parse(y)[a] : void 0;
g && (f = `${f}${l.keyValueSeparator}${g}`);
const d = [{
type: "Property",
key: { type: "Identifier", value: "value" },
value: { type: "Expression", value: r[0].value }
}];
if (r[2]?.properties)
for (const $ of r[2].properties)
d.push($);
const b = d.map(($) => `${$.key.value}: ${C($)}`).join(", ");
if (a !== e[e.length - 1]) {
const $ = r[3]?.properties?.map((P) => `${P.key.value}: ${C(P)}`)?.join(", "), w = Le(o, r[0].value, a, $);
s += w + ` && ${n}(${x(f)}, {${b}}, ${x(o)}) || `;
} else
s += `${n}(${x(f)}, {${b}}, ${x(o)})`;
}
return s += ")", s;
})(t), u;
}
function H(e, t) {
if (e?.[0]?.value) {
if (e[0].type === "Identifier" || e[0].type === "Literal" && /\${.*}/.test(e[0].value))
return q(t, "key"), !0;
if (e[1]?.type === "Identifier" || e[1]?.type === "CallExpression" || e[2]?.type === "Identifier" || e[2]?.type === "CallExpression")
return q(t, "params"), !0;
}
return !1;
}
function Z(e, t) {
return e?.[0]?.value && (e[1]?.type === "Identifier" || e[1]?.type === "CallExpression" || e[2]?.type === "Identifier" || e[2]?.type === "CallExpression" || e[3]?.type === "Identifier" || e[3]?.type === "CallExpression" || e[4]?.type === "Identifier" || e[4]?.type === "CallExpression") ? (q(t, "params"), !0) : !1;
}
function X(e, t, n) {
let r;
return t?.type === "Literal" && (r = n.supportedLangs.find((l) => l === t.value)), r ?? e;
}
function ke(e) {
return e.value;
}
function Ie(e) {
const t = [];
if (e.elements)
for (const n of e.elements)
n.type === "Literal" && t.push(n.value);
return t;
}
const B = (e, t) => e.split(t);
function M(e, t, n, r, l, u) {
let i;
[e, i] = B(e, l);
const o = e.split(r).reduce((s, a) => s && s[a] !== void 0 ? s[a] : void 0, t);
if (o) {
if (typeof o == "string")
return n ? K(o, n) : x(o);
if (typeof o == "object")
return n ? _(o, n) : o;
} else
u && Oe(u, e);
return i ? !/^[[{].*[\]}]$/.test(i) || /^{{/.test(i) ? n ? K(i, n) : x(i) : n ? _(JSON.parse(i), n) : JSON.parse(i) : x(A === "dev" ? e : "");
}
function _(e, t) {
return Object.keys(e).map((n) => {
typeof e[n] == "string" && (e[n] = t ? K(e[n], t) : x(e[n])), e[n] && typeof e[n] == "object" && (e[n] = _(e[n], t));
}), e;
}
function K(e, t) {
if (t.properties)
for (const n of t.properties)
e = e.replace(/{{\s?([^{}\s]*)\s?}}/g, (r, l) => l === n.key.value ? Ee(n) : r);
return x(e);
}
function x(e) {
return /^`.*`$/.test(e) ? e : "`" + e + "`";
}
function Ee(e) {
return e.value.type === "Literal" ? e.value.value : "${" + e.value.value + "}";
}
function C(e) {
return e.value.type === "Literal" ? x(e.value.value) : e.value.value;
}
function Le(e, t, n, r) {
return r ? `new Intl.PluralRules(${x(e)}, {${r}}).select(+${t}) === ${x(n)}` : `new Intl.PluralRules(${x(e)}).select(+${t}) === ${x(n)}`;
}
function je(e) {
let t = JSON.stringify(e, Te);
return t = t.replace(/("__qsOpenBt)|(__qsCloseBt")/g, "`"), t;
}
function Oe(e, t) {
const n = me(e, t);
O.includes(n) || O.push(n);
}
function q(e, t) {
const n = ge(Ve(e), t);
V.includes(n) || V.push(n);
}
function Ve(e) {
return e.replace(/\s+/g, " ").trim();
}
function Ae(e, t) {
return e.replace(new RegExp(`\\bconst\\s${t}\\s=\\sinlineTranslate\\(\\);?`, "g"), "");
}
function qe(e, t) {
return e.replace(new RegExp(`\\bconst\\s${t}\\s=\\sinlinePlural\\(\\);?`, "g"), "");
}
function Te(e, t) {
return typeof t == "string" && /^`.*`$/.test(t) ? t.replace(/^`/, "__qsOpenBt").replace(/`$/, "__qsCloseBt") : t;
}
function Ne(e, t) {
return t !== "production" ? e : e.map((r) => ({
prefix: r.domain ? void 0 : r.prefix,
paths: r.paths,
lang: r.prefix,
domain: r.domain,
withDomain: r.withDomain
}));
}
export {
Ke as qwikSpeakInline,
Ne as toPrefixAsNeeded
};