Pergi ke kandungan

Modul:Babel

Daripada Wikikamus

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

local function is_userpage()
	-- Categorization and tracking should be suppressed if the page is not a root user page.
	local title = mw.title.getCurrentTitle()
	return title:inNamespace("Pengguna") 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 [[Wikikamus: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/doc]] 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 {
			"Kategori:Pengguna " .. lang .. "-" .. script .. "-" .. proficiency,
			"Kategori:Pengguna " .. lang .. "-" .. script,
			"Kategori:Pengguna " .. lang .. "-" .. proficiency,
			"Kategori:Pengguna " .. lang,
			"Kategori:Pengguna " .. script .. "-" .. proficiency,
			"Kategori:Pengguna " .. script,
		}
	
	elseif fallback and fallback ~= lang then
		return {
			"Kategori:Pengguna " .. lang .. "-" .. proficiency,
			"Kategori:Pengguna " .. lang,
			"Kategori:Pengguna " .. fallback .. "-" .. proficiency,
			"Kategori:Pengguna " .. fallback,
		}
	
	elseif type == "programming" then
		return {
			"Kategori:Pengguna pengekod " .. lang .. "-" .. proficiency,
			"Kategori:Pengguna pengekod " .. lang,
		}
		
	else
		return {
			"Kategori:Pengguna " .. lang .. "-" .. proficiency,
			"Kategori:Pengguna " .. 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)
				if not find(arg, "%+[^+]+%+[^}]*}") then 
					return soft_error("missing second delimiter", "Message " .. arg .. " missing second + delimiter: ", lang) 
				end
				
				local forms = {
					match(arg, "{([^+]+)"),		-- masculine
					match(arg, "%+([^+]+)%+"),	-- feminine
					match(arg, "([^+]+)}")		-- neutral
				}
				
				return mw.getContentLanguage():gender(mw.title.getCurrentTitle().rootText, forms)
			end)
		end
		
		if find(data.text, "hiero") then
			data.text = mw.getCurrentFrame():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] .. " (tak aktif)]]"
			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 = "[[Wikikamus:Babel]]" },
		["color"] = { default = "inherit" },
		["footer"] = {
			default = "Cari [[:Kategori:Bahasa pengguna|"
				.. "bahasa]] atau [[:Kategori:Tulisan pengguna|tulisan pengguna]]"
		},
		["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.\">(tak aktif)</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\" name=\"userboxes\" id=\"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