UNPKG

hebrew-transliteration

Version:
544 lines 25.3 kB
export const tiberian = { VOCAL_SHEVA: "a", HATAF_SEGOL: "ɛ", HATAF_PATAH: "a", HATAF_QAMATS: "ɔ", HIRIQ: "i", TSERE: "e", SEGOL: "ɛ", PATAH: "a", QAMATS: "ɔ", HOLAM: "o", HOLAM_HASER: "o", QUBUTS: "u", DAGESH: "", DAGESH_CHAZAQ: true, MAQAF: "-", PASEQ: "", SOF_PASUQ: "", QAMATS_QATAN: "ɔ", FURTIVE_PATAH: "a", HIRIQ_YOD: "iː", TSERE_YOD: "eː", SEGOL_YOD: "ɛː", SHUREQ: "uː", HOLAM_VAV: "oː", QAMATS_HE: "ɔː", SEGOL_HE: "ɛː", TSERE_HE: "eː", MS_SUFX: "ɔw", ALEF: "ʔ", BET: "v", BET_DAGESH: "b", GIMEL: "ʁ", GIMEL_DAGESH: "g", DALET: "ð", DALET_DAGESH: "d", HE: "h", VAV: "v", ZAYIN: "z", HET: "ħ", TET: "tˁ", YOD: "j", FINAL_KAF: "χ", KAF: "χ", KAF_DAGESH: "kʰ", LAMED: "l", FINAL_MEM: "m", MEM: "m", FINAL_NUN: "n", NUN: "n", SAMEKH: "s", AYIN: "ʕ", FINAL_PE: "f", PE: "f", PE_DAGESH: "pʰ", FINAL_TSADI: "sˁ", TSADI: "sˁ", QOF: "q̟", RESH: "ʀ̟", SHIN: "ʃ", SIN: "s", TAV: "θ", TAV_DAGESH: "tʰ", DIVINE_NAME: "ʔaðoːˈnɔːɔj", DIVINE_NAME_ELOHIM: "ʔɛloːˈhiːim", STRESS_MARKER: { location: "before-syllable", mark: "ˈ" }, ADDITIONAL_FEATURES: [ { FEATURE: "cluster", HEBREW: "\u{05D9}\u{05BC}", TRANSLITERATION: (cluster, hebrew) => { return cluster.text.replace(hebrew, "ɟɟ"); } }, { FEATURE: "cluster", HEBREW: /תּ(?!\u{05B0})/u, TRANSLITERATION: (cluster, _, schema) => { // if there is a dagesh, but it is the beginning of the word // we can return the text, as the character w/ the dagesh will not be doubled if (!cluster.prev || cluster.prev.value?.isNotHebrew) { return cluster.text; } // if there is a dagesh, it may be that it is a dagesh qal (i.e. lene) // if it is a dagesh lene, then like the beginning of the word, // the character w/ the dagesh will not be doubled const prevCoda = cluster.syllable?.prev?.value?.codaWithGemination; if (!prevCoda?.includes("ת")) { return cluster.text; } // because the *_DAGESH value is a digraph, we need to replace the first character // or it will be doubled in rules.ts as "tʰtʰ" const noAspiration = schema["TAV_DAGESH"]?.replace("ʰ", "") ?? ""; return cluster.text.replace("תּ", `${noAspiration + schema["TAV_DAGESH"]}`); } }, { FEATURE: "cluster", HEBREW: /פ(?!\u{05b0})/u, TRANSLITERATION: (cluster, _, schema) => { // /ת(?!\u{05b0})/u rule for explanation if (!cluster.prev || cluster.prev.value?.isNotHebrew) { return cluster.text; } const prevCoda = cluster.syllable?.prev?.value?.codaWithGemination; if (!prevCoda?.includes("פ")) { return cluster.text; } const noAspiration = schema["PE_DAGESH"]?.replace("ʰ", "") ?? ""; return cluster.text.replace("פּ", `${noAspiration + schema["PE_DAGESH"]}`); } }, { FEATURE: "cluster", HEBREW: /טּ(?!\u{05b0})/u, TRANSLITERATION: (cluster, _, schema) => { // /ת(?!\u{05b0})/u rule for explanation if (!cluster.prev || cluster.prev.value?.isNotHebrew) { return cluster.text; } const prevCoda = cluster.syllable?.prev?.value?.codaWithGemination; if (!prevCoda?.includes("ט")) { return cluster.text; } const noPharyngealization = schema["TET"]?.replace("ˁ", "") ?? ""; return cluster.text.replace("ט", `${noPharyngealization + schema["TET"]}`); } }, { FEATURE: "cluster", HEBREW: /צּ(?!\u{05b0})/u, TRANSLITERATION: (cluster, _, schema) => { // /ת(?!\u{05b0})/u rule for explanation if (!cluster.prev || cluster.prev.value?.isNotHebrew) { return cluster.text; } const prevCoda = cluster.syllable?.prev?.value?.codaWithGemination; if (!prevCoda?.includes("צ")) { return cluster.text; } const noPharyngealization = schema["TSADI"]?.replace("ˁ", "") ?? ""; return cluster.text.replace("צ", `${noPharyngealization + schema["TSADI"]}`); } }, { FEATURE: "cluster", HEBREW: /(כּ|ךּ)(?!\u{05b0})/u, TRANSLITERATION: (cluster, _, schema) => { // /תּ[\u{05B4}-\u{05BB}]/u rule for explanation if (!cluster.prev || cluster.prev.value?.isNotHebrew) { return cluster.text; } const prevCoda = cluster.syllable?.prev?.value?.codaWithGemination; if (!prevCoda?.includes("כ") && !prevCoda?.includes("ך")) { return cluster.text; } const noAspiration = schema["KAF_DAGESH"]?.replace("ʰ", "") ?? ""; return cluster.text.replace(/כּ|ךּ/u, `${noAspiration + schema["KAF_DAGESH"]}`); } }, { FEATURE: "cluster", HEBREW: "\u{05D0}(?![\u{05B1}-\u{05BB}\u{05C7}])", TRANSLITERATION: (cluster) => { const next = cluster.next?.value; if (next && next.isShureq) { return cluster.text; } return ""; } }, { FEATURE: "cluster", HEBREW: "\u{05D0}\u{05BC}", TRANSLITERATION: (cluster) => { // remove the dagesh return cluster.text.replace("\u{05BC}", ""); } }, { FEATURE: "syllable", HEBREW: /ר/u, TRANSLITERATION: (syllable) => { // see TPT 229 for a summary if the pharyngealized resh const alveolars = /[דזצתטסלנ]|שׂ/; // find cluster containing resh const cluster = syllable.clusters.filter((c) => c.text.includes("ר"))[0]; const prevCluster = cluster.prev?.value; const currentSyllable = cluster?.syllable; const [onset, _, coda] = currentSyllable ? currentSyllable.structure(true) : ["", "", ""]; if (prevCluster && alveolars.test(prevCluster.text)) { if (onset.includes("ר") && !prevCluster.hasVowel) { return syllable.text.replace("ר", "rˁ"); } if (coda.includes("ר") && prevCluster.hasVowel) { return syllable.text.replace("ר", "rˁ"); } } const nextCluster = cluster.next?.value; const lamedAndNun = /[לנן]/; if (nextCluster && lamedAndNun.test(nextCluster.text)) { if (onset.includes("ר") && !cluster.hasVowel) { return syllable.text.replace("ר", "rˁ"); } if (coda.includes("ר") && cluster.hasSheva) { return syllable.text.replace("ר", "rˁ"); } } // default return syllable.text; } }, { FEATURE: "syllable", HEBREW: "ח\u{05B7}\u{05C3}?$", PASS_THROUGH: true, TRANSLITERATION: (syllable, _hebrew, schema) => { // furtive patach before het preceded by vav or yod const prevText = syllable.prev?.value?.text || ""; // see Khan 497-98 for examples involving length and the meteg // make sure to adjust other rules if (syllable.isFinal && prevText) { if (/[יו]/.test(prevText)) { const glide = /ו/.test(prevText) ? "w" : "j"; return glide + schema["PATAH"] + schema["HET"]; } return schema["PATAH"] + schema["HET"]; } return syllable.text; } }, { FEATURE: "syllable", HEBREW: "ע\u{05B7}\u{05C3}?$", PASS_THROUGH: true, TRANSLITERATION: (syllable, _hebrew, schema) => { // furtive patach before ayin preceded by vav or yod const prevText = syllable.prev?.value?.text; if (syllable.isFinal && prevText) { if (/[יו]/.test(prevText)) { const glide = /ו/.test(prevText) ? "w" : "j"; return glide + schema["PATAH"] + schema["AYIN"]; } return schema["PATAH"] + schema["AYIN"]; } return syllable.text; } }, { FEATURE: "syllable", HEBREW: "ה\u{05BC}\u{05B7}\u{05C3}?$", PASS_THROUGH: true, TRANSLITERATION: (syllable, _hebrew, schema) => { // furtive patach before he preceded by vav or yod const prevText = syllable.prev?.value?.text; if (syllable.isFinal && prevText) { if (/[יו]/.test(prevText)) { const glide = /ו/.test(prevText) ? "w" : "j"; return glide + schema["PATAH"] + schema["HE"]; } return schema["PATAH"] + schema["HE"]; } return syllable.text; } }, { FEATURE: "syllable", HEBREW: /וּ(?![\u{05B4}-\u{05BB}])/u, TRANSLITERATION: (syllable, _, schema) => { // finds a vav with a dagesh not followed by a vowel character // if the syllable is the first syllable, replace with wuː // syllable.clusters[0].isShureq is not totally necessary, but it's a good check if (!syllable.prev && syllable.clusters[0].isShureq) { const text = syllable.text; const hasMeteg = syllable.clusters.map((c) => c.hasMeteg).includes(true); // also called gaya marking half long vowel length (§1.2.8.2.2) const secondaryAccent = hasMeteg ? "ˌ" : ""; const halfLengthMarker = hasMeteg ? "ˑ" : ""; return text.replace("וּ", `${secondaryAccent}wu${halfLengthMarker}`); } if (syllable.isAccented && syllable.isClosed) { const noLength = schema["SHUREQ"].replace("ː", ""); return syllable.text.replace("וּ", schema["SHUREQ"] + noLength); } return syllable.text; } }, { FEATURE: "syllable", HEBREW: /[\u{05B4}-\u{05BB}\u{05C7}]/u, TRANSLITERATION: (syllable, _, schema) => { // this features matches any syllable that has a full vowel character (i.e. not sheva) const vowelName = syllable.vowelNames[0]; const vowel = syllable.vowels[0]; if (!vowel || !vowelName) { return syllable.text; } if (vowelName === "SHEVA") { throw new Error(`Syllable ${syllable.text} has a sheva as vowel, should not have matched`); } // half vowels do not have length; exit early const hasHalfVowel = syllable.clusters.map((c) => c.hasHalfVowel).includes(true); if (hasHalfVowel) { throw new Error(`Syllable ${syllable.text} has a hataf as vowel, should not have matched`); } const [onset, _nuclues, coda] = syllable.structure(true); /** * Determines the realization of a patach * * @param vowelChar the hebrew vowel character * @returns the back unrounded patach realization of the vowel or the original vowel if not patach */ function determinePatachRealization(vowelChar) { // see comment for explanation: https://github.com/charlesLoder/hebrew-transliteration/issues/45#issuecomment-1712186201 // exit early if not patach if (vowelName !== "PATAH" && vowelName !== "HATAF_PATAH") { return vowelChar; } // by this point, the resh has already been pharyngealized // but only for the current syllable const pharyngealized = /rˁ|ט|צ|ץ/; if (pharyngealized.test(onset) || pharyngealized.test(coda)) { return "ɑ"; } // the resh of the next syllable has not been transliterated yet // check if the next syllable has a resh in the onset // and if the current syllable's coda is an alveolar const nextSyllable = syllable.next?.value; const nextOnset = nextSyllable?.onset; const alveolars = /[דזצתטסלנ]|שׂ/; if (nextOnset === "ר" && alveolars.test(coda)) { return "ɑ"; } return vowelChar; } const noMaterText = syllable.clusters .filter((c) => !c.isMater) .map((c) => c.text) .join("") // a holam, tsere, or segol followed by a he without a mappiq is not a mater // but b/c the he is not pronounced, we need to remove the final he .replace(/([\u{05B5}\u{05B6}\u{05B9}].{1})\u{05D4}(?!\u{05BC})/u, "$1"); const hasMaters = syllable.clusters.map((c) => c.isMater).includes(true); const lengthMarker = "ː"; const halfLengthMarker = "ˑ"; // See TPT §1.2.10 concering meteg/gaya const hasMeteg = syllable.clusters.map((c) => c.hasMeteg).includes(true); if (hasMeteg) { const hasLongVowel = syllable.clusters.map((c) => c.hasLongVowel).includes(true); // when a meteg is present, the syllable implicitly has secondary stress // and the vowel is extended if it is not already long const firstConsonant = noMaterText[0]; return noMaterText .replace(firstConsonant, ${firstConsonant}`) .replace(vowel, `${determinePatachRealization(vowel)}${hasLongVowel ? lengthMarker : halfLengthMarker}`); } const isClosed = syllable.isClosed; const isAccented = syllable.isAccented; // TPT §1.2.4, p288 // When long vowels with the main stress occur in closed syllables, // there is evidence that an epenthetic with the same quality as that of the long vowel // occurred before the final consonant in its phonetic realization" if (isAccented && isClosed) { const syllableSeparator = schema["SYLLABLE_SEPARATOR"] || ""; const vowelRealization = determinePatachRealization(vowel); return noMaterText.replace(vowel, `${vowelRealization + lengthMarker + syllableSeparator + vowelRealization}`); } // https://github.com/charlesLoder/hebrew-transliteration/issues/45#issuecomment-1747967050 const longerVowels = ["HOLAM", "TSERE", "QAMATS"]; if (!isAccented && isClosed && !syllable.isFinal && longerVowels.includes(vowelName)) { const syllableSeparator = schema["SYLLABLE_SEPARATOR"] || ""; const vowelRealization = determinePatachRealization(vowel); return noMaterText.replace(vowel, `${vowelRealization + lengthMarker + syllableSeparator + vowelRealization}`); } // TPT §1.2.2.1 p268 // Vowels represented by basic vowel signs are long when they are either // (i) in a stressed syllable or (ii) in an unstressed open syllable. if (isAccented || (!isAccented && !isClosed)) { return noMaterText.replace(vowel, `${determinePatachRealization(vowel) + lengthMarker}`); } if (!hasMaters && !isClosed && !isAccented) { return noMaterText.replace(vowel, `${determinePatachRealization(vowel)}`); } return syllable.text.replace(vowel, `${determinePatachRealization(vowel)}`); } }, { FEATURE: "syllable", HEBREW: /[\u{05B1}-\u{05B3}]/u, TRANSLITERATION: (syllable) => { // this features matches any syllable that has a hataf vowel character const vowelName = syllable.vowelNames[0]; const vowel = syllable.vowels[0]; if (!vowel || !vowelName) { return syllable.text; } if (vowelName === "SHEVA") { throw new Error(`Syllable ${syllable.text} has a sheva as vowel, should not have matched`); } const hasNonHalfVowels = syllable.clusters.map((c) => c.hasShortVowel || c.hasLongVowel).includes(true); if (hasNonHalfVowels) { throw new Error(`Syllable ${syllable.text} does not have a hataf vowel, should not have matched`); } const [onset, _nuclues, coda] = syllable.structure(true); /** * Determines the realization of a patach * * @param vowelChar the hebrew vowel character * @returns the back unrounded patach realization of the vowel or the original vowel if not patach */ function determinePatachRealization(vowelChar) { // exit early if not hataf patach if (vowelName !== "HATAF_PATAH") { return vowelChar; } // by this point, the resh has already been pharyngealized in the transliteration // but only for the current syllable const pharyngealized = /rˁ|ט|צ|ץ/; if (pharyngealized.test(onset) || pharyngealized.test(coda)) { return "ɑ"; } // the resh of the next syllable has not been transliterated yet // check if the next syllable has a resh in the onset // and if the current syllable's coda is an alveolar const nextSyllable = syllable.next?.value; const nextOnset = nextSyllable?.onset; const alveolars = /[דזצתטסלנ]|שׂ/; if (nextOnset === "ר" && alveolars.test(coda)) { return "ɑ"; } // check for the "environment of pharyngealized consonants" // https://www.tiberianhebrew.com/patah if (nextOnset && /[צץט]/.test(nextOnset)) { return "ɑ"; } return vowelChar; } return syllable.text.replace(vowel, `${determinePatachRealization(vowel)}`); } }, { FEATURE: "syllable", HEBREW: /(?<!.*([\u{05B4}-\u{05BB}\u{05C7}]|\u{05D5}\u{05BC}).*)\u{05B0}/u, TRANSLITERATION: (syllable, _hebrew, schema) => { // matches any syllable that contains a sheva that is not preceded by a full vowel character [\u{05B4}-\u{05BB}\u{05C7}] // or shureq \u{5D5}\u{5BC} const nextSyllable = syllable.next?.value; if (!nextSyllable) return syllable.text; const nextSylFirstCluster = nextSyllable.clusters[0].text; if (!nextSylFirstCluster) return syllable.text; const [onset, _, coda] = syllable.structure(true); function isBackUnrounded() { // see comment for explanation: https://github.com/charlesLoder/hebrew-transliteration/issues/45#issuecomment-1712186201 // by this point, the resh has already been pharyngealized in the transliteration const pharyngealized = /rˁ|ט|צ|ץ/; if (pharyngealized.test(onset) || pharyngealized.test(coda)) { return true; } const nextSyllable = syllable.next?.value; if (!nextSyllable) { return false; } const nextOnset = nextSyllable.onset; if (pharyngealized.test(nextOnset)) { return true; } return false; } function transliterateShevaAsVowel(vowel) { const hasMeteg = syllable.clusters.map((c) => c.hasMeteg).includes(true); const secondaryAccent = hasMeteg ? "ˌ" : ""; const halfLengthMarker = hasMeteg ? "ˑ" : ""; const newVowel = vowel.replace("ː", "") + halfLengthMarker; return secondaryAccent + syllable.text.replace(/\u{05B0}/u, newVowel); } const isGuttural = /[אהחע]/.test(nextSylFirstCluster); if (!isGuttural) { return transliterateShevaAsVowel(isBackUnrounded() ? "ɑ" : schema["PATAH"]); } const nextVowel = nextSyllable.vowelNames[0]; if (!nextVowel) { throw new Error(`Syllable ${syllable.text} has a sheva as a vowel, but the next syllable ${nextSylFirstCluster} does not have a vowel`); } if (nextVowel === "SHEVA") { throw new Error(`Syllable ${syllable.text} has a sheva as a vowel, but the next syllable ${nextSylFirstCluster} also has a sheva as a vowel`); } return transliterateShevaAsVowel(schema[nextVowel]); } }, { FEATURE: "syllable", HEBREW: /^\u{5B4}\u{5DD}/u, TRANSLITERATION: (syl, heb, schema) => { // This rule attempts to find instances of Jerusalem spelled without a yod // this is just a sanity check that the previous syllable // should be a lamed with a qamats or a patah const prev = syl.prev?.value; if (prev && !prev.isClosed && !prev.hasVowelName("QAMATS") && !prev.hasVowelName("PATAH") && prev.onset !== "ל") { return syl.text; } // update this syllable to match the later spelling of Jerusalem return syl.text.replace(heb, `${schema["YOD"]}${schema["HIRIQ"]}${schema["FINAL_MEM"]}`); } }, { FEATURE: "word", HEBREW: /(וְ)?יִשָּׂשכָר/, PASS_THROUGH: true, TRANSLITERATION: (word, heb) => { // matches the name Issachar const taamim = /[\u{0590}-\u{05AF}\u{05BD}\u{05BF}]/gu; const text = word.text.replace(taamim, ""); const match = text.match(heb); const vav = match && match[1] ? match[1] : ""; const issachar = "jissɔːˈχɔːɔʀ̟"; return `${vav}${issachar}`; } } ], allowNoNiqqud: false, article: false, holemHaser: "remove", ketivQeres: [ { input: "הִוא", output: "הִיא", captureTaamim: true, ignoreTaamim: true } ], longVowels: false, qametsQatan: true, shevaAfterMeteg: false, shevaWithMeteg: true, sqnmlvy: false, strict: true, wawShureq: false }; //# sourceMappingURL=tiberian.js.map