Files
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

33 lines
2.4 KiB
JavaScript

import { pipeline, env } from '@huggingface/transformers';
env.allowLocalModels = false;
env.cacheDir = '/Users/Studio/Library/Application Support/Church Translate Models';
const pipe = await pipeline('translation','Xenova/nllb-200-distilled-600M',{dtype:'fp32'});
const G = [
["Habakkuk stood upon his watchtower.","He waited to see what the Lord would say to him.","And the Lord answered him and told him to write the vision.","Make it plain upon tablets, so that he may run who reads it."],
["Turn with me to Second Chronicles chapter seven.","If my people who are called by my name will humble themselves and pray,","and seek my face and turn from their wicked ways,","then will I hear from heaven and will heal their land."],
["Paul wrote to the church at Philippi from a prison cell.","He told them to rejoice in the Lord always.","And again he said unto them, rejoice.","Let your moderation be known unto all men, for the Lord is at hand."],
];
const L=[['hat_Latn','Haitian'],['arb_Arab','Arabic'],['kor_Hang','Korean']];
const t=async(x,c)=>{const s=Date.now();const r=await pipe(x,{src_lang:'eng_Latn',tgt_lang:c});return{text:(Array.isArray(r)?r[0]:r).translation_text.trim(),ms:Date.now()-s}};
await t(G[0][0],'spa_Latn');
function ng(s,n){const q=s.replace(/\s+/g,'');const m=new Map();for(let i=0;i+n<=q.length;i++){const g=q.slice(i,i+n);m.set(g,(m.get(g)||0)+1)}return m}
function chrF(h,r,N=6,b=2){let P=0,R=0,c=0;for(let n=1;n<=N;n++){const H=ng(h,n),F=ng(r,n);let hs=0,rs=0,ov=0;for(const[g,x]of H){hs+=x;ov+=Math.min(x,F.get(g)||0)}for(const[,x]of F)rs+=x;if(!hs||!rs)continue;P+=ov/hs;R+=ov/rs;c++}if(!c)return 0;const p=P/c,r2=R/c;return p+r2?(1+b*b)*p*r2/(b*b*p+r2):0}
console.log('lang nSent lenRatio(out/sum-of-singles) covLAST covFIRST ms');
for(const[code,name]of L){
for(const gi in G){
const S=G[gi];
const singles=[];for(const s of S)singles.push((await t(s,code)).text);
for(const n of [2,3,4]){
const src=S.slice(0,n).join(' ');
const o=await t(src,code);
const sumLen=singles.slice(0,n).reduce((a,x)=>a+x.length,0);
console.log(name.padEnd(9)+String(n).padStart(4)+
(' '+(o.text.length/sumLen).toFixed(2)).padStart(28)+
(' '+chrF(o.text,singles[n-1]).toFixed(3)).padStart(9)+
(' '+chrF(o.text,singles[0]).toFixed(3)).padStart(10)+
(' '+o.ms).padStart(8));
if(n===4)console.log(' OUT:',o.text.slice(0,240));
}
}
}