diff options
Diffstat (limited to 'src/translate-split.js')
| -rw-r--r-- | src/translate-split.js | 40 |
1 files changed, 33 insertions, 7 deletions
diff --git a/src/translate-split.js b/src/translate-split.js index 78833da..95f6468 100644 --- a/src/translate-split.js +++ b/src/translate-split.js @@ -4,6 +4,8 @@ // / 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). +import { scriptOf, LANG_SCRIPT } from './lang-guess.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) { @@ -20,16 +22,40 @@ export function splitProse(text) { // 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) { +// A hashtag stays a hashtag (it's a link to its topic), but one written in +// another script than the reader's means nothing to them: such a tag gets +// its translation in brackets after it, "#総合部絵チャ (general art chat)". +const tagWords = (tag) => tag.slice(1).replace(/_/g, ' '); +export function glossable(tag, target) { + if (!tag.startsWith('#') || !target) return false; + const s = scriptOf(tagWords(tag), 1); + return !!s && !!LANG_SCRIPT[target] && s !== LANG_SCRIPT[target]; +} +// What a translator gets to see of a post: its prose, then the words of the +// hashtags the reader can't read (also what language detection runs on). +export function translatable(text, target) { 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())); + return { + parts, + prose: parts.filter((p) => p.prose && hasWords(p.prose)), + tags: parts.filter((p) => p.keep && glossable(p.keep, target)), + }; +} + +// Translate the prose pieces and the glossed tags (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, target = null) { + const { parts, prose, tags } = translatable(text, target); + if (!prose.length && !tags.length) return text; + const outs = await translateMany([...prose.map((p) => p.prose.trim()), ...tags.map((p) => tagWords(p.keep))]); 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(''); + tags.forEach((p, i) => { + const g = String(outs[prose.length + i] ?? '').trim(); + p.out = g && g.toLowerCase() !== tagWords(p.keep).toLowerCase() ? p.keep + ' (' + g + ')' : p.keep; + }); + return parts.map((p) => p.out ?? p.keep ?? p.prose).join(''); } |
