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,186 @@
|
||||
/*
|
||||
* languages.js — the language catalog, shared by the NAS server, the listener
|
||||
* page, and the Mac engine.
|
||||
*
|
||||
* `code` is a FLORES-200 code, which is what NLLB-200 expects. `tts` is the
|
||||
* BCP-47 tag we hand to the phone's speechSynthesis; it is a best effort, and a
|
||||
* phone that has no matching voice simply shows captions without speaking.
|
||||
*
|
||||
* `native` is what the listener actually sees in the picker. Never show someone
|
||||
* the English name of their own language when you can help it.
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof module === 'object' && module.exports) module.exports = factory();
|
||||
else root.Languages = factory();
|
||||
}(typeof self !== 'undefined' ? self : this, function () {
|
||||
|
||||
// [ FLORES code, English name, native name, BCP-47 for TTS, rtl ]
|
||||
var RAW = [
|
||||
['eng_Latn', 'English', 'English', 'en-US'],
|
||||
['spa_Latn', 'Spanish (Latin America)', 'Español (América Latina)', 'es-MX'],
|
||||
['zho_Hans', 'Chinese (Simplified)', '简体中文', 'zh-CN'],
|
||||
['zho_Hant', 'Chinese (Traditional)','繁體中文', 'zh-TW'],
|
||||
['yue_Hant', 'Cantonese', '廣東話', 'zh-HK'],
|
||||
['fra_Latn', 'French', 'Français', 'fr-FR'],
|
||||
/* One NLLB Portuguese, two voices: the Brazilian and European variants
|
||||
share their translation (por_PT is aliased to por_Latn at the MT
|
||||
boundary in engine.js) and differ in the voice that reads it. */
|
||||
['por_Latn', 'Portuguese (Brazil)', 'Português (Brasil)', 'pt-BR'],
|
||||
['por_PT', 'Portuguese (Portugal)', 'Português (Portugal)', 'pt-PT'],
|
||||
/* Same NLLB Spanish, read in a Castilian voice. There is no grammatical
|
||||
transform between them: converting ustedes->vosotros needs to know
|
||||
whether "you" was one person or a room, and neither the English nor
|
||||
the Spanish carries that — see spa-variants.js for the measurements. */
|
||||
['spa_ES', 'Spanish (Spain)', 'Español (España)', 'es-ES'],
|
||||
/* Mexico shares Latin America's grammar exactly (RAE: vosotros "carecen
|
||||
de uso en América", and Mexico is tuteo-exclusive), so the text is
|
||||
identical — this exists so a Mexican listener sees their own country
|
||||
named, and so the operator can give it its own voice. */
|
||||
['spa_MX', 'Spanish (Mexico)', 'Español (México)', 'es-MX'],
|
||||
['kor_Hang', 'Korean', '한국어', 'ko-KR'],
|
||||
['vie_Latn', 'Vietnamese', 'Tiếng Việt', 'vi-VN'],
|
||||
['hat_Latn', 'Haitian Creole', 'Kreyòl Ayisyen', 'ht-HT'],
|
||||
['arb_Arab', 'Arabic', 'العربية', 'ar-SA', true],
|
||||
['rus_Cyrl', 'Russian', 'Русский', 'ru-RU'],
|
||||
['ukr_Cyrl', 'Ukrainian', 'Українська', 'uk-UA'],
|
||||
['ron_Latn', 'Romanian', 'Română', 'ro-RO'],
|
||||
['pol_Latn', 'Polish', 'Polski', 'pl-PL'],
|
||||
['deu_Latn', 'German', 'Deutsch', 'de-DE'],
|
||||
['ita_Latn', 'Italian', 'Italiano', 'it-IT'],
|
||||
['nld_Latn', 'Dutch', 'Nederlands', 'nl-NL'],
|
||||
['tgl_Latn', 'Tagalog', 'Tagalog', 'fil-PH'],
|
||||
['ceb_Latn', 'Cebuano', 'Cebuano', 'fil-PH'],
|
||||
['ind_Latn', 'Indonesian', 'Bahasa Indonesia', 'id-ID'],
|
||||
['zsm_Latn', 'Malay', 'Bahasa Melayu', 'ms-MY'],
|
||||
['tha_Thai', 'Thai', 'ไทย', 'th-TH'],
|
||||
['khm_Khmr', 'Khmer', 'ភាសាខ្មែរ', 'km-KH'],
|
||||
['lao_Laoo', 'Lao', 'ລາວ', 'lo-LA'],
|
||||
['mya_Mymr', 'Burmese', 'မြန်မာ', 'my-MM'],
|
||||
['shn_Mymr', 'Shan', ' လိၵ်ႈတႆး', 'shn'],
|
||||
['kac_Latn', 'Kachin (Jingpho)', 'Jinghpaw', 'kac'],
|
||||
['npi_Deva', 'Nepali', 'नेपाली', 'ne-NP'],
|
||||
['hin_Deva', 'Hindi', 'हिन्दी', 'hi-IN'],
|
||||
['urd_Arab', 'Urdu', 'اردو', 'ur-PK', true],
|
||||
['ben_Beng', 'Bengali', 'বাংলা', 'bn-BD'],
|
||||
['pan_Guru', 'Punjabi', 'ਪੰਜਾਬੀ', 'pa-IN'],
|
||||
['guj_Gujr', 'Gujarati', 'ગુજરાતી', 'gu-IN'],
|
||||
['tam_Taml', 'Tamil', 'தமிழ்', 'ta-IN'],
|
||||
['tel_Telu', 'Telugu', 'తెలుగు', 'te-IN'],
|
||||
['mal_Mlym', 'Malayalam', 'മലയാളം', 'ml-IN'],
|
||||
['kan_Knda', 'Kannada', 'ಕನ್ನಡ', 'kn-IN'],
|
||||
['mar_Deva', 'Marathi', 'मराठी', 'mr-IN'],
|
||||
['sin_Sinh', 'Sinhala', 'සිංහල', 'si-LK'],
|
||||
['pes_Arab', 'Persian (Farsi)', 'فارسی', 'fa-IR', true],
|
||||
['prs_Arab', 'Dari', 'دری', 'fa-AF', true],
|
||||
['pbt_Arab', 'Pashto', 'پښتو', 'ps-AF', true],
|
||||
['ckb_Arab', 'Kurdish (Sorani)', 'کوردی', 'ckb', true],
|
||||
['kmr_Latn', 'Kurdish (Kurmanji)', 'Kurmancî', 'ku'],
|
||||
['heb_Hebr', 'Hebrew', 'עברית', 'he-IL', true],
|
||||
['tur_Latn', 'Turkish', 'Türkçe', 'tr-TR'],
|
||||
['amh_Ethi', 'Amharic', 'አማርኛ', 'am-ET'],
|
||||
['tir_Ethi', 'Tigrinya', 'ትግርኛ', 'ti-ET'],
|
||||
['som_Latn', 'Somali', 'Soomaali', 'so-SO'],
|
||||
['swh_Latn', 'Swahili', 'Kiswahili', 'sw-KE'],
|
||||
['yor_Latn', 'Yoruba', 'Yorùbá', 'yo-NG'],
|
||||
['ibo_Latn', 'Igbo', 'Igbo', 'ig-NG'],
|
||||
['hau_Latn', 'Hausa', 'Hausa', 'ha-NG'],
|
||||
['lin_Latn', 'Lingala', 'Lingála', 'ln'],
|
||||
['kin_Latn', 'Kinyarwanda', 'Ikinyarwanda', 'rw-RW'],
|
||||
['run_Latn', 'Kirundi', 'Ikirundi', 'rn'],
|
||||
['lug_Latn', 'Luganda', 'Luganda', 'lg'],
|
||||
['nya_Latn', 'Chichewa', 'Chichewa', 'ny'],
|
||||
['sna_Latn', 'Shona', 'chiShona', 'sn'],
|
||||
['zul_Latn', 'Zulu', 'isiZulu', 'zu-ZA'],
|
||||
['xho_Latn', 'Xhosa', 'isiXhosa', 'xh-ZA'],
|
||||
['afr_Latn', 'Afrikaans', 'Afrikaans', 'af-ZA'],
|
||||
['jpn_Jpan', 'Japanese', '日本語', 'ja-JP'],
|
||||
['ell_Grek', 'Greek', 'Ελληνικά', 'el-GR'],
|
||||
['hun_Latn', 'Hungarian', 'Magyar', 'hu-HU'],
|
||||
['ces_Latn', 'Czech', 'Čeština', 'cs-CZ'],
|
||||
['slk_Latn', 'Slovak', 'Slovenčina', 'sk-SK'],
|
||||
['bul_Cyrl', 'Bulgarian', 'Български', 'bg-BG'],
|
||||
['srp_Cyrl', 'Serbian', 'Српски', 'sr-RS'],
|
||||
['hrv_Latn', 'Croatian', 'Hrvatski', 'hr-HR'],
|
||||
['bos_Latn', 'Bosnian', 'Bosanski', 'bs-BA'],
|
||||
['slv_Latn', 'Slovenian', 'Slovenščina', 'sl-SI'],
|
||||
['als_Latn', 'Albanian', 'Shqip', 'sq-AL'],
|
||||
['lit_Latn', 'Lithuanian', 'Lietuvių', 'lt-LT'],
|
||||
['lvs_Latn', 'Latvian', 'Latviešu', 'lv-LV'],
|
||||
['est_Latn', 'Estonian', 'Eesti', 'et-EE'],
|
||||
['fin_Latn', 'Finnish', 'Suomi', 'fi-FI'],
|
||||
['swe_Latn', 'Swedish', 'Svenska', 'sv-SE'],
|
||||
['nob_Latn', 'Norwegian', 'Norsk', 'nb-NO'],
|
||||
['dan_Latn', 'Danish', 'Dansk', 'da-DK'],
|
||||
['isl_Latn', 'Icelandic', 'Íslenska', 'is-IS'],
|
||||
['kat_Geor', 'Georgian', 'ქართული', 'ka-GE'],
|
||||
['hye_Armn', 'Armenian', 'Հայերեն', 'hy-AM'],
|
||||
['azj_Latn', 'Azerbaijani', 'Azərbaycan', 'az-AZ'],
|
||||
['uzn_Latn', 'Uzbek', 'Oʻzbek', 'uz-UZ'],
|
||||
['kaz_Cyrl', 'Kazakh', 'Қазақша', 'kk-KZ'],
|
||||
['kir_Cyrl', 'Kyrgyz', 'Кыргызча', 'ky-KG'],
|
||||
['tgk_Cyrl', 'Tajik', 'Тоҷикӣ', 'tg-TJ'],
|
||||
['khk_Cyrl', 'Mongolian', 'Монгол', 'mn-MN'],
|
||||
['bod_Tibt', 'Tibetan', 'བོད་སྐད་', 'bo'],
|
||||
['gle_Latn', 'Irish', 'Gaeilge', 'ga-IE'],
|
||||
['cym_Latn', 'Welsh', 'Cymraeg', 'cy-GB'],
|
||||
['eus_Latn', 'Basque', 'Euskara', 'eu-ES'],
|
||||
['cat_Latn', 'Catalan', 'Català', 'ca-ES'],
|
||||
['glg_Latn', 'Galician', 'Galego', 'gl-ES'],
|
||||
['quy_Latn', 'Quechua', 'Runasimi', 'qu'],
|
||||
['ayr_Latn', 'Aymara', 'Aymar aru', 'ay'],
|
||||
['grn_Latn', 'Guarani', "Avañe'ẽ", 'gn'],
|
||||
['mri_Latn', 'Maori', 'Te Reo Māori', 'mi-NZ'],
|
||||
['smo_Latn', 'Samoan', 'Gagana Samoa', 'sm'],
|
||||
['fij_Latn', 'Fijian', 'Na Vosa Vakaviti', 'fj'],
|
||||
['tpi_Latn', 'Tok Pisin', 'Tok Pisin', 'tpi']
|
||||
];
|
||||
|
||||
var LANGS = RAW.map(function (r) {
|
||||
return { code: r[0], name: r[1], native: r[2], tts: r[3], rtl: !!r[4] };
|
||||
});
|
||||
|
||||
var BY_CODE = {};
|
||||
LANGS.forEach(function (l) { BY_CODE[l.code] = l; });
|
||||
|
||||
// A sane opening set for a US church. The operator can change this at any time.
|
||||
var DEFAULT_FEATURED = [
|
||||
'spa_Latn', 'zho_Hans', 'por_Latn', 'fra_Latn',
|
||||
'kor_Hang', 'vie_Latn', 'hat_Latn', 'arb_Arab'
|
||||
];
|
||||
|
||||
function get(code) { return BY_CODE[code] || null; }
|
||||
|
||||
function label(code) {
|
||||
var l = BY_CODE[code];
|
||||
if (!l) return code;
|
||||
return l.native === l.name ? l.native : l.native + ' · ' + l.name;
|
||||
}
|
||||
|
||||
/* Case- and accent-insensitive search across both names and the code, so
|
||||
"espanol", "Español", "spanish" and "spa" all find Spanish. */
|
||||
function normalize(s) {
|
||||
return String(s || '')
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[̀-ͯ]/g, '');
|
||||
}
|
||||
|
||||
function search(query) {
|
||||
var q = normalize(query).trim();
|
||||
if (!q) return LANGS.slice();
|
||||
return LANGS.filter(function (l) {
|
||||
return normalize(l.name).indexOf(q) !== -1 ||
|
||||
normalize(l.native).indexOf(q) !== -1 ||
|
||||
normalize(l.code).indexOf(q) !== -1;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
LANGS: LANGS,
|
||||
DEFAULT_FEATURED: DEFAULT_FEATURED,
|
||||
get: get,
|
||||
label: label,
|
||||
search: search,
|
||||
codes: function () { return LANGS.map(function (l) { return l.code; }); }
|
||||
};
|
||||
}));
|
||||
Reference in New Issue
Block a user