aboutsummaryrefslogtreecommitdiffstats
path: root/src/translate-split.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/translate-split.js')
-rw-r--r--src/translate-split.js35
1 files changed, 35 insertions, 0 deletions
diff --git a/src/translate-split.js b/src/translate-split.js
new file mode 100644
index 0000000..78833da
--- /dev/null
+++ b/src/translate-split.js
@@ -0,0 +1,35 @@
+// What must come back from a translation exactly as it went in: links,
+// nostr: mentions, custom emoji shortcodes, hashtags, @handles, invoices and
+// other long codes. Translation models mangle them ("https: / / example.com
+// / fot.jpg"), so only the prose between them is translated and the text is
+// put back together around the untouched tokens. Shared by the registrar
+// (names/translate.js) and the browser's own translator (messages.js).
+const KEEP = /(https?:\/\/\S+|nostr:[a-z0-9]+|:[a-z0-9_+-]{1,40}:|#[\p{L}\p{N}_]+|@[\w.-]+(?:@[\w.-]+)?|\b(?:ln|lightning:|cashu|bitcoin:|nevent|npub|nprofile|note|naddr)[a-z0-9]{20,}|\S{40,})/giu;
+
+export function splitProse(text) {
+ const parts = [];
+ let last = 0;
+ for (const m of String(text || '').matchAll(KEEP)) {
+ if (m.index > last) parts.push({ prose: text.slice(last, m.index) });
+ parts.push({ keep: m[0] });
+ last = m.index + m[0].length;
+ }
+ if (last < text.length) parts.push({ prose: text.slice(last) });
+ return parts;
+}
+// a piece worth translating: has words, not just spaces and punctuation
+export const hasWords = (s) => /\p{L}{2,}/u.test(s || '');
+
+// Translate the prose pieces (one call for all of them) and reassemble.
+// Each piece keeps its own leading/trailing whitespace — line breaks matter.
+export async function translatePieces(text, translateMany) {
+ const parts = splitProse(text);
+ const prose = parts.filter((p) => p.prose && hasWords(p.prose));
+ if (!prose.length) return text;
+ const outs = await translateMany(prose.map((p) => p.prose.trim()));
+ prose.forEach((p, i) => {
+ const lead = p.prose.match(/^\s*/)[0], trail = p.prose.match(/\s*$/)[0];
+ p.out = lead + String(outs[i] ?? p.prose.trim()) + trail;
+ });
+ return parts.map((p) => p.keep ?? p.out ?? p.prose).join('');
+}