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
+22
View File
@@ -0,0 +1,22 @@
const fs=require('fs'), vm=require('vm');
const B=require('./bible-lexicon.js');
const src=fs.readFileSync('engine.js','utf8');
const sb={console};vm.createContext(sb);
vm.runInContext(src.slice(src.indexOf('const SCRIPT_NO_SPACES'), src.indexOf('function applyFixes')), sb);
const rows=require('/tmp/all-corpus.json');
for (const lang of B.languages()) {
const bl=B.forLanguage(lang)||{}, fx=B.fixesFor(lang)||{};
const p1=Object.keys(bl).map(f=>({from:f,to:bl[f]}));
const p2=Object.keys(fx).map(f=>({from:f,to:fx[f]}));
let n=0,tot=0; const seen={};
for (const r of rows){
const t=String((r.tr&&r.tr[lang])||''); if(!t) continue; tot++;
let out=sb.applyTable(t,p1); if(p2.length) out=sb.applyTable(out,p2);
if(out===t) continue; n++;
const a=t.split(/\s+/),b=out.split(/\s+/),d=[];
for(let i=0;i<Math.max(a.length,b.length);i++) if(a[i]!==b[i]) d.push((a[i]||'')+'→'+(b[i]||''));
const k=d.slice(0,3).join(' ');seen[k]=(seen[k]||0)+1;
}
console.log('\n'+lang+' '+tot+' rows, '+n+' changed'+(tot?' ('+(n/tot*100).toFixed(2)+'%)':''));
Object.entries(seen).sort((a,b)=>b[1]-a[1]).slice(0,10).forEach(([d,c])=>console.log(' '+String(c).padStart(3)+'x '+d));
}