Module:Th-rm: Difference between revisions

ReisuDesign (talk | contribs)
Created page with "local p = {} local U = mw.ustring local gsub = U.gsub local find = U.find local sub = U.sub local match = U.match local len = U.len local INITIAL = { ['ก'] = 'k', ['ข'] = 'kh', ['ฃ'] = 'kh', ['ค'] = 'kh', ['ฅ'] = 'kh', ['ฆ'] = 'kh', ['ง'] = 'ng', ['จ'] = 'ch', ['ฉ'] = 'ch', ['ช'] = 'ch', ['ซ'] = 's', ['ฌ'] = 'ch', ['ญ'] = 'y', ['ฎ'] = 'd', ['ฏ'] = 't', ['ฐ'] = 'th', ['ฑ'] = 'th', ['ฒ'] = 'th', ['ณ'] = 'n', ['ด'] = 'd'..."
 
ReisuDesign (talk | contribs)
No edit summary
Line 3: Line 3:
local U = mw.ustring
local U = mw.ustring
local gsub = U.gsub
local gsub = U.gsub
local find = U.find
local match = U.match
local sub = U.sub
local sub = U.sub
local match = U.match
local len = U.len
local len = U.len


Line 12: Line 11:
['ข'] = 'kh', ['ฃ'] = 'kh', ['ค'] = 'kh', ['ฅ'] = 'kh', ['ฆ'] = 'kh',
['ข'] = 'kh', ['ฃ'] = 'kh', ['ค'] = 'kh', ['ฅ'] = 'kh', ['ฆ'] = 'kh',
['ง'] = 'ng',
['ง'] = 'ng',
['จ'] = 'ch', ['ฉ'] = 'ch', ['ช'] = 'ch',
['จ'] = 'ch',
['ฉ'] = 'ch', ['ช'] = 'ch',
['ซ'] = 's',
['ซ'] = 's',
['ฌ'] = 'ch',
['ฌ'] = 'ch',
Line 46: Line 46:
['ข'] = 'k', ['ฃ'] = 'k', ['ค'] = 'k', ['ฅ'] = 'k', ['ฆ'] = 'k',
['ข'] = 'k', ['ฃ'] = 'k', ['ค'] = 'k', ['ฅ'] = 'k', ['ฆ'] = 'k',
['ง'] = 'ng',
['ง'] = 'ng',
['จ'] = 't', ['ฉ'] = 't', ['ช'] = 't', ['ฌ'] = '',
['จ'] = 't',
['ซ'] = 't',
['ฉ'] = 't', ['ช'] = 't', ['ซ'] = 't',
['ฌ'] = '',
['ญ'] = 'n',
['ญ'] = 'n',
['ฎ'] = 't', ['ฏ'] = 't',
['ฎ'] = 't', ['ฏ'] = 't',
Line 71: Line 72:
}
}


-- Vowel patterns are tested longest-first.
local CONSONANTS = 'กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรลวศษสหฬฮ'
local VOWELS = {
{ 'เ' , 'ะ', 'e' },
{ 'เ' , '็', 'e' },
{ 'เ' , '',  'e' },
 
{ 'แ' , 'ะ', 'ae' },
{ 'แ' , '',  'ae' },
 
{ 'โ' , 'ะ', 'o' },
{ 'โ' , '',  'o' },
{ 'เ' , 'าะ', 'o' },
{ ''  , 'อ', 'o' },
 
{ 'เ' , 'อะ', 'oe' },
{ 'เ' , 'ิ', 'oe' },
{ 'เ' , 'อ', 'oe' },
 
{ 'เ' , 'ียะ', 'ia' },
{ 'เ' , 'ีย', 'ia' },
 
{ 'เ' , 'ือะ', 'uea' },
{ 'เ' , 'ือ', 'uea' },
 
{ ''  , 'ัวะ', 'ua' },
{ ''  , 'ัว', 'ua' },
{ ''  , 'ว', 'ua' },
 
{ 'ใ', '', 'ai' },
{ 'ไ', '', 'ai' },
{ '' , 'ัย', 'ai' },
{ 'ไ', 'ย', 'ai' },
{ '' , 'าย', 'ai' },
 
{ 'เ', 'า', 'ao' },
{ '' , 'าว', 'ao' },
 
{ '' , 'ุย', 'ui' },
 
{ 'โ', 'ย', 'oi' },
{ '' , 'อย', 'oi' },
 
{ 'เ', 'ย', 'oei' },
{ 'เ', 'ือย', 'ueai' },
{ '' , 'วย', 'uai' },
 
{ '' , 'ิว', 'io' },
 
{ 'เ', '็ว', 'eo' },
{ 'เ', 'ว', 'eo' },
 
{ 'แ', '็ว', 'aeo' },
{ 'แ', 'ว', 'aeo' },
 
{ 'เ', 'ียว', 'iao' },
 
{ '' , 'ะ', 'a' },
{ '' , 'ั', 'a' },
{ '' , 'า', 'a' },
{ '' , 'ำ', 'am' },
 
{ '' , 'ิ', 'i' },
{ '' , 'ี', 'i' },
 
{ '' , 'ึ', 'ue' },
{ '' , 'ื', 'ue' },
{ '' , 'ือ', 'ue' },
 
{ '' , 'ุ', 'u' },
{ '' , 'ู', 'u' },
 
{ '' , 'รร', 'a' }
}


local function isConsonant(c)
local function isConsonant(c)
return c and INITIAL[c] ~= nil
return c and match(c, '[' .. CONSONANTS .. ']') ~= nil
end
end


Line 153: Line 82:
end
end


local function isToneMark(c)
local function isVowelMark(c)
return c and match(c, '[่้๊๋]') ~= nil
return c and match(c, '[ะาิีึืุูัำเแโใไอ]') ~= nil
end
 
local function isSilentMark(c)
return c == '์'
end
end


Line 168: Line 93:
end
end


local function applyOrthographicExceptions(str)
local function removeMarks(str)
-- Silent letters marked with ์.
-- Tone marks are not represented in RTGS.
-- Remove the marked letter and its ์ marker.
str = gsub(str, '([ก-ฮ])์', '')
 
-- Tone marks have no RTGS representation.
str = gsub(str, '[่้๊๋]', '')
str = gsub(str, '[่้๊๋]', '')


-- ฤ
-- Mai thanthakhat makes the marked consonant silent.
str = gsub(str, 'ฤกษ์', 'roe')
str = gsub(str, '([ก-ฮ])์', '')
str = gsub(str, 'ฤิ', 'ri')
str = gsub(str, 'ฤ', 'rue')
str = gsub(str, 'ฤๅ', 'rue')
 
-- ฦ / ฦๅ
str = gsub(str, 'ฦๅ', 'lue')
str = gsub(str, 'ฦ', 'lue')


return str
return str
Line 190: Line 104:


local function getVowel(str, pos)
local function getVowel(str, pos)
local remaining = sub(str, pos, -1)
local s = sub(str, pos, -1)


-- Explicit special vowel patterns.
-- Longest / most specific patterns first.
local patterns = {
local patterns = {
{ 'เ', 'ื', 'อ', 'ย', 'ueai' },
{ 'เ', 'ื', 'อ', 'ย', 'ueai' },
{ 'เ', 'ี', 'ย', 'ว', 'iao' },
{ 'เ', 'ี', 'ย', 'ว', 'iao' },
{ 'เ', 'ี', 'ย', 'ะ', 'ia' },
{ 'เ', 'ี', 'ย', 'ะ', 'ia' },
{ 'เ', 'ี', 'ย', '', 'ia' },
{ 'เ', 'ี', 'ย', '', 'ia' },
{ 'เ', 'ื', 'อ', 'ะ', 'uea' },
{ 'เ', 'ื', 'อ', 'ะ', 'uea' },
{ 'เ', 'ื', 'อ', '', 'uea' },
{ 'เ', 'ื', 'อ', '', 'uea' },


{ '', 'ั', 'ว', 'ะ', 'ua' },
{ '', 'ั', 'ว', '', 'ua' },
{ 'แ', '็', 'ว', '', 'aeo' },
{ 'แ', 'ว', '', '', 'aeo' },
{ 'เ', '็', 'ว', '', 'eo' },
{ 'เ', '็', 'ว', '', 'eo' },
{ 'เ', 'ว', '', '', 'eo' },
{ 'เ', 'ว', '', '', 'eo' },
{ 'แ', '็', 'ว', '', 'aeo' },
{ 'แ', 'ว', '', '', 'aeo' },


{ 'โ', 'ย', '', '', 'oi' },
{ '', 'ิ', 'ว', '', 'io' },
{ '', 'อ', 'ย', '', 'oi' },
{ '', 'ุ', 'ย', '', 'ui' },
{ 'เ', 'ย', '', '', 'oei' },
{ '', 'ว', 'ย', '', 'uai' },


{ '', 'ว', 'ย', '', 'uai' },
{ 'เ', 'ื', 'อ', '', 'ue' },
{ '', 'ุ', 'ย', '', 'ui' },
{ 'เ', 'ื', '', '', 'ue' },
{ '', 'ิ', 'ว', '', 'io' },


{ 'เ', 'า', '', '', 'ao' },
{ 'เ', 'า', '', '', 'ao' },
{ '', 'า', 'ว', '', 'ao' },
{ '', 'า', 'ว', '', 'ao' },


{ '', 'ั', 'ว', 'ะ', 'ua' },
{ 'โ', 'ย', '', '', 'oi' },
{ '', 'ั', 'ว', '', 'ua' },
{ '', 'อ', 'ย', '', 'oi' },
{ '', 'ว', '', '', 'ua' },
 
{ 'เ', 'ย', '', '', 'oei' },


{ 'ใ', '', '', '', 'ai' },
{ 'ใ', '', '', '', 'ai' },
{ 'ไ', '', '', '', 'ai' },
{ 'ไ', '', '', '', 'ai' },
{ '', 'ั', 'ย', '', 'ai' },
{ '', 'ั', 'ย', '', 'ai' },
{ 'ไ', 'ย', '', '', 'ai' },
{ '', 'า', 'ย', '', 'ai' },
{ '', 'า', 'ย', '', 'ai' },


{ 'เ', 'า', 'ะ', '', 'ao' },
{ 'เ', 'าะ', '', '', 'o' },
{ 'โ', 'ะ', '', '', 'o' },
{ 'โ', 'ะ', '', '', 'o' },
{ 'เ', 'า', '', '', 'ao' },
{ 'โ', '', '', '', 'o' },
{ 'เ', 'าะ', '', '', 'o' },
{ '', 'อ', '', '', 'o' },


{ 'เ', 'อะ', '', '', 'oe' },
{ 'เ', 'ิ', '', '', 'oe' },
{ 'เ', 'อ', '', '', 'oe' },
{ 'เ', 'อ', '', '', 'oe' },
{ 'เ', 'ิ', '', '', 'oe' },
{ 'เ', 'อะ', '', '', 'oe' },


{ 'แ', 'ะ', '', '', 'ae' },
{ 'แ', 'ะ', '', '', 'ae' },
{ 'แ', '', '', '', 'ae' },
{ 'แ', '', '', '', 'ae' },
{ 'เ', 'ะ', '', '', 'e' },
{ 'เ', 'ะ', '', '', 'e' },
{ 'เ', '็', '', '', 'e' },
{ 'เ', '็', '', '', 'e' },
{ 'เ', '', '', '', 'e' },
{ 'เ', '', '', '', 'e' },
{ 'โ', '', '', '', 'o' },


{ '', 'ำ', '', '', 'am' },
{ '', 'ำ', '', '', 'am' },
{ '', 'ะ', '', '', 'a' },
{ '', 'ะ', '', '', 'a' },
{ '', 'ั', '', '', 'a' },
{ '', 'ั', '', '', 'a' },
{ '', 'า', '', '', 'a' },
{ '', 'า', '', '', 'a' },
{ '', 'ิ', '', '', 'i' },
{ '', 'ิ', '', '', 'i' },
{ '', 'ี', '', '', 'i' },
{ '', 'ี', '', '', 'i' },
{ '', 'ึ', '', '', 'ue' },
{ '', 'ึ', '', '', 'ue' },
{ '', 'ือ', '', '', 'ue' },
{ '', 'ื', '', '', 'ue' },
{ '', 'ื', '', '', 'ue' },
{ '', 'ุ', '', '', 'u' },
{ '', 'ุ', '', '', 'u' },
{ '', 'ู', '', '', 'u' },
{ '', 'ู', '', '', 'u' }
{ '', 'อ', '', '', 'o' }
}
}


for _, v in ipairs(patterns) do
for _, v in ipairs(patterns) do
local a, b, c, d, roman = v
local a = v[1] or ''
local b = v[2] or ''
local c = v[3] or ''
local d = v[4] or ''
local roman = v[5] or ''
 
local pattern = a .. b .. c .. d
local pattern = a .. b .. c .. d
if pattern ~= '' and sub(remaining, 1, len(pattern)) == pattern then
 
if pattern ~= '' and sub(s, 1, len(pattern)) == pattern then
return roman, len(pattern)
return roman, len(pattern)
end
end
Line 269: Line 193:
end
end


local function nextVowelPosition(str, pos)
local function romanizeSyllable(str, start)
local i = pos
local pos = start
while i <= len(str) do
local n = len(str)
local c = sub(str, i, i)


if c == 'เ' or c == 'แ' or c == 'โ' or c == 'ใ' or c == 'ไ' then
local initial = ''
return i
local initialRom = ''
end


if match(c, '[ะาิีึืุูัำอ]') then
-- Preposed vowels are written before the initial consonant.
return i
local pre = sub(str, pos, pos)
end


i = i + 1
if pre == 'เ' or pre == 'แ' or pre == 'โ' or pre == 'ใ' or pre == 'ไ' then
end
 
return nil
end
 
local function romanizeSyllable(str, startPos)
local pos = startPos
local n = len(str)
 
-- Preposed vowel.
local preVowel = ''
local preLen = 0
local c = sub(str, pos, pos)
 
if c == 'เ' or c == 'แ' or c == 'โ' or c == 'ใ' or c == 'ไ' then
preVowel = c
preLen = 1
pos = pos + 1
pos = pos + 1
end
end


-- Initial consonant.
local first = sub(str, pos, pos)
local initial = sub(str, pos, pos)


if not isConsonant(initial) then
if not isConsonant(first) then
-- Non-Thai character.
return first, 1
return initial, 1
end
end


initial = first
initialRom = INITIAL[first] or ''
pos = pos + 1
pos = pos + 1
local initialRom = INITIAL[initial] or ''


-- ห- exception.
-- ห + sonorant consonant: ห is not romanized.
if initial == 'ห' then
local second = sub(str, pos, pos)
local second = sub(str, pos, pos)
 
if second and match(second, '[งนมยรลวญณฬ]') then
if initial == 'ห' and second and match(second, '[งนมยรลวญณฬ]') then
initialRom = INITIAL[second]
initial = second
pos = pos + 1
initialRom = INITIAL[second]
initial = second
pos = pos + 1
end
end
end


-- Consonant clusters where ร is not represented.
-- จร-, ซร-, ศร-, สร-: ร is not romanized.
if (initial == 'จ' or initial == 'ซ' or initial == 'ศ' or initial == 'ส')
second = sub(str, pos, pos)
and sub(str, pos, pos) == 'ร' then
 
initialRom = INITIAL[initial]
if second == 'ร'
and (initial == 'จ' or initial == 'ซ' or initial == 'ศ' or initial == 'ส') then
pos = pos + 1
pos = pos + 1
end
end


-- Find the vowel from the current position, including preposed vowels.
-- Read the vowel.
local vowelStart = startPos + preLen + 1
local vowelPos
local vowelRoman
local vowelLen


if preLen > 0 then
if pre == 'เ' or pre == 'แ' or pre == 'โ' or pre == 'ใ' or pre == 'ไ' then
vowelRoman, vowelLen = getVowel(str, startPos + 1)
vowelPos = start
else
else
vowelRoman, vowelLen = getVowel(str, pos)
vowelPos = pos
end
end


-- If the vowel was explicitly consumed by getVowel, advance.
local vowel, vowelLen = getVowel(str, vowelPos)
 
-- Advance over the vowel.
if vowelLen > 0 then
if vowelLen > 0 then
if preLen > 0 then
pos = vowelPos + vowelLen
pos = startPos + preLen + 1 + vowelLen
end
else
 
pos = pos + vowelLen
-- If there was a preposed vowel, getVowel consumed from its position,
end
-- so the resulting position already includes it.
if vowelLen == 0 and (pre == 'เ' or pre == 'แ' or pre == 'โ' or pre == 'ใ' or pre == 'ไ') then
pos = start + 1
end
end


-- Collect possible final consonant.
-- Final consonant.
local final = sub(str, pos, pos)
local final = sub(str, pos, pos)
local finalRom = ''
local finalRom = ''
Line 358: Line 263:
local after = sub(str, pos + 1, pos + 1)
local after = sub(str, pos + 1, pos + 1)


-- A consonant followed by a vowel-starting syllable is normally
-- If another vowel begins immediately afterward, this consonant
-- treated as the next syllable's initial rather than this syllable's final.
-- belongs to the following syllable.
local nextIsVowel = after and (
if not isVowelMark(after) and not (after == 'เ' or after == 'แ' or after == 'โ' or after == 'ใ' or after == 'ไ') then
match(after, '[ะาิีึืุูัำอ]') or
match(after, '[เแโใไ]')
)
 
if not nextIsVowel then
finalRom = FINAL[final] or ''
finalRom = FINAL[final] or ''
pos = pos + 1
pos = pos + 1


-- -ร following another final consonant is silent in RTGS.
-- Final -ร after another final consonant.
if final == 'ร' then
if final == 'ร' then
finalRom = 'n'
finalRom = 'n'
Line 382: Line 282:
end
end


return initialRom .. vowelRoman .. finalRom, math.max(1, pos - startPos)
-- -รร-
-- With a final consonant, รร = a.
-- Without a final consonant, รร = an.
if sub(str, vowelPos, vowelPos + 1) == 'รร' then
if finalRom ~= '' then
vowel = 'a'
else
vowel = 'an'
end
end
 
return initialRom .. vowel .. finalRom, math.max(1, pos - start)
end
end


local function romanizeWord(word)
local function romanizeWord(word)
if word == '' then
local result = {}
return ''
end
 
-- Manual markers:
-- < > = force space
-- <-> = force hyphen
word = gsub(word, '< >', ' ')
word = gsub(word, '<->', '-')
 
local output = {}
local pos = 1
local pos = 1
local n = len(word)
local n = len(word)
Line 404: Line 305:


if c == '<' then
if c == '<' then
local close = find(word, '>', pos + 1, true)
local close = mw.ustring.find(word, '>', pos + 1, true)
 
if close then
if close then
local marker = sub(word, pos, close)
if marker == '< >' then
table.insert(result, ' ')
elseif marker == '<->' then
table.insert(result, '-')
else
table.insert(result, marker)
end
pos = close + 1
pos = close + 1
else
else
table.insert(output, c)
table.insert(result, c)
pos = pos + 1
pos = pos + 1
end
end
elseif not isThai(c) then
elseif isThai(c) then
table.insert(output, c)
pos = pos + 1
else
local roman, consumed = romanizeSyllable(word, pos)
local roman, consumed = romanizeSyllable(word, pos)
table.insert(output, roman)
table.insert(result, roman)
pos = pos + consumed
pos = pos + consumed
else
table.insert(result, c)
pos = pos + 1
end
end
end
end


return table.concat(output)
return table.concat(result)
end
end


local function capitalize(str, sentence)
local function capitalizeWords(str)
local chars = mw.text.split(str, '')
local chars = mw.text.split(str, '')
local shouldCap = true
local capitalize = true


for i, c in ipairs(chars) do
for i, c in ipairs(chars) do
if match(c, '%a') then
if match(c, '[A-Za-z]') then
if shouldCap then
if capitalize then
chars[i] = U.upper(c)
chars[i] = U.upper(c)
shouldCap = false
capitalize = false
elseif sentence then
shouldCap = false
end
end
elseif c == ' ' then
elseif c == ' ' or c == '-' then
if sentence then
capitalize = true
shouldCap = true
end
else
end
shouldCap = true
 
return table.concat(chars)
end
 
local function sentenceCase(str)
local chars = mw.text.split(str, '')
local capitalize = true
 
for i, c in ipairs(chars) do
if match(c, '[A-Za-z]') then
if capitalize then
chars[i] = U.upper(c)
capitalize = false
end
end
elseif match(c, '[%.!?]') then
elseif match(c, '[.!?]') then
shouldCap = true
capitalize = true
end
end
end
end
Line 462: Line 384:
local str = clean(args[1])
local str = clean(args[1])


-- Don't romanize text which contains no Thai script.
-- Nothing to romanize.
if not match(str, '[ก-๛]') then
if not match(str, '[ก-๛]') then
return ''
return ''
end
end


str = applyOrthographicExceptions(str)
str = removeMarks(str)


local result = romanizeWord(str)
local result = romanizeWord(str)


if args.capi then
if args.capi then
result = capitalize(result, false)
result = capitalizeWords(result)
elseif args.sent then
elseif args.sent then
result = capitalize(result, true)
result = sentenceCase(result)
end
end