Jump to content

မေႃႇၵျူး:Babel

လုၵ်ႉတီႈ ဝိၵ်ႇသျိၼ်ႇၼရီႇ မႃး

Documentation for this module may be created at မေႃႇၵျူး:Babel/doc

local export = {}

--[==[ intro: This module implements {{tl|Babel}}, using [[Module:Babel/data]]
			  and [[Special:PrefixIndex/Module:Babel/data/|its subpages]]
			  to store translated text. Further usage can be found at the
			  documentation page of {{tl|Babel}}. ]==]

local m_data = "Module:Babel/data"
local m_fam = "Module:families"
local m_lang = "Module:languages"
local m_load = "Module:load"
local m_pages = "Module:pages"
local m_para = "Module:parameters"
local m_sc = "Module:scripts"
local m_sc_utils = "Module:script utilities"
local m_str_utils = "Module:string utilities"
local m_track = "Module:debug/track"

local concat = table.concat
local find = string.find
local gmatch = string.gmatch
local gsub = string.gsub
local lower = string.lower
local match = string.match
local next = next
local sub = string.sub
local upper = string.upper

-- Loaders for functions from other modules.

local function debug_track(...)
	debug_track = require(m_track)
	return debug_track("Babel/" .. ...)
end

local function fam_get_by_code(...)
	fam_get_by_code = require(m_fam).getByCode
	return fam_get_by_code(...)
end

local function lang_get_by_code(...)
	lang_get_by_code = require(m_lang).getByCode
	return lang_get_by_code(...)
end

local function load_data(...)
	load_data = require(m_load).load_data
	return load_data(...)
end

local function plain_gsub(...)
	plain_gsub = require(m_str_utils).plain_gsub
	return plain_gsub(...)
end

local function safe_load_data(...)
	safe_load_data = require(m_load).safe_load_data
	return safe_load_data(...)
end

local function sc_get_by_code(...)
	sc_get_by_code = require(m_sc).getByCode
	return sc_get_by_code(...)
end

local function tag_text(...)
	tag_text = require(m_sc_utils).tag_text
	return tag_text(...)
end

-- Loaders for objects.

local current_title
local function get_current_title()
	current_title, get_current_title = mw.title.getCurrentTitle(), nil
	return current_title
end

local current_frame
local function get_current_frame()
	current_frame, get_current_frame = mw.getCurrentFrame(), nil
	return current_frame
end

local content_language
local function get_content_language()
	content_language, get_content_language = mw.language.getContentLanguage(), nil
	return content_language
end

local function is_userpage()
	-- Categorization and tracking should be suppressed if the page is not a root user page.
	local title = current_title or get_current_title()
	return title:inNamespace("User") and title.text == title.rootText
end

local function soft_error(key, message)
	if is_userpage() then
		debug_track(key)
	end

	return '<span class="error">' .. message .. '</span>'
end

local function process(code)
	return
		gsub(code, "-[012345N]$", ""),
		match(code, "-([012345N])$") or "N"
end

local message_memo = {}

local function grab_message(lang, proficiency, type)
	if message_memo[lang .. proficiency] then
		return message_memo[lang .. proficiency]

	elseif type == ("language" or "sign language") then
		local subpage = safe_load_data("Module:Babel/data/" .. sub(lang, 1, 1))

		if subpage and subpage[lang .. "-" .. proficiency] then
			message_memo[lang .. proficiency] = subpage[lang .. "-" .. proficiency]
			return message_memo[lang .. proficiency], true -- The boolean indicates it's translated for tagging purposes.
		end

	elseif type == "script" then
		local message = load_data(m_data).script_messages[lang .. "-" .. proficiency]

		if message then
			message_memo[lang .. proficiency] = message
			return message
		end
	end

	if not proficiency then
		error("Message for " .. lang .. " was not grabbed because proficiency was not provided.")

	else
		-- Fetch from /data based on type.

		message_memo[type .. proficiency] = load_data(m_data)[type .. "_proficiencies"][proficiency]
		or soft_error("invalid proficiency", "Invalid proficiency: " .. proficiency)
	end

	return message_memo[type .. proficiency]
end

local function special_table(code)
	if not code then
		return "align=center style=\"font-style:italic;\" | "
			.. "You haven't set up any languages. "
			.. "Please see [[Wiktionary:Babel]] for help."

	elseif code == "!" then
		return "\n|\n"

	elseif code == "-" then
		return "<div style=\"float: left; margin: 2px;\">\n"
			.. "{| cellspacing=\"0\" style=\"width: 238px;\"\n"
			.. "| style=\"width: 45px; height: 45px;\" | &nbsp;\n"
			.. "|style=\"font-size: 8pt; padding: 4pt; line-height: 1.25em;\" | &nbsp;\n|}</div>"

	elseif code == "----" then
		return "<div style=\"clear: both; padding: 0.25em 0;\"><hr/></div>"

	else

		--[[ This doesn't have the same checks as make_table, so it needs to
			 be able to deal with the fact you can technically pass something like
			 ----- and bypass all the langcode checks. ]]

		error(code .. " is not a recognised feature; please see [[Template:"
			.. "Babel/documentation]] for a list of these.")
	end
end

local function generate_cats(lang, proficiency, type, script, fallback)
	if not lang then
		error("Categories could not be generated because language code was not provided.")

	elseif script then
		-- for example, ko-Kore should categorise as ko, ko-Kore, Kore
		return {
			"Category:User " .. lang .. "-" .. script .. "-" .. proficiency,
			"Category:User " .. lang .. "-" .. script,
			"Category:User " .. lang .. "-" .. proficiency,
			"Category:User " .. lang,
			"Category:User " .. script .. "-" .. proficiency,
			"Category:User " .. script,
		}

	elseif fallback and fallback ~= lang then
		return {
			"Category:User " .. lang .. "-" .. proficiency,
			"Category:User " .. lang,
			"Category:User " .. fallback .. "-" .. proficiency,
			"Category:User " .. fallback,
		}

	elseif type == "programming" then
		return {
			"Category:User " .. lang .. " coder-" .. proficiency,
			"Category:User " .. lang .. " coder",
		}

	else
		return {
			"Category:User " .. lang .. "-" .. proficiency,
			"Category:User " .. lang,
		}
	end
end

--[==[ This is where modules can interface with Babel. `data` is a table
	 containing the following arguments:
		* `obj` '''usually required''': Language, script, or family object.
		* `lang`: Language code. This is '''required''' if obj is not passed.
		* `code` '''required''': Language code affixed with proficiency (or, if unaffixed, proficiency should be native).
		* `proficiency` '''required''': This can be 0, 1, 2, 3, 4, 5, or N.
		* `text` '''required''': Text to display in the box.
		* `type`: Used to determine what formatting to use. This is '''required''' if the box being generated is for a script, (non-sign) language, or programming language.
		* `glyph`: The glyph to show above the script code. This is '''required''' for scripts.
		* `prog_obj`: Programming language data from [[Module:Babel/data]]. This is '''required''' if the box being generated is for a programming language.
		* `translated`: Whether `text` is in English. If this is set to {true}, the text will be appropriately tagged with [[Module:script utilities]].
	]==]
function export.generate_box(data)
	if not (data.obj or data.lang) then
		error("generate_box received neither `obj` nor `lang`.")
	elseif not (data.code and data.proficiency) then
		error("`code` and `proficiency` must be passed to generate_box.")
	end

	local lang = data.obj and data.obj:getCode() or data.lang

	local ret = '<div class="babel-box babel-' .. data.proficiency .. '">\n' ..
				'<table class="babel-content"><tr>'

	if data.type == "script" then
		if not data.glyph then
			error("Glyph must be passed to generate_box, because " .. lang .. " is a script code.")
		end

		if lang ~= "Latn" then
			if data.glyph == "A" then
				debug_track("Babel/missing script glyph")
			end

			if not find(lang, "[IU]PA") then
				data.glyph = tag_text(data.glyph, sc_get_by_code())

			else
				data.glyph = '<span class="IPA">' .. data.glyph .. '</span>'
			end
		end

		ret = ret .. '<td class="babel-code" style="font-size:9pt;line-height:100%>' ..
					 '<span style="font-size:14pt;line-height:130%;">' .. data.glyph ..
					 '</span><br><b>' .. data.code .. '</b></td><td class="babel-text">'
					 .. data.text .. '</span>'

	else
		if data.type == "programming" then
			data.link = "[[" .. (data.prog_obj.link) .. "|" .. (data.prog_obj.display_code or lang) .. "]]" or ""
			data.text = plain_gsub(data.text, "|" .. lang, "|" .. (data.prog_obj.display or data.prog_obj.display_code or lang))

			if lang ~= data.code then
				data.link = data.link .. "-" .. data.proficiency
			end

		else
			if data.type == "language" then
				data.dir = "left"

				if obj and find(obj:findBestScript(data.text):getDirection(), "rtl") then
					data.dir = "right"
				end

				if data.script then
					lang = lang .. "-" .. data.script
				end
			end

			-- Display what was inputted - en for en, en-N for en-N.
			local proficiency = ""
			data.link = data.link or lang
			if data.link ~= data.code then
				proficiency = "-" .. data.proficiency
			end
			data.link = "[[w:ISO 639:" .. data.link .. "|" .. data.link .. "]]" .. proficiency

			if data.obj and data.translated then
				local tagged = {}
				local n = 1

				for i in gmatch(gsub(data.text, "<[hb]r>", "\n"), "[^\n]+") do
					tagged[n] = tag_text(i, data.obj)
					n = n + 1
				end

				data.text = concat(tagged, match(data.text, "<[hb]r>"))
			end
		end

		ret = ret .. '<td class="babel-code" style="font-size:14pt;">' ..
					 '<b>' .. data.link .. '</b></td><td class="babel-text" style="text-align:' ..
					 (data.dir or "") .. ';">' .. data.text .. '</td>'
	end

	return ret
end

local function make_table(code, inactive)
	if not code or find(code, "^[!%-]+$") then
		return special_table(code)

	elseif find(code, "UNIQ") then

		--[[ If it has a MW strip marker, it's probably a template or some
			 other rubbish that shouldn't be passed. ]]

		error("Do not pass a template as a parameter.")
	end

	local lang, proficiency = process(code)

	local data = { code = code, proficiency = proficiency, lang = lang }

	--[[ Data needs to have 4 things to be able to build the table:
			- `type` of table to make: language, script, proglang, or family
			- `message`, which is either fetched from /data or /data/...
			- `langname`, for $3 in the generic messages
			- `link`, to be displayed on the left
		 If the message is translated, it will also have an item `translated`
		 indicating that.
		 Script data will also have a glyph.
		 Programming languages will sometimes have custom codes to display, and,
		 for internal use only, a titlecase version of the name (e.g. Php for
		 PHP, Asm for asm, etc.). ]]

	if find(lang, "-%u%l+$") then
		-- Allow language codes affixed with script codes.
		data.script = match(data.lang, "-(%u%l+)$")
		data.display_code = data.lang
		data.lang = gsub(data.lang, "-%u%l+$", "")
		if not sc_get_by_code(data.script) then
			return soft_error("invalid script code", "Invalid script code: " .. data.script)
		end
	end

	local type
	type, data.obj = next{
		language = lang_get_by_code(data.lang, nil, true),
		script = sc_get_by_code(data.lang),
		family = fam_get_by_code(data.lang),
	}

	if data.obj then
		data.langname = data.obj:getCanonicalName()

		if type == "language" then
			local full_code = data.obj:getFullCode()
			if full_code ~= data.lang then
				data.fallback = full_code
			end
		end

		data.type = type
		local is_sign_language = (type == "language" and data.obj:inFamily("sgn") and "sign_") or ""
		data.text, data.translated = grab_message(data.lang .. (data.script and "-" .. data.script or ""), proficiency, is_sign_language .. data.type)

	else
		data.titlecase = upper(sub(lang, 1, 1)) .. lower(sub(lang, 2, -1))
		local locations = {
			programming = load_data(m_data).proglangs[data.titlecase],
			language = load_data(m_data).custom_codes[data.lang],
			script = {
				--[[ Invalid in Module:scripts but still used, so exceptions should
					 be made for these two. Probably should be phased out. ]]
				["IPA"] = "IPA",
				["UPA"] = "UPA",
			},
		}

		locations.script = locations.script[data.lang]
		locations.language = locations.language and locations.language[1]

		local type, langname = next(locations)

		if type == "programming" then
			data.type = type
			data.text = grab_message(data.lang, data.proficiency, type)
			data.langname = data.lang

		elseif langname then
			data.type = type
			data.text, data.translated = grab_message(data.lang .. (data.script and "-" .. data.script or ""), data.proficiency, data.type)
			data.langname = langname
			if type == "language" then
				debug_track("custom code")
				local custom = load_data(m_data).custom_codes[data.lang]
				data.obj = lang_get_by_code(custom.fallback)	-- auto cat won't work for custom codes, so use the fallback to categorise
				data.link = data.lang
			end

		else
			return soft_error("invalid language code", "Invalid language code: " .. lang)
		end
	end

	if data.type == "script" then
		data.glyph = load_data(m_data).script_glyphs[lang] or "A"
	end

	if data.script then
		data.cats = generate_cats(data.obj:getCode(), data.proficiency, data.type, data.script)

	elseif data.type ~= "programming" then
		data.cats = generate_cats(data.obj and data.obj:getCode() or data.lang, data.proficiency, data.type, nil, data.fallback)

	else
		data.prog_obj = load_data(m_data).proglangs[data.titlecase]
		data.cats = generate_cats((data.prog_obj.cat or data.prog_obj.display or data.titlecase), proficiency, data.type)
	end

	if not data.translated then
		data.text = gsub(data.text, "$1", ":" .. data.cats[1])
		data.text = gsub(data.text, "$2", ":" .. data.cats[2])
		data.text = gsub(data.text, "$3", data.langname)

	else
		data.text = gsub(data.text, "$1", ":" .. data.cats[1])
		data.text = gsub(data.text, "$2", ":" .. data.cats[2])

		if find(data.text, "{") then
			data.text = gsub(data.text, "{[^}]-}",
			-- Gender parsing.
			function(arg)
				local parts = {}

				local n = 1
				for part in gmatch(arg, "[^/{}]+") do
					parts[n] = part
					n = n + 1
				end

				if not parts then
					soft_error("malformed gender switch", "Malformed gender switch in message for " .. code .. ": " .. arg)
				end

				return (content_language or get_content_language()):gender((current_title or get_current_title()).rootText, parts)
			end)
		end

		if find(data.text, "hiero") then
			data.text = (current_frame or get_current_frame()):preprocess(data.text)
		end
	end

	local ret = export.generate_box(data)

	if data.cats and is_userpage() and proficiency ~= "0" then
		if not inactive then
			ret = ret .. "[[" .. concat(data.cats, "]][[") .. "]]"

		else
			for i = 1, #data.cats do
				ret = ret .. "[[" .. data.cats[i] .. " (inactive)]]"
			end
		end
	end

	return ret .. '</tr></table></div>'
end

--[==[ Entry point for {{tl|Babel userbox}}. ]==]
function export.t_userbox(frame)
	local args = require(m_para).process(frame:getParent().args, {
		[1] = true,
		["inactive"] = true,
	})
	return make_table(args[1], args["inactive"])
end

--[==[ Main entry point, for {{tl|Babel}}. ]==]
function export.show(frame)
	local args = require(m_para).process(frame:getParent().args, {
		[1] = { list = true },
		["inactive"] = { type = "boolean" },
		["float"] = { set = { "left", "right", "none", "center" }, default = "right" },
		["align"] = { alias_of = "float" },
		["margin_left"] = { default = "1em" },
		["margin_right"] = { default = "0em" },
		["margin_bottom"] = { default = "0.5em" },
		["width"] = { default = "248px" },
		["border_color"] = { default = "#99B3FF" },
		["header"] = { default = "[[Wiktionary:Babel]]" },
		["color"] = { default = "inherit" },
		["footer"] = {
			default = "Search [[:Category:User languages|"
				.. "user languages]] or [[:Category:User scripts|scripts]]"
		},
		["border"] = { alias_of = "border_color" },
		["bordercolor"] = { alias_of = "border_color" },
		["gender"] = true,
		["g"] = { alias_of = "gender" },
		["no-table"] = { boolean = true },
	})

	local result = {}

	if not args[1][1] then
		result[1] = make_table()

	else
		for i = 1, #args[1] do
			result[i] = make_table(args[1][i], args["inactive"])
		end
	end

	local colspan = #result

	result = concat(result, "")

	if args["no-table"] then
		return result
	end

	if args["inactive"] then
		args["header"] = args["header"] .. " <span title=\"This user has not "
			.. "contributed in 2 or more years.\">(inactive)</span>"
	end

	if args["gender"] and require(m_pages).is_preview() then
		args["header"] = args["header"] .. "\n<span class=\"error\""
			.. "style =\"font-size:75%;\">"
			.. "Gender is automatically grabbed from your preferences.\n"
			.. "You can remove the gender parameter.</span>"
	end

	if args["float"] == "center" then
		debug_track("float center")
		args["float"] = "right"
	end

	return "{| class=\"babel-box-wrapper userboxes\" name=\"userboxes\""
		.. "style=\"float: " .. args["float"] .. "; margin-left: " .. args["margin_left"] .. "; margin-right: " .. args["margin_right"]
		.. "; margin-bottom: " .. args["margin_bottom"] .. "; width: " .. args["width"] .. "; border: "
		.. args["border_color"] .. " solid 1px;"
		.. "clear: " .. args["float"] .. ";\"\n"
		.. "! style=\"background-color: " .. args["color"] .. "; text-align:"
		.. "center\" colspan=\"10\" | " .. args["header"] .. "\n"
		.. "|- style=vertical-align:top\n"
		.. "|" .. result .. "\n"
		.. "|-\n| style=\"background-color: " .. args["color"] .. ";"
		.. "text-align: center;\" colspan=\"" .. colspan .. "\" | " .. args["footer"]
		.. "\n|}"
end

return export