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>
41 lines
3.1 KiB
JavaScript
41 lines
3.1 KiB
JavaScript
const fs=require('fs'),vm=require('vm');
|
|
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);
|
|
let pass=0,fail=0;
|
|
const t=(label,text,pairs,want)=>{const got=sb.applyTable(text,pairs);const ok=got===want;
|
|
console.log((ok?' PASS ':' FAIL ')+label); if(!ok)console.log(' got "'+got+'"\n want "'+want+'"');
|
|
ok?pass++:fail++;};
|
|
|
|
console.log('\nEXISTING BEHAVIOUR — must not regress');
|
|
t('lowercase forgiving','have a back up plan',[{from:'have a back up',to:'Habakkuk'}],'Habakkuk plan');
|
|
t('capital is exact','I finally got the job I wanted',[{from:'Job',to:'JOB-BOOK'}],'I finally got the job I wanted');
|
|
t('capital matches capital','Turn to Job chapter 1',[{from:'Job',to:'JOB-BOOK'}],'Turn to JOB-BOOK chapter 1');
|
|
t('term ending in period','read 1 Cor. today',[{from:'1 Cor.',to:'First Corinthians'}],'read First Corinthians today');
|
|
t('parenthesised term','sing (Selah) now',[{from:'(Selah)',to:'SELAH'}],'sing SELAH now');
|
|
t('longest first','first Corinthians here',[{from:'first Corinthians',to:'1 Corinthians'},{from:'Corinthians',to:'X'}],'1 Corinthians here');
|
|
t('live CJK override','向哈巴古书第一章',[{from:'哈巴古书',to:'哈巴谷书'}],'向哈巴谷书第一章');
|
|
t('CJK 2-char name FIRES','约翰说了',[{from:'约翰',to:'約翰'}],'約翰说了');
|
|
t('CJK 2-char name in compound','马太福音第一章',[{from:'马太',to:'馬太'}],'馬太福音第一章');
|
|
/* ACCEPTED COST of lowering the guard to one character: a two-character COMMON
|
|
noun now reaches inside a longer word again. Pinned here so it is a decision
|
|
on the record, not a surprise. */
|
|
t('accepted: 2-char common noun bleeds','구원자가 왔다',[{from:'구원',to:'救い'}],'救い자가 왔다');
|
|
/* ...and the remedy, which the table already supports: longest match wins. */
|
|
t('remedy: longer entry wins','구원자가 왔다',
|
|
[{from:'구원',to:'救い'},{from:'구원자',to:'救い主'}],'救い主가 왔다');
|
|
|
|
console.log('\nNEWLY FIXED — non-Latin boundaries');
|
|
t('Cyrillic no inside-match','Маркса продали',[{from:'Марк',to:'X'}],'Маркса продали');
|
|
t('Cyrillic real match','Марк написал',[{from:'Марк',to:'Маркос'}],'Маркос написал');
|
|
t('Arabic no inside-match','الربا حرام',[{from:'الرب',to:'X'}],'الربا حرام');
|
|
t('Arabic real match','الرب راعي',[{from:'الرب',to:'يهوه'}],'يهوه راعي');
|
|
t('accented capital exact','el éxodo de los jóvenes',[{from:'Éxodo',to:'X'}],'el éxodo de los jóvenes');
|
|
t('accented capital matches','leemos Éxodo 20',[{from:'Éxodo',to:'EXO'}],'leemos EXO 20');
|
|
t('accented lowercase forgiving','el éxodo grande',[{from:'éxodo',to:'salida'}],'el salida grande');
|
|
t('Devanagari boundary','यूहन्नाकी बात',[{from:'यूहन्ना',to:'X'}],'यूहन्नाकी बात');
|
|
t('Vietnamese tone marks','Phêrô đã nói',[{from:'Phêrô',to:'Phi-e-rơ'}],'Phi-e-rơ đã nói');
|
|
|
|
console.log('\n'+pass+' passed, '+fail+' failed');
|
|
process.exit(fail?1:0);
|