Files
church-translate/app/_probe-short.mjs
christianandClaude Opus 5 c9eef0672c 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>
2026-09-23 15:41:21 -04:00

35 lines
2.1 KiB
JavaScript

import { pipeline, env } from '@huggingface/transformers';
import { createRequire } from 'module';
import os from 'os'; import path from 'path'; import fs from 'fs'; import vm from 'vm';
const require = createRequire(import.meta.url);
const Liturgy=require('./liturgy.js'), Guard=require('./mt-guard.js');
const Preach=require('./preach.js'), B=require('./bible-lexicon.js'), Spa=require('./spa-variants.js');
const esrc=fs.readFileSync('engine.js','utf8');
const sb={console}; vm.createContext(sb);
vm.runInContext(esrc.slice(esrc.indexOf('const SCRIPT_NO_SPACES'), esrc.indexOf('function applyFixes')), sb);
env.allowLocalModels=false;
env.cacheDir=path.join(os.homedir(),'Library/Application Support/Church Translate Models');
const pipe=await pipeline('translation','Xenova/nllb-200-distilled-600M',{dtype:'fp32'});
async function full(sentence, lang){
const set=Liturgy.lookup(sentence,lang); if(set) return [set,'liturgy'];
const named=B.wholeUtterance(sentence,lang); if(named) return [named,'name'];
const forMt=Preach.forTranslation(sentence);
const sd=Preach.stripShortPeriod(forMt);
const o=await pipe(sd.text,{src_lang:'eng_Latn',tgt_lang:lang});
let raw=(Array.isArray(o)?o[0]:o).translation_text.trim();
raw=Guard.clean(sd.text,raw).text;
raw=Preach.restorePeriod(raw,sd.hadPeriod);
if(lang==='spa_Latn') raw=Spa.toLatinAmerican(Spa.toChurchRegister(raw));
const bl=B.forLanguage(lang);
if(bl) raw=sb.applyTable(raw,Object.keys(bl).map(f=>({from:f,to:bl[f]})));
return [raw,'model'];
}
const CASES=['Hello.','What?','Amen.','Hallelujah.','Peter.','Okay.','Check.','Faith.','Hello?',
'No.','Jesus.','Yeah.','All right.','Right?','Holy Spirit.','Death.','Yeah?',
'Praise God.','Grace.','Hope.','Love.','Sin.','Heaven.','Prayer.','Worship.',
'Testing.','One two.','Mic check.','Good.','Wow.','Listen.','Look.','Come.','Stand.'];
console.log(' English'.padEnd(18)+'now'.padEnd(30)+'via');
for (const t of CASES){ const [r,how]=await full(t,'spa_Latn'); console.log(' '+t.padEnd(16)+String(r).slice(0,28).padEnd(30)+how); }