UNPKG

qwik-speak

Version:

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

598 lines (597 loc) 19.5 kB
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 };