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>
46 lines
2.6 KiB
JavaScript
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)+'%)');
|