Module:vot-pronunciation

local export = {} local m_vot = require("Module:vot") local m_IPA = require("Module:IPA") local gsub_lookahead = require("Module:gsub lookahead")

local lang = m_vot.lang local U = mw.ustring.char

--- <<< DATA START >>> ---

local LONG = "ː" local STRESS_PRIMARY = "ˈ" local STRESS_SECONDARY = "ˌ" local NEVER_STRESSED = "#" local FRONTAL = U(0x0308) local NONSYLLABIC = U(0x032F) local TIE = U(0x0361) local VERYSHORT = U(0x0306) local SCHWA_BACK = U(0xEEE0) local SCHWA_FRONT = U(0xEEE1) local PALATAL = "ʲ" local IPA_VOWELS = "ɑeiouyæøɤɨ" .. SCHWA_BACK .. SCHWA_FRONT local AUTO_STRESS = U(0xEEEE)

local IPA_CONSONANTS = m_vot.consonants .. "ɫcčCɟɕʑɲʎïx" local IPA_CONSONANTS_GEMINATABLE = m_vot.consonants_geminatable .. "ɫcčCɕʑɲʎx"

local PALATALIZE = m_vot.palatalize local PALATALIZE_WEAK = '"' local UNGEMINATE = "/" local SHIFT_STRESS = "*" local ANY_DIACRITICS = "[" .. U(0x0300) .. "-" .. U(0x036F) .. "]*" local SOME_DIACRITICS = "[" .. U(0x0300) .. "-" .. U(0x036F) .. "]+"

local IPA_VOWEL = "[aõäöü" .. IPA_VOWELS .. "]"

local broken_vowel_sequences = { "i" .. UNGEMINATE .. "i" }

--- <<< DATA END >>> ---

--- <<< COMMON START >>> ---

local function split_syllables(word, keep_sep_symbols) local res = {} local syllable = "" local pos = 1 local found_vowel = false

-- the following consonants stick together while pos <= mw.ustring.len(word) do       if mw.ustring.find(mw.ustring.lower(word), "^[" .. IPA_CONSONANTS .. "][" .. PALATAL .. PALATALIZE .. PALATALIZE_WEAK .. "]?" .. IPA_VOWEL, pos) then -- CV: end current syllable if we have found a vowel if found_vowel then if #syllable > 0 then table.insert(res, syllable) end found_vowel = false syllable = "" end syllable = syllable .. mw.ustring.sub(word, pos, pos) pos = pos + 1 elseif mw.ustring.find(mw.ustring.lower(word), "^[" .. IPA_CONSONANTS .. "]", pos) then -- C: continue syllable = syllable .. mw.ustring.sub(word, pos, pos) pos = pos + 1 elseif mw.ustring.find(mw.ustring.lower(word), "^" .. IPA_VOWEL, pos) then if found_vowel then -- already found a vowel, end current syllable if #syllable > 0 then table.insert(res, syllable) end syllable = "" end found_vowel = true -- check for diphthongs or long vowels local seq_ok = false local seq_ok3 = false for k, v in pairs(broken_vowel_sequences) do if mw.ustring.find(mw.ustring.lower(word), "^" .. v, pos) then seq_ok3 = true break end end if not seq_ok3 then for k, v in pairs(m_vot.vowel_sequences) do if mw.ustring.find(mw.ustring.lower(word), "^" .. v, pos) then seq_ok = true break end end end

if seq_ok3 then syllable = syllable .. mw.ustring.sub(word, pos, pos + 2) pos = pos + 3 elseif seq_ok then syllable = syllable .. mw.ustring.sub(word, pos, pos + 1) pos = pos + 2 else syllable = syllable .. mw.ustring.sub(word, pos, pos) pos = pos + 1 end elseif mw.ustring.find(mw.ustring.lower(word), "^[" .. PALATALIZE .. PALATALIZE_WEAK .. PALATAL .. "]", pos) then syllable = syllable .. mw.ustring.sub(word, pos, pos) pos = pos + 1 elseif mw.ustring.find(mw.ustring.lower(word), "^[" .. UNGEMINATE .. "]", pos) then syllable = syllable .. UNGEMINATE pos = pos + 1 elseif mw.ustring.find(mw.ustring.lower(word), "^[" .. m_vot.sep_symbols .. AUTO_STRESS .. NEVER_STRESSED .. "%*]", pos) then -- separates syllables if #syllable > 0 then table.insert(res, syllable) end local sepchar = mw.ustring.sub(word, pos, pos) syllable = keep_sep_symbols and sepchar or "" pos = pos + 1 found_vowel = false else -- ?: continue syllable = syllable .. mw.ustring.sub(word, pos, pos) pos = pos + 1 end end if #syllable > 0 then table.insert(res, syllable) end return res end

local function zeroth_round_of_common_replacements(text, narrow) text = mw.ustring.gsub(text, "'", PALATALIZE) text = mw.ustring.gsub(text, PALATALIZE_WEAK, PALATAL) if narrow then text = mw.ustring.gsub(text, "l([aouõ])", "ɫ%1") text = mw.ustring.gsub(text, "lɫ", "ɫɫ") text = mw.ustring.gsub(text, "([^" .. STRESS_PRIMARY .. STRESS_SECONDARY .. "%*-]+)", function (word)			if mw.ustring.find(word, "[aouõ]") then				return mw.ustring.gsub(word, "l(.?)", function (v) return (mw.ustring.match(v, "[ieäöü" .. PALATALIZE .. "]") and "l" or "ɫ") .. v end)			else				return word			end		end) end text = mw.ustring.gsub(text, "tts", "cc") text = mw.ustring.gsub(text, "ts", "c") text = mw.ustring.gsub(text, "ttš", "čč") text = mw.ustring.gsub(text, "tš", "č") text = mw.ustring.gsub(text, PALATALIZE .. "(" .. m_vot.consonants .. PALATALIZE .. ")", "%1")	return text end

local function first_round_of_common_replacements(text) text = mw.ustring.gsub(text, "n([-" .. AUTO_STRESS .. "]?[kg])", "ŋ%1") text = mw.ustring.gsub(text, "n([-" .. AUTO_STRESS .. "]?[pb])", "m%1") text = mw.ustring.gsub(text, "[aäöõü’]", {		["a"] = "ɑ",		["ä"] = "æ",		["ö"] = "ø",		["õ"] = "ɤ",		["ü"] = "y", --		["-"] = STRESS_SECONDARY,	})

return text end

local function second_round_of_common_replacements(text, narrow, apical) text = mw.ustring.gsub(text, "%" .. SHIFT_STRESS, STRESS_PRIMARY) text = mw.ustring.gsub(text, LONG .. PALATAL, PALATAL .. LONG) text = mw.ustring.gsub(text, PALATAL .. "+", PALATAL) text = mw.ustring.gsub(text, "[cčCgïjšž" .. SCHWA_BACK .. SCHWA_FRONT .. "]", {		["c"] = "t͡s",		["č"] = "t͡ʃ",		["C"] = "c",		["g"] = "ɡ",		["ï"] = narrow and "ʝ" or "j",		["j"] = narrow and "ʝ" or "j",		["š"] = apical and "ʂ" or "ʃ",		["ž"] = apical and "ʐ" or "ʒ",		[SCHWA_BACK] = "ə̠",		[SCHWA_FRONT] = "ə̟",	}) text = mw.ustring.gsub(text, "[lɫ]([lɫ])", "%1" .. LONG) return text end

local function automatic_palatalization(text, filter) --, regressive_filter)	text = mw.ustring.gsub(text, "(" .. filter .. "+)([äöüi" .. SCHWA_FRONT .. "])", function (c1, v1) return c1 .. PALATAL .. v1	end)	-- text = mw.ustring.gsub(text, "(" .. regressive_filter .. "+)([" .. IPA_CONSONANTS .. "]" .. PALATAL .. ")", function (c1, c2) -- 	return c1 .. PALATAL .. c2	-- end)	return text end

local full_palatal = { ["d"] = "ɟ", ["t"] = "C", -- workaround ["z"] = "ʑ", ["n"] = "ɲ", ["l"] = "ʎ", ["s"] = "ɕ" }

local function manual_palatalization(text) if not mw.ustring.find(text, PALATALIZE) then return text end text = mw.ustring.gsub(text, "([" .. IPA_CONSONANTS .. "])" .. PALATALIZE, function(c) 		return full_palatal[c] or c .. PALATAL	end) text = mw.ustring.gsub(text, PALATALIZE, "") text = mw.ustring.gsub(text, PALATAL .. PALATAL, PALATAL) return text end

local IPA_diphthongs = { "[ɑeouɤæøy]i", "[iouɤ]ɑ", "[iøye]æ", "[ɑoɤei]u", "[ɑu]ɤ", "[æøie]y", "[ɑiæy]e", "[ɑi]o", }

local function long_vowels_and_diphthongs(text) text = mw.ustring.gsub(text, "([" .. IPA_VOWELS .. "])%1", "%1" .. LONG) for _, diphthong in ipairs(IPA_diphthongs) do		local mod_diphthong if mw.ustring.find(diphthong, "%]$") then mod_diphthong = mw.ustring.gsub(diphthong, "(.)(%^%-%])", "%1" .. VERYSHORT .. "?%2") mod_diphthong = mw.ustring.gsub(diphthong, "(%^%-%])(%^%-%])", "%1" .. VERYSHORT .. "?%2") else mod_diphthong = mw.ustring.sub(diphthong, 1, -2) .. VERYSHORT .. "?" .. mw.ustring.sub(diphthong, -1, -1) end text = mw.ustring.gsub(text, "(" .. mod_diphthong .. ")", "%1" .. NONSYLLABIC) end return text end

local function long_consonants(text) text = mw.ustring.gsub(text, "(%a)" .. PALATAL .. "%1" .. PALATAL, "%1" .. PALATAL .. LONG) text = mw.ustring.gsub(text, "(%a)%1", "%1" .. LONG) text = mw.ustring.gsub(text, LONG .. PALATAL, PALATAL .. LONG) return text end

local function add_primary_stress(text) text = mw.ustring.gsub(text, AUTO_STRESS, "-") text = mw.ustring.gsub(text, "-%.", "-") text = mw.ustring.gsub(text, "-", STRESS_SECONDARY) text = STRESS_PRIMARY .. mw.ustring.gsub(text, " ", " " .. STRESS_PRIMARY) text = mw.ustring.gsub(text, STRESS_PRIMARY .. "([^ ]+" .. STRESS_PRIMARY .. ")", "%1")	return mw.ustring.toNFC(text) end

local function is_stressed_syllable(syllable) return mw.ustring.find(syllable, "^[ " .. AUTO_STRESS .. "%*-]") end

local function add_secondary_stress(syllables, stress_last) local distance = 0 for index, syllable in ipairs(syllables) do		if not stress_last and index == #syllables then break end local stressed = index == 1 or is_stressed_syllable(syllable) if stressed then distance = 0 else distance = distance + 1 if distance == 2 then distance = 0 if (index == #syllables or not is_stressed_syllable(syllables[index + 1])) and not mw.ustring.find(syllable, NEVER_STRESSED) then syllables[index] = AUTO_STRESS .. syllable end end end end end

local function clean_ungeminate(text) return mw.ustring.gsub(text, UNGEMINATE, "") end

local function do_gemination(syllables, diacritic) local try_to_geminate = false for index, syllable in ipairs(syllables) do		local stressed = index == 1 or is_stressed_syllable(syllable) if try_to_geminate and not stressed then -- check if the initial consonant in this syllable is followed by two vowels local rest = syllable .. (syllables[index + 1] or "") if mw.ustring.find(rest, "^[" .. IPA_CONSONANTS_GEMINATABLE .. "]" .. PALATALIZE .. "?" .. m_vot.vowel .. m_vot.vowel) then -- CVCVV -> CVC:VV local cg = select(3, mw.ustring.find(syllable, "^([" .. IPA_CONSONANTS_GEMINATABLE .. "]" .. PALATALIZE .. "?)")) syllables[index - 1] = syllables[index - 1] .. cg syllables[index] = mw.ustring.gsub(syllable, "^" .. cg, diacritic) end end try_to_geminate = stressed and mw.ustring.find(syllable, "^[ " .. AUTO_STRESS .. "-]?[" .. IPA_CONSONANTS .. PALATALIZE .. TIE .. "]*" .. m_vot.vowel .. "$") end end

local function split_syllables_by_words(syllables) local i = 1 return function local r = {} local e = i		if e <= #syllables then table.insert(r, (mw.ustring.gsub(syllables[e], "^%s+", ""))) e = e + 1 while e <= #syllables and not mw.ustring.find(syllables[e], "^%s") do				table.insert(r, syllables[e]) e = e + 1 end i = e			return r		end end end

local function do_by_word_syllables(out_syllables, fn) local old_syllables = {} for k, v in pairs(out_syllables) do		old_syllables[k] = v		out_syllables[k] = nil end local next_word = false for syllables in split_syllables_by_words(old_syllables) do		fn(syllables) for i, syllable in ipairs(syllables) do			if next_word and i == 1 then table.insert(out_syllables, " " .. syllable) else table.insert(out_syllables, syllable) end end next_word = true end end

local function reduce_final_syllable(syl) local allowed_finals = { "(" .. m_vot.consonant .. ")%1", "g[lɫnr]", "mp", "šk", "lt" }

if not mw.ustring.find(syl, m_vot.consonant .. m_vot.consonant .. PALATALIZE .. "?$") then return syl end for _, allowed_final in ipairs(allowed_finals) do if not mw.ustring.find(syl, allowed_final .. PALATALIZE .. "?$") then return mw.ustring.gsub(syl, PALATALIZE .. "$", "") end end return mw.ustring.sub(mw.ustring.gsub(syl, PALATALIZE .. "$", ""), 1, -2) end

local function is_syllable_stressed_at(syllable, index) return index == 1 or is_stressed_syllable(syllable) end

local function do_reduction_word(syllables, narrow, reduce_completely) local prev_was_stressed = false local prev_was_long = false local syllables_since_last_stressed = 0 local final_vowel_dropped = false for index, syllable in ipairs(syllables) do		local stressed = is_syllable_stressed_at(syllable, index) local final = index == #syllables if stressed then syllables_since_last_stressed = 0 else syllables_since_last_stressed = syllables_since_last_stressed + 1 end prev_was_long = prev_was_long if not stressed and ((prev_was_stressed and prev_was_long) or (syllables_since_last_stressed > 1 or prev_was_long)) then syllables[index] = mw.ustring.gsub(syllable, "(" .. m_vot.vowel .. "+)(.*)", function (nucleus, coda)				if mw.ustring.find(nucleus, "(" .. m_vot.vowel .. ")%1") then					return mw.ustring.sub(nucleus, 1, 1) .. coda				end

if not narrow then local broad_reduce = { ["a"] = "õ", ["ä"] = "e" } return (broad_reduce[nucleus] or nucleus) .. coda end

--if mw.ustring.find(nucleus, "i[aä]") then --return (syllable.find(PALATALIZE) and "" or PALATALIZE) .. mw.ustring.sub(nucleus, 2) .. coda --end

if mw.ustring.find(nucleus, m_vot.vowel .. m_vot.vowel) then return nucleus .. coda end

local reduced = { ["a"] = SCHWA_BACK, ["ä"] = SCHWA_FRONT }				if not reduced[nucleus] then return nucleus .. coda end if final and reduce_completely and #coda < 1 and mw.ustring.match(nucleus, "[aä]") then if mw.ustring.find(syllable, "j[aä]") then return reduced[nucleus] .. VERYSHORT end

final_vowel_dropped = true return mw.ustring.find(nucleus, "ä") and PALATAL or "" end

return (reduced[nucleus] or nucleus) .. coda end)		end		-- reduce the next syllable only if the current syllable is stressed and heavy		prev_was_stressed = stressed		prev_was_long = mw.ustring.find(syllable, m_vot.vowel .. "[" .. IPA_CONSONANTS .. m_vot.vowels .. "]")	end

if final_vowel_dropped then syllables[#syllables - 1] = reduce_final_syllable(syllables[#syllables - 1] .. syllables[#syllables]) syllables[#syllables] = nil end end

local function do_reduction(syllables, narrow, reduce_completely) do_by_word_syllables(syllables, function(s) do_reduction_word(s, narrow, reduce_completely) end) end

local diphthongize_broad = { ["e"] = "ie", ["o"] = "uo", ["ø"] = "yø" } local diphthongize_narrow = { ["e"] = "ɪ̆e", ["o"] = "ʊ̆o", ["ø"] = "ʏ̆ø" } local function do_diphthongization_word(syllables, narrow, reduce_completely) for index, syllable in ipairs(syllables) do		local stressed = is_syllable_stressed_at(syllable, index) syllables[index] = mw.ustring.gsub(syllable, "([eoø])%1", function (v)			return ((narrow and not stressed) and diphthongize_narrow or diphthongize_broad)[v]		end) end end

local function do_diphthongization(syllables, narrow) do_by_word_syllables(syllables, function(s) do_diphthongization_word(s, narrow) end) end

local function pass_diacritics_through(map, consonant) local consonant, diacritics = mw.ustring.match(consonant, "([" .. IPA_CONSONANTS .. "])([" .. PALATAL .. "]?)")	return map[consonant] .. diacritics end

local voiceless_sounds = "kptcčfsšh" local function do_voicing(text, always_devoiced) local devoice = { ["g"] = "k", ["b"] = "p", ["d"] = "t", ["z"] = "s", ["ž"] = "š", ["ʑ"] = "ɕ" } local semivoice = { ["g"] = "g̊", ["b"] = "b̥", ["d"] = "d̥", ["z"] = "z̥", ["ž"] = "ž̥", ["ʑ"] = "ɕ̊" } if always_devoiced then semivoice = devoice end

local consonants_to_devoice = "[bdgzž][" .. PALATAL .. "]?"	local vowel = "[" .. IPA_VOWELS .. "]"

-- b/d/g/z/ž is semivoiced if it is not followed by anything text = mw.ustring.gsub(text, "(" .. consonants_to_devoice .. ")$",		function (consonant)			return pass_diacritics_through(semivoice, consonant)		end)

-- b/d/g/z/ž is devoiced if it is followed by a voiceless sound text = gsub_lookahead(text, "(" .. consonants_to_devoice .. ")([%s" .. AUTO_STRESS .. "-]+)([" .. voiceless_sounds .. "])",		function (consonant, space, after)			return pass_diacritics_through(devoice, consonant) .. space, after		end)

return text end

local palatalize_filter = "[dfghkmnprstvz]" local kattila_palatalize_filter = "[dlnrstz]" -- local regressive_palatalize_filter = "[dnrstz]"

--- <<< COMMON END >>> ---

--- <<< DIALECTS START >>> ---

-- narrow_level 0 = broad, 1 = rhyme, 2 = narrow

-- Luutsa, Liivtšülä local function IPA_luutsa_liivtsula(text, narrow_level) text = zeroth_round_of_common_replacements(text, narrow_level > 1)

if narrow_level > 0 then local syllables = split_syllables(text, true) add_secondary_stress(syllables) text = table.concat(syllables) end text = mw.ustring.gsub(text, NEVER_STRESSED, "")

local syllables = split_syllables(text, true) if narrow_level > 1 then do_gemination(syllables, LONG) do_reduction(syllables, true, false) end text = table.concat(syllables) if narrow_level > 0 then text = do_voicing(text) end

if narrow_level > 1 then text = automatic_palatalization(text, palatalize_filter) --, regressive_palatalize_filter) -- palatalization		text = mw.ustring.gsub(text, "h([kg])", "x%1")	end

text = clean_ungeminate(text) text = mw.ustring.gsub(text, "j" .. PALATALIZE, PALATALIZE) text = manual_palatalization(text) text = first_round_of_common_replacements(text) text = long_vowels_and_diphthongs(text) text = long_consonants(text) text = second_round_of_common_replacements(text, narrow_level > 1)

return add_primary_stress(text) end

-- Jõgõperä local function IPA_jogopera(text, narrow_level) text = zeroth_round_of_common_replacements(text, narrow_level > 1)

if narrow_level > 0 then local syllables = split_syllables(text, true) add_secondary_stress(syllables) text = table.concat(syllables) end text = mw.ustring.gsub(text, NEVER_STRESSED, "")

local syllables = split_syllables(text, true) if narrow_level > 1 then do_gemination(syllables, LONG) do_reduction(syllables, true, true) end text = table.concat(syllables) if narrow_level > 0 then text = do_voicing(text) end

if narrow_level > 1 then text = automatic_palatalization(text, palatalize_filter) --, regressive_palatalize_filter) -- palatalization		text = mw.ustring.gsub(text, "h([kg])", "x%1")	end

text = clean_ungeminate(text) text = mw.ustring.gsub(text, "j" .. PALATALIZE, PALATALIZE) text = manual_palatalization(text) text = first_round_of_common_replacements(text) text = long_vowels_and_diphthongs(text) text = long_consonants(text) text = second_round_of_common_replacements(text, narrow_level > 1)

return add_primary_stress(text) end

-- Kattila local function IPA_kattila(text, narrow_level) text = zeroth_round_of_common_replacements(text, narrow_level > 1)

if narrow_level > 0 then local syllables = split_syllables(text, true) add_secondary_stress(syllables, true) text = table.concat(syllables) end text = mw.ustring.gsub(text, NEVER_STRESSED, "")

local syllables = split_syllables(text, true) if narrow_level > 1 then do_gemination(syllables, LONG) end text = table.concat(syllables) if narrow_level > 0 then text = do_voicing(text, true) end

if narrow_level > 1 then text = mw.ustring.gsub(text, "h([kpt])", "H%1") text = mw.ustring.gsub(text, "[hH]", {["h"] = "ɦ", ["H"] = "h"}) end

text = clean_ungeminate(text) text = mw.ustring.gsub(text, "j" .. PALATALIZE, PALATALIZE) text = manual_palatalization(text) text = first_round_of_common_replacements(text) if narrow_level > 0 then local syllables = split_syllables(text, true) do_diphthongization(syllables, narrow_level > 1) text = table.concat(syllables) end text = long_vowels_and_diphthongs(text) text = long_consonants(text) text = second_round_of_common_replacements(text, narrow_level > 1, true)

return add_primary_stress(text) end

--- <<< DIALECTS END >>> ---

--- <<< INTERFACE START >>> ---

local function cleanup_for_hyphenate(text) local no_hyph_symbols = "[" .. UNGEMINATE .. "%-]"	return mw.ustring.gsub(mw.ustring.gsub(text, no_hyph_symbols, ""), "%*", ".") end

local function run_reductions(text) local syllables = split_syllables(text, true) add_secondary_stress(syllables) local prev_was_stressed = false local prev_was_long = false local syllables_since_last_stressed = 0 for index, syllable in ipairs(syllables) do		local stressed = is_syllable_stressed_at(syllable, index) local final = index == #syllables if stressed then syllables_since_last_stressed = 0 else syllables_since_last_stressed = syllables_since_last_stressed + 1 end prev_was_long = prev_was_long if not stressed and ((prev_was_stressed and prev_was_long) or (syllables_since_last_stressed > 1 or prev_was_long)) then syllables[index] = mw.ustring.gsub(syllable, "(" .. m_vot.vowel .. "+)(.*)", function (nucleus, coda)				if mw.ustring.find(nucleus, "(" .. m_vot.vowel .. ")%1") then					return mw.ustring.sub(nucleus, 1, 1) .. coda				end				local broad_reduce = { ["a"] = "õ", ["ä"] = "e" }				return (broad_reduce[nucleus] or nucleus) .. coda			end) end -- reduce the next syllable only if the current syllable is stressed and heavy prev_was_stressed = stressed prev_was_long = mw.ustring.find(syllable, m_vot.vowel .. "[" .. IPA_CONSONANTS .. m_vot.vowels .. "]") end

return mw.ustring.gsub(table.concat(syllables, ""), "[" .. NEVER_STRESSED .. AUTO_STRESS .. "]", "") end

local function match_spelling_with_title_for_hyphenation(sp, title) return title end

local function hyphenate_matches(sp, title) local resp = run_reductions(mw.ustring.lower(mw.ustring.gsub(sp, "%*", "."))) resp = mw.ustring.gsub(resp, "'", PALATALIZE) resp = mw.ustring.gsub(resp, "([bdgzž])([kpsšt])", function(c1, c2) return ({b = "p", d = "t", g = "k", z = "s", ["ž"] = "š"})[c1] .. c2 end) resp = mw.ustring.gsub(cleanup_for_hyphenate(resp), "%.", "") return resp == mw.ustring.lower(title) end

local function hyphenate(text) return m_vot.split_syllables(cleanup_for_hyphenate(text)) end

local function spell_long_consonants(text) text = mw.ustring.gsub(text, "(t[sš])" .. "(" .. PALATALIZE .. "?)" .. LONG,			function (c, p) return "t" .. c .. p end) text = mw.ustring.gsub(text, "([" .. m_vot.consonants .. "])" .. "(" .. PALATALIZE .. "?)" .. LONG,			function (c, p) return c .. c .. p end) text = mw.ustring.gsub(text, "iï", "i") return text end

local function generate_rhyme(tuple) local text = tuple.rhyme

local index = mw.ustring.find(text, "[" .. STRESS_PRIMARY .. STRESS_SECONDARY .. "][^" .. STRESS_PRIMARY .. STRESS_SECONDARY .. "]*$") if index ~= nil then text = mw.ustring.sub(text, index + 1) end

index = mw.ustring.find(text, "[" .. IPA_VOWELS .. "]") if index == nil then return nil end

return mw.ustring.sub(text, index) end

local function make_IPAs(fn, forms, variety) local p = {} for _, form in ipairs(forms) do		form = mw.ustring.lower(form) local suffix = mw.ustring.find(form, "^%-") local prefix = mw.ustring.find(form, "%-$") if suffix then form = mw.ustring.gsub(form, "^%-", "") end if prefix then form = mw.ustring.gsub(form, "%-$", "") end

local broad = fn(form, 0) local rhyme = fn(form, 1) local narrow = fn(form, 2) if prefix then broad = broad .. "-"			rhyme = nil narrow = narrow .. "-"		end if suffix then broad = "-" .. mw.ustring.gsub(broad, "^" .. STRESS_PRIMARY, "") rhyme = nil narrow = "-" .. mw.ustring.gsub(narrow, "^" .. STRESS_PRIMARY, "") end

table.insert(p, { broad = broad, rhyme = rhyme, narrow = narrow }) end local result = { forms = p,		varieties = { variety } }	return result end

local function format_IPAs(tuple, title, has_spaces) local dialects = require("Module:accent qualifier").format_qualifiers(lang, tuple.varieties) local p = {} for _, form in ipairs(tuple.forms) do table.insert(p, {pron = "/" .. form.broad .. "/"}) table.insert(p,	{pron = "[" .. form.narrow .. "]"}) end return "* " .. dialects .. " " .. m_IPA.format_IPA_full { lang = lang, items = p, no_count = has_spaces } end

local function get_arg_list(param, fallback, allow_dash, required) if not param or #param == 0 then return required and fallback or nil end if not allow_dash and #param == 1 and param[1] == "-" then return nil end if #param == 1 and param[1] == "+" then return fallback end return param end

local varieties = { ["Lu"] = { "Luutsa", IPA_luutsa_liivtsula }, ["Li"] = { "Liivtšülä", IPA_luutsa_liivtsula }, ["J"] = { "Jõgõperä", IPA_jogopera }, ["K"] = { "Kattila", IPA_kattila }, }

local variety_groups = { { "LL", {"Lu", "Li"}, true }, { nil, "J", false }, { nil, "K", false }, }

local varieties_merged = {} for _, group in ipairs(variety_groups) do	if group[1] then varieties_merged[group[1]] = group[2] end end

local function get_variety(variety_code) if varieties[variety_code] then return varieties[variety_code] end if varieties_merged[variety_code] then local subvarieties = varieties_merged[variety_code] local names = {} local fn = nil for _, subvariety_code in ipairs(subvarieties) do			local subvariety = varieties[subvariety_code] fn = subvariety[2] table.insert(names, subvariety[1]) end return {table.concat(names, ", "), fn} end error("Unrecognized variety code: " .. variety_code) end

function export.get_variety(variety_code) return get_variety(variety_code)[1] end

function export.generate_one(form, variety_code, transcription) local name, fn = unpack(get_variety(variety_code)) local result = make_IPAs(fn, {form}, name).forms[1] if transcription then result = result[transcription] end return result end

function export.generate_multiple(forms, variety_code, transcription) local param, name, fn = unpack(get_variety(variety_code)) local result = make_IPAs(fn, forms, name).forms if transcription then for i, form in ipairs(result) do			result[i] = form[transcription] end end return result end

local function add_IPAs(IPAs, spellings, main_code, args, required) local name, fn = unpack(get_variety(main_code)) local forms = get_arg_list(args, spellings, false, required) if forms then table.insert(IPAs, make_IPAs(fn, forms, name)) end end

function export.show(frame) local title = mw.title.getCurrentTitle.text local hyphenation = nil local rhymes = nil local categories = {}

local params = { [1] = { list = true },

["LL"] = { list = true }, -- Luutsa-Liivtšülä ["Lu"] = { list = true }, -- Luutsa ["Li"] = { list = true }, -- Liivtšülä ["J"] = { list = true }, -- Jõgõperä ["K"] = { list = true }, -- Kattila, ["dial"] = { type = "boolean" }, ["title"] = {}, -- for debugging or demonstration only }	local args = require("Module:parameters").process(frame:getParent.args, params) title = args["title"] or title local dialectal = args.dial

local spellings = get_arg_list(args[1], { mw.ustring.lower(title) }, true, true) local IPAs = {} for _, variety_group in ipairs(variety_groups) do		local param, codes, always = unpack(variety_group) if param then local split = false for _, code in ipairs(codes) do				if #args[code] > 0 then split = true break end end if split then for _, code in ipairs(codes) do					add_IPAs(IPAs, spellings, code, args[code] or (param and args[param] or nil), always and not dialectal) end else add_IPAs(IPAs, spellings, param, args[param], always and not dialectal) end else add_IPAs(IPAs, spellings, codes, args[codes], always and not dialectal) end end if #IPAs < 1 then error("No dialects to display IPA for") end

local results = {} local has_spaces = mw.ustring.find(title, " ") for _, tuple in ipairs(IPAs) do		table.insert(results, format_IPAs(tuple, title, has_spaces)) end

if not hyphenation then hyphenation = {} if not has_spaces then local sp = spellings[1] if not hyphenate_matches(sp, title) then -- try to geminate local syllables = m_vot.split_syllables(sp, true) do_gemination(syllables, LONG) sp = spell_long_consonants(clean_ungeminate(table.concat(syllables))) end if hyphenate_matches(sp, title) then table.insert(hyphenation, hyphenate(match_spelling_with_title_for_hyphenation(sp, title))) end end end

if not rhymes then rhymes = {} if not has_spaces then local found_rhymes = {} for _, tuple in ipairs(IPAs) do				for _, form in ipairs(tuple.forms) do					if form.rhyme then local rhyme = generate_rhyme(form) if not found_rhymes[rhyme] then found_rhymes[rhyme] = true table.insert(rhymes, rhyme) end end end end end end

if #rhymes > 0 then local sylkeys = {} local sylcounts = {} -- get all possible syllable counts from syllabifications for i, h in ipairs(hyphenation) do			local hl = #h if hl > 0 and not sylkeys[hl] then table.insert(sylcounts, hl) sylkeys[hl] = true end end local rhymeobjs = {} for _, rhyme in ipairs(rhymes) do			table.insert(rhymeobjs, {rhyme = rhyme}) end table.insert(results, "* " .. require("Module:rhymes").format_rhymes( { lang = lang, rhymes = rhymeobjs, num_syl = sylcounts })) end

if #hyphenation > 0 then local hyphs = {} for i, h in ipairs(hyphenation) do			table.insert(hyphs, { ["hyph"] = h }) end table.insert(results, "* " .. require("Module:hyphenation").format_hyphenations( { lang = lang, hyphs = hyphs, caption = "Hyphenation" })) end

return table.concat(results, "\n") .. require("Module:utilities").format_categories(categories, lang) end

--- <<< INTERFACE END >>> ---

return export