UNPKG

qwik-speak

Version:

Internationalization (i18n) library to translate texts, dates and numbers in Qwik apps

435 lines (434 loc) 15.4 kB
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 };