Church Translate, as it runs at the campuses

Live sermon translation into 100+ languages, running entirely on one Mac per
campus: audio in, voice detection, Whisper on the GPU, NLLB-200 in its own
process, and a built-in web server the congregation reads from on their
phones. No audio leaves the building.

This is the source only. What used to sit beside it — 14 GB of speech and
translation models, 8 GB of voice bundles, 6 GB of natural voices, the Electron
caches, and the app data holding the operator PIN and every service transcript
— is machine state and downloaded assets, and stays in the NAS backup where it
belongs. The vendored node, cloudflared and Windows ffmpeg binaries are out too
(533 MB, re-fetched for packaging); the browser-side runtime beside them is
kept, so a checkout still serves the congregation page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 15:41:21 -04:00
co-authored by Claude Opus 5
commit c9eef0672c
118 changed files with 36031 additions and 0 deletions
+269
View File
@@ -0,0 +1,269 @@
/*
* mt-guard.js — catch what NLLB invents, before a congregation hears it.
*
* WHY THIS IS NOT A GENERATION-PARAMETER FIX. Measured against the shipped
* model (nllb-200-distilled-600M fp32), "Donald Trump." produces
* "Donald Trump. ¿Qué es eso?" under ALL of: default params, an input-derived
* max_new_tokens cap, no_repeat_ngram_size:3, and num_beams:2. The model
* genuinely believes that is the translation. Decoding cannot be tuned out of
* it; the output has to be checked.
*
* WHY THE OBVIOUS RULE IS WRONG. "Source has no '?', target has one, so strip
* the question" corrupts real output: Whisper routinely drops the question mark
* on a rhetorical aside, so "Are you with me" arrives unpunctuated and a
* correct Spanish rendering is "¿Estás conmigo?" — which that rule would
* delete. The reliable signal is not the question mark, it is the SENTENCE
* COUNT: one sentence in, two out, and the second one invented.
*
* WHY ONLY SHORT INPUTS. Hallucination is a short-input failure — the model has
* no context and fills the gap. Long sentences are also the ones that
* legitimately split in the target (many languages prefer shorter sentences
* than English), so clamping them would delete real scripture. The length gate
* keeps the clamp where the evidence is.
*/
(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory();
else root.MtGuard = factory();
}(typeof self !== 'undefined' ? self : this, function () {
'use strict';
/* Sentence-final punctuation across the scripts this app serves: Latin,
CJK ideographic stop, fullwidth marks, Arabic question mark, Devanagari
danda, Armenian, Greek, Thai has none. */
const TERMINATORS = '.!?。!?؟۔।॥…՜՞;';
const TERM_RE = new RegExp('[' + TERMINATORS.replace(/[.\-\]\\]/g, '\\$&') + ']');
const QUESTION_RE = /[??؟՞]/;
/* Beyond this the input carries enough context that the model stops
inventing, and legitimate sentence-splitting starts. Measured failures all
sit far below it. */
/* Narrow deliberately. Every hallucination measured against this model sat
at one or two words; a wider gate starts reaching utterances that split
into two target sentences legitimately, and deleting real scripture is a
far worse failure than leaving one invented clause in. */
const SHORT_WORDS = 6;
function words(s) {
return String(s || '').trim().split(/\s+/).filter(Boolean).length;
}
/* Count sentences by terminator runs. "Mr. Smith went." must not read as two,
so a terminator followed by a lowercase letter does not end a sentence. */
function sentenceCount(s) {
const parts = splitSentences(s);
return parts.length || 1;
}
function splitSentences(s) {
const text = String(s || '').trim();
if (!text) return [];
const out = [];
let cur = '';
for (let i = 0; i < text.length; i++) {
const ch = text[i];
cur += ch;
if (!TERM_RE.test(ch)) continue;
/* Absorb a run of terminators and any closing quote/bracket. */
while (i + 1 < text.length && (TERM_RE.test(text[i + 1]) || /["'\)\]”’」』]/.test(text[i + 1]))) {
cur += text[++i];
}
const rest = text.slice(i + 1);
/* An abbreviation or a decimal: "Dr. Smith", "3.5" — not a boundary. */
if (/^\s*[a-zà-ÿ0-9]/.test(rest)) continue;
out.push(cur.trim());
cur = '';
}
if (cur.trim()) out.push(cur.trim());
return out;
}
/*
* NLLB was trained on a lot of subtitles and emits their dialogue dash. It is
* never correct here — nothing in this app is a screenplay.
*/
function stripDash(s) {
return String(s || '').replace(/^\s*[-‐-―−]\s+/, '').trim();
}
/*
* The main entry. Returns { text, trimmed, reason } — `trimmed` is true when
* something was removed, so callers can log it and the operator can see how
* often the model is inventing.
*/
/*
* A ONE-WORD source that comes back with a comma-clause. Measured across the
* archive, 13.4% of everything the preacher says is one or two words, and the
* model answers the short ones conversationally:
*
* "Hello." -> "Hola, ¿qué pasa?" "Yeah." -> "Sí, es cierto."
* "No." -> "No, no." "Good." -> "Bien, muy bien."
* "Come." -> "Ven, ven." "Look." -> "Mira, ¿qué?"
*
* One English word cannot mean two clauses, so the tail is invented. Keeping
* only the head fixes all of these and touches nothing that was already
* right ("La fe.", "Sin embargo.", "Por lo tanto." have no comma).
*
* Restricted to EXACTLY one word deliberately — at two words a comma can be
* legitimate. Handles the CJK and ideographic commas too, since this is the
* one output rule that needs no per-language knowledge.
*/
function trimInventedClause(src, out) {
const words = String(src == null ? '' : src).replace(/[.!?,;:]+$/, '').split(/\s+/).filter(Boolean);
if (words.length !== 1) return out;
const m = String(out).match(/^([^,\uff0c\u3001]+)[,\uff0c\u3001]/u);
if (!m) return out;
const head = m[1].trim();
if (!head) return out;
const end = (String(src).match(/[.!?]$/) || [''])[0];
return /[.!?\u3002\uff01\uff1f]$/.test(head) ? head : head + end;
}
/*
* THE WHOLE OUTPUT IS AN INVENTED QUESTION.
*
* The sentence-count rule above catches an invented question APPENDED to a
* real translation ("Donald Trump. ¿Qué es eso?"). It cannot catch the case
* where the invention IS the output: one sentence in, one sentence out, and
* the one out is "¿Qué es eso?" for a source of "Mmm" or "them" — MEASURED
* in a live service, spoken aloud to the Spanish congregation three times in
* one morning, and produced by 34 distinct short sources in the archive.
*
* Narrow on purpose. Source: at most two words and not itself a question.
* Output: every sentence is a question. And the source must not be an
* English interrogative or a tag — "Why.", "Who.", "Right?", "You know." —
* because for those a Spanish question IS the correct rendering (measured:
* 'why.' -> '¿Por qué?', 'Isn't it.' -> '¿No es cierto?'). Whisper drops
* question marks routinely, so the word list, not the punctuation, is the
* exemption.
*/
/*
* Widened with the auxiliaries and contractions that FRONT a real English
* question, because the rule below now reads every word of a short source
* rather than the single word it used to be limited to.
*
* MEASURED: without `could` and `you're`, widening the rule deleted
* 'Could it be' -> '¿Podría ser' and "You're interested in" -> '¿Te interesa?'
* — both correct renderings of questions Whisper handed over unpunctuated.
* With them the widened rule breaks NOTHING in 22,526 real short lines.
*/
const INTERROGATIVE_SRC = /^(?:why|who|whom|whose|what|where|when|when'?s|how|which|isn'?t|right|okay|ok|see|really|huh|anybody|anyone|somebody|someone|guess|any|says|you|you'?re|we'?re|they'?re|there'?s|it'?s|are|am|is|was|were|do|does|did|can|could|will|would|shall|should|may|might|must|have|has|had|ready|let'?s)$/i;
function isQuestionSentence(p) {
const t = String(p || '').trim();
return /^[¿]/.test(t) || /[??؟՞]["'\)\]”’」』]*$/.test(t);
}
function bareSource(src) {
return String(src || '').replace(/^[\s"'“‘(\[]+/u, '').replace(/[.!,;:…\-\s"'”’)\]]+$/u, '').trim();
}
/* Short enough that the model had no context to work from. Four, measured:
see the numbers below. */
const INVENTED_MAX_WORDS = 4;
/*
* UP TO FOUR words, not one.
*
* This was restricted to a single word because a two-word rule was measured
* blanking 'Why not.' -> '¿Por qué no?', 'Guess what.' -> '¿Adivina qué?',
* 'You ready.' -> '¿Están listos?'. That finding was right; the conclusion
* drawn from it was too narrow. What those cases have in common is not their
* LENGTH, it is that they contain an interrogative word — so the exemption
* below, not the word count, is what protects them. Testing every word of
* the source instead of only a lone one keeps all of them and frees the
* count to rise.
*
* MEASURED over this church's own archive — 349 confirmed inventions and
* 22,526 legitimate short translations from ten real services:
*
* one word (before) 198/349 caught 57% 0 legitimate lines broken
* <= 4 words 254/349 caught 73% 0 legitimate lines broken
*
* The congregation was hearing 'Check, check.' as '¿Qué quieres?', 'Holy
* Spirit.' as '我們必須要做什麼?' and 'Hello.' as '¿Qué pasa?' — all of them
* two words, all of them one word past where this rule used to stop looking.
*
* A source of NO words reaches here too ('-', '...', a stray dash Whisper
* emits between speakers). Nothing cannot mean a question, so it is not
* exempted: an empty source with an all-question output is an invention by
* definition.
*/
function inventedQuestion(src, out) {
if (QUESTION_RE.test(src)) return false;
const words = bareSource(src).split(/\s+/).filter(Boolean);
if (words.length > INVENTED_MAX_WORDS) return false;
/* ANY interrogative word, anywhere in the source — 'Guess what.' and
'Why not.' put it first, 'How about now' and 'And why' do not. */
if (words.some(function (w) { return INTERROGATIVE_SRC.test(w); })) return false;
const parts = splitSentences(out);
return parts.length > 0 && parts.every(isQuestionSentence);
}
/*
* THE PORTUGUESE TWIN. NLLB's stock invention for a fragment in Portuguese is
* not a question but the statement "Não." ('See.' -> 'Não.', 'Wow.' ->
* 'Não.', 'way.' -> 'Não.', 'You understand?' -> '- Não.' — measured in
* the archive), and the Spanish "No, no lo sé." family. A short source with
* no negation in it cannot mean "No".
*/
const NEGATION_SRC = /\b(?:no|not|never|nope|nah|nay|nix|negative|nothing|none|nobody|neither|nor|don'?t|can'?t|won'?t|isn'?t|ain'?t|didn'?t|doesn'?t|wasn'?t|aren'?t|couldn'?t|wouldn'?t|shouldn'?t)\b|\b(?:uh|nuh)-?uh\b/i;
const NEGATION_OUT = /^[\s¿¡\-‐-―−"'“”]*(?:não|nao|no|non|nein|nee|niet|nej|nie|нет|いいえ|不|아니요|لا)[\s.!?…"'”]*$/iu;
function inventedNegation(src, out) {
const bare = bareSource(src);
const words = bare.split(/\s+/).filter(Boolean);
if (!words.length || words.length > 2) return false;
if (NEGATION_SRC.test(bare)) return false;
return NEGATION_OUT.test(String(out || ''));
}
function clean(source, target) {
const src = String(source == null ? '' : source).trim();
let out = stripDash(target);
if (!out) return { text: '', trimmed: false, reason: '' };
const dashed = out !== String(target || '').trim();
const oneWord = trimInventedClause(src, out);
if (oneWord !== out) {
return { text: oneWord, trimmed: true,
reason: 'dropped invented clause after a one-word line \u2014 "' + out + '"' };
}
if (inventedQuestion(src, out)) {
return { text: '', trimmed: true,
reason: 'dropped an invented question for a short line \u2014 "' + out + '"' };
}
if (inventedNegation(src, out)) {
return { text: '', trimmed: true,
reason: 'dropped an invented negation for a short line \u2014 "' + out + '"' };
}
if (words(src) > SHORT_WORDS) {
return { text: out, trimmed: dashed, reason: dashed ? 'subtitle dash' : '' };
}
const nSrc = sentenceCount(src);
const parts = splitSentences(out);
if (parts.length <= nSrc) {
return { text: out, trimmed: dashed, reason: dashed ? 'subtitle dash' : '' };
}
/*
* More sentences came out than went in, on an input too short to justify
* it. Keep the first nSrc — NLLB translates in order, so the real content
* is at the front and the invention is appended.
*/
const kept = parts.slice(0, nSrc).join(' ').trim();
const dropped = parts.slice(nSrc).join(' ').trim();
if (!kept) return { text: out, trimmed: dashed, reason: '' };
return {
text: kept,
trimmed: true,
reason: 'dropped invented ' + (QUESTION_RE.test(dropped) ? 'question' : 'text') +
' "' + dropped + '"'
};
}
return { clean, splitSentences, sentenceCount, stripDash, trimInventedClause, inventedQuestion, inventedNegation, SHORT_WORDS };
}));