Files
church-translate/app/_probe-number.mjs
T
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

46 lines
2.6 KiB
JavaScript

import { pipeline, env } from '@huggingface/transformers';
import os from 'os'; import path from 'path';
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'});
const say=async(t)=>{const o=await pipe(t,{src_lang:'eng_Latn',tgt_lang:'spa_Latn'});return (Array.isArray(o)?o[0]:o).translation_text.trim();};
/* Unambiguous number markers only. Verb endings are skipped deliberately —
they are where the ambiguity lives, so counting them would beg the question. */
function number(s){
const SING=/\b(tú|ti|tu|tus|te|contigo|usted|consigo)\b/i;
const PLUR_LA=/\b(ustedes|sus)\b/i;
const PLUR_ES=/\b(vosotros|vuestr[oa]s?|os)\b/i;
const vosVerb=/\w(áis|éis)\b|\b\w+[aei]d\b/; // habláis / hablad
const sing=SING.test(s), la=PLUR_LA.test(s), es=PLUR_ES.test(s)||vosVerb.test(s);
if (es && !sing) return 'PLURAL (Peninsular)';
if (la && !sing) return 'PLURAL (LatAm)';
if (sing && !la && !es) return 'SINGULAR';
if (sing && (la||es)) return 'MIXED — both in one sentence';
return 'unmarked';
}
/* Every one of these is a preacher speaking to a room, so every one should be
PLURAL in Spanish. Anything singular is the model guessing wrong. */
const TO_THE_ROOM=[
'Open your Bibles.','Bow your heads with me.','Lift your hands.','Close your eyes.',
'Stand to your feet.','Repeat after me.','Pray with me.','Look at verse four with me.',
'Turn to the person next to you.','Give thanks to the Lord.','Come to the altar.',
'Listen to me carefully.','I want you to understand this.','You are the light of the world.',
'Do you believe that?','Love one another.','Do not be afraid.','Remember what He did for you.',
'God has a plan for you.','You have been forgiven.','Trust in the Lord with all your heart.',
'Forgive those who hurt you.','You cannot serve two masters.','Take out your notes.',
'Say it with me.','Are you ready?','Let us pray together.','Turn the page with me.'
];
const tally={};
console.log('every line below is addressed to a ROOM, so every one should be PLURAL\n');
for (const t of TO_THE_ROOM) {
const s=await say(t); const n=number(s);
tally[n]=(tally[n]||0)+1;
const bad = n==='SINGULAR' ? ' <-- singular: wrong for a congregation' : '';
console.log(' '+n.padEnd(24)+s+bad);
}
console.log('\nTALLY of '+TO_THE_ROOM.length+' congregation-directed sentences:');
for (const [k,v] of Object.entries(tally).sort((a,b)=>b[1]-a[1]))
console.log(' '+String(v).padStart(3)+' '+k+' ('+Math.round(v/TO_THE_ROOM.length*100)+'%)');