Editing Module:Th-rm
Appearance
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 = '[ก-๛]' | ||
local consonants = { | local consonants = { | ||
['ก'] = 'k', | ['ก'] = 'k', | ||
| Line 56: | Line 55: | ||
} | } | ||
local finals = { | local finals = { | ||
['ก'] = 'k', | ['ก'] = 'k', | ||
| Line 104: | Line 102: | ||
} | } | ||
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: | ||
} | } | ||
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 | ||
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 | |||
} | } | ||
local h_silent_clusters = { | local h_silent_clusters = { | ||
['หง'] = true, | ['หง'] = true, | ||
| Line 276: | Line 291: | ||
} | } | ||
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 | 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( | local function skipMarks(chars_, pos) | ||
while | while chars_[pos] and ( | ||
chars_[pos] == '็' or | |||
chars_[pos] == '่' or | |||
chars_[pos] == '้' or | |||
chars_[pos] == '๊' or | |||
chars_[pos] == '๋' or | |||
chars_[pos] == '์' or | |||
chars_[pos] == 'ํ' or | |||
chars_[pos] == '๎' | |||
) do | ) do | ||
pos = pos + 1 | pos = pos + 1 | ||
| Line 322: | Line 353: | ||
end | end | ||
local function | local function findNextVowel(chars_, pos) | ||
local i = pos | |||
local i = | |||
while chars_[i] do | while chars_[i] do | ||
if chars_[i] | if isFrontVowel(chars_[i]) then | ||
return i | |||
end | |||
for _, form in ipairs(vowel_order) do | |||
if mw.ustring.sub(table.concat(chars_, '', i), 1, mw.ustring.len(form)) == form then | |||
return i | |||
end | end | ||
end | |||
if chars_[i] == '์' then | |||
i = i + 2 | |||
else | else | ||
i = i + 1 | i = i + 1 | ||
end | end | ||
end | end | ||
return | return nil | ||
end | end | ||
local function | local function hasVowelAt(chars_, pos) | ||
local | local form = getVowel(chars_, pos) | ||
return | return form ~= nil | ||
end | end | ||
local function getInitial( | local function getInitial(chars_, pos) | ||
local c1 = | local c1 = chars_[pos] | ||
if not isConsonant(c1) then | if not isConsonant(c1) then | ||
| Line 371: | Line 390: | ||
end | end | ||
local c2 = | local c2 = chars_[pos + 1] | ||
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 | ||
if c2 and valid_clusters[c1 .. c2] then | |||
if c1 == 'ท' and c2 == 'ร' then | |||
return 's', pos + 2, false, c1 .. c2 | |||
end | |||
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( | local function getCoda(chars_, pos) | ||
local | local c = chars_[pos] | ||
if not | if not c or not isConsonant(c) then | ||
return '', pos | return '', pos | ||
end | end | ||
if c == 'อ' or c == 'ห' or c == 'ฮ' then | |||
if | |||
return '', pos | return '', pos | ||
end | end | ||
if c == 'ย' or c == 'ว' then | |||
if | |||
return '', pos | return '', pos | ||
end | end | ||
if finals[ | if finals[c] then | ||
return finals[ | return finals[c], pos + 1 | ||
end | end | ||
| Line 421: | Line 433: | ||
end | end | ||
local function | local function removeSilent(chars_) | ||
if | local result = {} | ||
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 | ||
return result | |||
end | |||
local function cleanWord(word) | |||
word = mw.ustring.gsub(word, '์', '') | |||
return | return word | ||
end | end | ||
local function parseSyllable( | local function parseSyllable(chars_, start) | ||
local n = #chars_ | |||
local pos = start | local pos = start | ||
if not | if not chars_[pos] then | ||
return nil, pos | return nil, pos | ||
end | end | ||
local front = '' | local front = '' | ||
if isFrontVowel( | if isFrontVowel(chars_[pos]) then | ||
front = | front = chars_[pos] | ||
pos = pos + 1 | pos = pos + 1 | ||
end | end | ||
local | local initial, afterInitial, silentH, cluster = | ||
getInitial(chars_, pos) | |||
getInitial( | |||
if | 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 | ||
if front ~= '' then | if front ~= '' then | ||
local rest = front | |||
for _, form in ipairs(vowel_order) do | |||
local candidate = front .. mw.ustring.sub( | |||
table.concat(chars_, '', pos), | |||
1, | |||
mw.ustring.len(form) | |||
) | |||
if vowels[candidate] then | |||
vowelForm = candidate | |||
vowel = vowels[candidate] | |||
pos = pos + mw.ustring.len(form) | |||
break | |||
end | |||
end | |||
if not vowel then | |||
if front == 'เ' then | |||
if chars_[pos] == 'า' then | |||
vowelForm = 'เา' | |||
vowel = 'ao' | |||
pos = pos + 1 | |||
else | |||
vowelForm = 'เ' | |||
vowel = 'e' | |||
end | |||
elseif front == 'แ' then | |||
vowelForm = 'แ' | |||
vowel = 'ae' | |||
elseif front == 'โ' then | |||
vowelForm = 'โ' | |||
vowel = 'o' | |||
elseif front == 'ใ' or front == 'ไ' then | |||
vowelForm = front | |||
vowel = 'ai' | |||
end | |||
end | end | ||
else | else | ||
vowelForm, vowel = getVowel( | vowelForm, vowel = getVowel(chars_, pos) | ||
if vowelForm then | if vowelForm then | ||
| Line 518: | Line 540: | ||
end | end | ||
-- | -- No written vowel: determine an implied vowel. | ||
if not vowel then | if not vowel then | ||
local nextC = chars_[pos] | |||
if | |||
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( | pos = skipMarks(chars_, pos) | ||
local coda = '' | local coda = '' | ||
local codaPos = pos | local codaPos = pos | ||
if | if chars_[pos] and isConsonant(chars_[pos]) then | ||
-- If | local nextC = chars_[pos] | ||
-- it | local nextNext = chars_[pos + 1] | ||
if | |||
-- 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( | coda, codaPos = getCoda(chars_, pos) | ||
end | end | ||
end | end | ||
pos = codaPos | pos = codaPos | ||
pos = skipMarks( | 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 | ||
-- | -- 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 | ||
-- | -- 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 | ||
-- | -- 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 | ||
pos = pos + 1 | pos = pos + 1 | ||
end | end | ||
| Line 610: | Line 673: | ||
local output = {} | local output = {} | ||
for i, | for i, s in ipairs(syllables) do | ||
if shouldHyphen(syllables[i - 1], | if s.raw == 'ๆ' then | ||
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 | ||
end | end | ||
| Line 626: | Line 694: | ||
end | end | ||
local function romanizeThaiText(text) | local function romanizeThaiText(text) | ||
local result = {} | local result = {} | ||
local parts = mw.text.split( | |||
text, | |||
'(\1MANUALHYPHEN\1|\1MANUALSPACE\1)', | |||
true | |||
) | |||
for _, part in ipairs(parts) do | |||
if part == '\1MANUALHYPHEN\1' then | |||
table.insert(result, '-') | table.insert(result, '-') | ||
elseif part == '\1MANUALSPACE\1' then | |||
elseif | |||
table.insert(result, ' ') | table.insert(result, ' ') | ||
else | |||
local last = 1 | |||
local | |||
while true do | |||
local startPos, endPos = | |||
mw.ustring.find(part, THAI .. '+', last) | |||
if not startPos then | |||
table.insert(result, mw.ustring.sub(part, last)) | |||
break | break | ||
end | end | ||
if mw.ustring.sub( | if startPos > last then | ||
table.insert( | |||
result, | |||
mw.ustring.sub(part, last, startPos - 1) | |||
) | |||
end | end | ||
local word = | |||
mw.ustring.sub(part, startPos, endPos) | |||
table.insert(result, romanizeWord(word)) | |||
last = endPos + 1 | |||
end | |||
end | end | ||
end | end | ||
| Line 690: | Line 740: | ||
end | end | ||
local function | local function hasThai(text) | ||
return mw.ustring.find(text, THAI) ~= nil | |||
end | |||
return text | local function containsThai(text) | ||
return mw.ustring.find(text, '[ก-๛]') ~= nil | |||
end | end | ||
local function | local function hasOnlyThaiOrFormatting(text) | ||
return mw.ustring.find(text, | return not mw.ustring.find(text, '[ก-๛]') | ||
end | end | ||
local function validateRomanization(text) | local function validateRomanization(text) | ||
-- | -- The automatic result must never leak Thai characters. | ||
if | 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 | 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]*) | '^([%s%p]*)' .. first, | ||
'%1' .. upper, | |||
1 | |||
) | ) | ||
end | |||
local function convertThaiDigits(text) | |||
local digits = { | |||
['๐'] = '0', | |||
['๑'] = '1', | |||
['๒'] = '2', | |||
['๓'] = '3', | |||
['๔'] = '4', | |||
['๕'] = '5', | |||
['๖'] = '6', | |||
['๗'] = '7', | |||
['๘'] = '8', | |||
['๙'] = '9' | |||
} | |||
for thai, latin in pairs(digits) do | |||
text = mw.ustring.gsub(text, thai, latin) | |||
end | end | ||
return | return text | ||
end | end | ||
| Line 754: | Line 819: | ||
end | end | ||
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 | -- Automatically generated romanizations are deliberately marked | ||
-- with the tooltip from Module:Tt. | |||
return tt._main({ | return tt._main({ | ||
[1] = result, | [1] = result, | ||