@ingglish/dom 0.1.0
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/index.d.mts +155 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.js +601 -0
- package/dist/index.mjs +572 -0
- package/package.json +67 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
DEFAULT_SKIP_CLASSES: () => DEFAULT_SKIP_CLASSES,
|
|
24
|
+
DEFAULT_SKIP_TAGS: () => DEFAULT_SKIP_TAGS,
|
|
25
|
+
applyTranslationsMap: () => applyTranslationsMap,
|
|
26
|
+
collectTextNodes: () => collectTextNodes,
|
|
27
|
+
extractWordsFromNodes: () => extractWordsFromNodes,
|
|
28
|
+
injectTooltipBehavior: () => injectTooltipBehavior,
|
|
29
|
+
injectTooltipStyles: () => injectTooltipStyles,
|
|
30
|
+
restoreDOM: () => restoreDOM,
|
|
31
|
+
translateDOM: () => translateDOM
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(index_exports);
|
|
34
|
+
|
|
35
|
+
// src/translate/apply-map.ts
|
|
36
|
+
var import_normalize3 = require("@ingglish/normalize");
|
|
37
|
+
|
|
38
|
+
// src/constants.ts
|
|
39
|
+
var WORD_SPAN_CLASS = "ingglish-word";
|
|
40
|
+
var TOOLTIP_STYLES_ID = "ingglish-tooltip-styles";
|
|
41
|
+
var ATTR_ORIGINAL_WORD = "data-ingglish-orig";
|
|
42
|
+
var ATTR_ORIGINAL_CONTENT = "data-ingglish-original";
|
|
43
|
+
var ATTR_SKIP = "data-ingglish-skip";
|
|
44
|
+
var ATTR_ORIGINAL_PREFIX = "data-ingglish-original-";
|
|
45
|
+
var FORMAT_DIFF_CLASS = "ingglish-format-diff";
|
|
46
|
+
var NOT_FOUND_CLASS = "ingglish-not-found";
|
|
47
|
+
|
|
48
|
+
// src/traversal/browser.ts
|
|
49
|
+
function isBrowser() {
|
|
50
|
+
return typeof document !== "undefined" && globalThis.window !== void 0;
|
|
51
|
+
}
|
|
52
|
+
function requireBrowser() {
|
|
53
|
+
if (!isBrowser()) {
|
|
54
|
+
throw new Error("DOM translation requires a browser environment");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/traversal/extract.ts
|
|
59
|
+
var import_normalize = require("@ingglish/normalize");
|
|
60
|
+
function extractWordsFromNodes(textNodes) {
|
|
61
|
+
const uniqueWords = /* @__PURE__ */ new Set();
|
|
62
|
+
for (const node of textNodes) {
|
|
63
|
+
const text = node.textContent ?? "";
|
|
64
|
+
const normalized = (0, import_normalize.normalizeApostrophes)(text);
|
|
65
|
+
const tokens = normalized.split(import_normalize.WORD_SPLIT_REGEX);
|
|
66
|
+
for (const token of tokens) {
|
|
67
|
+
if (token !== "" && import_normalize.WORD_TEST_REGEX.test(token)) {
|
|
68
|
+
uniqueWords.add(token.toLowerCase());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return [...uniqueWords];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/traversal/skip-rules.ts
|
|
76
|
+
var DEFAULT_SKIP_TAGS = [
|
|
77
|
+
"SCRIPT",
|
|
78
|
+
"STYLE",
|
|
79
|
+
"CODE",
|
|
80
|
+
"PRE",
|
|
81
|
+
"KBD",
|
|
82
|
+
"SAMP",
|
|
83
|
+
"VAR",
|
|
84
|
+
"NOSCRIPT",
|
|
85
|
+
"TEXTAREA",
|
|
86
|
+
"INPUT",
|
|
87
|
+
"SVG",
|
|
88
|
+
"MATH",
|
|
89
|
+
"CANVAS"
|
|
90
|
+
];
|
|
91
|
+
var DEFAULT_SKIP_TAGS_SET = new Set(DEFAULT_SKIP_TAGS);
|
|
92
|
+
var DEFAULT_SKIP_CLASSES = ["no-translate", "notranslate"];
|
|
93
|
+
var DEFAULT_SKIP_CLASSES_SET = new Set(DEFAULT_SKIP_CLASSES);
|
|
94
|
+
var TRANSLATABLE_ATTRIBUTES = [
|
|
95
|
+
"title",
|
|
96
|
+
"alt",
|
|
97
|
+
"placeholder",
|
|
98
|
+
"aria-label",
|
|
99
|
+
"aria-description"
|
|
100
|
+
];
|
|
101
|
+
function shouldSkipElement(element, skipTags, skipClasses) {
|
|
102
|
+
const tagsSet = skipTags === DEFAULT_SKIP_TAGS ? DEFAULT_SKIP_TAGS_SET : new Set(skipTags);
|
|
103
|
+
const classesSet = skipClasses === DEFAULT_SKIP_CLASSES ? DEFAULT_SKIP_CLASSES_SET : new Set(skipClasses);
|
|
104
|
+
return checkElementSkip(element, tagsSet, classesSet);
|
|
105
|
+
}
|
|
106
|
+
function checkElementSkip(element, skipTagsSet, skipClassesSet) {
|
|
107
|
+
if (skipTagsSet.has(element.tagName)) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const classList = element.classList;
|
|
111
|
+
const classCount = classList.length;
|
|
112
|
+
for (let i = 0; i < classCount; i++) {
|
|
113
|
+
if (skipClassesSet.has(classList[i])) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (element.getAttribute("contenteditable") === "true") {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (element.hasAttribute(ATTR_SKIP)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (element.hasAttribute(ATTR_ORIGINAL_WORD)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/traversal/text-nodes.ts
|
|
130
|
+
function collectTextNodes(root, skipTags = DEFAULT_SKIP_TAGS, skipClasses = DEFAULT_SKIP_CLASSES) {
|
|
131
|
+
requireBrowser();
|
|
132
|
+
const textNodes = [];
|
|
133
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {
|
|
134
|
+
acceptNode(node) {
|
|
135
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
136
|
+
return shouldSkipElement(node, skipTags, skipClasses) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_SKIP;
|
|
137
|
+
}
|
|
138
|
+
const text = node.textContent?.trim() ?? "";
|
|
139
|
+
return text.length > 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
while (walker.nextNode()) {
|
|
143
|
+
if (walker.currentNode.nodeType === Node.TEXT_NODE) {
|
|
144
|
+
textNodes.push(walker.currentNode);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return textNodes;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/traversal/tooltip.ts
|
|
151
|
+
var TOOLTIP_BEHAVIOR_ID = "ingglish-tooltip-behavior";
|
|
152
|
+
var TOOLTIP_STYLES = `
|
|
153
|
+
.${WORD_SPAN_CLASS} {
|
|
154
|
+
cursor: help;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.ingglish-tooltip {
|
|
158
|
+
position: fixed !important;
|
|
159
|
+
background: #333 !important;
|
|
160
|
+
color: #fff !important;
|
|
161
|
+
padding: 4px 8px !important;
|
|
162
|
+
border-radius: 4px !important;
|
|
163
|
+
font-size: 12px !important;
|
|
164
|
+
font-family: system-ui, -apple-system, sans-serif !important;
|
|
165
|
+
line-height: 1.4 !important;
|
|
166
|
+
white-space: nowrap !important;
|
|
167
|
+
z-index: 2147483647 !important;
|
|
168
|
+
pointer-events: none !important;
|
|
169
|
+
opacity: 0;
|
|
170
|
+
animation: ingglish-tooltip-fade-in 0.15s ease-out forwards;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.ingglish-tooltip::after {
|
|
174
|
+
content: '' !important;
|
|
175
|
+
position: absolute !important;
|
|
176
|
+
left: var(--arrow-left, 50%) !important;
|
|
177
|
+
transform: translateX(-50%) !important;
|
|
178
|
+
border: 5px solid transparent !important;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ingglish-tooltip--above::after {
|
|
182
|
+
top: 100% !important;
|
|
183
|
+
border-top-color: #333 !important;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.ingglish-tooltip--below::after {
|
|
187
|
+
bottom: 100% !important;
|
|
188
|
+
border-bottom-color: #333 !important;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@keyframes ingglish-tooltip-fade-in {
|
|
192
|
+
to { opacity: 1; }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.${FORMAT_DIFF_CLASS} {
|
|
196
|
+
border-bottom: 1.5px dotted currentColor;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.${NOT_FOUND_CLASS} {
|
|
200
|
+
opacity: 0.55;
|
|
201
|
+
text-decoration: underline dotted currentColor;
|
|
202
|
+
text-decoration-thickness: 1.5px;
|
|
203
|
+
text-underline-offset: 0.2em;
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
function injectTooltipBehavior(targetDoc = document) {
|
|
207
|
+
if (targetDoc.documentElement.hasAttribute(`data-${TOOLTIP_BEHAVIOR_ID}`)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
targetDoc.documentElement.setAttribute(`data-${TOOLTIP_BEHAVIOR_ID}`, "true");
|
|
211
|
+
let activeTooltip = null;
|
|
212
|
+
let activeWord = null;
|
|
213
|
+
function removeTooltip() {
|
|
214
|
+
if (activeTooltip) {
|
|
215
|
+
activeTooltip.remove();
|
|
216
|
+
activeTooltip = null;
|
|
217
|
+
activeWord = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
targetDoc.addEventListener(
|
|
221
|
+
"mouseover",
|
|
222
|
+
(e) => {
|
|
223
|
+
const word = e.target.closest?.(`.${WORD_SPAN_CLASS}`);
|
|
224
|
+
if (word === activeWord) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
removeTooltip();
|
|
228
|
+
if (!word) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const orig = word.getAttribute(ATTR_ORIGINAL_WORD);
|
|
232
|
+
if (orig === null || orig === "") {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const tooltip = targetDoc.createElement("div");
|
|
236
|
+
tooltip.className = "ingglish-tooltip";
|
|
237
|
+
tooltip.textContent = orig;
|
|
238
|
+
targetDoc.body.append(tooltip);
|
|
239
|
+
activeTooltip = tooltip;
|
|
240
|
+
activeWord = word;
|
|
241
|
+
const wordRect = word.getBoundingClientRect();
|
|
242
|
+
const tipRect = tooltip.getBoundingClientRect();
|
|
243
|
+
const viewportWidth = targetDoc.defaultView?.innerWidth ?? 0;
|
|
244
|
+
const showAbove = wordRect.top > tipRect.height + 10;
|
|
245
|
+
tooltip.classList.add(showAbove ? "ingglish-tooltip--above" : "ingglish-tooltip--below");
|
|
246
|
+
const top = showAbove ? wordRect.top - tipRect.height - 5 : wordRect.bottom + 5;
|
|
247
|
+
tooltip.style.top = `${top}px`;
|
|
248
|
+
let left = wordRect.left + wordRect.width / 2 - tipRect.width / 2;
|
|
249
|
+
left = Math.max(4, Math.min(left, viewportWidth - tipRect.width - 4));
|
|
250
|
+
tooltip.style.left = `${left}px`;
|
|
251
|
+
const arrowLeft = wordRect.left + wordRect.width / 2 - left;
|
|
252
|
+
tooltip.style.setProperty("--arrow-left", `${arrowLeft}px`);
|
|
253
|
+
},
|
|
254
|
+
true
|
|
255
|
+
);
|
|
256
|
+
targetDoc.addEventListener("scroll", removeTooltip, true);
|
|
257
|
+
}
|
|
258
|
+
function injectTooltipStyles(targetDoc = document) {
|
|
259
|
+
if (targetDoc.querySelector(`#${TOOLTIP_STYLES_ID}`)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const style = targetDoc.createElement("style");
|
|
263
|
+
style.id = TOOLTIP_STYLES_ID;
|
|
264
|
+
style.textContent = TOOLTIP_STYLES;
|
|
265
|
+
targetDoc.head?.append(style);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/translate/chunked.ts
|
|
269
|
+
function processChunked(items, processFn, chunkSize, onProgress, syncThreshold = 0) {
|
|
270
|
+
const total = items.length;
|
|
271
|
+
if (total === 0) {
|
|
272
|
+
return Promise.resolve();
|
|
273
|
+
}
|
|
274
|
+
if (total <= syncThreshold) {
|
|
275
|
+
for (let i = 0; i < total; i++) {
|
|
276
|
+
processFn(items[i]);
|
|
277
|
+
if (onProgress) {
|
|
278
|
+
onProgress(i + 1, total);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return Promise.resolve();
|
|
282
|
+
}
|
|
283
|
+
return new Promise((resolve) => {
|
|
284
|
+
let index = 0;
|
|
285
|
+
function processChunk() {
|
|
286
|
+
const endIndex = Math.min(index + chunkSize, total);
|
|
287
|
+
while (index < endIndex) {
|
|
288
|
+
processFn(items[index]);
|
|
289
|
+
index++;
|
|
290
|
+
if (onProgress) {
|
|
291
|
+
onProgress(index, total);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (index < total) {
|
|
295
|
+
requestAnimationFrame(processChunk);
|
|
296
|
+
} else {
|
|
297
|
+
resolve();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
requestAnimationFrame(processChunk);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/translate/tooltip-fragment.ts
|
|
305
|
+
var import_ingglish = require("ingglish");
|
|
306
|
+
var import_normalize2 = require("@ingglish/normalize");
|
|
307
|
+
var templateSpan = null;
|
|
308
|
+
function createTooltipFragment(text, format = "ingglish") {
|
|
309
|
+
const tokens = (0, import_ingglish.translateSyncWithMapping)(text, { format });
|
|
310
|
+
const stdTokens = format === "ingglish" ? null : (0, import_ingglish.translateSyncWithMapping)(text, { format: "ingglish" });
|
|
311
|
+
return buildTooltipFragment(
|
|
312
|
+
tokens.map((token, i) => {
|
|
313
|
+
if (token.isWord && token.original !== token.translated) {
|
|
314
|
+
const stdSpelling = stdTokens?.[i]?.translated;
|
|
315
|
+
const isDiff = stdSpelling !== void 0 && stdSpelling.toLowerCase() !== token.translated.toLowerCase();
|
|
316
|
+
const notFound = !token.matched;
|
|
317
|
+
const tooltip = isDiff ? `${token.original} (Ingglish: ${stdSpelling})` : token.original;
|
|
318
|
+
return { isDiff, notFound, text: token.translated, tooltip };
|
|
319
|
+
}
|
|
320
|
+
return { text: token.translated };
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
function createTooltipFragmentFromMap(text, translations) {
|
|
325
|
+
const normalized = (0, import_normalize2.normalizeApostrophes)(text);
|
|
326
|
+
const tokens = normalized.split(import_normalize2.WORD_SPLIT_REGEX);
|
|
327
|
+
return buildTooltipFragment(
|
|
328
|
+
tokens.filter(Boolean).map((token) => {
|
|
329
|
+
if (import_normalize2.WORD_TEST_REGEX.test(token)) {
|
|
330
|
+
const lowerToken = token.toLowerCase();
|
|
331
|
+
const translated = translations[lowerToken];
|
|
332
|
+
if (translated !== void 0 && translated !== lowerToken) {
|
|
333
|
+
const pattern = (0, import_normalize2.detectCasePattern)(token);
|
|
334
|
+
return { text: (0, import_normalize2.applyCasePattern)(translated, pattern, token), tooltip: token };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return { text: token };
|
|
338
|
+
})
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
function createTooltipFragmentFromTokens(tokens) {
|
|
342
|
+
return buildTooltipFragment(
|
|
343
|
+
tokens.map((token) => {
|
|
344
|
+
if (token.isWord && token.original !== token.translated) {
|
|
345
|
+
return { notFound: !token.matched, text: token.translated, tooltip: token.original };
|
|
346
|
+
}
|
|
347
|
+
if (token.isWord && !token.matched) {
|
|
348
|
+
return { notFound: true, text: token.translated, tooltip: token.original };
|
|
349
|
+
}
|
|
350
|
+
return { text: token.translated };
|
|
351
|
+
})
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
function buildTooltipFragment(items) {
|
|
355
|
+
const fragment = document.createDocumentFragment();
|
|
356
|
+
let pendingText = "";
|
|
357
|
+
for (const item of items) {
|
|
358
|
+
if (item.tooltip === void 0) {
|
|
359
|
+
pendingText += item.text;
|
|
360
|
+
} else {
|
|
361
|
+
if (pendingText) {
|
|
362
|
+
fragment.append(document.createTextNode(pendingText));
|
|
363
|
+
pendingText = "";
|
|
364
|
+
}
|
|
365
|
+
const span = getTemplateSpan().cloneNode(false);
|
|
366
|
+
span.setAttribute(ATTR_ORIGINAL_WORD, item.tooltip);
|
|
367
|
+
if (item.isDiff === true) {
|
|
368
|
+
span.classList.add(FORMAT_DIFF_CLASS);
|
|
369
|
+
}
|
|
370
|
+
if (item.notFound === true) {
|
|
371
|
+
span.classList.add(NOT_FOUND_CLASS);
|
|
372
|
+
}
|
|
373
|
+
span.textContent = item.text;
|
|
374
|
+
fragment.append(span);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (pendingText) {
|
|
378
|
+
fragment.append(document.createTextNode(pendingText));
|
|
379
|
+
}
|
|
380
|
+
return fragment;
|
|
381
|
+
}
|
|
382
|
+
function getTemplateSpan() {
|
|
383
|
+
if (templateSpan === null) {
|
|
384
|
+
templateSpan = document.createElement("span");
|
|
385
|
+
templateSpan.className = WORD_SPAN_CLASS;
|
|
386
|
+
}
|
|
387
|
+
return templateSpan;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/translate/apply-map.ts
|
|
391
|
+
var DEFAULT_CHUNK_SIZE = 100;
|
|
392
|
+
var SYNC_THRESHOLD = 500;
|
|
393
|
+
var WORD_REGEX = /(?<!\d)[a-zA-Z\u00C0-\u024F']+(?!\d)/g;
|
|
394
|
+
function applyTranslationsMap(root, translations, options = {}) {
|
|
395
|
+
requireBrowser();
|
|
396
|
+
const {
|
|
397
|
+
chunkSize = DEFAULT_CHUNK_SIZE,
|
|
398
|
+
onProgress,
|
|
399
|
+
showTooltips = false,
|
|
400
|
+
textNodes: preCollectedNodes
|
|
401
|
+
} = options;
|
|
402
|
+
const targetDoc = root instanceof Document ? root : root.ownerDocument;
|
|
403
|
+
if (showTooltips && targetDoc !== null) {
|
|
404
|
+
injectTooltipStyles(targetDoc);
|
|
405
|
+
injectTooltipBehavior(targetDoc);
|
|
406
|
+
}
|
|
407
|
+
const textNodes = preCollectedNodes ?? collectTextNodes(root);
|
|
408
|
+
return processChunked(
|
|
409
|
+
textNodes,
|
|
410
|
+
(node) => {
|
|
411
|
+
processTextNode(node, translations, showTooltips);
|
|
412
|
+
},
|
|
413
|
+
chunkSize,
|
|
414
|
+
onProgress,
|
|
415
|
+
SYNC_THRESHOLD
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
function processTextNode(textNode, translations, showTooltips) {
|
|
419
|
+
const parent = textNode.parentElement;
|
|
420
|
+
if (parent && !parent.hasAttribute(ATTR_ORIGINAL_CONTENT)) {
|
|
421
|
+
parent.setAttribute(ATTR_ORIGINAL_CONTENT, textNode.textContent ?? "");
|
|
422
|
+
}
|
|
423
|
+
if (showTooltips) {
|
|
424
|
+
const fragment = createTooltipFragmentFromMap(textNode.textContent ?? "", translations);
|
|
425
|
+
textNode.replaceWith(fragment);
|
|
426
|
+
} else {
|
|
427
|
+
const text = textNode.textContent ?? "";
|
|
428
|
+
const normalized = (0, import_normalize3.normalizeApostrophes)(text);
|
|
429
|
+
let result = "";
|
|
430
|
+
let lastIndex = 0;
|
|
431
|
+
let match;
|
|
432
|
+
WORD_REGEX.lastIndex = 0;
|
|
433
|
+
while ((match = WORD_REGEX.exec(normalized)) !== null) {
|
|
434
|
+
result += normalized.slice(lastIndex, match.index);
|
|
435
|
+
lastIndex = match.index + match[0].length;
|
|
436
|
+
const word = match[0];
|
|
437
|
+
const translated = translations[word.toLowerCase()];
|
|
438
|
+
if (translated === void 0) {
|
|
439
|
+
result += word;
|
|
440
|
+
} else {
|
|
441
|
+
const pattern = (0, import_normalize3.detectCasePattern)(word);
|
|
442
|
+
result += (0, import_normalize3.applyCasePattern)(translated, pattern, word);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
result += normalized.slice(lastIndex);
|
|
446
|
+
textNode.textContent = result;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/translate/restore.ts
|
|
451
|
+
function restoreDOM(root) {
|
|
452
|
+
requireBrowser();
|
|
453
|
+
const wordSpans = Array.from(
|
|
454
|
+
root.querySelectorAll(`.${WORD_SPAN_CLASS}[${ATTR_ORIGINAL_WORD}]`)
|
|
455
|
+
);
|
|
456
|
+
for (const span of wordSpans) {
|
|
457
|
+
const originalWord = span.getAttribute(ATTR_ORIGINAL_WORD);
|
|
458
|
+
if (originalWord !== null) {
|
|
459
|
+
const textNode = document.createTextNode(originalWord);
|
|
460
|
+
span.replaceWith(textNode);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
const elementsWithOriginal = Array.from(
|
|
464
|
+
root.querySelectorAll(`[${ATTR_ORIGINAL_CONTENT}]`)
|
|
465
|
+
);
|
|
466
|
+
for (const element of elementsWithOriginal) {
|
|
467
|
+
element.removeAttribute(ATTR_ORIGINAL_CONTENT);
|
|
468
|
+
}
|
|
469
|
+
const attrSelector = TRANSLATABLE_ATTRIBUTES.map(
|
|
470
|
+
(attr) => `[${ATTR_ORIGINAL_PREFIX}${attr}]`
|
|
471
|
+
).join(",");
|
|
472
|
+
const elementsWithTranslatedAttrs = Array.from(root.querySelectorAll(attrSelector));
|
|
473
|
+
for (const element of elementsWithTranslatedAttrs) {
|
|
474
|
+
for (const attrName of TRANSLATABLE_ATTRIBUTES) {
|
|
475
|
+
const originalAttrName = `${ATTR_ORIGINAL_PREFIX}${attrName}`;
|
|
476
|
+
const originalValue = element.getAttribute(originalAttrName);
|
|
477
|
+
if (originalValue !== null) {
|
|
478
|
+
element.setAttribute(attrName, originalValue);
|
|
479
|
+
element.removeAttribute(originalAttrName);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/translate/translator.ts
|
|
486
|
+
var import_ingglish2 = require("ingglish");
|
|
487
|
+
var DEFAULT_CHUNK_SIZE2 = 100;
|
|
488
|
+
async function translateDOM(root, options = {}) {
|
|
489
|
+
if (!options.translateWithMappingFn) {
|
|
490
|
+
await (0, import_ingglish2.translate)("");
|
|
491
|
+
}
|
|
492
|
+
const result = translateDOMSync(root, options);
|
|
493
|
+
if (result instanceof Promise) {
|
|
494
|
+
await result;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function translateDOMSync(root, options = {}) {
|
|
498
|
+
requireBrowser();
|
|
499
|
+
const {
|
|
500
|
+
chunked = false,
|
|
501
|
+
chunkSize = DEFAULT_CHUNK_SIZE2,
|
|
502
|
+
onProgress,
|
|
503
|
+
outputFormat = "ingglish",
|
|
504
|
+
showTooltips = false,
|
|
505
|
+
skipClasses = DEFAULT_SKIP_CLASSES,
|
|
506
|
+
skipTags = DEFAULT_SKIP_TAGS,
|
|
507
|
+
translateAttributes = true,
|
|
508
|
+
translateWithMappingFn
|
|
509
|
+
} = options;
|
|
510
|
+
const targetDoc = root instanceof Document ? root : root.ownerDocument;
|
|
511
|
+
if (showTooltips && targetDoc !== null) {
|
|
512
|
+
injectTooltipStyles(targetDoc);
|
|
513
|
+
injectTooltipBehavior(targetDoc);
|
|
514
|
+
}
|
|
515
|
+
const textNodes = collectTextNodes(root, skipTags, skipClasses);
|
|
516
|
+
const totalNodes = textNodes.length;
|
|
517
|
+
const customTranslateFn = translateWithMappingFn ? (text, format) => translateWithMappingFn(text, format).map((t) => t.translated).join("") : void 0;
|
|
518
|
+
if (translateAttributes) {
|
|
519
|
+
translateElementAttributes(root, skipTags, skipClasses, outputFormat, customTranslateFn);
|
|
520
|
+
}
|
|
521
|
+
if (chunked) {
|
|
522
|
+
return processChunked(
|
|
523
|
+
textNodes,
|
|
524
|
+
(node) => {
|
|
525
|
+
translateTextNode(
|
|
526
|
+
node,
|
|
527
|
+
showTooltips,
|
|
528
|
+
outputFormat,
|
|
529
|
+
customTranslateFn,
|
|
530
|
+
translateWithMappingFn
|
|
531
|
+
);
|
|
532
|
+
},
|
|
533
|
+
chunkSize,
|
|
534
|
+
onProgress
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
for (let i = 0; i < totalNodes; i++) {
|
|
538
|
+
translateTextNode(
|
|
539
|
+
textNodes[i],
|
|
540
|
+
showTooltips,
|
|
541
|
+
outputFormat,
|
|
542
|
+
customTranslateFn,
|
|
543
|
+
translateWithMappingFn
|
|
544
|
+
);
|
|
545
|
+
if (onProgress) {
|
|
546
|
+
onProgress(i + 1, totalNodes);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function translateElementAttributes(root, skipTags, skipClasses, format = "ingglish", customTranslateFn) {
|
|
551
|
+
const doTranslate = customTranslateFn ?? ((text, fmt) => (0, import_ingglish2.translateSync)(text, { format: fmt }));
|
|
552
|
+
const attrSelector = TRANSLATABLE_ATTRIBUTES.map((attr) => `[${attr}]`).join(",");
|
|
553
|
+
const elements = Array.from(root.querySelectorAll(attrSelector));
|
|
554
|
+
for (const element of elements) {
|
|
555
|
+
if (shouldSkipElement(element, skipTags, skipClasses)) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
for (const attrName of TRANSLATABLE_ATTRIBUTES) {
|
|
559
|
+
const attrValue = element.getAttribute(attrName);
|
|
560
|
+
if (attrValue !== null && attrValue.length > 0) {
|
|
561
|
+
const originalAttrName = `${ATTR_ORIGINAL_PREFIX}${attrName}`;
|
|
562
|
+
if (!element.hasAttribute(originalAttrName)) {
|
|
563
|
+
element.setAttribute(originalAttrName, attrValue);
|
|
564
|
+
}
|
|
565
|
+
element.setAttribute(attrName, doTranslate(attrValue, format));
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function translateTextNode(textNode, showTooltips, outputFormat, customTranslateFn, customMappingFn) {
|
|
571
|
+
const originalText = textNode.textContent;
|
|
572
|
+
if (!originalText) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const parent = textNode.parentElement;
|
|
576
|
+
if (showTooltips) {
|
|
577
|
+
const fragment = customMappingFn ? createTooltipFragmentFromTokens(customMappingFn(originalText, outputFormat)) : createTooltipFragment(originalText, outputFormat);
|
|
578
|
+
if (parent && !parent.hasAttribute(ATTR_ORIGINAL_CONTENT)) {
|
|
579
|
+
parent.setAttribute(ATTR_ORIGINAL_CONTENT, originalText);
|
|
580
|
+
}
|
|
581
|
+
textNode.replaceWith(fragment);
|
|
582
|
+
} else {
|
|
583
|
+
if (parent && !parent.hasAttribute(ATTR_ORIGINAL_CONTENT)) {
|
|
584
|
+
parent.setAttribute(ATTR_ORIGINAL_CONTENT, originalText);
|
|
585
|
+
}
|
|
586
|
+
const doTranslate = customTranslateFn ?? ((text, fmt) => (0, import_ingglish2.translateSync)(text, { format: fmt }));
|
|
587
|
+
textNode.textContent = doTranslate(originalText, outputFormat);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
591
|
+
0 && (module.exports = {
|
|
592
|
+
DEFAULT_SKIP_CLASSES,
|
|
593
|
+
DEFAULT_SKIP_TAGS,
|
|
594
|
+
applyTranslationsMap,
|
|
595
|
+
collectTextNodes,
|
|
596
|
+
extractWordsFromNodes,
|
|
597
|
+
injectTooltipBehavior,
|
|
598
|
+
injectTooltipStyles,
|
|
599
|
+
restoreDOM,
|
|
600
|
+
translateDOM
|
|
601
|
+
});
|