Jump to content

Editing Module:Th-rm

Warning: You are not logged in. Your IP address will be publicly visible if you make any edits. If you log in or create an account, your edits will be attributed to your username, along with other benefits.
The edit can be undone. Please check the comparison below to verify that this is what you want to do, and then publish the changes below to finish undoing the edit.
Latest revision Your text
Line 8: Line 8:
local THAI = '[ก-๛]'
local THAI = '[ก-๛]'


-- Initial consonants.
local consonants = {
local consonants = {
['ก'] = 'k',
['ก'] = 'k',
Line 56: Line 55:
}
}


-- Final consonants.
local finals = {
local finals = {
['ก'] = 'k',
['ก'] = 'k',
Line 104: Line 102:
}
}


-- Vowels.
local vowels = {
local vowels = {
-- Longest forms first.
['เียะ'] = 'ia',
['เียะ'] = 'ia',
['เีย'] = 'ia',
['เีย'] = 'ia',
Line 118: Line 116:
['เิ็ย'] = 'oei',
['เิ็ย'] = 'oei',
['เิย'] = 'oei',
['เิย'] = 'oei',
['เย'] = 'oei',
['เอย'] = 'oei',
['เอย'] = 'oei',


['็อย'] = 'oi',
['็อย'] = 'oi',
['อย'] = 'oi',
['อย'] = 'oi',
['โย'] = 'oi',


['ุย'] = 'ui',
['ุย'] = 'ui',
Line 136: Line 136:
['เา'] = 'ao',
['เา'] = 'ao',
['าว'] = 'ao',
['าว'] = 'ao',
['เอว'] = 'oeu',
['แว'] = 'aeo',
['แว'] = 'aeo',
['เว'] = 'eo',
['เว'] = 'eo',
['โว'] = 'ou',


['ิว'] = 'io',
['ิว'] = 'io',
Line 146: Line 148:
['ือ'] = 'ue',
['ือ'] = 'ue',
['ื'] = 'ue',
['ื'] = 'ue',
['ึ'] = 'ue',
['ึ'] = 'ue',


Line 175: Line 178:
}
}


-- Longest vowel forms must be checked first.
local vowel_order = {
local vowel_order = {
'เือย',
'เือย',
Line 188: Line 190:
'เิย',
'เิย',
'็อย',
'็อย',
'เอว',
'แ็ว',
'เ็ว',
'เ็ว',
'แ็ว',
'เอย',
'เอย',
'เา',
'เา',
Line 195: Line 198:
'เว',
'เว',
'แว',
'แว',
'โว',
'าย',
'าย',
'ไย',
'ไย',
Line 201: Line 205:
'วย',
'วย',
'อย',
'อย',
'โย',
'เย',
'ำ',
'ำ',
'ิว',
'ีว',
'ิ',
'ิ',
'ี',
'ี',
Line 214: Line 218:
'เาะ',
'เาะ',
'เอะ',
'เอะ',
'เอ',
'เะ',
'เะ',
'ะ',
'ะ',
Line 225: Line 228:
'ใ',
'ใ',
'ไ'
'ไ'
}
local vowels_with_front = {
['เ'] = true,
['แ'] = true,
['โ'] = true,
['ใ'] = true,
['ไ'] = true
}
}


local consonant_set = {}
local consonant_set = {}
for c in pairs(consonants) do
for c in pairs(consonants) do
consonant_set[c] = true
consonant_set[c] = true
end
end


-- Thai consonant clusters.
local valid_clusters = {
local valid_clusters = {
['กร'] = true,
['กร'] = true,
Line 261: Line 270:
['ซร'] = true,
['ซร'] = true,
['ฝร'] = true,
['ฝร'] = true,
['ฟร'] = true
['ฟร'] = true,
['หน'] = true,
['หม'] = true,
['หย'] = true,
['หร'] = true,
['หล'] = true,
['หว'] = true,
['หญ'] = true
}
}


-- ห is silent in these clusters when followed by a vowel.
local h_silent_clusters = {
local h_silent_clusters = {
['หง'] = true,
['หง'] = true,
Line 276: Line 291:
}
}


-- ร is silent in these clusters when followed by a vowel.
local r_silent_clusters = {
local r_silent_clusters = {
['จร'] = true,
['จร'] = true,
Line 300: Line 314:
local function isThai(c)
local function isThai(c)
return c and mw.ustring.find(c, THAI) ~= nil
return c and mw.ustring.find(c, THAI) ~= nil
end
local function isTone(c)
return c == '่' or c == '้' or c == '๊' or c == '๋' or c == '์'
end
end


local function isFrontVowel(c)
local function isFrontVowel(c)
return c == 'เ' or c == 'แ' or c == 'โ' or c == 'ใ' or c == 'ไ'
return c and vowels_with_front[c] == true
end
 
local function getVowel(chars_, pos)
local remaining = table.concat(chars_, '', pos)
 
for _, form in ipairs(vowel_order) do
if mw.ustring.sub(remaining, 1, mw.ustring.len(form)) == form then
return form, vowels[form]
end
end
 
return nil, nil
end
end


local function skipMarks(c, pos)
local function skipMarks(chars_, pos)
while c[pos] and (
while chars_[pos] and (
c[pos] == '็' or
chars_[pos] == '็' or
c[pos] == '่' or
chars_[pos] == '่' or
c[pos] == '้' or
chars_[pos] == '้' or
c[pos] == '๊' or
chars_[pos] == '๊' or
c[pos] == '๋' or
chars_[pos] == '๋' or
c[pos] == 'ํ' or
chars_[pos] == '์' or
c[pos] == '๎'
chars_[pos] == 'ํ' or
chars_[pos] == '๎'
) do
) do
pos = pos + 1
pos = pos + 1
Line 322: Line 353:
end
end


local function removeSilent(chars_)
local function findNextVowel(chars_, pos)
local result = {}
local i = pos
local i = 1


while chars_[i] do
while chars_[i] do
if chars_[i] == '์' then
if isFrontVowel(chars_[i]) then
-- ์ makes the following consonant(s) silent.
return i
i = i + 1
end


while chars_[i] and (
for _, form in ipairs(vowel_order) do
isConsonant(chars_[i]) or
if mw.ustring.sub(table.concat(chars_, '', i), 1, mw.ustring.len(form)) == form then
chars_[i] == '็' or
return i
chars_[i] == '่' or
chars_[i] == '้' or
chars_[i] == '๊' or
chars_[i] == '๋'
) do
i = i + 1
end
end
end
if chars_[i] == '์' then
i = i + 2
else
else
table.insert(result, chars_[i])
i = i + 1
i = i + 1
end
end
end
end


return result
return nil
end
end


local function getVowel(c, pos)
local function hasVowelAt(chars_, pos)
local remaining = table.concat(c, '', pos)
local form = getVowel(chars_, pos)
 
for _, form in ipairs(vowel_order) do
local length = mw.ustring.len(form)
 
if mw.ustring.sub(remaining, 1, length) == form then
return form, vowels[form]
end
end


return nil, nil
return form ~= nil
end
end


local function getInitial(c, pos)
local function getInitial(chars_, pos)
local c1 = c[pos]
local c1 = chars_[pos]


if not isConsonant(c1) then
if not isConsonant(c1) then
Line 371: Line 390:
end
end


local c2 = c[pos + 1]
local c2 = chars_[pos + 1]


-- ห is not pronounced in หง หน หม หย หร หล หว หญ.
if c1 == 'ห' and c2 and h_silent_clusters[c1 .. c2] then
if c1 == 'ห' and c2 and h_silent_clusters[c1 .. c2] then
return consonants[c2], pos + 2, true
return consonants[c2], pos + 2, true, c1 .. c2
end
end


-- ทร is pronounced s in the relevant Sanskrit-derived words.
if c2 and valid_clusters[c1 .. c2] then
-- This is the broad automatic fallback; exceptions can be manually
if c1 == 'ท' and c2 == 'ร' then
-- supplied with th_rm.
return 's', pos + 2, false, c1 .. c2
if c1 == 'ท' and c2 == 'ร' then
end
return 's', pos + 2, false
end


if c2 and valid_clusters[c1 .. c2] then
-- ร is silent in these clusters.
if r_silent_clusters[c1 .. c2] then
if r_silent_clusters[c1 .. c2] then
return consonants[c1], pos + 2, false
return consonants[c1], pos + 2, false, c1 .. c2
end
end


return consonants[c1] .. consonants[c2], pos + 2, false
return consonants[c1] .. consonants[c2], pos + 2, false, c1 .. c2
end
end


return consonants[c1], pos + 1, false
return consonants[c1], pos + 1, false, c1
end
end


local function getCoda(c, pos)
local function getCoda(chars_, pos)
local current = c[pos]
local c = chars_[pos]


if not current or not isConsonant(current) then
if not c or not isConsonant(c) then
return '', pos
return '', pos
end
end


-- These do not act as ordinary codas here.
if c == 'อ' or c == 'ห' or c == 'ฮ' then
if current == 'อ' or current == 'ห' or current == 'ฮ' then
return '', pos
return '', pos
end
end


-- ย and ว are generally part of the vowel/diphthong.
if c == 'ย' or c == 'ว' then
if current == 'ย' or current == 'ว' then
return '', pos
return '', pos
end
end


if finals[current] then
if finals[c] then
return finals[current], pos + 1
return finals[c], pos + 1
end
end


Line 421: Line 433:
end
end


local function startsNextSyllable(c, pos)
local function removeSilent(chars_)
if not c[pos] or not isConsonant(c[pos]) then
local result = {}
return false
local i = 1
 
while chars_[i] do
if chars_[i] == '์' then
i = i + 1
 
while chars_[i] and (
isConsonant(chars_[i]) or
chars_[i] == '็' or
chars_[i] == '่' or
chars_[i] == '้' or
chars_[i] == '๊' or
chars_[i] == '๋'
) do
i = i + 1
end
else
table.insert(result, chars_[i])
i = i + 1
end
end
end


if isFrontVowel(c[pos + 1]) then
return result
return true
end
end


local _, vowel = getVowel(c, pos + 1)
local function cleanWord(word)
word = mw.ustring.gsub(word, '์', '')


return vowel ~= nil
return word
end
end


local function parseSyllable(c, start)
local function parseSyllable(chars_, start)
local n = #chars_
local pos = start
local pos = start


if not c[pos] then
if not chars_[pos] then
return nil, pos
return nil, pos
end
end


local initial = ''
local cluster = false
-- Preposed vowels.
local front = ''
local front = ''


if isFrontVowel(c[pos]) then
if isFrontVowel(chars_[pos]) then
front = c[pos]
front = chars_[pos]
pos = pos + 1
pos = pos + 1
end
end


local initialPos = pos
local initial, afterInitial, silentH, cluster =
 
getInitial(chars_, pos)
initial, pos, _, cluster =
getInitial(c, pos)


if initial == '' and not isConsonant(c[initialPos]) then
if not chars_[pos] or not isConsonant(chars_[pos]) then
return nil, start + 1
return nil, start + 1
end
end


pos = afterInitial
local vowelForm
local vowel
local vowel
local vowelForm


if front ~= '' then
if front ~= '' then
-- Handle the vowel forms which contain a preposed vowel.
local rest = front
if front == 'เ' and c[pos] == 'ี' and c[pos + 1] == 'ย' then
vowel = 'ia'
vowelForm = 'เีย'
pos = pos + 2


elseif front == 'เ' and c[pos] == 'ื' and c[pos + 1] == 'อ' then
for _, form in ipairs(vowel_order) do
vowel = 'uea'
local candidate = front .. mw.ustring.sub(
vowelForm = 'เือ'
table.concat(chars_, '', pos),
pos = pos + 2
1,
mw.ustring.len(form)
)


elseif front == 'เ' and c[pos] == 'อ' then
if vowels[candidate] then
vowel = 'oe'
vowelForm = candidate
vowelForm = 'เอ'
vowel = vowels[candidate]
pos = pos + 1
pos = pos + mw.ustring.len(form)
break
end
end


elseif front == 'เ' and c[pos] == 'า' then
if not vowel then
vowel = 'ao'
if front == 'เ' then
vowelForm = 'เา'
if chars_[pos] == 'า' then
pos = pos + 1
vowelForm = 'เา'
 
vowel = 'ao'
elseif front == 'เ' and c[pos] == '็' then
pos = pos + 1
vowel = 'e'
else
vowelForm = 'เ็'
vowelForm = 'เ'
pos = pos + 1
vowel = 'e'
 
end
elseif front == 'แ' and c[pos] == '็' then
elseif front == 'แ' then
vowel = 'ae'
vowelForm = 'แ'
vowelForm = 'แ็'
vowel = 'ae'
pos = pos + 1
elseif front == 'โ' then
 
vowelForm = 'โ'
elseif front == 'โ' and c[pos] == 'ะ' then
vowel = 'o'
vowel = 'o'
elseif front == 'ใ' or front == 'ไ' then
vowelForm = 'โะ'
vowelForm = front
pos = pos + 1
vowel = 'ai'
 
end
elseif front == 'ใ' or front == 'ไ' then
vowel = 'ai'
vowelForm = front
 
else
vowel = vowels[front]
vowelForm = front
end
end
else
else
vowelForm, vowel = getVowel(c, pos)
vowelForm, vowel = getVowel(chars_, pos)


if vowelForm then
if vowelForm then
Line 518: Line 540:
end
end


-- Implied vowels.
-- No written vowel: determine an implied vowel.
if not vowel then
if not vowel then
if c[pos] and isConsonant(c[pos]) then
local nextC = chars_[pos]
if startsNextSyllable(c, pos) then
 
if nextC and isConsonant(nextC) then
local nextNext = chars_[pos + 1]
 
if nextNext and (
isFrontVowel(nextNext) or
hasVowelAt(chars_, pos + 1)
) then
-- Open syllable with an implied a.
vowelForm = ''
vowel = 'a'
vowel = 'a'
else
else
-- Closed syllable with an implied o.
vowelForm = ''
vowel = 'o'
vowel = 'o'
end
end
else
else
vowelForm = ''
vowel = 'a'
vowel = 'a'
end
end
end
end


pos = skipMarks(c, pos)
pos = skipMarks(chars_, pos)


-- Final consonant.
local coda = ''
local coda = ''
local codaPos = pos
local codaPos = pos


if c[pos] and isConsonant(c[pos]) then
if chars_[pos] and isConsonant(chars_[pos]) then
-- If another consonant begins the next syllable, don't consume
local nextC = chars_[pos]
-- it as a coda.
local nextNext = chars_[pos + 1]
if startsNextSyllable(c, pos) then
 
-- If the following consonant is followed by a vowel,
-- it normally starts the next syllable rather than being a coda.
if nextNext and (
isFrontVowel(nextNext) or
hasVowelAt(chars_, pos + 1)
) then
coda = ''
coda = ''
else
else
coda, codaPos = getCoda(c, pos)
coda, codaPos = getCoda(chars_, pos)
end
end
end
end


pos = codaPos
pos = codaPos
pos = skipMarks(c, pos)
pos = skipMarks(chars_, pos)


return {
return {
initial = initial,
initial = initial,
vowel = vowel,
vowel = vowel,
vowelForm = vowelForm or '',
coda = coda,
coda = coda,
next = pos
next = pos,
cluster = cluster,
silentH = silentH
}, pos
}, pos
end
end
Line 563: Line 605:
end
end


-- Before a succeeding syllable beginning with a vowel.
-- A hyphen is used before a syllable beginning with a vowel.
if current.initial == '' and current.vowel ~= '' then
if current.initial == '' and current.vowel ~= '' then
return true
return true
end
end


-- Before ng when the preceding syllable ends in a vowel.
-- A syllable ending in a vowel followed by initial ng.
if previous.coda == '' and current.initial == 'ng' then
if previous.coda == '' and current.initial == 'ng' then
return true
return true
Line 577: Line 619:


local function romanizeWord(word)
local function romanizeWord(word)
word = cleanWord(word)
local c = chars(word)
local c = chars(word)
c = removeSilent(c)
c = removeSilent(c)
Line 587: Line 631:
safety = safety + 1
safety = safety + 1


if isConsonant(c[pos]) or isFrontVowel(c[pos]) then
if not c[pos] then
break
end
 
if c[pos] == 'ๆ' then
table.insert(syllables, {
raw = 'ๆ',
initial = '',
vowel = '',
coda = ''
})
pos = pos + 1
elseif isConsonant(c[pos]) or isFrontVowel(c[pos]) then
local syllable, nextPos = parseSyllable(c, pos)
local syllable, nextPos = parseSyllable(c, pos)


Line 599: Line 655:
end
end
else
else
-- Fail safely rather than looping forever.
-- Unknown Thai structure: consume one character so that
-- the parser can never become stuck.
table.insert(syllables, {
raw = c[pos],
initial = consonants[c[pos]] or '',
vowel = 'a',
coda = ''
})
 
pos = pos + 1
pos = pos + 1
end
end
else
else
-- Thai punctuation/marks that aren't part of a syllable.
pos = pos + 1
pos = pos + 1
end
end
Line 610: Line 673:
local output = {}
local output = {}


for i, syllable in ipairs(syllables) do
for i, s in ipairs(syllables) do
if shouldHyphen(syllables[i - 1], syllable) then
if s.raw == 'ๆ' then
table.insert(output, '-')
table.insert(output, 'ๆ')
else
local result = (s.initial or '') .. (s.vowel or '') .. (s.coda or '')
 
if result == '' then
result = 'a'
end
 
if shouldHyphen(syllables[i - 1], s) then
table.insert(output, '-')
end
 
table.insert(output, result)
end
end
table.insert(
output,
(syllable.initial or '') ..
(syllable.vowel or '') ..
(syllable.coda or '')
)
end
end


Line 626: Line 694:
end
end


-- Parse the text while treating <> and <-> as hard boundaries.
--
-- <>  = literal space
-- <-> = literal hyphen
--
-- Because each Thai section is romanized independently, automatic
-- hyphenation can never cross one of these markers.
local function romanizeThaiText(text)
local function romanizeThaiText(text)
local result = {}
local result = {}
local pos = 1
local length = mw.ustring.len(text)


while pos <= length do
local parts = mw.text.split(
-- Manual hyphen.
text,
if mw.ustring.sub(text, pos, pos + 2) == '<->' then
'(\1MANUALHYPHEN\1|\1MANUALSPACE\1)',
true
)
 
for _, part in ipairs(parts) do
if part == '\1MANUALHYPHEN\1' then
table.insert(result, '-')
table.insert(result, '-')
pos = pos + 3
elseif part == '\1MANUALSPACE\1' then
 
-- Manual space.
elseif mw.ustring.sub(text, pos, pos + 1) == '<>' then
table.insert(result, ' ')
table.insert(result, ' ')
pos = pos + 2
else
 
local last = 1
-- Thai run.
elseif isThai(mw.ustring.sub(text, pos, pos)) then
local start = pos
 
while pos <= length do
local character = mw.ustring.sub(text, pos, pos)


if not isThai(character) then
while true do
break
local startPos, endPos =
end
mw.ustring.find(part, THAI .. '+', last)


-- Stop before a manual control marker.
if not startPos then
if mw.ustring.sub(text, pos, pos + 2) == '<->' then
table.insert(result, mw.ustring.sub(part, last))
break
break
end
end


if mw.ustring.sub(text, pos, pos + 1) == '<>' then
if startPos > last then
break
table.insert(
result,
mw.ustring.sub(part, last, startPos - 1)
)
end
end


pos = pos + 1
local word =
end
mw.ustring.sub(part, startPos, endPos)


local word = mw.ustring.sub(text, start, pos - 1)
table.insert(result, romanizeWord(word))


table.insert(result, romanizeWord(word))
last = endPos + 1
 
end
-- Everything else is preserved.
else
table.insert(
result,
mw.ustring.sub(text, pos, pos)
)
 
pos = pos + 1
end
end
end
end
Line 690: Line 740:
end
end


local function convertThaiDigits(text)
local function hasThai(text)
local digits = {
return mw.ustring.find(text, THAI) ~= nil
['๐'] = '0',
end
['๑'] = '1',
['๒'] = '2',
['๓'] = '3',
['๔'] = '4',
['๕'] = '5',
['๖'] = '6',
['๗'] = '7',
['๘'] = '8',
['๙'] = '9'
}
 
for thai, latin in pairs(digits) do
text = mw.ustring.gsub(text, thai, latin)
end


return text
local function containsThai(text)
return mw.ustring.find(text, '[ก-๛]') ~= nil
end
end


local function hasThai(text)
local function hasOnlyThaiOrFormatting(text)
return mw.ustring.find(text, THAI) ~= nil
return not mw.ustring.find(text, '[ก-๛]')
end
end


local function validateRomanization(text)
local function validateRomanization(text)
-- Never allow Thai characters to leak into the final result.
-- The automatic result must never leak Thai characters.
if hasThai(text) then
if containsThai(text) then
return false
end
 
-- Thai marks should never survive either.
if mw.ustring.find(text, '[ะ-๛]') then
return false
return false
end
end
Line 735: Line 777:


local function capitalizeSentence(text)
local function capitalizeSentence(text)
local prefix, first = mw.ustring.match(
local first = mw.ustring.match(text, '^[%s%p]*([a-z])')
 
if not first then
return text
end
 
local upper = mw.ustring.upper(first)
 
return mw.ustring.gsub(
text,
text,
'^([%s%p]*)(%a)'
'^([%s%p]*)' .. first,
'%1' .. upper,
1
)
)
end
local function convertThaiDigits(text)
local digits = {
['๐'] = '0',
['๑'] = '1',
['๒'] = '2',
['๓'] = '3',
['๔'] = '4',
['๕'] = '5',
['๖'] = '6',
['๗'] = '7',
['๘'] = '8',
['๙'] = '9'
}


if not first then
for thai, latin in pairs(digits) do
return text
text = mw.ustring.gsub(text, thai, latin)
end
end


return prefix ..
return text
mw.ustring.upper(first) ..
mw.ustring.sub(text, mw.ustring.len(prefix) + 2)
end
end


Line 754: Line 819:
end
end


-- Do not automatically romanize Latin-only text.
if not hasThai(text) then
if not hasThai(text) then
return ''
return ''
end
end
-- Manual spacing/hyphen controls.
text = text
:gsub('<->', '\1')
:gsub('<>', '\2')


text = convertThaiDigits(text)
text = convertThaiDigits(text)


local result = romanizeThaiText(text)
local result = romanizeThaiText(text)
-- Restore manual separators.
result = result
:gsub('\1', '-')
:gsub('\2', ' ')


result = mw.ustring.gsub(result, '%s+', ' ')
result = mw.ustring.gsub(result, '%s+', ' ')
Line 785: Line 859:
end
end


-- If there is no Thai at all, this module should not create
-- an automatic romanization.
if not hasThai(text) then
if not hasThai(text) then
return ''
return ''
Line 803: Line 879:
end
end


-- Automatically generated romanizations get a Tt warning.
-- Automatically generated romanizations are deliberately marked
-- with the tooltip from Module:Tt.
return tt._main({
return tt._main({
[1] = result,
[1] = result,
Please note that all contributions to The Petit Planet Wiki are considered to be released under the Creative Commons Attribution-ShareAlike (see Petit Planet Wiki:Copyrights for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource. Do not submit copyrighted work without permission!
Cancel Editing help (opens in new window)
Preview page with this template
Below are commonly used wikitext from MediaWiki:Edittools. Click on any to insert them in the edit box.
File Pages==Summary====Licensing=={{Fairuse}}{{File|}}
Dynamic Page List¦²«»²{¦}²«»«/»«!----»
Array in DPL¹^2{}^2^2{¹}^2
Parser Functions{{#if:||}}
{{#ifeq:|||}}
{{#expr:}}
{{#switch:||#default=}}
{{#replace:||}}
{{#titleparts:}}
{{#tag:|}}
Markup{{}}|[][[]][[Category:]]#REDIRECT [[]] [[Category:Redirect Pages]]<code></code><includeonly></includeonly><noinclude></noinclude><nowiki></nowiki><!----><br>§
Notices{{Stub|}}{{Stub Dialogue}}{{Upcoming}}{{Under Construction}}{{Placeholder|}}
Magic Words{{!}}{{formatnum:}}{{lc:}}{{uc:}}{{PAGENAME}}{{FULLPAGENAME}}{{ROOTPAGENAME}}{{BASEPAGENAME}}{{SUBPAGENAME}}{{DISPLAYTITLE:|noreplace}}__TOC____NOTOC____EXPECTUNUSEDCATEGORY____HIDDENCAT__
HTML Entities&mdash;&nbsp;&comma;&shy;