qwik-speak
Version:
Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps
435 lines (434 loc) • 15.4 kB
JavaScript
import { readdir as q, readFile as z, writeFile as H } from "fs/promises";
import { existsSync as C, mkdirSync as G } from "fs";
import { normalize as B, join as X, extname as R, parse as M } from "path";
import Y 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 ee(t, e = 0) {
const n = [], c = [], u = [];
let o = "", l = e;
const d = (r) => (e = l, { type: r, value: L(), position: { start: e, end: l } }), O = (r) => {
r.value = L(), r.position.start = e, r.position.end = l;
}, w = () => t[++l], K = () => l < t.length - 1 ? t[l + 1] : "", k = () => l > 0 ? t[l - 1] : "", L = () => t.substring(e, l + 1), I = (r) => /["'`0-9+-]/.test(r), E = (r) => /[a-zA-Z_$]/.test(r), P = (r) => /[(){},:;.[\]?!=<>]/.test(r), $ = (r) => /\//.test(r) && /\*/.test(K()), A = (r, f) => {
if (!f)
f = d("Literal"), n.push(f);
else {
if (u.length === 0 && !/[0-9.]/.test(r))
return y(r);
O(f);
}
return u.length === 0 && /["'`]/.test(r) ? u.push(r) : u.length > 0 && u[u.length - 1] === r && (/\\/.test(k()) || u.pop()), A(w(), f);
}, i = (r, f) => {
if (!f)
o = r, f = d("Identifier"), n.push(f);
else if (/[0-9a-zA-Z_$. ]/.test(r))
o += r, (o == "null" || o == "undefined" || o == "true" || o == "false" || o == "void 0") && (f.type = "Literal"), O(f);
else
return y(r);
return i(w(), f);
}, p = (r, f) => (f = d("Punctuator"), n.push(f), /\(/.test(r) ? c.push(r) : /\)/.test(r) && c.pop(), y(w())), s = (r) => /\//.test(r) && /\*/.test(k()) ? y(r) : s(w()), a = () => n[n.length - 1]?.type === "Punctuator" && c.length === 0 || l === t.length, y = (r = t[l]) => a() ? n : I(r) ? A(r) : E(r) ? i(r) : P(r) ? p(r) : $(r) ? s(r) : y(w());
return y();
}
function te(t, e, n) {
let c, u = 0;
const o = [], l = () => u < t.length - 1 ? t[u + 1] : { type: "", value: "", position: { start: 0, end: 0 } }, d = () => t[++u], O = () => t[t.length - 1], w = (i) => i.replace(/^["'`]|["'`]$/g, ""), K = (i) => (c.arguments.push({ type: "Literal", value: w(i.value) }), $(d())), k = (i) => (c.arguments.push({ type: "Identifier", value: i.value }), $(d())), L = (i, p, s) => (s || (s = {
type: "Property",
key: { type: "Identifier", value: i.value },
value: { type: "Literal", value: "" }
}, p.push(s)), !/^:$/.test(i.value) && !/^:$/.test(l().value) ? i.type === "Literal" ? (s.value.value = w(i.value), E(d(), p)) : (s.value.type = "Expression", I(i, p, s)) : L(d(), p, s)), I = (i, p, s) => (s.value.value += i.value, /^[({]$/.test(i.value) ? o.push(i.value) : /^[)}]$/.test(i.value) && o.pop(), /^[,}]$/.test(l().value) && o.length === 0 ? E(d(), p) : I(d(), p, s)), E = (i, p) => (p || (p = [], c.arguments.push({ type: "ObjectExpression", properties: p })), /^}$/.test(i.value) ? $(d()) : /^:$/.test(l().value) ? L(i, p) : E(d(), p)), P = (i, p) => (p || (p = [], c.arguments.push({ type: "ArrayExpression", elements: p })), /^]$/.test(i.value) ? $(d()) : (i.type === "Literal" && p.push({ type: "Literal", value: w(i.value) }), i.type === "Identifier" && p.push({ type: "Identifier", value: i.value }), P(d(), p))), $ = (i) => Object.is(i, O()) ? c : i.type === "Literal" ? K(i) : i.type === "Identifier" && /^\($/.test(l().value) ? A(i) : i.type === "Identifier" ? k(i) : /^{$/.test(i.value) ? E(i) : /^\[$/.test(i.value) ? P(i) : $(d()), A = (i, p, s = i.position.start) => p ? /^\)$/.test(i.value) ? (p.value = e.substring(s, i.position.end + 1), $(d())) : A(d(), p, s) : new RegExp(n).test(i.value) ? (c = {
type: "CallExpression",
value: e.substring(s, O().position.end + 1),
arguments: []
}, $(d())) : (p = { type: "CallExpression", value: "" }, c.arguments.push(p), A(d(), p, s));
return A(t[u]);
}
function se(t, e) {
const n = new RegExp(`${e}\\(`, "gs"), c = [];
let u;
for (; (u = n.exec(t)) !== null; )
c.push(u.index);
return c;
}
function D(t, e) {
const n = [], c = se(t, e);
let u = [];
for (const o of c) {
try {
u = ee(t, o);
} catch (l) {
console.error(l), console.error(
`
\x1B[31mQwik Speak Tokenizer error\x1B[0m
%s`,
t.substring(o, o + 100) + ` [...]
`
);
}
if (u.length > 0)
try {
const l = te(u, t, e);
l && n.push(l);
} catch (l) {
console.error(l), console.error(
`
\x1B[31mQwik Speak Parser error\x1B[0m
%s`,
t.substring(o, u[u.length - 1].position.end) + `
`
);
}
}
return n;
}
function ne(t) {
return /inlineTranslate/.test(t);
}
function re(t) {
return /inlinePlural/.test(t);
}
function ae(t) {
let e = t.match(/(?<=\bconst\s).*?(?=\s?=\s?inlineTranslate\(\);?)/)?.[0]?.trim();
return e ? (e.startsWith("$") || (e = `\\b${e}`), e = e.replace(/\$/g, "\\$"), e) : null;
}
function ie(t) {
let e = t.match(/(?<=\bconst\s).*?(?=\s?=\s?inlinePlural\(\);?)/)?.[0]?.trim();
return e ? (e.startsWith("$") || (e = `\\b${e}`), e = e.replace(/\$/g, "\\$"), e) : null;
}
function le(t) {
return JSON.parse(t, (e, n) => n === null ? void 0 : n);
}
function oe(t, e, n) {
let c = 0;
const u = e.length;
for (; c < u; ) {
const o = e[c++];
t[o] = t[o] && !n ? t[o] : c === u ? n : typeof t[o] == "object" ? t[o] : {}, t = t[o];
}
}
function Z(t, e) {
return typeof t == "object" && typeof e == "object" ? (Object.keys(e).map((n) => {
t[n] = Z(t[n], e[n]);
}), t) : e;
}
function ue(t, e) {
typeof t == "object" && typeof e == "object" && Object.keys(e).forEach((n) => {
typeof e[n] == "object" ? n in t ? ue(t[n], e[n]) : Object.assign(t, { [n]: e[n] }) : !t[n] && e[n] && Object.assign(t, { [n]: e[n] });
});
}
function fe(t) {
return typeof t == "object" ? JSON.parse(JSON.stringify(t)) : t;
}
function ce(t, e) {
return t = { ...t, ...e }, t;
}
function Q(t, e, n = ".", c = "") {
const u = [];
for (const o in t) {
const l = c ? `${c}${n}${o}` : o;
typeof t[o] == "object" && typeof e[o] == "object" ? (u.push(...Q(t[o], e[o], n, l)), Object.keys(t[o]).length === 0 && delete t[o]) : o in e || (delete t[o], u.push(l));
}
return u;
}
function pe(t) {
return JSON.stringify(t, ye, 2);
}
function T(t) {
return Object.keys(t).sort().reduce(
(e, n) => (t[n] !== null && typeof t[n] == "object" && !Array.isArray(t[n]) ? e[n] = T(t[n]) : e[n] = t[n], e),
{}
);
}
function ye(t, e) {
return typeof e == "string" ? e.replace(/\\/g, "") : e;
}
function de(t, e) {
const n = /* @__PURE__ */ new Set();
for (let c = 0; c < 20; c++) {
const u = new Intl.PluralRules(t, e).select(c);
n.add(u);
}
return Array.from(n);
}
function me(t) {
let e;
if (t) {
e = {};
for (const n of t)
e = { ...e, [n.key.value]: n.value.value };
}
return e;
}
function _(t) {
let e = "";
switch (typeof t) {
case "string":
e = t;
break;
case "object":
e = JSON.stringify(T(t));
break;
case "number":
e = t.toString();
break;
}
return `autoKey_${$e(e)}`;
}
function he(t, e) {
if (!new RegExp(`^(?!\\${e})(?!.*\\${e}\\s)(?!.*\\s\\${e})(?!.*\\${e}$)(?=.*\\${e}.*[^\\s]+)$`).test(t))
return !1;
const c = t.split(e), u = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
for (const o of c)
if (!u.test(o))
return !1;
return !0;
}
function ge(t, e, n) {
if (!t)
return !1;
const c = e.split(n);
let u = !1;
if (t instanceof Map)
for (const o of t.values()) {
let l = o;
for (const d of c) {
if (l[d] !== void 0)
u = !0;
else {
u = !1;
break;
}
l = l[d];
}
if (!u)
break;
}
else if (typeof t == "object") {
let o = t;
for (const l of c) {
if (o[l] !== void 0)
u = !0;
else {
u = !1;
break;
}
o = o[l];
}
}
return u;
}
function $e(t) {
return Y.createHash("md5").update(t).digest("hex");
}
async function we(t) {
const e = {
...t,
basePath: t.basePath ?? "./",
sourceFilesPaths: t.sourceFilesPaths ?? ["src"],
excludedPaths: t.excludedPaths ?? [],
assetsPath: t.assetsPath ?? "i18n",
format: t.format ?? "json",
filename: t.filename ?? "app",
fallback: t.fallback ?? ((s) => s),
keySeparator: t.keySeparator ?? ".",
keyValueSeparator: t.keyValueSeparator ?? "@@",
autoKeys: t.autoKeys ?? !1,
unusedKeys: t.unusedKeys ?? !1,
runtimeAssets: t.runtimeAssets ?? []
}, n = /* @__PURE__ */ new Map(), c = e.sourceFilesPaths.map((s) => B(`${e.basePath}/${s}`)), u = e.excludedPaths.map((s) => B(`${e.basePath}/${s}`)), o = [];
let l = Object.fromEntries(e.supportedLangs.map((s) => [s, {}]));
const d = async (s, a) => {
const y = await q(s, { withFileTypes: !0 });
for (const r of y) {
const f = X(s, r.name), m = R(r.name);
r.isDirectory() ? a.includes(f) || await d(f, a) : /\.js|\.ts|\.jsx|\.tsx/.test(m) && !/test|spec/.test(r.name) && o.push(f);
}
}, O = (s) => s.type === "Identifier" || s.type === "Literal" && s.value && /\${.*}/.test(s.value) ? (n.set("dynamic", (n.get("dynamic") ?? 0) + 1), !0) : !1, w = async (s) => {
const a = [];
let y = await z(B(`${e.basePath}/${s}`), "utf8");
const r = (m) => {
y = y.replace(new RegExp(`${m}<.*>\\(`, "g"), `${m.replace("\\b", "")}(`);
}, f = (m) => {
for (const h of m) {
const x = h.arguments;
if (x?.length > 0) {
if (x[0].type === "ArrayExpression") {
if (x[0].elements) {
for (const S of x[0].elements)
if (S.type === "Literal") {
if (O(S))
continue;
a.push(S.value);
}
}
} else if (x?.[0]?.value) {
if (O(x[0]))
continue;
a.push(x[0].value);
}
}
}
};
if (ne(y)) {
const m = ae(y);
if (m) {
r(m);
const h = D(y, m);
f(h);
}
}
if (re(y)) {
const m = ie(y);
if (m) {
const h = D(y, m);
for (const x of h) {
const S = x.arguments;
if (S?.length > 0) {
if (S[1]?.type === "Identifier" || S[1]?.type === "CallExpression" || S[3]?.type === "Identifier" || S[3]?.type === "CallExpression") {
n.set("dynamic plural", (n.get("dynamic plural") ?? 0) + 1);
continue;
}
const b = /* @__PURE__ */ new Set(), V = me(S[3]?.properties);
for (const g of e.supportedLangs) {
const J = de(g, V);
for (const W of J)
b.add(W);
}
const F = (g) => !g || g === "undefined" || g === "null", U = (g, J) => g.split(J);
let v = S[1]?.value, N;
v && ([v, N] = U(v, e.keyValueSeparator), !N && /^{.*}$/.test(v) && (N = v, v = void 0));
const j = N ? JSON.parse(N) : void 0;
if (!F(v) && !j) {
const g = {};
for (const J of b)
g[J] = "";
a.push(`${v}${e.keyValueSeparator}${JSON.stringify(g)}`);
} else if (F(v) && !j)
for (const g of b)
a.push(g);
else if (!F(v) && j) {
for (const g of b)
j[g] || (j[g] = "");
a.push(`${v}${e.keyValueSeparator}${JSON.stringify(j)}`);
} else if (F(v) && j) {
v = _(j);
for (const g of b)
j[g] || (j[g] = "");
a.push(`${v}${e.keyValueSeparator}${JSON.stringify(j)}`);
}
}
}
}
}
return a;
}, K = async () => {
const s = /* @__PURE__ */ new Map();
let a = /* @__PURE__ */ new Set();
for (const y of e.supportedLangs) {
const r = B(`${e.basePath}/${e.assetsPath}/${y}`);
if (C(r)) {
let f = await q(r);
if (f.length > 0) {
if (e.runtimeAssets.length > 0 && (f = f.filter((b) => !e.runtimeAssets.includes(M(b).name))), f.length === 0)
return [s, a];
const m = R(f[0]);
let h = {};
const x = f.map((b) => z(`${r}/${b}`, "utf8")), S = await Promise.all(x);
for (const b of S)
if (b) {
let V = {};
switch (m) {
case ".json":
V = le(b);
break;
}
h = ce(h, V);
}
s.set(y, h), a = /* @__PURE__ */ new Set([...a, ...f.map((b) => M(b).name)]);
}
}
}
return [s, a];
}, k = async (s) => {
for (const a of e.supportedLangs) {
const y = B(`${e.basePath}/${e.assetsPath}/${a}`);
C(y) || G(y, { recursive: !0 });
const r = Object.keys(l[a]).filter((h) => s.has(h)), f = Object.keys(l[a]).filter((h) => !s.has(h)), m = {};
l[a][e.filename] && (m[e.filename] = l[a][e.filename]);
for (const h of f)
m[h] = l[a][h];
Object.keys(m).length > 0 && await L(m, e.filename, y);
for (const h of r.filter((x) => x !== e.filename))
await L({ [h]: l[a][h] }, h, y);
}
}, L = async (s, a, y) => {
let r;
switch (e.format) {
case "json":
r = pe(s);
break;
}
const f = B(`${y}/${a}.${e.format}`);
await H(f, r), console.log(f);
};
for (const s of c)
await d(s, u);
const I = o.map((s) => w(s)), E = await Promise.all(I);
let P = [];
for (const s of E)
P = P.concat(s);
P = [...new Set(P)], n.set("unique keys", (n.get("unique keys") ?? 0) + P.length);
const [$, A] = await K(), i = /* @__PURE__ */ new Set();
for (let s of P) {
let a;
[s, a] = s.split(e.keyValueSeparator), /^[[{].*[\]}]$/.test(a) && !/^{{/.test(a) && (a = JSON.parse(a)), e.autoKeys && s && !a && !ge($, s, e.keySeparator) && !he(s, e.keySeparator) && (a = `${s}`, s = _(s));
for (const y of e.supportedLangs)
oe(l[y], s.split(e.keySeparator), fe(a || ""));
i.add(s);
}
const p = new Set(A);
for (const s of i) {
const a = s.split(e.keySeparator);
a.length > 1 && isNaN(+a[1]) && p.add(a[0]);
}
if (e.unusedKeys) {
const s = /* @__PURE__ */ new Set();
for (const a of e.supportedLangs) {
const y = $.get(a);
if (y) {
const r = Q(y, l[a], e.keySeparator);
for (const f of r)
s.add(f);
}
}
n.set("unused keys", (n.get("unused keys") ?? 0) + s.size);
}
if ($.size > 0)
for (const [s, a] of $)
Z(l[s], a);
for (const s of e.supportedLangs)
l[s] = T(l[s]);
l = e.fallback(l), await k(p);
for (const [s, a] of n)
switch (s) {
case "unique keys":
console.log("\x1B[32m%s\x1B[0m", `extracted keys: ${a}`);
break;
case "dynamic":
console.log("\x1B[32m%s\x1B[0m", `translations skipped due to dynamic keys: ${a}`);
break;
case "dynamic plural":
console.log("\x1B[32m%s\x1B[0m", `plurals skipped due to dynamic keys/options: ${a}`);
break;
case "unused keys":
console.log("\x1B[32m%s\x1B[0m", `unused keys removed: ${a}`);
break;
}
}
export {
ue as deepMergeMissing,
we as qwikSpeakExtract
};