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:
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* short-questions.js — one-word questions that must never be guessed at.
|
||||
*
|
||||
* MEASURED: "What?" is the single most frequent invented line left after every
|
||||
* other guard — the shipped NLLB renders it "¿Qué es eso?" (15 archive rows),
|
||||
* "Yeah?" -> "¿Qué es eso?" (6), "Hello?" -> "¿Qué pasa?" (6), "How?" -> "¿Cómo
|
||||
* es que lo hizo?" (3). The preacher says "What?" about three times a service.
|
||||
* Because the SOURCE is itself a question, the hallucination guard rightly
|
||||
* refuses to blank the output (a Spanish question is the correct shape) — so
|
||||
* these bypass the model altogether, the way liturgy.js does for "Amen".
|
||||
*
|
||||
* Whole-utterance match only, one word, terminal '?' or '.' or nothing. The
|
||||
* table holds only words whose one-word rendering is not a matter of register
|
||||
* or denomination. Languages not listed fall through to the model unchanged.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) module.exports = factory();
|
||||
else root.ShortQuestions = factory();
|
||||
}(typeof self !== 'undefined' ? self : this, function () {
|
||||
'use strict';
|
||||
|
||||
/* word -> { lang: rendering } — the base language stands for its variants. */
|
||||
const TABLE = {
|
||||
what: { spa_Latn: '¿Qué?', por_Latn: 'O quê?', fra_Latn: 'Quoi ?', deu_Latn: 'Was?', ita_Latn: 'Cosa?' },
|
||||
why: { spa_Latn: '¿Por qué?', por_Latn: 'Por quê?', fra_Latn: 'Pourquoi ?', deu_Latn: 'Warum?', ita_Latn: 'Perché?' },
|
||||
who: { spa_Latn: '¿Quién?', por_Latn: 'Quem?', fra_Latn: 'Qui ?', deu_Latn: 'Wer?', ita_Latn: 'Chi?' },
|
||||
how: { spa_Latn: '¿Cómo?', por_Latn: 'Como?', fra_Latn: 'Comment ?', deu_Latn: 'Wie?', ita_Latn: 'Come?' },
|
||||
where: { spa_Latn: '¿Dónde?', por_Latn: 'Onde?', fra_Latn: 'Où ?', deu_Latn: 'Wo?', ita_Latn: 'Dove?' },
|
||||
when: { spa_Latn: '¿Cuándo?', por_Latn: 'Quando?', fra_Latn: 'Quand ?', deu_Latn: 'Wann?', ita_Latn: 'Quando?' },
|
||||
which: { spa_Latn: '¿Cuál?', por_Latn: 'Qual?', fra_Latn: 'Lequel ?', deu_Latn: 'Welche?', ita_Latn: 'Quale?' },
|
||||
yeah: { spa_Latn: '¿Sí?', por_Latn: 'Sim?', fra_Latn: 'Oui ?', deu_Latn: 'Ja?', ita_Latn: 'Sì?' },
|
||||
yes: { spa_Latn: '¿Sí?', por_Latn: 'Sim?', fra_Latn: 'Oui ?', deu_Latn: 'Ja?', ita_Latn: 'Sì?' },
|
||||
hello: { spa_Latn: '¿Hola?', por_Latn: 'Olá?', fra_Latn: 'Allô ?', deu_Latn: 'Hallo?', ita_Latn: 'Pronto?' },
|
||||
right: { spa_Latn: '¿Verdad?', por_Latn: 'Certo?', fra_Latn: "N'est-ce pas ?", deu_Latn: 'Oder?', ita_Latn: 'Vero?' },
|
||||
really: { spa_Latn: '¿En serio?', por_Latn: 'Sério?', fra_Latn: 'Vraiment ?', deu_Latn: 'Wirklich?', ita_Latn: 'Davvero?' },
|
||||
okay: { spa_Latn: '¿Está bien?', por_Latn: 'Está bem?', fra_Latn: "D'accord ?", deu_Latn: 'Okay?', ita_Latn: 'Va bene?' },
|
||||
ok: { spa_Latn: '¿Está bien?', por_Latn: 'Está bem?', fra_Latn: "D'accord ?", deu_Latn: 'Okay?', ita_Latn: 'Va bene?' },
|
||||
ready: { spa_Latn: '¿Listos?', por_Latn: 'Prontos?', fra_Latn: 'Prêts ?', deu_Latn: 'Bereit?', ita_Latn: 'Pronti?' },
|
||||
huh: { spa_Latn: '¿Eh?', por_Latn: 'Hã?', fra_Latn: 'Hein ?', deu_Latn: 'Hä?', ita_Latn: 'Eh?' }
|
||||
};
|
||||
|
||||
const VARIANT_OF = { spa_ES: 'spa_Latn', spa_MX: 'spa_Latn', por_PT: 'por_Latn' };
|
||||
|
||||
/* "What?", "what.", "WHAT", '"What?"' — one word, optionally punctuated. */
|
||||
function key(text) {
|
||||
const t = String(text || '').trim().replace(/^[\s"'“‘(\[]+/u, '').replace(/[\s"'”’)\]]+$/u, '');
|
||||
const m = t.match(/^([A-Za-z]+)[?.!…]*$/);
|
||||
return m ? m[1].toLowerCase() : null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only a source that IS a question (ends in '?') or is a bare interrogative
|
||||
* word: "What." is Whisper dropping the mark; "What" alone is the same. A
|
||||
* bare "yes"/"okay"/"right"/"ready" with a period is an ANSWER, not a
|
||||
* question, and must reach the model as one — so those need the '?'.
|
||||
*/
|
||||
const NEEDS_MARK = new Set(['yeah', 'yes', 'hello', 'right', 'really', 'okay', 'ok', 'ready', 'huh']);
|
||||
|
||||
function lookup(text, lang) {
|
||||
const k = key(text);
|
||||
if (!k || !TABLE[k]) return null;
|
||||
const asked = /\?[?!.…]*\s*["'”’)\]]*$/.test(String(text || '').trim());
|
||||
if (!asked && NEEDS_MARK.has(k)) return null;
|
||||
const row = TABLE[k];
|
||||
return row[lang] || row[VARIANT_OF[lang]] || null;
|
||||
}
|
||||
|
||||
return { lookup, TABLE };
|
||||
}));
|
||||
Reference in New Issue
Block a user