Module:Other Languages

Revision as of 22:07, 29 August 2025 by 919dd (talk | contribs) (Protected "Module:Other Languages" ([Edit=Allow only administrators] (indefinite) [Move=Allow only administrators] (indefinite)))
This Lua module is used primarily on pages in the Main namespace. It can be found on 544 pages, or on about 73.41% of those pages.
Edits to this Lua module will be widely noticed. To avoid large-scale disruption and unnecessary server load, any changes to this Lua module should be tested in its sandbox or testcases subpages. The tested changes can then be added to this page in one single edit by an administrator or a content moderator.
Please consider discussing any changes in the wiki's Discord.

This module implements {{Other Languages}}.


local p 			= {}
local lib			= require('Module:Feature')
local KOrm			= require('Module:Ko-rm')
local i18n			= require('Module:I18n').loadMessages('Other Languages')
local SelectiveSpan	= require('Module:Lang').SelectiveSpan
local search		= lib.inArray
local ne			= lib.isNotEmpty
local ie			= lib.isEmpty
local langOrder 	= {'en', 'zhs', 'zht', 'ja', 'ko', 'es', 'fr', 'ru', 'th', 'vi', 'de', 'id', 'pt', 'tr', 'it'}
local categories	= mw.html.create()
local noCat		 = {}
local Notes		 = {reuse={}}

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		parentFirst = true,
		removeBlanks = false,
		wrappers = {'Template:Other Languages'}
	})
	if ne(args['Transclude']) then
		local new_index = lib.split(args['Transclude'], '#')
		local transclude = lib.split(new_index[1], ';')
		p.Transclude(args, transclude[1], transclude[2], new_index[2])
	else
		local Y = 1
		while ne(args['Transclude_' .. tostring(Y)]) or ne(args[Y]) do
			local new_index = lib.split((args['Transclude_' .. tostring(Y)] or args[Y]), '#')
			local transclude = lib.split(new_index[1], ';')
			p.Transclude(args, transclude[1], transclude[2], new_index[2])
			Y = Y + 1
		end
	end
	p.ParseNotes(args)
	return p._main(args, frame)
end

--- Entry point for other modules.
--	@param args The table of arguments for which to generate an HTML table
--	@param frame Optional argument to specify a frame, which will be used for its extensionTag.
--		If not specified, the current frame will be used.
function p._main(args, frame)
	local out
	if ne(args['1_en']) then
		categories:wikitext('[[Category:Other Languages with Multiple Tables]]')
		out = mw.html.create('div'):css{['display']='flex', ['column-gap'] = '15px', ['flex-direction'] = 'row', ['flex-wrap'] = 'wrap'}
		local Y = 1
		local y = tostring(Y)
		while args[y .. '_en'] do
			local entry = out:tag('div')
			entry:tag('h3'):wikitext(args[y .. '_header'] or args[y .. '_en'])
			entry:node(p.Table(args, frame, (y .. '_')))
			
			--continue loop
			Y = Y+1
			y = tostring(Y)
		end
	else
		out = p.Table(args, frame)
	end
	
	return tostring(out:node(require('Module:Namespace detect').main{["main"]=categories})) .. (not args.noref and frame:extensionTag{ name = 'references', args = { group = '•' } } or '')
end

function p.Table(args, frame, prefix)
	prefix = prefix or ''
	local TLcol = false
	
	--[[list of all lang codes and some settings used in the loop
	['lang_code'] = {
		rm = setting for the loop to check if lang should have rm,
		span = lang code to use in the transcript span if different from base lang code
	}
	--]]
	local lang = {
		['zhs'] = { rm = true, span = 'zh-Hans' },
		['zht'] = { rm = true, span = 'zh-Hant' },
		['ja']  = { rm = true },
		['ko']  = { rm = true },
		['ru']  = { rm = true },
		['th']  = { rm = true }
	}
	--initialize the table in html syntax and the category variable
	local articletable = mw.html.create('table'):addClass('article-table'):newline()
	
	--header row
	local tr = articletable:tag('tr')
	tr:tag('th'):wikitext('Language')
	tr:tag('th'):wikitext(args.name or 'Official Name')
	
	--check if any [lang]_tl exists to show tl column if true
	local validTable = false
	for _, langCode in ipairs(langOrder) do
		if ne(args[prefix .. langCode]) then
			if langCode ~= 'en' then validTable = true end
			if args[prefix .. 'default_hidden'] or args['default_hidden'] then
				if ((ne(args[prefix .. langCode .. '_tl']) or ne(args[prefix .. 'zh_tl'])) and TLcol == false) then
					tr:tag('th'):wikitext('Literal Meaning')
					TLcol = true
				end
			else
				if ((args[prefix .. langCode .. '_tl'] or args[prefix .. 'zh_tl']) and TLcol == false) then
					tr:tag('th'):wikitext('Literal Meaning')
					TLcol = true
				end
			end
		end
	end
	if not validTable then categories:wikitext('[[Category:Empty Other Languages]]') end
	
	--start loop to create the lang rows with langOrder as iterator list to determine the order of the rows
	for i, langCode in ipairs(langOrder) do
		--variables for ease of use
		local Tx = args[prefix .. langCode] or nil
		local Tl = args[prefix .. langCode .. '_tl'] or nil
		local Rm = args[prefix .. langCode .. '_rm'] or nil
		local Loanword = args[prefix .. langCode .. '_lang'] or nil
		local Loanword_Tl = args[prefix .. langCode .. '_lang_tl'] or nil
		local Note = args[prefix .. langCode .. '_note'] or nil
		local Lang = lang[langCode] or {}
		
		--default to pagename if en transcript is not given
		if langCode == 'en' then
			if Tx then if ie(Tx) then Tx = mw.title.getCurrentTitle().text end else Tx = mw.title.getCurrentTitle().text end
		end
		
		--do a check for if transcript for the lang exists and is not blank, move on to next lang if false
		if Tx then
			if ie(Tx) then
				if not noCat[prefix .. langCode] then
					categories:wikitext('[[Category:Incomplete Transcripts for Other Languages]][[Category:',string.upper(langCode),' Transcript Missing]]')
				end
			elseif Tx == '_UNAVAILABLE' then
				if not noCat[prefix .. langCode] then
					categories:wikitext('[[Category:Unavailable Transcripts for Other Languages]][[Category:',string.upper(langCode),' Unavailable Transcripts]]')
				end
			else
				local tr = articletable:tag('tr')
			
				--open and close "Language" column cell with value and open "Official Name" column cell with transcript
				tr:tag('td'):wikitext(i18n:msg(langCode))
				local td = tr:tag('td')
				local displayTx = Tx
				if langCode == 'zhs' or langCode == 'zht' or langCode == 'ja' then
					displayTx = Tx
								:gsub('·', '・') -- convert halfwidth middle dot to fullwidth middle dot
				elseif langCode == 'ko' then
					displayTx = KOrm.strip(Tx) -- strip TX from keywords used in Module:Ko-rm
								:gsub('·', '・') -- convert halfwidth middle dot to fullwidth middle dot
				elseif langCode == 'fr' then
					displayTx = Tx
								:gsub(' !', ' !') -- add   after/before punctuation so it does not break from the text it relates to
								:gsub(' %?', ' ?') -- 
								:gsub(' :', ' :') -- 
								:gsub(' ~', ' ~') -- 
								:gsub(' %-', ' -') -- 
								:gsub(' &', ' &') -- 
								:gsub(' »', ' »') -- 
								:gsub('« ', '« ') -- 
				elseif langCode == 'ru' then
					displayTx = p.ruBreak(Tx) -- add <wbr> after hyphens as chromium based browsers do not support Russian properly
				elseif langCode == 'th' then
					displayTx = Tx
								:gsub('%.%.%.', '...<wbr>') -- add <wbr> after ellipses as they do not break properly due to not spacing in Thai, making scroll horizontally
				elseif langCode == 'de' then
					displayTx = Tx
								:gsub('%.%.%.', '...<wbr>') -- add <wbr> after ellipses as they do not break properly due to not spacing in German, making scroll horizontally
				end
				local code = Lang.span or langCode
				td:node(SelectiveSpan(displayTx,code,(code == 'vi' and true or false)))
				
				--lang notes next to transcript if corresponding translation parameter is blank or translation column is not shown
					--general lang note
					if ((ie(Tl) and TLcol) or TLcol == false) then
						for _, ref in ipairs(Notes) do
							if ref.langs[prefix .. langCode] then
								td:wikitext( frame:extensionTag{ name = 'ref', content = ref.ref, args = { name = prefix .. ref.langs.name, group = '•' } } )
							end
						end
						if Notes.reuse[prefix .. langCode] then
							td:wikitext( frame:extensionTag{ name = 'ref', args = { name = Notes.reuse[prefix .. langCode], group = '•' } } )
						end
					end
				
				-- romanization section
				if Lang.rm then
					
					--automatic russian romanization through [[Module:Ru-rm]]
					if langCode == 'ru' then
						local ru_rm = p.CleanTT(mw.text.unstrip(Tx))
						ru_rm = require('Module:Ru-rm')._main{ru_rm}
						
						--display romanization only if it differs from transcript
						if p.CleanTT(mw.text.unstrip(Tx)) ~= ru_rm then
							td:wikitext('<br />'):tag('small'):tag('i'):wikitext(ru_rm)
						end
						
					--automatic korean romanization through [[Module:Ko-rm]]
					elseif langCode == 'ko' then
						local ko_rm = p.CleanTT(mw.text.unstrip(Tx))
						local ko_mode = args.ko_mode or '1'
						
						--if manual rm exists, use it, otherwise automatic, and add category if param exists but no manual rm was given
						if ko_mode == '2' then
							--debug mode, **DO NOT SAVE WITH THIS SETTING, ONLY PREVIEW**
							mw.logObject(Tx, 'Raw tx')
							mw.logObject(ko_rm, 'Input to ko_rm')
							ko_rm = KOrm._main{ko_rm, capi = 1, nn = args[prefix .. 'ko_nn'] or nil, ni = args[prefix .. 'ko_ni'] or nil, bcred = args[prefix .. 'ko_bcred'] or nil}
							td:wikitext('<br />Auto-Rm: ['):tag('small'):tag('i'):wikitext(ko_rm):done():wikitext(']')
							td:wikitext('<br />Manual-Rm: ['):tag('small'):tag('i'):wikitext((Rm or '')):done():wikitext(']')
						elseif (Rm and ne(Rm) and ko_mode == '1') then
							td:wikitext('<br />'):tag('small'):tag('i'):wikitext(Rm)
						elseif ko_mode == '1' then
							ko_rm = KOrm._main{ko_rm, capi = 1, nn = args[prefix .. 'ko_nn'] or nil, ni = args[prefix .. 'ko_ni'] or nil, bcred = args[prefix .. 'ko_bcred'] or nil}
							if Rm == '' and not noCat[prefix .. 'ko_rm'] then categories:wikitext('[[Category:KO Romanization Missing]]') end
							--display romanization only if it differs from transcript
							if mw.text.unstrip(Tx) ~= ko_rm then
								td:wikitext('<br />'):tag('small'):tag('i'):wikitext(ko_rm)
							end
						end
						
						if Rm ~= nil then
							ko_rm2 = require('Module:Ko-rm')._main{ko_rm, capi = 1, nn = args[prefix .. 'ko_nn'] or nil, ni = args[prefix .. 'ko_ni'] or nil, bcred = args[prefix .. 'ko_bcred'] or nil}
							if not noCat[prefix .. 'ko_rm'] and ne(Rm) then
								if Rm == ko_rm2 then
									categories:wikitext('[[Category:Korean Manual Romanization Matches Automatic]]')
								else
									categories:wikitext('[[Category:Korean Manual Romanization Mismatches Automatic]]')
								end
							end
						end
						
					--automatic korean romanization through [[Module:Ja-rm]]
					elseif langCode == 'ja' then
						local ja_rm = p.CleanTT(mw.text.unstrip(Tx))
						ja_rm = require('Module:Ja-rm')._main{ja_rm, errorcat=true}
						
						if ne(Rm) then
							-- manual rm
							td:wikitext('<br />'):tag('small'):tag('i'):wikitext(Rm)
							if ja_rm ~= '' then
								categories:wikitext(
									'[[Category:Japanese Manual Romanization ', 
									lib.ternary(string.lower(Rm):gsub('[%s%-\']', '') == string.lower(ja_rm):gsub('%[%[category:.+$', ''):gsub('[%s%-\']', ''), 'Matches', 'Mismatches'),
									' Automatic]]'
								)
							end
						elseif ja_rm ~= '' and ja_rm~=Tx then
							-- valid automatic rm
							td:wikitext('<br />'):tag('small'):tag('i'):wikitext(ja_rm)
						end
						if ja_rm == '' and Rm == '' and not noCat[prefix .. 'ja_rm'] then categories:wikitext('[[Category:JA Romanization Missing]]') end
						
					elseif Rm and (langCode == 'zhs' or langCode == 'zht') then
						td:wikitext('<br />'):tag('small'):tag('i'):tag('span'):attr('lang', 'zh-rm'):wikitext(Rm)
						if ie(Rm) and not noCat[prefix .. langCode .. '_rm'] then categories:wikitext('[[Category:',string.upper(langCode),' Romanization Missing]]') end
					--general romanization inclusion
					elseif Rm then
						td:wikitext('<br />'):tag('small'):tag('i'):wikitext(Rm)
						if ie(Rm) and not noCat[prefix .. langCode .. '_rm'] then categories:wikitext('[[Category:',string.upper(langCode),' Romanization Missing]]') end
					end
					
				end
				
				--translation section
				if Tl and TLcol then
					local td = tr:tag('td')
					--special format for translations which are non-english loanwords
					if (Loanword and Loanword_Tl) then
						local CODE = mw.loadData('Module:Lang/code')[Loanword] or nil
						if CODE and ne(Loanword_Tl) then td:wikitext(Tl, '<br />(<u>', CODE, '</u>: ', Loanword_Tl, ')')
						elseif CODE then td:wikitext('<u>', CODE, '</u>: ', Tl) end
						if ie(Tl) and not noCat[prefix .. langCode .. '_tl'] then categories:wikitext('[[Category:',string.upper(langCode),' Translation With Missing Loanword Language]]') end
						if ie(Loanword_Tl) and not noCat[prefix .. langCode .. '_lang_tl'] then categories:wikitext('[[Category:',string.upper(langCode),' Loanword With Missing Translation]]') end
						
					--special format for translations which are english loanwords
					elseif Loanword then
						local CODE = mw.loadData('Module:Lang/code')[Loanword] or nil
						if CODE then td:wikitext('<u>', CODE, '</u>: ', Tl) end
						if ie(Tl) and not noCat[prefix .. langCode .. '_tl'] then categories:wikitext('[[Category:',string.upper(langCode),' Translation With Missing Loanword Language]]') end
						
					--general [lang]_tl formatting
					else
						td:wikitext(Tl)
					end
					if ne(Tl) then
						for _, ref in ipairs(Notes) do
							if ref.langs[prefix .. langCode] then
								td:wikitext( frame:extensionTag{ name = 'ref', content = ref.ref, args = { name = prefix .. ref.langs.name, group = '•' } } )
							end
						end
						if Notes.reuse[prefix .. langCode] then
							td:wikitext( frame:extensionTag{ name = 'ref', args = { name = Notes.reuse[prefix .. langCode], group = '•' } } )
						end
					end
					
					
					if TLcol == true and ie(Tl) and not noCat[prefix .. langCode .. '_tl'] then
					categories:wikitext('[[Category:',string.upper(langCode),' Translation Missing]]')
					end
				
				--default behaviour if no [lang]_tl but shown "Literal Meaning" column
				elseif TLcol then
					--special zh_tl when zhs logic
					if (args[prefix .. 'zh_tl'] and search({'zhs','zht'},langCode)) then
						
						if ne(args[prefix .. 'zht']) and langCode == 'zhs' then
							local td = tr:tag('td')
							td:attr('rowspan',2):wikitext(args[prefix .. 'zh_tl'])
							
							for _, ref in ipairs(Notes) do
								if ref.langs[prefix .. 'zh'] then
									td:wikitext( frame:extensionTag{ name = 'ref', content = ref.ref, args = { name = prefix .. ref.langs.name, group = '•' } } )
								end
							end
							if Notes.reuse[prefix .. 'zh'] then
								td:wikitext( frame:extensionTag{ name = 'ref', args = { name = Notes.reuse[prefix .. 'zh'], group = '•' } } )
							end
							
						elseif ne(args[prefix .. 'zhs']) and langCode == 'zht' then
							-- do nothing
						else
							local td = tr:tag('td')
							td:wikitext(args[prefix .. 'zh_tl'])
							
							for _, ref in ipairs(Notes) do
								if ref.langs[prefix .. 'zh'] then
									td:wikitext( frame:extensionTag{ name = 'ref', content = ref.ref, args = { name = prefix .. ref.langs.name, group = '•' } } )
								end
							end
							if Notes.reuse[prefix .. 'zh'] then
								td:wikitext( frame:extensionTag{ name = 'ref', args = { name = Notes.reuse[prefix .. 'zh'], group = '•' } } )
							end
						end
						
						if ie(args[prefix .. 'zh_tl']) and not noCat[prefix .. 'zh_tl'] then categories:wikitext('[[Category:ZH Translation Missing]]') end
					
					--default behaviour if "Literal Meaning" column is shown but no [lang]_tl
					else
						tr:tag('td'):wikitext('&mdash;')
					end
				end
			end
		end
	
	--end of loop
	end
	
	--check for administrative categories
	if (args[prefix .. 'default_hidden'] or args['default_hidden']) and TLcol == true then
		categories:wikitext('[[Category:TL Parameters Filled In Despite default_hidden Given]]')
	end
	if args['hide_tl'] or args['multi'] then
		categories:wikitext('[[Category:Pages Using Deprecated Template Parameters]]')
	end
	if ne(args['revise']) then
		categories:wikitext('[[Category:Other Languages Revision Requests]]')
	end
	
	--return finished table
	return articletable
end

function p.Transclude(retArgs, page, og_index, new_index)
	local Parser = require('Module:Parser').getTemplateArgs
	local frame = mw.getCurrentFrame()
	
	local og_prefix = ''
	local new_index = new_index or og_index or 0
	if ne(og_index) then
		og_prefix = tostring(og_index) .. '_'
	end
	
	local params = Parser(page, {only='Other Languages'})
	local at_least_one = false
	
	if params == nil then
		categories:wikitext('[[Category:Other Languages Invalid Transclusions]]')
		return
	end
	
	local new_prefix = tostring(new_index) .. '_'
	if new_prefix == '0_' then
		new_prefix = ''
	end
	
	for param, value in pairs(params) do
		local retParam = param
		if (tonumber(retParam) ~= nil and tonumber(retParam) == new_index+1)
		or (retParam == 'Transclude' and new_index == 0)
		or (retParam == ('Transclude_' .. tostring(new_index))) then
			error('\n[[Category:Other Languages Invalid Transclusions]]Attempted transclusion of a transcluded table, please utilize the page with the original table [possibly: ' .. value .. ' ]')
		elseif retParam:find('^%d_') then
			retParam = param:gsub('^%d+_', new_prefix, 1)
		elseif new_prefix ~= '' then
			retParam = new_prefix .. retParam
		end
		if retArgs[retParam] == nil and (not param:find('revise')) then
			if (og_prefix ~= '' and param:find('^' .. og_prefix)) or (og_prefix == '' and (not param:find('^%d+_'))) then
				retArgs[retParam] = frame:preprocess(value)
				noCat[retParam] = true
				at_least_one = true
			end
		elseif retArgs[retParam] == '{nil}' then
			retArgs[retParam] = nil
		end
	end
	if not at_least_one then
		categories:wikitext('[[Category:Other Languages Invalid Transclusions]]')
	else
		categories:wikitext('[[Category:Other Languages Transcluding Tables]]')
	end
end

function p.ParseNotes(args)
	-- table concat in sentence format
	local function join(tab)
		if #tab == 0 then return ''
		elseif #tab == 1 then return tab[1]
		elseif #tab == 2 then return tab[1] .. ' and ' .. tab[2]
		else
			local last = table.remove(tab, #tab)
			result = table.concat(tab, ', ') .. ', and ' .. last
			return result
		end
	end
	local parsedNotes = {}
	
	for param, value in pairs(args) do
		if type(param) == 'string' and param:find('_note$') and ne(value) then
			param = param:gsub('_note$', '')
			local note = { ref = {}, langs = { name = param }, prefix = '' }
			local prefix = ''
			parsedNotes[param] = true
			if param:find('^%d+_') then
				prefix, param = string.match(param, '^(%d+_)(.-)$')
				note.prefix = prefix
				note.langs.name = param
			end
			
			-- zhs and zht checks as they're different length
			if math.fmod(#param, 2) == 1 and param:find('zht') and not param:find('zhs')then
				param = param:gsub('zht', '')
				note.langs[note.prefix .. 'zht'] = true
				table.insert(note.ref, i18n:msg('zht-ref'))
			elseif math.fmod(#param, 2) == 1 and param:find('zhs') and not param:find('zht') then
				param = param:gsub('zhs', '')
				note.langs[note.prefix .. 'zhs'] = true
				table.insert(note.ref, i18n:msg('zhs-ref'))
			elseif math.fmod(#param, 2) == 0 and param:find('zht') and param:find('zhs') then
				param = param:gsub('zht', ''):gsub('zhs', '')
				note.langs[note.prefix .. 'zht'] = true
				note.langs[note.prefix .. 'zhs'] = true
				table.insert(note.ref, i18n:msg('zhs-ref'))
				table.insert(note.ref, i18n:msg('zht-ref'))
			end
			
			-- rest of langs
			for lang in string.gmatch(param, '%w%w') do
				note.langs[note.prefix .. lang] = true
				table.insert(note.ref, i18n:msg(lang .. '-ref'))
			end
			
			-- build ref content and include in return
			note.ref = '<b>' .. join(note.ref) .. ':</b>&nbsp;' .. value
			table.insert(Notes, note)
		end
	end
	
	for param, value in pairs(args) do
		if type(param) == 'string' and param:find('_noteName$') and ne(value) and parsedNotes[value] then
			Notes.reuse[param:gsub('_noteName$', '')] = value
		end
	end
end

function p.CleanTT(tx)
	local full = tx
	local result = tx
	for tooltip, display in string.gmatch(full, '<span[^>]+title="([^>]-)">(.-)</span>') do
		search_str = '<span[^>]+title="' .. tooltip .. '">(' .. display .. ')</span><span[^>]+><span[^>]+>' .. display .. '</span><span[^>]+>' .. tooltip .. '</span></span>'
		result = result:gsub(search_str, display)
	end
	return result
end

function p.ruBreak(tx)
	local split = {}
	local allowed = true
	--separate the string through unicode codepoints as default lua split cannot handle non-UTF-8 characters
	for c in mw.ustring.gcodepoint(tx) do
		table.insert(split, mw.ustring.char(c))
	end
	-- mw.logObject(split,'split')   --debug
	
	--separate the split string into ASCII and non-ASCII, include anything inside '<>' as ASCII to not mess with html tags rendering
	local loopnum = 1
	local html, ref = 0, false
	while split[loopnum] do
		--check for start of ref tag
		if split[loopnum] == '\'' and split[loopnum+1] == '"' and split[loopnum+2] == '`' then
			ref = true
		end
		
		--check for start of html
		if (split[loopnum] == '<' and split[loopnum+1]:find('%w')) then
			html = html + 1
		end
		
		if (split[loopnum]:find('%-') and html == 0 and ref == false) then
			split[loopnum] = '-<wbr>'
		end
		
		--check for end of ref tag
		if split[loopnum] == '`' and split[loopnum+1] == '"' and split[loopnum+2] == '\'' then
			ref = false
		end
		
		--check for end of html
		if (split[loopnum] == '|' or (split[loopnum] == ']' and split[loopnum+1] == ']')) then
			link = false
		end
		
		loopnum = loopnum + 1
	end
	return table.concat(split, '')
end

return p