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
+68
View File
@@ -0,0 +1,68 @@
/*
* ui-strings.js — the handful of words on the listener page itself.
*
* Someone who needs a Spanish sermon should not have to read an English button
* to get it. These are hand-written for the languages a US church sees most;
* anything else falls back to English, and the picker still shows every
* language in its own script.
*
* Order: choose, waiting, speak, stop, reconnecting, change, original, live
*/
(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory();
else root.UIStrings = factory();
}(typeof self !== 'undefined' ? self : this, function () {
var KEYS = ['choose', 'waiting', 'speak', 'stop', 'reconnecting', 'change', 'original', 'live'];
var TABLE = {
eng_Latn: ['Choose your language', "The message hasn't started yet", 'Speak aloud', 'Stop', 'Reconnecting…', 'Change language', 'Show original', 'Live'],
spa_Latn: ['Elige tu idioma', 'El mensaje aún no ha comenzado', 'Leer en voz alta', 'Detener', 'Reconectando…', 'Cambiar idioma', 'Ver original', 'En vivo'],
zho_Hans: ['选择您的语言', '信息尚未开始', '朗读', '停止', '正在重新连接…', '更换语言', '显示原文', '直播中'],
zho_Hant: ['選擇您的語言', '信息尚未開始', '朗讀', '停止', '正在重新連線…', '更換語言', '顯示原文', '直播中'],
yue_Hant: ['揀你嘅語言', '信息仲未開始', '朗讀', '停止', '重新連線緊…', '轉語言', '顯示原文', '直播中'],
fra_Latn: ['Choisissez votre langue', "Le message n'a pas encore commencé", 'Lire à voix haute', 'Arrêter', 'Reconnexion…', 'Changer de langue', "Voir l'original", 'En direct'],
por_Latn: ['Escolha o seu idioma', 'A mensagem ainda não começou', 'Ler em voz alta', 'Parar', 'Reconectando…', 'Mudar idioma', 'Ver original', 'Ao vivo'],
por_PT: ['Escolha o seu idioma', 'A mensagem ainda não começou', 'Ler em voz alta', 'Parar', 'A restabelecer ligação…', 'Mudar idioma', 'Ver original', 'Em direto'],
kor_Hang: ['언어를 선택하세요', '메시지가 아직 시작되지 않았습니다', '소리내어 읽기', '중지', '다시 연결 중…', '언어 변경', '원문 보기', '실시간'],
vie_Latn: ['Chọn ngôn ngữ của bạn', 'Bài giảng chưa bắt đầu', 'Đọc to', 'Dừng', 'Đang kết nối lại…', 'Đổi ngôn ngữ', 'Xem bản gốc', 'Trực tiếp'],
hat_Latn: ['Chwazi lang ou', 'Mesaj la poko kòmanse', 'Li awotvwa', 'Sispann', 'N ap rekonekte…', 'Chanje lang', 'Montre orijinal la', 'An dirèk'],
arb_Arab: ['اختر لغتك', 'لم تبدأ الرسالة بعد', 'القراءة بصوت عالٍ', 'إيقاف', 'جارٍ إعادة الاتصال…', 'تغيير اللغة', 'عرض النص الأصلي', 'مباشر'],
rus_Cyrl: ['Выберите язык', 'Проповедь ещё не началась', 'Читать вслух', 'Стоп', 'Переподключение…', 'Сменить язык', 'Показать оригинал', 'В эфире'],
ukr_Cyrl: ['Оберіть мову', 'Проповідь ще не почалася', 'Читати вголос', 'Стоп', 'Перепідключення…', 'Змінити мову', 'Показати оригінал', 'Наживо'],
deu_Latn: ['Wähle deine Sprache', 'Die Predigt hat noch nicht begonnen', 'Vorlesen', 'Stopp', 'Neu verbinden…', 'Sprache ändern', 'Original anzeigen', 'Live'],
ita_Latn: ['Scegli la tua lingua', 'Il messaggio non è ancora iniziato', 'Leggi ad alta voce', 'Ferma', 'Riconnessione…', 'Cambia lingua', 'Mostra originale', 'In diretta'],
tgl_Latn: ['Piliin ang iyong wika', 'Hindi pa nagsisimula ang mensahe', 'Basahin nang malakas', 'Itigil', 'Kumokonekta muli…', 'Palitan ang wika', 'Ipakita ang orihinal', 'Live'],
swh_Latn: ['Chagua lugha yako', 'Ujumbe bado haujaanza', 'Soma kwa sauti', 'Simamisha', 'Inaunganisha tena…', 'Badilisha lugha', 'Onyesha asili', 'Moja kwa moja'],
amh_Ethi: ['ቋንቋዎን ይምረጡ', 'መልእክቱ ገና አልጀመረም', 'ጮክ ብሎ ማንበብ', 'አቁም', 'እንደገና በመገናኘት ላይ…', 'ቋንቋ ቀይር', 'ዋናውን አሳይ', 'በቀጥታ'],
pes_Arab: ['زبان خود را انتخاب کنید', 'پیام هنوز شروع نشده است', 'خواندن با صدای بلند', 'توقف', 'در حال اتصال مجدد…', 'تغییر زبان', 'نمایش متن اصلی', 'زنده'],
hin_Deva: ['अपनी भाषा चुनें', 'संदेश अभी शुरू नहीं हुआ है', 'ज़ोर से पढ़ें', 'रोकें', 'पुनः कनेक्ट हो रहा है…', 'भाषा बदलें', 'मूल दिखाएँ', 'लाइव'],
jpn_Jpan: ['言語を選択してください', 'メッセージはまだ始まっていません', '読み上げる', '停止', '再接続中…', '言語を変更', '原文を表示', 'ライブ'],
pol_Latn: ['Wybierz swój język', 'Kazanie jeszcze się nie zaczęło', 'Czytaj na głos', 'Zatrzymaj', 'Ponowne łączenie…', 'Zmień język', 'Pokaż oryginał', 'Na żywo'],
ron_Latn: ['Alege limba ta', 'Mesajul nu a început încă', 'Citește cu voce tare', 'Oprește', 'Reconectare…', 'Schimbă limba', 'Arată originalul', 'În direct'],
som_Latn: ['Dooro luqaddaada', 'Farriintu weli ma bilaaban', 'Kor u akhri', 'Jooji', 'Dib u xidhiidhinaya…', 'Beddel luqadda', 'Tus qoraalka asalka ah', 'Toos'],
npi_Deva: ['आफ्नो भाषा छान्नुहोस्', 'सन्देश अझै सुरु भएको छैन', 'ठूलो स्वरमा पढ्नुहोस्', 'रोक्नुहोस्', 'पुन: जडान हुँदै…', 'भाषा परिवर्तन गर्नुहोस्', 'मूल देखाउनुहोस्', 'प्रत्यक्ष'],
mya_Mymr: ['သင့်ဘာသာစကားကို ရွေးပါ', 'တရားဒေသနာ မစသေးပါ', 'အသံထွက်ဖတ်ပါ', 'ရပ်ပါ', 'ပြန်လည်ချိတ်ဆက်နေသည်…', 'ဘာသာစကားပြောင်းပါ', 'မူရင်းပြပါ', 'တိုက်ရိုက်']
};
function pack(arr) {
var o = {};
KEYS.forEach(function (k, i) { o[k] = arr[i]; });
return o;
}
var STRINGS = {};
Object.keys(TABLE).forEach(function (code) { STRINGS[code] = pack(TABLE[code]); });
function forLang(code) {
/* Fall back to English rather than showing a key name. A missing string is
a cosmetic problem; a blank button is a broken page. */
/* A regional variant reads its base language's strings; showing a Spanish
speaker an English page because the code says spa_ES rather than
spa_Latn is exactly what languages.js says never to do. */
var VARIANT_OF = { por_PT: 'por_Latn', spa_ES: 'spa_Latn', spa_MX: 'spa_Latn' };
return Object.assign({}, STRINGS.eng_Latn, STRINGS[code] || STRINGS[VARIANT_OF[code]] || {});
}
return { KEYS: KEYS, STRINGS: STRINGS, forLang: forLang, has: function (c) { return !!STRINGS[c]; } };
}));