Sakizaya pronunciation module. See Template:szy-IPA for usage and examples.


--[=[
This module implements the templates {{szy-IPA}}.

Author: TongcyDai
]=]

local export = {}
local m_IPA = require("Module:IPA")
local lang = require("Module:languages").getByCode("szy")

local u = mw.ustring.char
local ufind = mw.ustring.find
local ugsub = mw.ustring.gsub
local tsplit = mw.text.split
local ulower = mw.ustring.lower
local usub = mw.ustring.sub
local ulen = mw.ustring.len
local ugmatch = mw.ustring.gmatch
local ulower = mw.ustring.lower

local vowels = "aiuoeə"
local glides = "jw"
local consonants = "ptkʡbdshzʦmlŋng"

local phoneme_map = {
    ['i'] = 'i', ['u'] = 'u', ['e'] = 'ə', ['o'] = 'o', ['a'] = 'a',
    ['w'] = 'w', ['y'] = 'j',
    ['p'] = 'p', ['t'] = 't', ['k'] = 'k', ["'"] = 'ʡ', ["’"] = 'ʡ',
    ['b'] = 'b', ['d'] = 'd', ['s'] = 's', ['h'] = 'h', ['z'] = 'z',
    ['c'] = 'ʦ', ['m'] = 'm', ['ŋ'] = 'ŋ', ['n'] = 'n', ['g'] = 'g', ['l'] = 'l'
}

function export.mapping(word)
    word = ugsub(word, 'ng', 'ŋ')
    local ipa_word = ugsub(word, '.', phoneme_map)
    return ipa_word
end

function export.to_broad_ipa(word)
    local syllables = {}
    local i = 1
    local len = ulen(word)

    while i <= len do
        local syllable = ''

        -- 'Consonant + Glide + Vowel'
        if i + 2 <= len and consonants:find(usub(word, i, i), 1, true) and
           glides:find(usub(word, i + 1, i + 1), 1, true) and
           vowels:find(usub(word, i + 2, i + 2), 1, true) then
            syllable = usub(word, i, i + 2)
            i = i + 3

        -- 'Consonant + Vowel + Glide'
        elseif i + 2 <= len and consonants:find(usub(word, i, i), 1, true) and
               vowels:find(usub(word, i + 1, i + 1), 1, true) and
               glides:find(usub(word, i + 2, i + 2), 1, true) then
            syllable = usub(word, i, i + 2)
            i = i + 3

        -- 'Consonant + Vowel + Consonant'
        elseif i + 2 <= len and consonants:find(usub(word, i, i), 1, true) and
               vowels:find(usub(word, i + 1, i + 1), 1, true) and
               consonants:find(usub(word, i + 2, i + 2), 1, true) then
            syllable = usub(word, i, i + 2)
            i = i + 3

        -- 'Glide + Vowel'
        elseif i + 1 <= len and glides:find(usub(word, i, i), 1, true) and
               vowels:find(usub(word, i + 1, i + 1), 1, true) then
            syllable = usub(word, i, i + 1)
            i = i + 2

        -- 'Consonant + Vowel'
        elseif i + 1 <= len and consonants:find(usub(word, i, i), 1, true) and
               vowels:find(usub(word, i + 1, i + 1), 1, true) then
            syllable = usub(word, i, i + 1)
            i = i + 2

        -- 'Vowel + Glide'
        elseif i + 1 <= len and vowels:find(usub(word, i, i), 1, true) and
               glides:find(usub(word, i + 1, i + 1), 1, true) then
            syllable = usub(word, i, i + 1)
            i = i + 2

        -- 'Vowel + Consonant'
        elseif i + 1 <= len and vowels:find(usub(word, i, i), 1, true) and
               (i == len or consonants:find(usub(word, i + 1, i + 1), 1, true)) then
            syllable = usub(word, i, i + 1)
            i = i + 2

        -- 'Vowel'
        elseif vowels:find(usub(word, i, i), 1, true) then
            syllable = usub(word, i, i)
            i = i + 1
        end

        if syllable ~= '' then
            table.insert(syllables, syllable)
        else
            -- If no rule applies, just move to the next character
            i = i + 1
            mw.log('Error: No matching rule for syllable at position ' .. i)
        end
    end

    -- Rearrange syllables to accommodate the position of consonants
    -- for i = 1, #syllables - 1 do
    --     if (consonants:find(usub(syllables[i], -1, -1), 1, true) and
    --        (vowels:find(usub(syllables[i + 1], 1, 1), 1, true)) or
    --         glides:find(usub(syllables[i + 1], 1, 1), 1, true)) then
    --         syllables[i + 1] = usub(syllables[i], -1, -1) .. syllables[i + 1]
    --         syllables[i] = usub(syllables[i], 1, -2)
    --     end
    -- end
    for i = 1, #syllables - 1 do
    local last_char_of_current_syllable = usub(syllables[i], -1, -1)
    local first_char_of_next_syllable = usub(syllables[i + 1], 1, 1)

    if consonants:find(last_char_of_current_syllable, 1, true) and
       (vowels:find(first_char_of_next_syllable, 1, true) or glides:find(first_char_of_next_syllable, 1, true)) or
       glides:find(last_char_of_current_syllable, 1, true) and vowels:find(first_char_of_next_syllable, 1, true) then
        syllables[i + 1] = last_char_of_current_syllable .. syllables[i + 1]
        syllables[i] = usub(syllables[i], 1, -2)
    end
end
    return syllables
end

function export.fix_ipa_symbol(ipa_str)
    ipa_str = ugsub(ipa_str, 'ʨ', 't͡ɕ')
    ipa_str = ugsub(ipa_str, 'ʦ', 't͡s')
    ipa_str = ugsub(ipa_str, 'g', 'ɡ')
    return ipa_str
end

function export.to_narrow_ipa(old_syllables, stress_idx)
    local syllables = {}
    
    for i, v in ipairs(old_syllables) do
        syllables[i] = v
    end

    local stress_idx = stress_idx or 1
    local alveolars = 'szʦ'
    local alveolo_palatal = 'ɕʑʨ'
    local dorsal_guttural = 'hkʡ'
    local fronts = 'ij'
    local glides = 'jw'
    local combs_al = {}
    local combs_ap = {}
    local combs_du = {}
    local combs_do = {}

    for a in ugmatch(alveolars, '.') do
	    for f in ugmatch(fronts, '.') do
	        table.insert(combs_al, a .. f)
	        if a == 's' then
	            table.insert(combs_ap, 'ɕ' .. f)
	        elseif a == 'z' then
	            table.insert(combs_ap, 'ʑ' .. f)
	        elseif a == 'ʦ' then
	            table.insert(combs_ap, 'ʨ' .. f)
	        end
	    end
	end
    for d in ugmatch(dorsal_guttural, ".") do
        table.insert(combs_du, 'u' .. d)
        table.insert(combs_do, 'o' .. d)
    end

    for i = 1, #syllables do
        -- palatalization
	    for j, c1 in ipairs(combs_al) do
	        if ufind(syllables[i], c1) then
	            syllables[i] = ugsub(syllables[i], c1, combs_ap[j])
	        end
	    end
        -- /u/ tongue position lowering
        for j, c1 in ipairs(combs_du) do
            if ufind(syllables[i], c1) then
                syllables[i] = ugsub(syllables[i], c1, combs_do[j])
            end
        end
        -- /e/ voicelessness, rules should be checked further
        if ulen(syllables[i]) == 2 and not ufind(glides, usub(syllables[i], 1, 1)) and usub(syllables[i], 2, 2) == 'ə' and i ~= ulen(syllables[i]) - stress_idx + 1 then
            syllables[i] = usub(syllables[i], 1, 1) .. '(ə)'
        end
        -- /i, u/ + V (not /a/) = /j, w/ + V, already shown in orthography
        -- /i, u/ + /a/ = /ija, uwa/, already shown in orthography
    end

    return syllables
end

function export.stress_and_format(broad, narrow, stress_idx)
    stress_idx = stress_idx or 1
    if #broad > 1 then
        broad[#broad - stress_idx + 1] = "ˈ" .. broad[#broad - stress_idx + 1]
    end
    if #narrow > 1 then
        narrow[#narrow - stress_idx + 1] = "ˈ" .. narrow[#narrow - stress_idx + 1]
    end
    local broad_str = export.fix_ipa_symbol(table.concat(broad, '.'))
    local narrow_str = export.fix_ipa_symbol(table.concat(narrow, '.'))
    return broad_str, narrow_str
end

function export.to_ipa(word, stress)
    -- 將所有字母轉成小寫,並移除標點符號
    local text = ulower(word)
    local puncs = '[%.%,%;“”\"%?!%(%)%[%]]'
    text = ugsub(text, puncs, ' ')

    -- 拆分為多個單詞
    local words = tsplit(text, ' ')
    local ipa_results, n_ipa_results = {}, {}

    for _, word in ipairs(words) do
        if word ~= "" then
            local ipa = export.mapping(word)
            local broad_ipa = export.to_broad_ipa(ipa)
            local narrow_ipa = export.to_narrow_ipa(broad_ipa, stress)
            local broad_str, narrow_str = export.stress_and_format(broad_ipa, narrow_ipa, stress)
            table.insert(ipa_results, broad_str)
            table.insert(n_ipa_results, narrow_str)
        end
    end

    local IPA_result = {
        ["broad"] = table.concat(ipa_results, ' '),
        ["narrow"] = table.concat(n_ipa_results, ' ')
    }
    return IPA_result
end

-- 更新後的 show 函數
function export.show(frame)
    local params = {
        [1] = {},
        ["stress"] = {type = "number", default = 1},
        ["pre"] = {},
        ["bullets"] = {type = "number", default = 1},
    }

    local parargs = frame:getParent().args
    local args = require("Module:parameters").process(parargs, params)
    local pre = args.pre and args.pre .. " " or ""
    local bullet = (args.bullets ~= 0) and "* " or ""
    local stress = args.stress

    local results = {}
    local text = args[1] or mw.title.getCurrentTitle().text

    local IPA_result = export.to_ipa(text, stress)

    table.insert(results, { pron = "/" .. IPA_result["broad"] .. "/" })
    table.insert(results, { pron = "[" .. IPA_result["narrow"] .. "]" })

    return bullet .. pre .. m_IPA.format_IPA_full { lang = lang, items = results }
end

return export