Skip to content

Add Module:th-pron from wiktionary #1435

Description

@wannaphong

Add Module:th-pron from wiktionary to pythainlp.transliterate

Module:th-pron: https://en.wiktionary.org/wiki/Module:th-pron

import re
import unicodedata

thai_range = r"[ก-๛̄]"

systems = {
    "paiboon": 0,
    "royin": 1,
    "ipa": 2
}

initial = {
    "ก": {"seq": ["g", "k", "k"], "class": "mid"},
    "จ": {"seq": ["j", "ch", "t͡ɕ"], "class": "mid"},
    "ด": {"seq": ["d", "d", "d"], "class": "mid"},
    "ฎ": {"seq": ["d", "d", "d"], "class": "mid"},
    "ฏ": {"seq": ["dt", "t", "t"], "class": "mid"},
    "ต": {"seq": ["dt", "t", "t"], "class": "mid"},
    "บ": {"seq": ["b", "b", "b"], "class": "mid"},
    "ป": {"seq": ["bp", "p", "p"], "class": "mid"},
    "อ": {"seq": ["", "@", "ʔ"], "class": "mid"},
    "ง": {"seq": ["ng", "$ng", "ŋ"], "class": "low"},
    "ณ": {"seq": ["n", "n", "n"], "class": "low"},
    "น": {"seq": ["n", "n", "n"], "class": "low"},
    "ม": {"seq": ["m", "m", "m"], "class": "low"},
    "ญ": {"seq": ["y", "y", "j"], "class": "low"},
    "ย": {"seq": ["y", "y", "j"], "class": "low"},
    "ร": {"seq": ["r", "r", "r"], "class": "low"},
    "ล": {"seq": ["l", "l", "l"], "class": "low"},
    "ฬ": {"seq": ["l", "l", "l"], "class": "low"},
    "ว": {"seq": ["w", "w", "w"], "class": "low"},
    "ค": {"seq": ["k", "kh", "kʰ"], "class": "low"},
    "ฅ": {"seq": ["k", "kh", "kʰ"], "class": "low"},
    "ฆ": {"seq": ["k", "kh", "kʰ"], "class": "low"},
    "ข": {"seq": ["k", "kh", "kʰ"], "class": "high"},
    "ฃ": {"seq": ["k", "kh", "kʰ"], "class": "high"},
    "ช": {"seq": ["ch", "ch", "t͡ɕʰ"], "class": "low"},
    "ฌ": {"seq": ["ch", "ch", "t͡ɕʰ"], "class": "low"},
    "ฉ": {"seq": ["ch", "ch", "t͡ɕʰ"], "class": "high"},
    "ฑ": {"seq": ["t", "th", "tʰ"], "class": "low"},
    "ฒ": {"seq": ["t", "th", "tʰ"], "class": "low"},
    "ท": {"seq": ["t", "th", "tʰ"], "class": "low"},
    "ธ": {"seq": ["t", "th", "tʰ"], "class": "low"},
    "ฐ": {"seq": ["t", "th", "tʰ"], "class": "high"},
    "ถ": {"seq": ["t", "th", "tʰ"], "class": "high"},
    "พ": {"seq": ["p", "ph", "pʰ"], "class": "low"},
    "ภ": {"seq": ["p", "ph", "pʰ"], "class": "low"},
    "ผ": {"seq": ["p", "ph", "pʰ"], "class": "high"},
    "ฟ": {"seq": ["f", "f", "f"], "class": "low"},
    "ฝ": {"seq": ["f", "f", "f"], "class": "high"},
    "ซ": {"seq": ["s", "s", "s"], "class": "low"},
    "ศ": {"seq": ["s", "s", "s"], "class": "high"},
    "ษ": {"seq": ["s", "s", "s"], "class": "high"},
    "ส": {"seq": ["s", "s", "s"], "class": "high"},
    "ฮ": {"seq": ["h", "h", "h"], "class": "low"},
    "ห": {"seq": ["h", "h", "h"], "class": "high"},
    "หง": {"seq": ["ng", "$ng", "ŋ"], "class": "high"},
    "หน": {"seq": ["n", "n", "n"], "class": "high"},
    "หม": {"seq": ["m", "m", "m"], "class": "high"},
    "หญ": {"seq": ["y", "y", "j"], "class": "high"},
    "หย": {"seq": ["y", "y", "j"], "class": "high"},
    "หร": {"seq": ["r", "r", "r"], "class": "high"},
    "หล": {"seq": ["l", "l", "l"], "class": "high"},
    "หว": {"seq": ["w", "w", "w"], "class": "high"},
    "…": {"seq": ["…", "…", "…"], "class": ""},
    "": {"seq": ["", "", ""], "class": ""},
}

vowel = {
    "open": {
        "ะ": ["a", "a", "a"], "": ["a", "a", "a"], "ิ": ["i", "i", "i"],
        "ึ": ["ʉ", "ue", "ɯ"], "ุ": ["u", "u", "u"], "เะ": ["e", "e", "eʔ"],
        "แะ": ["ɛ", "ae", "ɛʔ"], "โะ": ["o", "o", "oʔ"], "เาะ": ["ɔ", "o", "ɔʔ"],
        "็": ["ɔ", "o", "ɔ"], "เิ": ["ə", "oe", "ɤ"], "เอะ": ["ə", "oe", "ɤʔ"],
        "า": ["aa", "a", "aː"], "ี": ["ii", "i", "iː"], "ู": ["uu", "u", "uː"],
        "ือ": ["ʉʉ", "ue", "ɯː"], "เ": ["ee", "e", "eː"], "แ": ["ɛɛ", "ae", "ɛː"],
        "โ": ["oo", "o", "oː"], "อ": ["ɔɔ", "o", "ɔː"], "ร": ["ɔɔn", "on", "ɔːn"],
        "เอ": ["əə", "oe", "ɤː"], "เียะ": ["ia", "ia", "ia̯ʔ"], "เือะ": ["ʉa", "uea", "ɯa̯ʔ"],
        "ัวะ": ["ua", "ua", "ua̯ʔ"], "เีย": ["iia", "ia", "ia̯"], "เือ": ["ʉʉa", "uea", "ɯa̯"],
        "ัว": ["uua", "ua", "ua̯"], "ิว": ["iu", "io", "iw"], "ีว": ["iiu", "io", "iːw"],
        "เ็ว": ["eo", "eo", "ew"], "แ็ว": ["ɛo", "aeo", "ɛw"], "เา": ["ao", "ao", "aw"],
        "เว": ["eeo", "eo", "eːw"], "แว": ["ɛɛo", "aeo", "ɛːw"], "าว": ["aao", "ao", "aːw"],
        "เอว": ["əəo", "oeu", "ɤːw"], "โว": ["oow", "ou", "oːw"], "เียว": ["iao", "iao", "ia̯w"],
        "ัย": ["ai", "ai", "aj"], "ใ": ["ai", "ai", "aj"], "ไ": ["ai", "ai", "aj"],
        "ไย": ["ai", "ai", "aj"], "ึย": ["ʉi", "uei", "ɯj"], "็อย": ["ɔi", "oi", "ɔj"],
        "เิ็ย": ["əi", "oei", "ɤj"], "ุย": ["ui", "ui", "uj"], "าย": ["aai", "ai", "aːj"],
        "อย": ["ɔɔi", "oi", "ɔːj"], "โย": ["ooi", "oi", "oːj"], "เย": ["əəi", "oei", "ɤːj"],
        "ูย": ["uui", "ui", "uːj"], "วย": ["uai", "uai", "ua̯j"], "เือย": ["ʉai", "ueai", "ɯa̯j"],
        "ำ": ["am", "am", "am"],
    },
    "closed": {
        "ั": ["a", "a", "a"], "รร": ["a", "a", "a"], "ิ": ["i", "i", "i"],
        "ึ": ["ʉ", "ue", "ɯ"], "ุ": ["u", "u", "u"], "เ": ["ee", "e", "eː"],
        "เ็": ["e", "e", "e"], "แ็": ["ɛ", "ae", "ɛ"], "แ": ["ɛɛ", "ae", "ɛː"],
        "": ["o", "o", "o"], "็อ": ["ɔ", "o", "ɔ"], "เิ็": ["ə", "oe", "ɤ"],
        "า": ["aa", "a", "aː"], "ี": ["ii", "i", "iː"], "ื": ["ʉʉ", "ue", "ɯː"],
        "ู": ["uu", "u", "uː"], "โ": ["oo", "o", "oː"], "อ": ["ɔɔ", "o", "ɔː"],
        "เิ": ["əə", "oe", "ɤː"], "เอ": ["əə", "oe", "ɤː"], "เีย": ["iia", "ia", "ia̯"],
        "เือ": ["ʉʉa", "uea", "ɯa̯"], "ว": ["uua", "ua", "ua̯"], "ไ": ["ai", "ai", "aj"],
        "เา": ["ao", "ao", "aw"], "็อย": ["ɔi", "oi", "ɔj"],
    }
}

unromLong = {"เีย": True, "เือ": True, "ัว": True, "ว": True, "เือย": True, "วาย": True, "เอว": True, "เียว": True}
liveExc = {"ัย": True, "ใ": True, "ไ": True, "ไย": True, "ุย": True, "วย": True, "็อย": True, "เิ็ย": True, "เา": True, "ิว": True, "เ็ว": True, "แ็ว": True, "ำ": True}

coda = {
    "ก": ["k", "k", "k̚"], "ข": ["k", "k", "k̚"], "ฃ": ["k", "k", "k̚"], "ค": ["k", "k", "k̚"], 
    "ฅ": ["k", "k", "k̚"], "ฆ": ["k", "k", "k̚"], "จ": ["t", "t", "t̚"], "ฉ": ["t", "t", "t̚"], 
    "ช": ["ch", "ch", "t͡ɕʰ"], "ซ": ["s", "s", "s"], "ฌ": ["t", "t", "t̚"], "ฎ": ["t", "t", "t̚"], 
    "ฏ": ["t", "t", "t̚"], "ฐ": ["t", "t", "t̚"], "ฑ": ["t", "t", "t̚"], "ฒ": ["t", "t", "t̚"], 
    "ด": ["t", "t", "t̚"], "ต": ["t", "t", "t̚"], "ถ": ["t", "t", "t̚"], "ท": ["t", "t", "t̚"], 
    "ธ": ["t", "t", "t̚"], "ศ": ["t", "t", "t̚"], "ษ": ["t", "t", "t̚"], "ส": ["s", "s", "s"], 
    "บ": ["p", "p", "p̚"], "ป": ["p", "p", "p̚"], "ผ": ["p", "p", "p̚"], "ฝ": ["p", "p", "p̚"], 
    "พ": ["p", "p", "p̚"], "ฟ": ["f", "f", "f"], "ภ": ["p", "p", "p̚"], "ง": ["ng", "ng$", "ŋ"], 
    "ญ": ["n", "n", "n"], "ณ": ["n", "n", "n"], "น": ["n", "n", "n"], "ร": ["n", "n", "n"], 
    "ล": ["l", "l", "l"], "ฬ": ["n", "n", "n"], "ม": ["m", "m", "m"], "ฯ": ["ʔ", "ʔ", "ʔ"]
}

tFromMark = {
    "่": {"high": "low", "mid": "low", "low": "falling"},
    "้": {"high": "falling", "mid": "falling", "low": "high"},
    "๊": {"high": "high", "mid": "high", "low": "high"},
    "๋": {"high": "rising", "mid": "rising", "low": "rising"},
    "̄": {"high": "mid", "mid": "mid", "low": "mid"}
}

tNoMark = {
    "dead-short": {"high": "low", "mid": "low", "low": "high"},
    "dead-long": {"high": "low", "mid": "low", "low": "falling"},
    "live": {"high": "rising", "mid": "mid", "low": "mid"}
}

tRomMarks = {"high": "́", "mid": "", "low": "̀", "rising": "̌", "falling": "̂"}
tLevels = {"high": "˦˥", "mid": "˧", "low": "˨˩", "rising": "˩˩˦", "falling": "˥˩"}
symbols = {"๐": "0", "๑": "1", "๒": "2", "๓": "3", "๔": "4", "๕": "5", "๖": "6", "๗": "7", "๘": "8", "๙": "9"}
permitted_cluster = {"ks": 1, "ms": 1, "ns": 1, "ps": 1, "ts": 1}

mgvc_pattern = re.compile(r"^([รลว]?)([ำู]?[าอรยว]?[วยร]?ะ?)([คฅฆกขฃพฟภบปชฌฑฒทธจฎฏดตฐถศษสมญณนรลฬงฯ]?)$")
full_pattern = re.compile(r"^([เแโใไ]?)(หฺ[ก-รลว-ฮ])(ฺ?[รลว]?)([]?็?[]?[าอรยวำ]?[วยร]?ะ?)([คฅฆกขฃพฟภบปชฌฑฒทธจฎฏดตฐถศษสมญณนรลฬงฯ]?[คฅฆกขฃพฟภบปชฌฑฒทธจฎฏดตฐถศษสมญณนรลฬงฯ]?)$")
partial_pattern = re.compile(r"^([เแโใไ]?)([ก-รลว-ฮ])(ฺ?[รลว]?)([]?็?[]?[าอรยวำ]?[วยร]?ะ?)([คฅฆกขฃพฟภบปชฌฑฒทธจฎฏดตฐถศษสมญณนรลฬงฯ]?[คฅฆกขฃพฟภบปชฌฑฒทธจฎฏดตฐถศษสมญณนรลฬงฯ]?)$")

def c2_decomp(c2_char, seq_idx):
    converted_c2 = []
    for character in list(c2_char):
        val = coda.get(character)
        converted_c2.append(val[seq_idx] if val else "")
    cluster = "".join(converted_c2)
    return cluster

def _translit_word(text, mode):
    seq_idx = systems.get(mode)
    if seq_idx is None:
        return text
        
    def process_word(match_word):
        word = match_word.group(0)
        
        if re.search(r"[].?[]", word):
            return word
            
        def syllable(match):
            nonlocal word
            v1, c1, g, v2, c2 = match.groups()
            
            tMark_match = re.search(r"[]", v2)
            tMark = tMark_match.group(0) if tMark_match else False
            v2 = re.sub(r"[]", "", v2)
            
            if re.match(r"^ห.$", c1):
                mgvc_match = mgvc_pattern.match(c1[1] + g + v2 + c2)
                if mgvc_match:
                    g_new, v2_new, c2_new = mgvc_match.groups()
                    c1, g, v2, c2 = "ห", g_new, v2_new, c2_new
                    if g != "" and v2 != "ย":
                        c1, g = c1 + g, ""
                        
            if g == "ล" and (v2 + c2) == "":
                c2 = g
                g = ""
                
            openness = "closed" if c2 != "" else "open"
                
            if (v1 + g + v2) in vowel[openness]:
                orig_v = v1 + g + v2
                v = vowel[openness][orig_v][seq_idx]
                g = ""
            else:
                orig_v = v1 + v2
                v_lookup = vowel[openness].get(v1 + v2)
                v = v_lookup[seq_idx] if v_lookup else (v1 + v2)
                g_clean = g.replace("ฺ", "")
                g_lookup = initial.get(g_clean) or initial[""]
                g = g_lookup["seq"][seq_idx]
                
            c1_clean = c1.replace("ฺ", "")
            if c1_clean in initial:
                ini = initial[c1_clean]["seq"][seq_idx]
                cls = initial[c1_clean]["class"]
            else:
                return match.group(0) # fallback
                
            length = "long" if re.search(r"([aiʉueɛoɔə])\1", v) or "ː" in v or unromLong.get(orig_v) else "short"
            life = "live" if re.search(r"[มญณนรลฬง]", c2) or (orig_v.endswith("ย") and v.endswith("i")) or (c2 + length == "long") or liveExc.get(orig_v) else "dead"
            
            if c2 in coda:
                c2 = coda[c2][seq_idx]
            else:
                c2 = c2_decomp(c2, seq_idx)
                
            tone_dict = tFromMark.get(tMark) if tMark else tNoMark.get(f"{life}-{length}", tNoMark.get(life))
            tone = tone_dict.get(cls) if tone_dict else None
            
            if mode == "paiboon":
                v = re.sub(r"^([^aiʉueɛoɔə]*)([aiʉueɛoɔə])", f"\\g<1>\\g<2>{tRomMarks.get(tone, '')}", v)
            elif mode == "ipa":
                c2 = c2 + tLevels.get(tone, "")
                
            return ini + g + v + c2
            
        word = full_pattern.sub(syllable, word)
        word = partial_pattern.sub(syllable, word)
        return word

    words_iter = list(re.finditer(f"{thai_range}+", text))
    for match_word in words_iter:
        processed = process_word(match_word)
        if processed:
            text = text.replace(match_word.group(0), processed, 1)

    text = re.sub(r"[๐-๙]", lambda m: symbols.get(m.group(0), m.group(0)), text)

    if mode == "royin":
        text = re.sub(r"^@", "", text)
        text = re.sub(r"([\s\W])@", r"\1", text)
        text = text.replace("@", "-")
        text = re.sub(r"^\$ng", "ng", text)
        text = re.sub(r"([\s\W])\$ng", r"\1ng", text)
        text = re.sub(r"([aeiou])\$ng", r"\1-ng", text)
        text = text.replace("$ng", "ng")
        text = re.sub(r"ng\$([^\w\s])", r"ng\1", text)
        text = re.sub(r"ng\$", "ng", text)

    if mode == "ipa":
        text, count_syl = re.subn(r"[ \-–]", ".", text)
        text = re.sub(r"([aiɯu])([˥-˩]+)$", r"\1ʔ\2", text)

    return unicodedata.normalize('NFC', text)

def get_word_dict(word: str) -> dict:
    """
    Takes a Thai string and returns a dictionary with different
    transliteration standards.
    """
    return {
        "word": word,
        "paiboon": _translit_word(word, mode="paiboon"),
        "royin": _translit_word(word, mode="royin"),
        "ipa": _translit_word(word, mode="ipa")
    }

# --- Example Usage ---
if __name__ == "__main__":
    test_word = "ปฺระ-ชา"
    result = get_word_dict(test_word)
    print(result)

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions