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>
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* spa-variants.js — one Spanish translation, two Spanish congregations.
|
||||
*
|
||||
* WHY ONLY ONE DIRECTION IS IMPLEMENTED, AND IT IS NOT THE ONE ASKED FOR.
|
||||
*
|
||||
* Converting Latin American Spanish INTO Peninsular (ustedes -> vosotros) was
|
||||
* specified, built on paper, and then measured against this church's own
|
||||
* archive of 5,954 English->Spanish pairs. It has to be abandoned, because the
|
||||
* information it needs is not in the input:
|
||||
*
|
||||
* - Spanish 3rd-person-plural is IDENTICAL for "ustedes" (you all) and
|
||||
* "ellos" (they). English resolves person but not NUMBER, and in 98.9% of
|
||||
* the sentences a gate would fire on, whether "you" was one person or a
|
||||
* room is simply undetermined.
|
||||
* - Spanish uses 3PL as its impersonal/passive, so the very condition that
|
||||
* triggers a conversion is what creates the false positives. Measured, real:
|
||||
* "You're not accepted on the basis of your religious works."
|
||||
* NLLB: "No te aceptan por tus obras religiosas."
|
||||
* after: "no te aceptáis" = "you don't accept yourself"
|
||||
* That inverts grace into works-salvation, fluently, in a synthesized voice,
|
||||
* for listeners who cannot detect it.
|
||||
* - "su" means his/her/its/their/your-formal. Of the sentences carrying it,
|
||||
* 25.2% provably could NOT mean "your" — the English has no "your" at all.
|
||||
* "Él te recibirá como su hijo" ("as HIS son") would become "as YOUR son".
|
||||
* - Provable wins 68, provable losses 46, on 22.6% of rows. Every uncounted
|
||||
* loss class pushes that under 1:1.
|
||||
*
|
||||
* THE INVERSE DIRECTION IS SAFE, and that asymmetry is the whole design.
|
||||
* vosotros, vuestro, os, -áis, -éis and the -ad/-ed/-id imperative can ONLY be
|
||||
* second-person-plural. There is nothing to disambiguate. So Peninsular forms
|
||||
* can be normalised OUT of Latin American output with no risk at all — and NLLB
|
||||
* does leak them: 27 rows in the archive, including "lavará vuestros pecados"
|
||||
* and "Hacéis esto".
|
||||
*
|
||||
* Result: Spain gets the same words in a Castilian voice (the thing a Spaniard
|
||||
* notices first), and Latin America gets Spanish that is consistently Latin
|
||||
* American instead of occasionally Peninsular.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) module.exports = factory();
|
||||
else root.SpaVariants = factory();
|
||||
}(typeof self !== 'undefined' ? self : this, function () {
|
||||
'use strict';
|
||||
|
||||
/* Accent-aware boundaries. JS \b is [A-Za-z0-9_], so /\bvosotros\b/ is fine
|
||||
but /\bhabláis\b/ breaks at the á — the trap pt-variants.js documents. */
|
||||
const W = '\\p{L}\\p{N}\\p{M}';
|
||||
const RE = (body, flags) => new RegExp('(?<![' + W + '])(?:' + body + ')(?![' + W + '])', flags || 'giu');
|
||||
|
||||
/*
|
||||
* Verb forms are ENUMERATED, never derived from suffixes. A suffix rule is
|
||||
* unusable here: "-ís" would eat país and anís, and "-ad"/"-ed" would eat
|
||||
* usted, verdad, ciudad, bondad, pared, red and sed. pt-variants.js reached
|
||||
* the same conclusion and shipped a whitelist for the same reason.
|
||||
* Each entry is 2nd-person-plural -> 3rd-person-plural (the ustedes form).
|
||||
*
|
||||
* THREE FORMS ARE DELIBERATELY ABSENT. Matching is case-insensitive, so any
|
||||
* short form that is also an ordinary word is a landmine:
|
||||
* "sed" (ser, "be ye") is the noun THIRST — measured 5 times in this
|
||||
* church's archive, every one of them the noun, in a sermon about a
|
||||
* man dying of thirst in the desert. It turned "morir de sed" into
|
||||
* "morir de sean". Caught only by running the real corpus.
|
||||
* "id" (ir, "go ye") collides with the English "ID".
|
||||
* "dad" (dar, "give ye") collides with the English "dad".
|
||||
* All three are vanishingly rare in NLLB output and common as other words.
|
||||
* Do not add them back.
|
||||
*/
|
||||
const VERBS = {
|
||||
/* ser / estar / haber / ir */
|
||||
'sois': 'son', 'erais': 'eran', 'fuisteis': 'fueron', 'seréis': 'serán', 'seáis': 'sean',
|
||||
'estáis': 'están', 'estabais': 'estaban', 'estuvisteis': 'estuvieron', 'estaréis': 'estarán', 'estéis': 'estén',
|
||||
'habéis': 'han', 'habíais': 'habían', 'hubisteis': 'hubieron', 'habréis': 'habrán', 'hayáis': 'hayan',
|
||||
'vais': 'van', 'ibais': 'iban', 'fuisteis2': 'fueron', 'iréis': 'irán', 'vayáis': 'vayan',
|
||||
/* high-frequency irregulars */
|
||||
'tenéis': 'tienen', 'teníais': 'tenían', 'tuvisteis': 'tuvieron', 'tendréis': 'tendrán', 'tengáis': 'tengan', 'tened': 'tengan',
|
||||
'hacéis': 'hacen', 'hacíais': 'hacían', 'hicisteis': 'hicieron', 'haréis': 'harán', 'hagáis': 'hagan', 'haced': 'hagan',
|
||||
'podéis': 'pueden', 'podíais': 'podían', 'pudisteis': 'pudieron', 'podréis': 'podrán', 'podáis': 'puedan',
|
||||
'queréis': 'quieren', 'queríais': 'querían', 'quisisteis': 'quisieron', 'querréis': 'querrán', 'queráis': 'quieran',
|
||||
'decís': 'dicen', 'decíais': 'decían', 'dijisteis': 'dijeron', 'diréis': 'dirán', 'digáis': 'digan', 'decid': 'digan',
|
||||
'venís': 'vienen', 'veníais': 'venían', 'vinisteis': 'vinieron', 'vendréis': 'vendrán', 'vengáis': 'vengan', 'venid': 'vengan',
|
||||
'sabéis': 'saben', 'sabíais': 'sabían', 'supisteis': 'supieron', 'sabréis': 'sabrán', 'sepáis': 'sepan',
|
||||
'dais': 'dan', 'dabais': 'daban', 'disteis': 'dieron', 'daréis': 'darán', 'deis': 'den',
|
||||
'veis': 'ven', 'veíais': 'veían', 'visteis': 'vieron', 'veréis': 'verán', 'veáis': 'vean', 'ved': 'vean',
|
||||
'ponéis': 'ponen', 'pusisteis': 'pusieron', 'pondréis': 'pondrán', 'pongáis': 'pongan', 'poned': 'pongan',
|
||||
'salís': 'salen', 'salisteis': 'salieron', 'saldréis': 'saldrán', 'salgáis': 'salgan', 'salid': 'salgan',
|
||||
'oís': 'oyen', 'oísteis': 'oyeron', 'oiréis': 'oirán', 'oigáis': 'oigan', 'oíd': 'oigan',
|
||||
/* regular verbs a preacher actually uses */
|
||||
'habláis': 'hablan', 'hablabais': 'hablaban', 'hablasteis': 'hablaron', 'hablaréis': 'hablarán', 'habléis': 'hablen', 'hablad': 'hablen',
|
||||
'miráis': 'miran', 'mirasteis': 'miraron', 'miréis': 'miren', 'mirad': 'miren',
|
||||
'escucháis': 'escuchan', 'escuchasteis': 'escucharon', 'escuchéis': 'escuchen', 'escuchad': 'escuchen',
|
||||
'oráis': 'oran', 'orasteis': 'oraron', 'oréis': 'oren', 'orad': 'oren',
|
||||
'rezáis': 'oran', 'rezasteis': 'oraron', 'recéis': 'oren', 'rezad': 'oren',
|
||||
'creéis': 'creen', 'creíais': 'creían', 'creísteis': 'creyeron', 'creáis': 'crean', 'creed': 'crean',
|
||||
'vivís': 'viven', 'vivíais': 'vivían', 'vivisteis': 'vivieron', 'viváis': 'vivan', 'vivid': 'vivan',
|
||||
'abrís': 'abren', 'abristeis': 'abrieron', 'abráis': 'abran', 'abrid': 'abran',
|
||||
'recibís': 'reciben', 'recibisteis': 'recibieron', 'recibáis': 'reciban', 'recibid': 'reciban',
|
||||
'entendéis': 'entienden', 'entendisteis': 'entendieron', 'entendáis': 'entiendan',
|
||||
'buscáis': 'buscan', 'buscasteis': 'buscaron', 'busquéis': 'busquen', 'buscad': 'busquen',
|
||||
'amáis': 'aman', 'amasteis': 'amaron', 'améis': 'amen', 'amad': 'amen',
|
||||
'sentís': 'sienten', 'sentisteis': 'sintieron', 'sintáis': 'sientan',
|
||||
'pedís': 'piden', 'pedisteis': 'pidieron', 'pidáis': 'pidan', 'pedid': 'pidan',
|
||||
'volvéis': 'vuelven', 'volvisteis': 'volvieron', 'volváis': 'vuelvan', 'volved': 'vuelvan',
|
||||
'llegáis': 'llegan', 'llegasteis': 'llegaron', 'lleguéis': 'lleguen',
|
||||
'levantáis': 'levantan', 'levantad': 'levanten', 'levantaos': 'levántense',
|
||||
'sentaos': 'siéntense', 'arrepentíos': 'arrepiéntanse', 'acercaos': 'acérquense',
|
||||
/* Reflexive imperatives change the stem and move the accent, so they are
|
||||
named rather than derived. These are the ones that actually occur in
|
||||
Peninsular scripture. */
|
||||
'despojaos': 'despójense', 'renovaos': 'renuévense', 'vestíos': 'vístanse',
|
||||
'airaos': 'aírense', 'alegraos': 'alégrense', 'regocijaos': 'regocíjense',
|
||||
'humillaos': 'humíllense', 'apartaos': 'apártense', 'acordaos': 'acuérdense',
|
||||
'guardaos': 'guárdense', 'someteos': 'sométanse', 'revestíos': 'revístanse',
|
||||
'volveos': 'vuélvanse', 'allegaos': 'acérquense', 'mirad': 'miren'
|
||||
};
|
||||
delete VERBS['fuisteis2'];
|
||||
|
||||
/*
|
||||
* Pronouns and possessives. This direction needs NO noun gender, which is
|
||||
* precisely what made the reverse direction impossible: "su" is invariant,
|
||||
* while "vuestro" must agree with the possessed noun in gender AND number.
|
||||
*/
|
||||
const GRAMMAR = [
|
||||
[RE('vosotros|vosotras'), 'ustedes'],
|
||||
[RE('vuestros|vuestras'), 'sus'],
|
||||
[RE('vuestro|vuestra'), 'su'],
|
||||
/* The 2pl clitic. Anchored, because "os" is two letters and sits inside
|
||||
hundreds of words. */
|
||||
[RE('os'), 'les']
|
||||
];
|
||||
|
||||
/*
|
||||
* Lexical Peninsular leakage, all observed in the archive. Deliberately tiny:
|
||||
* an unobserved entry is a guess, and a guess here is a silent error.
|
||||
*/
|
||||
const LEXICON = [
|
||||
[RE('coches'), 'carros'], [RE('coche'), 'carro'],
|
||||
[RE('gafas'), 'lentes'],
|
||||
[RE('ordenadores'), 'computadoras'], [RE('ordenador'), 'computadora'],
|
||||
[RE('móviles'), 'celulares'],
|
||||
[RE('zumo'), 'jugo'], [RE('patatas'), 'papas'], [RE('patata'), 'papa'],
|
||||
[RE('piso'), 'departamento'], [RE('chavales'), 'muchachos'], [RE('chaval'), 'muchacho']
|
||||
];
|
||||
|
||||
/*
|
||||
* "vale" is a homograph and the archive contains both senses:
|
||||
* interjection "Vale, pensemos..." -> Peninsular, convert
|
||||
* the verb "No vale la pena." -> universal, MUST NOT convert
|
||||
* Only the sentence-initial interjection followed by a comma is touched.
|
||||
*/
|
||||
const VALE = /(^|[.!?¡¿]\s*)Vale,/g;
|
||||
|
||||
/*
|
||||
* Regular paradigm, as ORDERED SUFFIX rules, applied after the explicit table
|
||||
* so irregulars (estáis->están, sois->son) always win.
|
||||
*
|
||||
* These endings are safe where a bare "-ís" or "-ad" is not: -áis, -éis,
|
||||
* -abais, -íais, -asteis, -isteis, -réis and -ríais occur in Spanish ONLY as
|
||||
* second-person-plural verb inflections. There is no noun or adjective with
|
||||
* any of them, so no homograph can be caught.
|
||||
*
|
||||
* ORDER MATTERS. The future is -réis and maps to -rán, not to -ren: without
|
||||
* the -réis rule first, "hablaréis" would become "hablaren" rather than
|
||||
* "hablarán". Longest and most specific first, throughout.
|
||||
*/
|
||||
const SUFFIX = [
|
||||
[/(\w{2,})ríais\b/giu, '$1rían'], // conditional: hablaríais -> hablarían
|
||||
[/(\w{2,})réis\b/giu, '$1rán'], // future: hablaréis -> hablarán
|
||||
[/(\w{2,})abais\b/giu, '$1aban'], // imperfect -ar
|
||||
[/(\w{2,})íais\b/giu, '$1ían'], // imperfect -er/-ir
|
||||
[/(\w{2,})asteis\b/giu,'$1aron'], // preterite -ar
|
||||
[/(\w{2,})isteis\b/giu,'$1ieron'], // preterite -er/-ir
|
||||
[/(\w{2,})áis\b/giu, '$1an'], // present -ar / subjunctive -er,-ir
|
||||
[/(\w{2,})éis\b/giu, '$1en'] // present -er / subjunctive -ar
|
||||
];
|
||||
|
||||
/*
|
||||
* DENOMINATIONAL REGISTER, and it applies to every Spanish variant — this is
|
||||
* not a regional question.
|
||||
*
|
||||
* RAE separates the two verbs: `orar` is "dirigirse mentalmente o de palabra
|
||||
* a una divinidad" — extempore prayer, the evangelical default. `rezar` is
|
||||
* from Latin recitare, "recitar la misa, una oracion", i.e. saying a SET
|
||||
* prayer, and is Catholic-coded. NLLB picks between them at random: measured
|
||||
* across this church's archive, 15 rows chose rezar and 14 chose orar, for
|
||||
* the same English word "pray". So a Protestant congregation hears
|
||||
* "Rezo para que..." — which reads as reciting a liturgical formula.
|
||||
*
|
||||
* Straight lexical substitution, no ambiguity in the verb forms. ONE guard:
|
||||
* "rezo" is also a NOUN (a recited prayer), and mapping the noun would say
|
||||
* "oro" = GOLD. Every occurrence in the archive is the verb, but the
|
||||
* determiner lookbehind makes the noun unreachable regardless.
|
||||
*/
|
||||
const PRAY = [
|
||||
[RE('rezar'), 'orar'], [RE('rezando'), 'orando'], [RE('rezado'), 'orado'],
|
||||
[RE('rezamos'), 'oramos'], [RE('rezaban'), 'oraban'], [RE('rezaba'), 'oraba'],
|
||||
[RE('rezaron'), 'oraron'], [RE('rezan'), 'oran'], [RE('rezas'), 'oras'],
|
||||
[RE('recemos'), 'oremos'], [RE('recen'), 'oren'], [RE('reces'), 'ores'],
|
||||
[RE('rece'), 'ore'], [RE('reza'), 'ora'],
|
||||
/* the noun guard */
|
||||
[/(?<![\p{L}\p{N}\p{M}])(?<!\b(?:el|un|los|unos|mi|su|tu|este|ese|aquel)\s)rezo(?![\p{L}\p{N}\p{M}])/giu, 'oro']
|
||||
];
|
||||
|
||||
/*
|
||||
* "coger" is routine in Spain and vulgar across Mexico and much of Latin
|
||||
* America — RAE marks it "vulg. ... Realizar el acto sexual" for Mexico
|
||||
* among others. Zero occurrences in the archive so far, but the cost of the
|
||||
* guard is nothing and the cost of the failure is a sermon.
|
||||
*/
|
||||
const SAFE = [
|
||||
[RE('coger'), 'tomar'], [RE('cogiendo'), 'tomando'], [RE('cogemos'), 'tomamos'],
|
||||
[RE('cogen'), 'toman'], [RE('coges'), 'tomas'], [RE('cojan'), 'tomen'],
|
||||
[RE('coja'), 'tome'], [RE('chaqueta'), 'chamarra'], [RE('chaquetas'), 'chamarras']
|
||||
];
|
||||
|
||||
/*
|
||||
* CONJUGATION REPAIRS — non-words only.
|
||||
*
|
||||
* MEASURED on the shipped NLLB (fp32, greedy — the library exposes no beam
|
||||
* search): "We bless…" came back as a bendecir form in 47 of 49 worship
|
||||
* sentences and 15 of those were NOT SPANISH WORDS — bendecemos, bendicemos,
|
||||
* bendimos for the correct bendecimos (bendecir conjugates like decir:
|
||||
* bendigo, bendices, bendice, bendecimos, bendecís, bendicen). The
|
||||
* tokenizer explains it: the right form needs three pieces (▁bende·c·imos)
|
||||
* while each wrong one is a likelier two-piece path, and greedy decoding
|
||||
* commits to the likelier piece. Forced-decode scoring showed the model
|
||||
* genuinely prefers the non-word, so no decoding setting fixes this.
|
||||
*
|
||||
* Every 'from' here is a string that is not a word in any Spanish. That is
|
||||
* what makes the table safe by construction: a non-word can never be the
|
||||
* preacher's meaning, so replacing it cannot corrupt a correct line. A
|
||||
* sweep of the church's 16,132 archived Spanish fields changed 28 and every
|
||||
* one of the 13 distinct changes was non-word -> correct form, with zero
|
||||
* collateral. Real-but-wrong forms ("Bendijo" for "I bless") are NOT here:
|
||||
* they are words, and a table cannot know the person the speaker meant.
|
||||
*
|
||||
* bendíalo/bendíelo -> bendigámoslo (1pl hortative "let us bless him"),
|
||||
* deliberately not the fluent-but-wrong tú imperative bendícelo.
|
||||
* bendicié -> bendice is kept: "Bendice tu nombre" is grammatical praise
|
||||
* where the non-word was noise; flagged advisory in review.
|
||||
*/
|
||||
const CONJ = [
|
||||
[RE('bendecemos|bendicemos|bendimos|bendicimos'), 'bendecimos'],
|
||||
[RE('bendico'), 'bendigo'],
|
||||
[RE('bendecen'), 'bendicen'],
|
||||
[RE('bendicié'), 'bendice'],
|
||||
[RE('bendíalo|bendíelo'), 'bendigámoslo'],
|
||||
[RE('bendiciré'), 'bendeciré'],
|
||||
[RE('benditaremos|bendiciremos'), 'bendeciremos'],
|
||||
[RE('maldiciré'), 'maldeciré'],
|
||||
[RE('pedamos'), 'pidamos'],
|
||||
[RE('ungas'), 'unjas'],
|
||||
[RE('undémonos'), 'úngenos'],
|
||||
[RE('redémonos'), 'redímenos'],
|
||||
[RE('dás'), 'das']
|
||||
];
|
||||
|
||||
/* Applied to EVERY Spanish variant, before any regional pass. */
|
||||
function toChurchRegister(text) {
|
||||
let s = String(text == null ? '' : text);
|
||||
if (!s) return s;
|
||||
for (const [re, to] of PRAY.concat(SAFE, CONJ)) s = s.replace(re, (m) =>
|
||||
/^[\p{Lu}]/u.test(m) ? to.charAt(0).toUpperCase() + to.slice(1) : to);
|
||||
return s;
|
||||
}
|
||||
|
||||
function toLatinAmerican(text) {
|
||||
let s = String(text == null ? '' : text);
|
||||
if (!s) return s;
|
||||
|
||||
/* Verbs first, longest form first, so "estabais" is not shadowed by a
|
||||
shorter overlapping entry. */
|
||||
const forms = Object.keys(VERBS).sort((a, b) => b.length - a.length);
|
||||
for (const f of forms) s = s.replace(RE(f), (m) => {
|
||||
const to = VERBS[f];
|
||||
/* Preserve the capital: sentence-initial imperatives are common. */
|
||||
return /^[\p{Lu}]/u.test(m) ? to.charAt(0).toUpperCase() + to.slice(1) : to;
|
||||
});
|
||||
|
||||
/* Then the regular paradigm, for verbs the table does not name. */
|
||||
for (const [re, to] of SUFFIX) s = s.replace(re, to);
|
||||
|
||||
for (const [re, to] of GRAMMAR) s = s.replace(re, (m) =>
|
||||
/^[\p{Lu}]/u.test(m) ? to.charAt(0).toUpperCase() + to.slice(1) : to);
|
||||
|
||||
for (const [re, to] of LEXICON) s = s.replace(re, (m) =>
|
||||
/^[\p{Lu}]/u.test(m) ? to.charAt(0).toUpperCase() + to.slice(1) : to);
|
||||
|
||||
s = s.replace(VALE, '$1Bueno,');
|
||||
return s;
|
||||
}
|
||||
|
||||
/*
|
||||
* Spain keeps NLLB's words untouched and is distinguished by its VOICE.
|
||||
* Deliberately a no-op rather than an absent function, so the call site reads
|
||||
* symmetrically and the reason lives here rather than in a comment there.
|
||||
*/
|
||||
function toPeninsular(text) { return String(text == null ? '' : text); }
|
||||
|
||||
return { toLatinAmerican, toPeninsular, toChurchRegister, VERBS, LEXICON, PRAY, CONJ };
|
||||
}));
|
||||
Reference in New Issue
Block a user