Files
church-translate/app/_test-names-e2e.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

44 lines
2.2 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');
const Spa = require('./spa-variants.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);
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' });
/* The pipeline exactly as engine.js now runs it. */
async function translate(sentence, lang) {
const set = Liturgy.lookup(sentence, lang);
if (set) return set + ' [liturgy]';
const named = B.wholeUtterance(sentence, lang);
if (named) return named + ' [name lookup]';
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] })));
const fx = B.fixesFor(lang);
if (fx) raw = sb.applyTable(raw, Object.keys(fx).map((f) => ({ from: f, to: fx[f] })));
return raw;
}
const CASES = ['Peter.','John.','Matthew.','Jesus.','Mary.','Noah.','Moses.','Habakkuk.',
'Simon Peter.','Donald Trump.','Amen.','Peter denied him three times.',
'His name was John.','Open up to the book of John.'];
for (const lang of ['spa_Latn','fra_Latn','por_Latn','zho_Hans']) {
console.log('\n=== ' + lang + ' ===');
for (const t of CASES) console.log(' ' + t.padEnd(32) + await translate(t, lang));
}