react-email-locale-lab 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/browser-translator-provider.d.ts +3 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/index.js +103 -99
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { TranslationProvider } from './types';
|
|
2
2
|
type BrowserTranslator = {
|
|
3
|
-
translate: (text: string
|
|
3
|
+
translate: (text: string, options?: {
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
}) => Promise<string>;
|
|
4
6
|
destroy?: () => void;
|
|
5
7
|
};
|
|
6
8
|
type TranslatorFactory = {
|
package/dist/core/types.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsxs as d, jsx as i } from "react/jsx-runtime";
|
|
2
|
-
import { createElement as
|
|
3
|
-
import { renderToStaticMarkup as
|
|
4
|
-
const
|
|
2
|
+
import { createElement as F, useMemo as C, useState as T, useEffect as P } from "react";
|
|
3
|
+
import { renderToStaticMarkup as _ } from "react-dom/server";
|
|
4
|
+
const O = ["alt", "title", "aria-label"], H = /* @__PURE__ */ new Set([
|
|
5
5
|
"ar",
|
|
6
6
|
"arc",
|
|
7
7
|
"ckb",
|
|
@@ -18,43 +18,43 @@ const _ = ["alt", "title", "aria-label"], O = /* @__PURE__ */ new Set([
|
|
|
18
18
|
"ug",
|
|
19
19
|
"ur",
|
|
20
20
|
"yi"
|
|
21
|
-
]),
|
|
21
|
+
]), j = /* @__PURE__ */ new Set(["adlm", "arab", "hebr", "mand", "nkoo", "rohg", "samr", "syrc", "thaa"]), z = (e) => {
|
|
22
22
|
const t = e.trim().split(/[-_]/), r = t[0].toLowerCase(), s = t.findIndex((o, c) => c > 0 && /^[a-z0-9]$/i.test(o)), a = t.slice(1, s === -1 ? void 0 : s).find((o) => /^[a-z]{4}$/i.test(o));
|
|
23
|
-
return a ?
|
|
24
|
-
},
|
|
23
|
+
return a ? j.has(a.toLowerCase()) ? "rtl" : "ltr" : H.has(r) ? "rtl" : "ltr";
|
|
24
|
+
}, U = (e) => new RegExp("\\p{L}", "u").test(e), q = (e) => {
|
|
25
25
|
const t = [], r = e.createTreeWalker(e.body, NodeFilter.SHOW_TEXT);
|
|
26
26
|
let s = r.nextNode();
|
|
27
27
|
for (; s; ) {
|
|
28
28
|
const n = s, a = n.parentElement, o = n.data.trim();
|
|
29
|
-
if (a && !["STYLE", "SCRIPT"].includes(a.tagName) &&
|
|
29
|
+
if (a && !["STYLE", "SCRIPT"].includes(a.tagName) && U(o)) {
|
|
30
30
|
const c = n.data.match(/^\s*/)?.[0] ?? "", u = n.data.match(/\s*$/)?.[0] ?? "";
|
|
31
|
-
t.push({ value: o, apply: (
|
|
32
|
-
n.data = `${c}${
|
|
31
|
+
t.push({ value: o, apply: (v) => {
|
|
32
|
+
n.data = `${c}${v}${u}`;
|
|
33
33
|
} });
|
|
34
34
|
}
|
|
35
35
|
s = r.nextNode();
|
|
36
36
|
}
|
|
37
37
|
for (const n of e.body.querySelectorAll("*"))
|
|
38
|
-
for (const a of
|
|
38
|
+
for (const a of O) {
|
|
39
39
|
const o = n.getAttribute(a)?.trim();
|
|
40
|
-
o &&
|
|
40
|
+
o && U(o) && t.push({ value: o, apply: (c) => n.setAttribute(a, c) });
|
|
41
41
|
}
|
|
42
42
|
return t;
|
|
43
|
-
},
|
|
43
|
+
}, D = async (e, t, r, s) => {
|
|
44
44
|
if (r === t) return e;
|
|
45
45
|
const n = new DOMParser().parseFromString(e, "text/html");
|
|
46
|
-
n.documentElement.lang = r, n.documentElement.dir =
|
|
47
|
-
const a =
|
|
46
|
+
n.documentElement.lang = r, n.documentElement.dir = z(r);
|
|
47
|
+
const a = q(n), o = await s(a.map((c) => c.value));
|
|
48
48
|
return a.forEach((c, u) => c.apply(o[u] ?? c.value)), `<!doctype html>${n.documentElement.outerHTML}`;
|
|
49
|
-
},
|
|
49
|
+
}, $ = async (e) => {
|
|
50
50
|
const t = new TextEncoder().encode(e), r = await crypto.subtle.digest("SHA-256", t);
|
|
51
51
|
return Array.from(new Uint8Array(r).slice(0, 6), (s) => s.toString(16).padStart(2, "0")).join("");
|
|
52
|
-
},
|
|
52
|
+
}, G = (e) => {
|
|
53
53
|
const t = new DOMParser().parseFromString(e, "text/html");
|
|
54
54
|
return Array.from(t.body.querySelectorAll("[style]")).find(
|
|
55
55
|
(n) => n.style.display === "none" && n.textContent?.trim()
|
|
56
56
|
)?.textContent?.replace(/[\u00A0\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g, "").trim() || void 0;
|
|
57
|
-
},
|
|
57
|
+
}, W = ({ locale: e, preview: t, sourceRevision: r, onRefresh: s }) => /* @__PURE__ */ d("article", { className: "preview-card", children: [
|
|
58
58
|
/* @__PURE__ */ d("header", { className: "preview-header", children: [
|
|
59
59
|
/* @__PURE__ */ d("div", { children: [
|
|
60
60
|
/* @__PURE__ */ i("strong", { children: e.label }),
|
|
@@ -73,98 +73,100 @@ const _ = ["alt", "title", "aria-label"], O = /* @__PURE__ */ new Set([
|
|
|
73
73
|
] }),
|
|
74
74
|
/* @__PURE__ */ d("div", { className: "preheader", children: [
|
|
75
75
|
/* @__PURE__ */ i("span", { children: "Preheader" }),
|
|
76
|
-
/* @__PURE__ */ i("strong", { children:
|
|
76
|
+
/* @__PURE__ */ i("strong", { children: G(t.html) ?? "Not set" })
|
|
77
77
|
] }),
|
|
78
78
|
/* @__PURE__ */ i("iframe", { title: `${e.label} preview`, srcDoc: t.html, sandbox: "allow-popups allow-popups-to-escape-sandbox" }),
|
|
79
79
|
t.error && /* @__PURE__ */ i("div", { className: "preview-error", children: t.error }),
|
|
80
80
|
t.status !== "source" && /* @__PURE__ */ i("button", { className: "refresh", onClick: s, children: "Regenerate translation" })
|
|
81
|
-
] }),
|
|
81
|
+
] }), V = (e) => {
|
|
82
82
|
if ("render" in e && e.render) return e.render();
|
|
83
83
|
const t = e.props ?? e.component.PreviewProps ?? {};
|
|
84
|
-
return
|
|
85
|
-
},
|
|
86
|
-
const s =
|
|
84
|
+
return F(e.component, t);
|
|
85
|
+
}, x = "/preview", M = (e = x) => `/${e}`.replace(/\/+/g, "/").replace(/\/$/, "") || x, A = (e, t, r) => {
|
|
86
|
+
const s = M(r), a = (e.pathname.startsWith(`${s}/`) ? decodeURIComponent(e.pathname.slice(s.length + 1)) : void 0) || e.searchParams.get("template") || void 0;
|
|
87
87
|
return a && t.includes(a) ? a : t[0];
|
|
88
|
-
},
|
|
88
|
+
}, I = (e, t = 3) => e.searchParams.get("langs")?.split(",").filter(Boolean).slice(0, t) ?? [], k = (e, t, r) => {
|
|
89
89
|
const s = new URL(e);
|
|
90
|
-
return s.pathname = `${
|
|
91
|
-
},
|
|
90
|
+
return s.pathname = `${M(r)}/${encodeURIComponent(t)}`, s.searchParams.delete("template"), s;
|
|
91
|
+
}, J = (e, t) => {
|
|
92
92
|
const r = new URL(e);
|
|
93
93
|
return t.length ? r.searchParams.set("langs", t.join(",")) : r.searchParams.delete("langs"), r;
|
|
94
|
-
}, N = 3,
|
|
95
|
-
const t =
|
|
96
|
-
() => `<!doctype html>${
|
|
97
|
-
[
|
|
98
|
-
),
|
|
99
|
-
return
|
|
100
|
-
const l =
|
|
101
|
-
window.history.replaceState({}, "",
|
|
102
|
-
const
|
|
103
|
-
s(
|
|
94
|
+
}, N = 3, Y = (e) => {
|
|
95
|
+
const t = C(() => Object.keys(e.templates), [e.templates]), [r, s] = T(() => A(new URL(window.location.href), t, e.routeBasePath)), [n, a] = T(() => I(new URL(window.location.href), N)), [o, c] = T({}), [u, v] = T(""), [g, y] = T(0), L = e.templates[r] ?? e.templates[t[0]], m = C(
|
|
96
|
+
() => `<!doctype html>${_(V(L))}`,
|
|
97
|
+
[L, g]
|
|
98
|
+
), f = e.locales.filter((l) => n.includes(l.code)), b = [e.sourceLocale, ...f];
|
|
99
|
+
return P(() => {
|
|
100
|
+
const l = A(new URL(window.location.href), t, e.routeBasePath);
|
|
101
|
+
window.history.replaceState({}, "", k(new URL(window.location.href), l, e.routeBasePath));
|
|
102
|
+
const w = () => {
|
|
103
|
+
s(A(new URL(window.location.href), t, e.routeBasePath)), a(I(new URL(window.location.href), N));
|
|
104
104
|
};
|
|
105
|
-
return window.addEventListener("popstate",
|
|
106
|
-
}, [e.routeBasePath, t]),
|
|
107
|
-
|
|
108
|
-
}), [e.sourceUpdates]),
|
|
105
|
+
return window.addEventListener("popstate", w), () => window.removeEventListener("popstate", w);
|
|
106
|
+
}, [e.routeBasePath, t]), P(() => e.sourceUpdates?.subscribe(() => {
|
|
107
|
+
y((l) => l + 1);
|
|
108
|
+
}), [e.sourceUpdates]), P(() => {
|
|
109
109
|
let l = !1;
|
|
110
|
-
return
|
|
111
|
-
l || w
|
|
110
|
+
return $(m).then((w) => {
|
|
111
|
+
l || v(w);
|
|
112
112
|
}), () => {
|
|
113
113
|
l = !0;
|
|
114
114
|
};
|
|
115
|
-
}, [
|
|
115
|
+
}, [m]), P(() => {
|
|
116
116
|
let l = !1;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const w = new AbortController();
|
|
118
|
+
return c((p) => Object.fromEntries(b.map((h) => [
|
|
119
|
+
h.code,
|
|
120
|
+
h.code === e.sourceLocale.code ? { html: m, status: "source" } : { html: p[h.code]?.html ?? m, status: "stale", revision: p[h.code]?.revision }
|
|
120
121
|
]))), (async () => {
|
|
121
|
-
for (const
|
|
122
|
+
for (const p of f) {
|
|
122
123
|
if (l) return;
|
|
123
|
-
c((
|
|
124
|
-
...
|
|
125
|
-
[
|
|
124
|
+
c((h) => ({
|
|
125
|
+
...h,
|
|
126
|
+
[p.code]: { ...h[p.code], status: "translating", error: void 0 }
|
|
126
127
|
}));
|
|
127
128
|
try {
|
|
128
|
-
const
|
|
129
|
-
texts:
|
|
129
|
+
const h = await D(m, e.sourceLocale.code, p.code, (E) => e.provider.translate({
|
|
130
|
+
texts: E,
|
|
130
131
|
sourceLocale: e.sourceLocale.translationCode ?? e.sourceLocale.code,
|
|
131
|
-
targetLocale:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
132
|
+
targetLocale: p.translationCode ?? p.code,
|
|
133
|
+
signal: w.signal
|
|
134
|
+
})), S = await $(m);
|
|
135
|
+
l || c((E) => ({ ...E, [p.code]: { html: h, status: "ready", revision: S } }));
|
|
136
|
+
} catch (h) {
|
|
137
|
+
l || c((S) => ({
|
|
138
|
+
...S,
|
|
139
|
+
[p.code]: {
|
|
140
|
+
...S[p.code],
|
|
139
141
|
status: "error",
|
|
140
|
-
error:
|
|
142
|
+
error: h instanceof Error ? h.message : String(h)
|
|
141
143
|
}
|
|
142
144
|
}));
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
})(), () => {
|
|
146
|
-
l = !0;
|
|
148
|
+
l = !0, w.abort();
|
|
147
149
|
};
|
|
148
|
-
}, [
|
|
150
|
+
}, [m, g, n.join(",")]), {
|
|
149
151
|
activeLocaleCodes: n,
|
|
150
|
-
activeLocales:
|
|
151
|
-
previewLocales:
|
|
152
|
+
activeLocales: f,
|
|
153
|
+
previewLocales: b,
|
|
152
154
|
previews: o,
|
|
153
|
-
refreshPreviews: () =>
|
|
155
|
+
refreshPreviews: () => y((l) => l + 1),
|
|
154
156
|
selectTemplate: (l) => {
|
|
155
|
-
s(l), window.history.pushState({}, "",
|
|
157
|
+
s(l), window.history.pushState({}, "", k(new URL(window.location.href), l, e.routeBasePath));
|
|
156
158
|
},
|
|
157
159
|
selectedTemplateId: r,
|
|
158
|
-
sourceHtml:
|
|
160
|
+
sourceHtml: m,
|
|
159
161
|
sourceRevision: u,
|
|
160
162
|
templateIds: t,
|
|
161
163
|
toggleLocale: (l) => {
|
|
162
|
-
const
|
|
163
|
-
a(
|
|
164
|
+
const w = n.includes(l) ? n.filter((R) => R !== l) : [...n, l].slice(0, N);
|
|
165
|
+
a(w), window.history.replaceState({}, "", J(new URL(window.location.href), w));
|
|
164
166
|
}
|
|
165
167
|
};
|
|
166
|
-
},
|
|
167
|
-
const t =
|
|
168
|
+
}, ae = ({ config: e }) => {
|
|
169
|
+
const t = Y(e);
|
|
168
170
|
return /* @__PURE__ */ d("main", { className: "email-lab", children: [
|
|
169
171
|
/* @__PURE__ */ d("header", { className: "topbar", children: [
|
|
170
172
|
/* @__PURE__ */ d("div", { className: "topbar-copy", children: [
|
|
@@ -213,7 +215,7 @@ const _ = ["alt", "title", "aria-label"], O = /* @__PURE__ */ new Set([
|
|
|
213
215
|
/* @__PURE__ */ i("span", { children: "The source template renders immediately; translation starts only on demand." })
|
|
214
216
|
] }),
|
|
215
217
|
/* @__PURE__ */ i("section", { className: "grid", children: t.previewLocales.map((r) => /* @__PURE__ */ i(
|
|
216
|
-
|
|
218
|
+
W,
|
|
217
219
|
{
|
|
218
220
|
locale: r,
|
|
219
221
|
preview: t.previews[r.code] ?? { html: t.sourceHtml, status: "stale" },
|
|
@@ -223,9 +225,9 @@ const _ = ["alt", "title", "aria-label"], O = /* @__PURE__ */ new Set([
|
|
|
223
225
|
r.code
|
|
224
226
|
)) })
|
|
225
227
|
] });
|
|
226
|
-
},
|
|
228
|
+
}, B = "react-email-locale-lab:translations:v1", K = () => {
|
|
227
229
|
try {
|
|
228
|
-
const e = window.sessionStorage?.getItem(
|
|
230
|
+
const e = window.sessionStorage?.getItem(B);
|
|
229
231
|
if (!e) return /* @__PURE__ */ new Map();
|
|
230
232
|
const t = JSON.parse(e);
|
|
231
233
|
return Array.isArray(t) ? new Map(t.filter(
|
|
@@ -234,26 +236,26 @@ const _ = ["alt", "title", "aria-label"], O = /* @__PURE__ */ new Set([
|
|
|
234
236
|
} catch {
|
|
235
237
|
return /* @__PURE__ */ new Map();
|
|
236
238
|
}
|
|
237
|
-
},
|
|
239
|
+
}, X = (e) => {
|
|
238
240
|
try {
|
|
239
|
-
window.sessionStorage?.setItem(
|
|
241
|
+
window.sessionStorage?.setItem(B, JSON.stringify([...e]));
|
|
240
242
|
} catch {
|
|
241
243
|
}
|
|
242
|
-
},
|
|
243
|
-
const e = /* @__PURE__ */ new Map(), t =
|
|
244
|
+
}, se = () => {
|
|
245
|
+
const e = /* @__PURE__ */ new Map(), t = K(), r = /* @__PURE__ */ new Map(), s = async (a, o) => {
|
|
244
246
|
const c = r.get(a) ?? Promise.resolve();
|
|
245
247
|
let u = () => {
|
|
246
248
|
};
|
|
247
|
-
const
|
|
248
|
-
u =
|
|
249
|
-
}),
|
|
250
|
-
}).then(() =>
|
|
251
|
-
r.set(a,
|
|
249
|
+
const v = new Promise((y) => {
|
|
250
|
+
u = y;
|
|
251
|
+
}), g = c.catch(() => {
|
|
252
|
+
}).then(() => v);
|
|
253
|
+
r.set(a, g), await c.catch(() => {
|
|
252
254
|
});
|
|
253
255
|
try {
|
|
254
256
|
return await o();
|
|
255
257
|
} finally {
|
|
256
|
-
u(), r.get(a) ===
|
|
258
|
+
u(), r.get(a) === g && r.delete(a);
|
|
257
259
|
}
|
|
258
260
|
}, n = async (a, o) => {
|
|
259
261
|
if (!window.Translator)
|
|
@@ -268,28 +270,30 @@ const _ = ["alt", "title", "aria-label"], O = /* @__PURE__ */ new Set([
|
|
|
268
270
|
};
|
|
269
271
|
return {
|
|
270
272
|
name: "Browser Translator · on-device",
|
|
271
|
-
async translate({ texts: a, sourceLocale: o, targetLocale: c }) {
|
|
273
|
+
async translate({ texts: a, sourceLocale: o, targetLocale: c, signal: u }) {
|
|
272
274
|
if (a.length === 0) return [];
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
u?.throwIfAborted();
|
|
276
|
+
const v = await n(o, c), g = `${o}:${c}`;
|
|
277
|
+
return s(g, async () => {
|
|
278
|
+
const y = [];
|
|
279
|
+
let L = !1;
|
|
277
280
|
try {
|
|
278
|
-
for (const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
281
|
+
for (const m of a) {
|
|
282
|
+
u?.throwIfAborted();
|
|
283
|
+
const f = `${g}:${m}`;
|
|
284
|
+
let b = t.get(f);
|
|
285
|
+
b || (b = await v.translate(m, { signal: u }), t.set(f, b), L = !0), y.push(b);
|
|
282
286
|
}
|
|
283
|
-
return
|
|
287
|
+
return y;
|
|
284
288
|
} finally {
|
|
285
|
-
|
|
289
|
+
L && X(t);
|
|
286
290
|
}
|
|
287
291
|
});
|
|
288
292
|
}
|
|
289
293
|
};
|
|
290
|
-
},
|
|
294
|
+
}, ne = (e) => e;
|
|
291
295
|
export {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
296
|
+
ae as EmailLabApp,
|
|
297
|
+
se as browserTranslatorProvider,
|
|
298
|
+
ne as defineEmailLab
|
|
295
299
|
};
|