Module:Language
require('strict')
 local m_data = mw.loadData(&quot;Module:Language/data&quot;)
 local langData = m_data.languages or m_data
 
 local p = {}
 
 local function ifNotEmpty(value)
 	if value == &quot;&quot; then
 		return nil
 	else
 		return value
 	end
 end
 
 local function makeLinkedName(languageCode)
 	local data = langData[languageCode]
 	local article = data[&quot;article&quot;]
 	local name = data[&quot;Wikipedia_name&quot;] or data[&quot;name&quot;]
 	return &quot;[[&quot; .. article .. &quot;|&quot; .. name .. &quot;]]:&amp;nbsp;&quot;
 end
 
 local function makeEntryName(word, languageCode)
 	local data = langData[languageCode]
 	local ugsub = mw.ustring.gsub
 	word = tostring(word)
 	if word == nil then
 		error(&quot;The function makeEntryName requires a string argument&quot;)
 	elseif word == &quot;&quot; then
 		return &quot;&quot;
 	else
 		-- Remove bold and italics, so that words that contain bolding or emphasis can be linked without piping.
 		word = word:gsub(&quot;\'\'\'&quot;, &quot;&quot;)
 		word = word:gsub(&quot;\'\'&quot;, &quot;&quot;)
 		if data == nil then
 			return word
 		else
 			local replacements = data and data[&quot;replacements&quot;]
 			if replacements == nil then
 				return word
 			else
 				-- Decompose so that the diacritics of characters such
 				-- as á can be removed in one go.
 				-- No need to compose at the end, because the MediaWiki software
 				-- will handle that.
 				if replacements.decompose then
 					word = mw.ustring.toNFD(word)
 					for i, from in ipairs(replacements.from) do
 						word = ugsub(
 							word,
 							from,
 							replacements.to and replacements.to[i] or &quot;&quot;)
 					end
 				else
 					for regex, replacement in pairs(replacements) do
 						word = ugsub(word, regex, replacement)
 					end
 				end
 				return word
 			end
 		end
 	end
 end
 
 p.makeEntryName = makeEntryName
 
 local function fixScriptCode(firstLetter, threeLetters)
 	return string.upper(firstLetter) .. string.lower(threeLetters)
 end
 
 local function getCodes(codes, text)
 	local languageCode, scriptCode, invalidCode
 	local errorText
 	if codes == nil or codes == &quot;&quot; then
 		errorText = 'no language or script code provided'
 	elseif codes:find(&quot;^%a%a%a?$&quot;) or codes:find(&quot;^%a%a%a?%-%a%a%a%a$&quot;) then
 		-- A three- or two-letter lowercase sequence at beginning of first parameter
 		languageCode =
 			codes:find(&quot;^%a%a%a?&quot;) and (
 				codes:match(&quot;^(%l%l%l?)&quot;)
 				or codes:match(&quot;^(%a%a%a?)&quot;)
 					:gsub(&quot;(%a%a%a?)&quot;, string.lower, 1)
 			)
 		-- One uppercase and three lowercase letters at the end of the first parameter
 		scriptCode =
 			codes:find(&quot;%a%a%a%a$&quot;) and (
 				codes:match(&quot;(%u%l%l%l)$&quot;)
 				or gsub(
 					codes:match(&quot;(%a%a%a%a)$&quot;),
 					&quot;(%a)(%a%a%a)&quot;,
 					fixScriptCode,
 					1
 				)
 			)
 	elseif codes:find(&quot;^%a%a%a?%-%a%a%a?$&quot;)
 	or codes:find(&quot;^%a%a%a%-%a%a%a%-%a%a%a$&quot;) then
 		languageCode = codes
 	
 	-- Private-use subtag: x followed by one or more sequences of 1-8 lowercase
 	-- letters separated by hyphens. This only allows for one sequence, as it is
 	-- needed for proto-languages such as ine-x-proto (Proto-Indo-European).
 	elseif codes:find(&quot;^%a%a%a?%-x%-%a%a?%a?%a?%a?%a?%a?%a?$&quot;) then
 		languageCode, scriptCode =
 			codes:match(&quot;^(%a%a%a%-x%-%a%a?%a?%a?%a?%a?%a?%a?)%-?(.*)$&quot;)
 		if not languageCode then
 			errorText = '&lt;code&gt;'..codes..'&lt;/code&gt; is not a valid language or script code.'
 		elseif scriptCode ~= &quot;&quot; and not scriptCode:find(&quot;%a%a%a%a&quot;) then
 			errorText = '&lt;code&gt;'..scriptCode..'&lt;/code&gt; is not a valid script code.'
 		else
 			scriptCode = scriptCode:gsub(
 				&quot;(%a)(%a%a%a)&quot;,
 				fixScriptCode,
 				1
 			)
 		end
 	elseif codes:find(&quot;^%a%a%a?&quot;) then
 		languageCode, invalidCode = codes:match(&quot;^(%a%a%a?)%-?(.*)&quot;)
 		languageCode = string.lower(languageCode)
 		errorText = '&lt;code&gt;'..invalidCode..'&lt;/code&gt; is not a valid script code.'
 	elseif codes:find(&quot;%-?%a%a%a%a$&quot;) then
 		invalidCode, scriptCode = codes:match(&quot;(.*)%-?(%a%a%a%a)$&quot;)
 		scriptCode = gsub(
 			scriptCode,
 			&quot;(%a)(%a%a%a)&quot;,
 			fixScriptCode
 		)
 		errorText = '&lt;code&gt;'..invalidCode..'&lt;/code&gt; is not a valid language code.'
 	else
 		errorText = '&lt;code&gt;'..codes..'&lt;/code&gt; is not a valid language or script code.'
 	end
 	if not scriptCode or scriptCode == &quot;&quot; then
 		scriptCode = require(&quot;Module:Unicode data&quot;).is_Latin(text) and &quot;Latn&quot; or &quot;unknown&quot;
 	end
 	if errorText then
 		errorText = ' &lt;span style=&quot;font-size: smaller&quot;&gt;[' .. errorText .. ']&lt;/span&gt;'
 	else
 		errorText = &quot;&quot;
 	end
 	languageCode = m_data.redirects[languageCode] or languageCode
 	return languageCode, scriptCode, errorText
 end
 
 local function tag(text, languageCode, script, italics)
 	local data = langData[languageCode]
 	-- Use Wikipedia code if it has been given: for instance,
 	-- Proto-Indo-European has the Wiktionary code &quot;ine-pro&quot; but the Wikipedia
 	-- code &quot;ine-x-proto&quot;.
 	languageCode = data and data.Wikipedia_code or languageCode
 	
 	local italicize = script == &quot;Latn&quot; and italics
 	
 	if not text then text = &quot;[text?]&quot; end
 	
 	local textDirectionMarkers = { &quot;&quot;, &quot;&quot;, &quot;&quot; }
 	if data and data[&quot;direction&quot;] == &quot;rtl&quot; then
 		textDirectionMarkers = { ' dir=&quot;rtl&quot;', '&amp;rlm;', '&amp;lrm;' }
 	end
 	
 	local out = { textDirectionMarkers[2] }
 	if italicize then
 		table.insert(out, &quot;&lt;i lang=\&quot;&quot; .. languageCode .. &quot;\&quot;&quot; .. textDirectionMarkers[1] .. &quot;&gt;&quot; .. text .. &quot;&lt;/i&gt;&quot;)
 	else
 		table.insert(out, &quot;&lt;span lang=\&quot;&quot; .. languageCode .. &quot;\&quot;&quot; .. textDirectionMarkers[1] .. &quot;&gt;&quot; .. text .. &quot;&lt;/span&gt;&quot;)
 	end
 	table.insert(out, textDirectionMarkers[3])
 	
 	return table.concat(out)
 end
 
 
 
 function p.lang(frame)
 	local parent = frame:getParent()
 	local args = parent.args[1] and parent.args or frame.args
 	
 	local codes = args[1] and mw.text.trim(args[1])
 	local text = args[2] or error(&quot;Provide text in the second parameter&quot;)
 	
 	local languageCode, scriptCode, errorText = getCodes(codes, text)
 	
 	local italics = args.italics or args.i or args.italic
 	italics = not (italics == &quot;n&quot; or italics == &quot;-&quot; or italics == &quot;no&quot;)
 	
 	return tag(text, languageCode, scriptCode, italics) .. errorText
 end
 
 local function linkToWiktionary(entry, linkText, languageCode)
 	local data = langData[languageCode]
 	local name
 	if languageCode then
 		if data and data.name then
 			name = data.name
 		else
 			-- On other languages' wikis, use mw.getContentLanguage():getCode(),
 			-- or replace 'en' with that wiki's language code.
 			name = mw.language.fetchLanguageName(languageCode, 'en')
 			if name == &quot;&quot; then
 				error(&quot;Name for the language code &quot; .. (&quot;%q&quot;):format(languageCode or nil)
 					.. &quot; could not be retrieved with mw.language.fetchLanguageName, &quot;
 					.. &quot;so it should be added to [[Module:Language/data]]&quot;)
 			end
 		end
 		if entry:sub(1, 1) == &quot;*&quot; then
 			if name ~= &quot;&quot; then
 				entry = &quot;Reconstruction:&quot; .. name .. &quot;/&quot; .. entry:sub(2)
 			else
 				error(&quot;Language name is empty&quot;)
 			end
 		elseif data and data.type == &quot;reconstructed&quot; then
 			mw.log(&quot;Reconstructed language without asterisk:&quot;, languageCode, name, entry)
 			local frame = mw.getCurrentFrame()
 			-- Track reconstructed entries with no asterisk by transcluding
 			-- a nonexistent template. This technique is used in Wiktionary:
 			-- see [[wikt:Module:debug]].
 			-- [[Special:WhatLinksHere/tracking/wikt-lang/reconstructed with no asterisk]]
 			pcall(frame.expandTemplate, frame,
 				{ title = 'tracking/wikt-lang/reconstructed with no asterisk' })
 			if name ~= &quot;&quot; then
 				entry = &quot;Reconstruction:&quot; .. name .. &quot;/&quot; .. entry
 			else
 				error(&quot;Language name is empty&quot;)
 			end
 		elseif data and data.type == &quot;appendix&quot; then
 			if name ~= &quot;&quot; then
 				entry = &quot;Appendix:&quot; .. name .. &quot;/&quot; .. entry
 			else
 				error(&quot;Language name is empty&quot;)
 			end
 		end
 		if entry and linkText then
 			return &quot;[[wikt:&quot; .. entry .. &quot;#&quot; .. name .. &quot;|&quot; .. linkText .. &quot;]]&quot;
 		else
 			error(&quot;linkToWiktionary needs a Wiktionary entry or link text, or both&quot;)
 		end
 	else
 		return &quot;[[wikt:&quot; .. entry .. &quot;|&quot; .. linkText .. &quot;]]&quot;
 	end
 end
 
 function p.wiktlang(frame)
 	local parent = frame:getParent()
 	local args = parent.args[1] and parent.args or frame.args
 	
 	local codes = args[1] and mw.text.trim(args[1])
 	local word1 = ifNotEmpty(args[2])
 	local word2 = ifNotEmpty(args[3])
 	
 	if not args[2] then
 		error(&quot;Parameter 2 is required&quot;)
 	end
 	
 	local languageCode, scriptCode, errorText = getCodes(codes, word2 or word1)
 	
 	local italics = args.italics or args.i or args.italic
 	italics = not (italics == &quot;n&quot; or italics == &quot;-&quot; or italics == &quot;no&quot;)
 	
 	local entry, linkText
 	if word2 and word1 then
 		entry = makeEntryName(word1, languageCode)
 		linkText = word2
 	elseif word1 then
 		entry = makeEntryName(word1, languageCode)
 		linkText = word1
 	end
 	
 	local out
 	if languageCode and entry and linkText then
 		out = tag(linkToWiktionary(entry, linkText, languageCode), languageCode, scriptCode, italics)
 	elseif entry and linkText then
 		out = linkToWiktionary(entry, linkText)
 	else
 		out = '&lt;span style=&quot;font-size: smaller;&quot;&gt;[text?]&lt;/span&gt;'
 	end
 	
 	if out and errorText then
 		return out .. errorText
 	else
 		return errorText or error(&quot;The function wiktlang generated nothing&quot;)
 	end
 end
 
 function p.wikt(frame)
 	local parent = frame:getParent()
 	local args = parent.args[1] and parent.args or frame.args
 	
 	local codes = args[1] and mw.text.trim(args[1])
 	local word1 = ifNotEmpty(args[2])
 	local word2 = ifNotEmpty(args[3])
 	
 	if not word1 then
 		error(&quot;Provide a word in parameter 2.&quot;)
 	end
 	
 	local languageCode, scriptCode, errorText = getCodes(codes, word1)
 	
 	local entry, linkText
 	if word2 and word1 then
 		entry = makeEntryName(word1, languageCode)
 		linkText = word2
 	elseif word1 then
 		entry = makeEntryName(word1, languageCode)
 		linkText = word1
 	end
 	
 	local out
 	if languageCode and entry and linkText then
 		out = linkToWiktionary(entry, linkText, languageCode) 
 	elseif entry and linkText then
 		out = linkToWiktionary(entry, linkText)
 	else
 		out = '&lt;span style=&quot;font-size: smaller;&quot;&gt;[text?]&lt;/span&gt;'
 	end
 	
 	if out and errorText then
 		return out and out .. errorText
 	else
 		return errorText or error(&quot;The function wikt generated nothing&quot;)
 	end
 end