/* * preach.js — plain English for the model, the preacher's words for the room. * * NLLB was trained on documents, not sermons. Handed a preaching idiom it * translates the words and loses the instruction, and it does it fluently, so * nobody watching a caption in a language they cannot read has any way to know. * Measured against the shipped model, into Spanish, Portuguese and French: * * "Circle that in your Bible." -> spa "En su Biblia." VERB GONE * fra "Envoyez-le dans..." (send it) * por "Envolva isso..." (wrap it) * "Walk it out." -> fra "Je vais sortir." (I'm leaving) * "Let that sink in." -> spa "Deja que se hunde." (drowns) * "Open up to the book of John."-> spa "...la obra de Juan." (the WORKS of John) * * Every entry below was measured before and after in at least three languages * and kept only where the rewrite was better in more than one. "God showed up." * was tested and REMOVED: "Dios apareció" was already right, so the rewrite was * just a different guess. Do not add entries by intuition — measure them. * * CRITICAL: this rewrites ONLY what is sent to the translator. The English * caption, the transcript archive and the operator's monitor all keep what was * actually said. Normalising the source itself would put words in the * preacher's mouth, and one source-side edit propagates to every language and * the permanent record at once. */ (function (root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory(); else root.Preach = factory(); }(typeof self !== 'undefined' ? self : this, function () { 'use strict'; /* Longest and most specific first — "open up to the book of" has to win over the general "open up to". Applied in order, each at most once per sentence. */ const RULES = [ /* Scripture navigation. "open up to" is the one that produced "obra". */ [/\bopen up to the book of\b/gi, 'turn to the book of'], [/\bopen up to\b/gi, 'turn to'], [/\bdrop down to\b/gi, 'go to'], [/\bback up to\b/gi, 'go back to'], [/\bcircle (that|this|it)\b/gi, 'mark $1'], /* Instructions that became statements, or vanished. */ [/\blet (that|this|it) sink in\b/gi, 'think carefully about $1'], [/\bwalk (it|that) out\b/gi, 'put $1 into practice'], [/\bunpack (that|this|it)\b/gi, 'explain $1'], [/\bcamp out here\b/gi, 'stay here'], [/\bgive God a hand of praise\b/gi, 'applaud to praise God'], [/\bput a hand on your neighbou?r\b/gi, 'put a hand on the person next to you'], [/\bturn to your neighbou?r\b/gi, 'turn to the person next to you'], /* Rhetorical checks. Wrong sense, not wrong words. */ [/\bare you tracking with me\b/gi, 'are you following me'], [/\b(catch|get) (this|that)\b/gi, 'understand $2'], [/\bthat will preach\b/gi, 'that is worth preaching'] ]; /* * A rewrite must not change how the sentence is capitalised — the target * inherits sentence case from the source, and lowercasing the first word * produced mid-sentence-looking captions. */ function forTranslation(text) { let out = String(text == null ? '' : text); if (!out) return out; const had = out; for (const [re, to] of RULES) out = out.replace(re, to); if (out === had) return had; /* Restore the opening capital if a rule replaced the first word. */ if (/^[a-z]/.test(out) && /^[A-Z]/.test(had)) out = out.charAt(0).toUpperCase() + out.slice(1); return out; } /* * THE TERMINAL PERIOD ON A SHORT UTTERANCE. One character, and it is the * difference between a name and invented dialogue. * * Measured against the shipped model: * "Peter." -> spa "¿Qué es eso?" | "Peter" -> "Pedro" * "Jesus." -> spa "¿Qué quieres?" | "Jesus" -> "Jesús" * "Matthew." -> spa "¿Qué es eso?" | "Matthew" -> "Mateo" * "Habakkuk." -> fra "Habakuk est mort" | "Habakkuk" -> "Habakuk" * "Simon Peter." -> spa "Sí, es el Simon Peter." | -> "Simón Pedro" * * With the period the model reads a complete conversational turn and answers * it; without, it translates the term. A preacher naming people one at a time * — "Peter. James. John." — got back a run of questions containing no names. * * Only the PERIOD is removed, and only from utterances of three words or * fewer. "?" and "!" carry real meaning and are left alone, and a full * sentence needs its punctuation for the model to parse it at all. */ const SHORT_WORDS_FOR_DOT = 3; function stripShortPeriod(text) { const s = String(text == null ? '' : text); const t = s.trim(); if (!/[^.!?]\.$/.test(t)) return { text: s, hadPeriod: false }; if (t.split(/\s+/).filter(Boolean).length > SHORT_WORDS_FOR_DOT) return { text: s, hadPeriod: false }; return { text: t.slice(0, -1), hadPeriod: true }; } /* Put the period back, so the caption and the voice both get a finished line (espeak takes its final intonation from terminal punctuation). */ function restorePeriod(out, hadPeriod) { const s = String(out == null ? '' : out).trim(); if (!hadPeriod || !s) return s; return /[.!?\u3002\uff01\uff1f\u061f]$/.test(s) ? s : s + '.'; } function rules() { return RULES.length; } return { forTranslation, stripShortPeriod, restorePeriod, rules, RULES }; }));