This module implements {{se-IPA}}
.
local export = {}
local lang = require("Module:languages").getByCode("se")
local m_str_utils = require("Module:string utilities")
local find = m_str_utils.find
local gmatch = m_str_utils.gmatch
local gsub = m_str_utils.gsub
local len = m_str_utils.len
local lower = m_str_utils.lower
local sub = m_str_utils.sub
local u = require("Module:string/char")
local BREVE = u(0x0306)
local letters_phonemes = {
= "ː",
= "a", = "a",
= "aː", = "aˑ", = "a",
= "b",
= "t͡s",
= "t͡ʃ",
= "d",
= "ð",
= "e", = "eː",
= "ɡ",
= "iː",
= "kʰ",
= "o", = "oː",
= "pʰ",
= "ʃ",
= "tʰ",
= "θ",
= "uː",
= "d͡z",
= "d͡ʒ",
= "ea̯", = "e̯a", = "ĕă̯",
= "ie̯", = "i̯e", = "ĭĕ̯",
= "oɑ̯", = "o̯ɑ", = "ŏɑ̯̆",
= "uo̯", = "u̯o", = "ŭŏ̯",
= "ɟ",
= "ʎ",
= "ɲ",
= "j̥", = "j̥.j̥", = "j̥ː.j̥",
= "l̥", = "l̥.l̥", = "l̥ː.l̥",
= "m̥", = "m̥.m̥", = "m̥ː.m̥",
= "n̥", = "n̥.n̥", = "n̥ː.n̥",
= "r̥", = "r̥.r̥", = "r̥ː.r̥",
}
-- This adds letters_phonemes = "e", letters_phonemes = "i", etc.
for letter in gmatch("efhijklmnŋoprstuv", ".") do
letters_phonemes = letter
end
-- Preaspirated
for letter in gmatch("ptcčk", ".") do
letters_phonemes = "h" .. letters_phonemes
letters_phonemes = "hː" .. letters_phonemes
end
local function get_phoneme(remainder)
-- Find the longest string of letters that matches a recognised sequence in the list
local longestmatch = ""
for letter, _ in pairs(letters_phonemes) do
if sub(remainder, 1, len(letter)) == letter and len(letter) > len(longestmatch) then
longestmatch = letter
end
end
if len(longestmatch) > 0 then
return longestmatch, sub(remainder, len(longestmatch) + 1)
else
return sub(remainder, 1, 1), sub(remainder, 2)
end
end
local function get_syllable(remainder)
local syll = {cons = {}, vowel = ""}
local cons
while find(remainder, "^(+)") do
cons, remainder = get_phoneme(remainder)
if cons == "nˈnj" then
require("Module:debug").track("se-IPA/nnj")
end
if cons == "ˈ" then
syll.cons.quantity = 3
else
if cons == "dj" or cons == "lj" then
if syll.cons == string.sub(cons, 1, 1) then
syll.cons = cons
syll.cons.quantity = 3
else
table.insert(syll.cons, cons)
end
elseif cons == "nj" and syll.cons == "n" then
syll.cons = "nj"
end
table.insert(syll.cons, cons)
end
end
if find(remainder, "^(+)") then
syll.vowel, remainder = get_phoneme(remainder)
end
if remainder == "" then
remainder = nil
end
-- Determine consonant quantity
if not syll.cons.quantity then
if not syll.cons then
syll.cons.quantity = 1
else
if find(syll.cons, "(.)%1$") or (syll.cons == syll.cons and not find(syll.cons, "^$")) or (syll.cons == "p" and syll.cons == "m") or (syll.cons == "t" and syll.cons == "n") or (syll.cons == "t" and syll.cons == "nj") or (syll.cons == "k" and syll.cons == "ŋ") then
syll.cons.quantity = 2
else
syll.cons.quantity = 3
end
end
end
return syll, remainder
end
-- Split the word into syllables of C(C)V shape
local function split_syllables(remainder)
remainder = lower(remainder)
remainder = gsub(remainder, "()i", "%1j")
local syllables = {}
local syll
while remainder do
syll, remainder = get_syllable(remainder)
table.insert(syllables, syll)
end
syllables.count = #syllables
if syllables.vowel == "" then
syllables.count = syllables.count - 1
end
return syllables
end
local function shorten(vowel)
vowel = gsub(vowel, "^$", { = "e", = "i", = "o", = "u"})
for _, v in ipairs({"á", "ea", "ie", "oa", "uo"}) do
vowel = gsub(vowel, v, v .. BREVE)
end
return vowel
end
local function shift(vowel)
for _, v in ipairs({"á", "ea", "ie", "oa", "uo"}) do
vowel = gsub(vowel, v, v .. "ˈ")
end
return vowel
end
local function lengthen(vowel)
vowel = gsub(vowel, "^$", { = "ē", = "ī", = "ō", = "ū"})
vowel = gsub(vowel, BREVE, "")
return vowel
end
-- Determine whether long vowels should be shortened before certain consonants
local function should_shorten(syll, nextsyll)
if nextsyll.cons then
if find(nextsyll.cons, "^h()%1$") then
-- Long preaspirate
return true
elseif find(nextsyll.cons, "^()ˈ%1$") then
-- Overlong vowel
return true
elseif (syll.vowel == "ie" or syll.vowel == "uo") and find(nextsyll.vowel, "^$") then
if find(nextsyll.cons, "^()%1$") then
-- Geminate stop
return true
elseif nextsyll.cons == "pm" or nextsyll.cons == "tn" or nextsyll.cons == "tnj" or nextsyll.cons == "kŋ" then
-- Glottalised nasal
return true
elseif nextsyll.cons and not find(nextsyll.cons, "^h$") then
-- Clusters, except when the second element is a strong-grade preaspirate
return true
end
elseif (syll.vowel == "ie" or syll.vowel == "uo") and nextsyll.vowel == "a" then
if find(nextsyll.cons, "^()%1$") then
-- Geminate voiced stop
return true
elseif nextsyll.cons and not find(nextsyll.cons, "(.)%1$") and not find(nextsyll.cons, "^h$") and not (nextsyll.cons == "pm" or nextsyll.cons == "tn" or nextsyll.cons == "tnj" or nextsyll.cons == "kŋ") then
-- Clusters, except when the second element is long, or a preaspirate, or a preglottalised nasal
return true
end
end
end
return false
end
local function convert_spelling(syllables)
local foot = 0
for i, syll in ipairs(syllables) do
if syll.vowel == "" then
if syll.cons == "t" then
syll.cons = "ht"
elseif syll.cons == "d" then
syll.cons = "t"
end
break
end
local nextsyll = syllables or {cons = {}, vowel = ""}
foot = foot + 1
if foot == 3 and i ~= syllables.count then
foot = 1
end
-- Make i and u long in even syllables
if foot == 2 and (syll.vowel == "i" or syll.vowel == "u") and nextsyll.cons ~= "j" then
syll.vowel = lengthen(syll.vowel)
end
if #syll.cons == 1 then
if foot == 1 then
-- Postaspiration
syll.cons = gsub(syll.cons, "^()$", "%1h")
elseif foot == 3 then
-- d is đ between two unstressed vowels
syll.cons = gsub(syll.cons, "d", "đ")
end
elseif #syll.cons > 1 then
if syll.cons == syll.cons and syll.cons and find(syll.cons, "$") then
-- Ungeminate last consonant after voiceless
syll.cons = nil
elseif find(syll.cons, "$") then
-- Ungeminate last consonant after voiceless
syll.cons = gsub(syll.cons, "(.)%1$", "%1")
else
-- Preaspirate final voiceless consonant after voiced
syll.cons = gsub(syll.cons, "^()$", "h%1")
syll.cons = gsub(syll.cons, "^()%1$", "h%1%1")
end
-- Devoice final geminates
if syll.cons == "bb" then
syll.cons = "pp"
elseif syll.cons == "dd" then
syll.cons = "tt"
elseif syll.cons == "gg" then
syll.cons = "kk"
elseif syll.cons == "zz" then
syll.cons = "cc"
elseif syll.cons == "žž" then
syll.cons = "čč"
end
end
-- Devoice remaining single voiced consonants
for j, cons in ipairs(syll.cons) do
if cons == "b" and syll.cons ~= "b" and (j ~= 1 or syll.cons ~= "b" and syll.cons ~= "m") then
syll.cons = "p"
elseif cons == "d" and syll.cons ~= "d" and (j ~= 1 or syll.cons ~= "d" and syll.cons ~= "n" and syll.cons ~= "nj") then
syll.cons = "t"
elseif cons == "g" and syll.cons ~= "g" and (j ~= 1 or syll.cons ~= "g" and syll.cons ~= "ŋ") then
syll.cons = "k"
elseif cons == "z" and syll.cons ~= "z" and (j ~= 1 or syll.cons ~= "z") then
syll.cons = "c"
elseif cons == "ž" and syll.cons ~= "ž" and (j ~= 1 or syll.cons ~= "ž") then
syll.cons = "č"
end
end
-- Regularise divergent spellings in clusters
--if #syll.cons > 2 then
-- error("Clusters with more than 2 consonants are not yet supported.")
--end
if foot == 2 and syll.cons.quantity == 3 then
-- Lengthen initial sonorant in quantity 3
table.insert(syll.cons, 2, "ˈ")
end
-- Secondary stress
if foot == 1 and i > 1 then
if #syll.cons == 1 then
table.insert(syll.cons, 1, "ˌ")
elseif #syll.cons == 2 then
table.insert(syll.cons, 2, "ˌ")
end
end
end
-- This needs to be a separate pass because otherwise unstressed ī and ū won't have been lengthened yet
for i, syll in ipairs(syllables) do
local nextsyll = syllables or {cons = {}, vowel = ""}
-- if should_shorten(syll, nextsyll) then
-- syll.vowel = shorten(syll.vowel)
if find(nextsyll.vowel, "^$") then
syll.vowel = shift(syll.vowel)
end
end
end
-- Dialect-specific conversions
local function dialect(syllables)
for i, syll in ipairs(syllables) do
-- Western Finnmark dialect
if syll.cons then
if syll.cons == "ŋ" then
syll.cons = "nj"
if syll.cons == "ˈ" then
if syll.cons then
syll.cons = gsub(syll.cons, "^$", { = "d", = "t", = "nj"})
end
else
if syll.cons then
syll.cons = gsub(syll.cons, "^$", { = "d", = "t", = "nj"})
end
end
end
end
end
end
-- Convert word to IPA
local function to_IPA(syllables)
for i, syll in ipairs(syllables) do
for j, cons in ipairs(syll.cons) do
if syll.vowel == "" and cons == "ht" then
syll.cons = "h(t)"
elseif letters_phonemes then
-- Drop the final part after the tie bar
if string.find(letters_phonemes, "͡", nil, true) and syll.cons == syll.cons == "ˈ" and 2 or 1)] then
syll.cons = gsub(letters_phonemes, "͡.*$", "")
else
syll.cons = letters_phonemes
end
end
end
syll.vowel = letters_phonemes or syll.vowel
syllables = table.concat(syll.cons) .. syll.vowel
end
return "ˈ" .. table.concat(syllables)
end
function export.IPA(frame)
local params = {
= {default = mw.title.getCurrentTitle().text},
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local syllables = split_syllables(args)
convert_spelling(syllables)
dialect(syllables)
return
require("Module:accent qualifier").format_qualifiers(lang, {"Kautokeino"}) .. " " ..
require("Module:IPA").format_IPA_full { lang = lang, items = {{pron = "/" .. to_IPA(syllables) .. "/"}} } ..
require("Module:utilities").format_categories(lang:getCanonicalName() .. " " .. tostring(syllables.count) .. "-syllable words", lang)
end
return export