Module:Multilingual/sandbox

local Multilingual = { suite   = "Multilingual",
					   serial  = "2020-12-10",
					   item	= 47541920,
					   globals = { ISO15924 = 71584769,
								   WLink	= 19363224 }
					 }
--[=[
Utilities for multilingual texts and ISO 639 (BCP47) issues etc.
* fair()
* fallback()
* findCode()
* fix()
* format()
* getBase()
* getLang()
* getName()
* i18n()
* int()
* isLang()
* isLangWiki()
* isMinusculable()
* isRTL()
* message()
* sitelink()
* tabData()
* userLang()
* userLangCode()
* wikibase()
* failsafe()
loadData: Multilingual/config Multilingual/names
]=]
local Failsafe   = Multilingual
local GlobalMod  = Multilingual
local GlobalData = Multilingual
local User	   = { sniffer = "showpreview" }
Multilingual.globals.Multilingual = Multilingual.item



Multilingual.exotic = { simple = true,
						no	 = true }
Multilingual.prefer = { cs = true,
						de = true,
						en = true,
						es = true,
						fr = true,
						it = true,
						nl = true,
						pt = true,
						ru = true,
						sv = true }

local foreignModule = function(access, advanced, append, alt, alert)
	-- Fetch global module
	-- Precondition:
	--	 access	-- string, with name of base module
	--	 advanced  -- true, for require(); else mw.loadData()
	--	 append	-- string, with subpage part, if any; or false
	--	 alt	   -- number, of wikidata item of root; or false
	--	 alert	 -- true, for throwing error on data problem
	-- Postcondition:
	--	 Returns whatever, probably table
	-- 2020-01-01
	local storage = access
	local finer = function()
		if append then
			storage = string.format("%s/%s", storage, append)
		end
	 end
	local fun, lucky, r, suited
	if advanced then
		fun = require
	else
		fun = mw.loadData
	end
	GlobalMod.globalModules = GlobalMod.globalModules or {}
	suited = GlobalMod.globalModules[access]
	if not suited then
		finer()
		lucky, r = pcall(fun,  "Module:" .. storage)
	end
	if not lucky then
		if not suited and
		   type(alt) == "number" and
		   alt > 0 then
			suited = string.format("Q%d", alt)
			suited = mw.wikibase.getSitelink(suited)
			GlobalMod.globalModules[access] = suited or true
		end
		if type(suited) == "string" then
			storage = suited
			finer()
			lucky, r = pcall(fun, storage)
		end
		if not lucky and alert then
			error("Missing or invalid page: " .. storage)
		end
	end
	return r
end -- foreignModule()

local fetchData = function(access)
	-- Retrieve translated keyword from commons:Data:****.tab
	-- Precondition:
	--	 access  -- string, with page identification on Commons
	--	 Returns table, with data, or string, with error message
	-- 2019-12-05
	local storage = access
	local r
	if type(storage) == "string" then
		local s
		storage = mw.text.trim(storage)
		s = storage:lower()
		if s:sub(1, 2) == "c:" then
			storage = mw.text.trim(storage:sub(3))
			s = storage:lower()
		elseif s:sub(1, 8) == "commons:" then
			storage = mw.text.trim(storage:sub(9))
			s = storage:lower()
		end
		if s:sub(1, 5) == "data:" then
			storage = mw.text.trim(storage:sub(6))
			s = storage:lower()
		end
		if s == "" or s == ".tab" then
			storage = false
		elseif s:sub(-4) == ".tab" then
			storage = storage:sub(1, -5) .. ".tab"
		else
			storage = storage .. ".tab"
		end
	end
	if type(storage) == "string" then
		local data
		if type(GlobalData.TabDATA) ~= "table" then
			GlobalData.TabDATA = {}
		end
		data = GlobalData.TabDATA[storage]
		if data then
			r = data
		else
			local lucky
			lucky, data = pcall(mw.ext.data.get, storage, "_")
			if type(data) == "table" then
				data = data.data
				if type(data) == "table" then
					GlobalData.TabDATA[storage] = data
				else
					r = string.format("%s [[%s%s]]",
									   "INVALID Data:*.tab",
									   "commons:Data:",
									   storage)
				end
			else
				r = "BAD PAGE Data:*.tab – commons:" .. storage
			end
			if r then
				GlobalData.TabDATA[storage] = r
				data = false
			else
				r = data
			end
		end
	else
		r = "BAD PAGE commons:Data:*.tab"
	end
	return r
end -- fetchData()

local favorites = function()
	-- Provide fallback codes
	-- Postcondition:
	--	 Returns table with sequence of preferred languages
	--	 * ahead elements
	--	 * user (not yet accessible)
	--	 * page content language (not yet accessible)
	--	 * page name subpage
	--	 * project
	--	 * en
	local r = Multilingual.polyglott
	if not r then
		local self = mw.language.getContentLanguage():getCode():lower()
		local sub  = mw.title.getCurrentTitle().subpageText
		local f	= function(add)
			local s = add
			for i = 1, #r do
				if r[i] == s then
					s = false
					break -- for i
				end
			end -- for i
			if s then
				table.insert(r, s)
			end
		end
		r = {}
		if sub:find("/", 2, true) then
			sub = sub:match("/(%l%l%l?)$")
			if sub then
				table.insert(r, sub)
			end
		elseif sub:find("^%l%l%l?%-?%a?%a?%a?%a?$") and
			   mw.language.isSupportedLanguage(sub) then
			table.insert(r, sub)
		end
		f(self)
		f("en")
		Multilingual.polyglott = r
	end
	return r
end -- favorites()

local feasible = function(ask, accept)
	-- Is ask to be supported by application?
	-- Precondition:
	--	 ask	 -- lowercase code
	--	 accept  -- sequence table, with offered lowercase codes
	-- Postcondition:
	--	 nil, or true
	local r
	for i = 1, #accept do
		if accept[i] == ask then
			r = true
			break -- for i
		end
	end -- for i
	return r
end -- feasible()

local fetch = function(access, append)
	-- Attach config or library module
	-- Precondition:
	--	 access  -- module title
	--	 append  -- string, with subpage part of this; or false
	-- Postcondition:
	--	 Returns:  table, with library, or false
	local got, sign
	if append then
		sign = string.format("%s/%s", access, append)
	else
		sign = access
	end
	if type(Multilingual.ext) ~= "table" then
		Multilingual.ext = {}
	end
	got = Multilingual.ext[sign]
	if got == nil then
		local global = Multilingual.globals[access]
		local lib = (not append or append == "config")
		got = foreignModule(access, lib, append, global)
		if type(got) == "table" then
			if lib then
				local startup = got[access]
				if type(startup) == "function" then
					got = startup()
				end
			end
		else
			got = false
		end
		Multilingual.ext[sign] = got
	end
	return got
end -- fetch()

local fetchISO639 = function(access)
	-- Retrieve table from commons:Data:ISO639/***.tab
	-- Precondition:
	--	 access  -- string, with subpage identification
	-- Postcondition:
	--	 Returns table, with data, even empty
	local r
	if type(Multilingual.iso639) ~= "table" then
		Multilingual.iso639 = {}
	end
	r = Multilingual.iso639[access]
	if type(r) == "nil" then
		local raw = fetchData("ISO639/" .. access)
		if type(raw) == "table" then
			local t
			r = {}
			for i = 1, #raw do
				t = raw[i]
				if type(t) == "table" and
				   type(t[1]) == "string" and
				   type(t[2]) == "string" then
					r[t[1]] = t[2]
				else
					break -- for i
				end
			end -- for i
		else
			r = false
		end
		Multilingual.iso639[access] = r
	end
	return r or {}
end -- fetchISO639()

local fill = function(access, alien, frame)
	-- Expand language name template
	-- Precondition:
	--	 access  -- string, with language code
	--	 alien   -- language code for which to be generated
	--	 frame   -- frame, if available
	-- Postcondition:
	--	 Returns string
	local template = Multilingual.tmplLang
	if type(template) ~= "table" then
		local cnf = fetch("Multilingual", "config")
		if cnf then
			template = cnf.tmplLang
		end
	end
	if type(template) == "table" then
		local source = template.title
		local f, lucky, s
		Multilingual.tmplLang = template
		if type(source) ~= "string" and
		   type(template.namePat) == "string" and
		   template.namePat:find("%s", 1, true) then
			source = string.format(template.namePat, access)
		end
		if type(source) == "string" then
			if not Multilingual.frame then
				Multilingual.frame = frame or mw.getCurrentFrame()
			end
			f = function(a)
				return Multilingual.frame:expandTemplate{ title = a }
			end
			lucky, s = pcall(f, source)
			if lucky then
				return s
			end
		end
	end
	return nil
end -- fill()

local find = function(ask, alien)
	-- Derive language code from name
	-- Precondition:
	--	 ask	-- language name, downcased
	--	 alien  -- language code of ask
	-- Postcondition:
	--	 nil, or string
	local codes = mw.language.fetchLanguageNames(alien, "all")
	local r
	for k, v in pairs(codes) do
		if mw.ustring.lower(v) == ask then
			r = k
			break -- for k, v
		end
	end -- for k, v
	if not r then
		r = Multilingual.fair(ask)
	end
	return r
end -- find()



local fold = function(frame)
	-- Merge template and #invoke arglist
	-- Precondition:
	--	 frame   -- template frame
	-- Postcondition:
	--	 table, with combined arglist
	local r = {}
	local f = function(apply)
		if type(apply) == "table" and
			type(apply.args) == "table" then
			for k, v in pairs(apply.args) do
				v = mw.text.trim(v)
				if v ~= "" then
					r[tostring(k)] = v
				end
			end -- for k, v
		end
	 end -- f()
	f(frame:getParent())
	f(frame)
	return r
end -- fold()

User.favorize = function(accept, frame)
	-- Guess user language
	-- Precondition:
	--	 accept  -- sequence table, with offered ISO 639 etc. codes
	--	 frame   -- frame, if available
	-- Postcondition:
	--	 Returns string with best code, or nil
	if not (User.self or User.langs) then
		if not User.trials then
			User.tell = mw.message.new(User.sniffer)
			if User.tell:exists() then
				User.trials = {}
				if not Multilingual.frame then
					if frame then
						Multilingual.frame = frame
					else
						Multilingual.frame = mw.getCurrentFrame()
					end
				end
				User.sin = Multilingual.frame:callParserFunction("int",
														   User.sniffer)
			else
				User.langs = true
			end
		end
		if User.sin then
			local order  = {}
			local post   = {}
			local three  = {}
			local unfold = {}
			local s, sin
			for i = 1, #accept do
				s = accept[i]
				if not User.trials[s] then
					if #s > 2 then
						if s:find("-", 3, true) then
							table.insert(unfold, s)
						else
							table.insert(three, s)
						end
					elseif Multilingual.prefer[s] then
						table.insert(order, s)
					else
						table.insert(post, s)
					end
				end
			end -- for i
			for i = 1, #post do
				table.insert(order, post[i])
			end -- for i
			for i = 1, #three do
				table.insert(order, three[i])
			end -- for i
			for i = 1, #unfold do
				table.insert(order, unfold[i])
			end -- for i
			for i = 1, #order do
				s = order[i]
				sin = User.tell:inLanguage(s):plain()
				if sin == User.sin then
					User.self = s
					break -- for i
				else
					User.trials[s] = true
				end
			end -- for i
		end
	end
	return User.self
end -- User.favorize()

Multilingual.fair = function(ask)
	-- Format language specification according to RFC 5646 etc.
	-- Precondition:
	--	 ask  -- string or table, as created by .getLang()
	-- Postcondition:
	--	 Returns string, or false
	local s = type(ask)
	local q, r
	if s == "table" then
		q = ask
	elseif s == "string" then
		q = Multilingual.getLang(ask)
	end
	if q and
	   q.legal and
	   mw.language.isKnownLanguageTag(q.base) then
		r = q.base
		if q.n > 1 then
			local order = { "extlang",
							"script",
							"region",
							"other",
							"extension" }
			for i = 1, #order do
				s = q[order[i]]
				if s then
					r = string.format("%s-%s", r, s)
				end
			end -- for i
		end
	end
	return r or false
end -- Multilingual.fair()

Multilingual.fallback = function(able, another)
	-- Is another language suitable as replacement?
	-- Precondition:
	--	 able	 -- language version specifier to be supported
	--	 another  -- language specifier of a possible replacement,
	--				 or not to retrieve a fallback table
	-- Postcondition:
	--	 Returns boolean, or table with fallback codes
	local r
	if type(able) == "string" and #able > 0 then
		if type(another) == "string" and #another > 0 then
			if able == another then
				r = true
			else
				local s = Multilingual.getBase(able)
				if s == another then
					r = true
				else
					local others = mw.language.getFallbacksFor(s)
					r = feasible(another, others)
				end
			end
		else
			local s = Multilingual.getBase(able)
			if s then
				r = mw.language.getFallbacksFor(s)
				if r[1] == "en" then
					local d = fetchISO639("fallback")
					if type(d) == "table" and
					   type(d[s]) == "string" then
						r = mw.text.split(d[s], "|")
						table.insert(r, "en")
					end
				end
			end
		end
	end
	return r or false
end -- Multilingual.fallback()

Multilingual.findCode = function(ask)
	-- Retrieve code of local (current project or English) language name
	-- Precondition:
	--	 ask  -- string, with presumable language name
	--			 A code itself will be identified, too.
	-- Postcondition:
	--	 Returns string, or false
	local seek = mw.text.trim(ask)
	local r = false
	if #seek > 1 then
		if seek:find("[", 1, true) then
			local wlink = fetch("WLink")
			if wlink and
			   type(wlink.getPlain) == "function" then
				seek = wlink.getPlain(seek)
			end
		end
		seek = mw.ustring.lower(seek)
		if Multilingual.isLang(seek) then
			r = Multilingual.fair(seek)
		else
			local collection = favorites()
			for i = 1, #collection do
				r = find(seek, collection[i])
				if r then
					break -- for i
				end
			end -- for i
		end
	end
	return r
end -- Multilingual.findCode()

Multilingual.fix = function(attempt)
	-- Fix frequently mistaken language code
	-- Precondition:
	--	 attempt  -- string, with presumable language code
	-- Postcondition:
	--	 Returns string with correction, or false if no problem known
	local r = fetchISO639("correction")[attempt:lower()]
	return r or false
end -- Multilingual.fix()

Multilingual.format = function(apply, alien, alter, active, alert,
								 frame, assembly, adjacent, ahead)
	-- Format one or more languages
	-- Precondition:
	--	 apply	 -- string with language list or item
	--	 alien	 -- language of the answer
	--				  -- nil, false, "*": native
	--				  -- "!": current project
	--				  -- "#": code, downcased, space separated
	--				  -- "-": code, mixcase, space separated
	--				  -- any valid code
	--	 alter	 -- capitalize, if "c"; downcase all, if "d"
	--				  capitalize first item only, if "f"
	--				  downcase every first word only, if "m"
	--	 active	-- link items, if true
	--	 alert	 -- string with category title in case of error
	--	 frame	 -- if available
	--	 assembly  -- string with split pattern, if list expected
	--	 adjacent  -- string with list separator, else assembly
	--	 ahead	 -- string to prepend first element, if any
	-- Postcondition:
	--	 Returns string, or false if apply empty
	local r = false
	if apply then
		local slang
		if assembly then
			local bucket = mw.text.split(apply, assembly)
			local shift = alter
			local separator
			if adjacent then
				separator = adjacent
			elseif alien == "#" or alien == "-" then
				separator = " "
			else
				separator = assembly
			end
			for k, v in pairs(bucket) do
				slang = Multilingual.format(v, alien, shift, active,
											 alert)
				if slang then
					if r then
						r = string.format("%s%s%s",
										   r, separator, slang)
					else
						r = slang
						if shift == "f" then
							shift = "d"
						end
					end
				end
			end -- for k, v
			if r and ahead then
				r = ahead .. r
			end
		else
			local single = mw.text.trim(apply)
			if single == "" then
				r = false
			else
				local lapsus, slot
				slang = Multilingual.findCode(single)
				if slang then
					if alien == "-" then
						r = slang
					elseif alien == "#" then
						r = slang:lower()
					else
						r = Multilingual.getName(slang, alien)
						if active then
							slot = fill(slang, false, frame)
							if slot then
								local wlink = fetch("WLink")
								if wlink and
								   type(wlink.getTarget) == "function" then
									slot = wlink.getTarget(slot)
								end
							else
								lapsus = alert
							end
						end
					end
				else
					r = single
					if active then
						local title = mw.title.makeTitle(0, single)
						if title.exists then
							slot = single
						end
					end
					lapsus = alert
				end
				if not r then
					r = single
				elseif alter == "c" or alter == "f" then
					r = mw.ustring.upper(mw.ustring.sub(r, 1, 1))
						.. mw.ustring.sub(r, 2)
				elseif alter == "d" then
					if Multilingual.isMinusculable(slang, r) then
						r = mw.ustring.lower(r)
					end
				elseif alter == "m" then
					if Multilingual.isMinusculable(slang, r) then
						r = mw.ustring.lower(mw.ustring.sub(r, 1, 1))
							.. mw.ustring.sub(r, 2)
					end
				end
				if slot then
					if r == slot then
						r = string.format("[[%s]]", r)
					else
						r = string.format("[[%s|%s]]", slot, r)
					end
				end
				if lapsus and alert then
					r = string.format("%s[[Category:%s]]", r, alert)
				end
			end
		end
	end
	return r
end -- Multilingual.format()

Multilingual.getBase = function(ask)
	-- Retrieve base language from possibly combined ISO language code
	-- Precondition:
	--	 ask  -- language code
	-- Postcondition:
	--	 Returns string, or false
	local r
	if ask then
		local slang = ask:match("^%s*(%a%a%a?)-?%a*%s*$")
		if slang then
			r = slang:lower()
		else
			r = false
		end
	else
		r = false
	end
	return r
end -- Multilingual.getBase()

Multilingual.getLang = function(ask)
	-- Retrieve components of a RFC 5646 language code
	-- Precondition:
	--	 ask  -- language code with subtags
	-- Postcondition:
	--	 Returns table with formatted subtags
	--			 .base
	--			 .region
	--			 .script
	--			 .suggest
	--			 .year
	--			 .extension
	--			 .other
	--			 .n
	local tags = mw.text.split(ask, "-")
	local s	= tags[1]
	local r
	if s:match("^%a%a%a?$") then
		r = { base  = s:lower(),
			  legal = true,
			  n	 = #tags }
		for i = 2, r.n do
			s = tags[i]
			if #s == 2 then
				if r.region or not s:match("%a%a") then
					r.legal = false
				else
					r.region = s:upper()
				end
			elseif #s == 4 then
				if s:match("%a%a%a%a") then
					r.legal = (not r.script)
					r.script = s:sub(1, 1):upper() ..
							   s:sub(2):lower()
				elseif s:match("20%d%d") or
					   s:match("1%d%d%d") then
					r.legal = (not r.year)
					r.year = s
				else
					r.legal = false
				end
			elseif #s == 3 then
				if r.extlang or not s:match("%a%a%a") then
					r.legal = false
				else
					r.extlang = s:lower()
				end
			elseif #s == 1 then
				s = s:lower()
				if s:match("[tux]") then
					r.extension = s
					for k = i + 1, r.n do
						s = tags[k]
						if s:match("^%w+$") then
							r.extension = string.format("%s-%s",
														 r.extension, s)
						else
							r.legal = false
						end
					end -- for k
				else
					r.legal = false
				end
				break -- for i
			else
				r.legal = (not r.other) and
						  s:match("%a%a%a")
				r.other = s:lower()
			end
			if not r.legal then
				break -- for i
			end
		end -- for i
		if r.legal then
			r.suggest = Multilingual.fix(r.base)
			if r.suggest then
				r.legal = false
			end
		end
	else
		r = { legal = false }
	end
	if not r.legal then
		local cnf = fetch("Multilingual", "config")
		if cnf and type(cnf.scream) == "string" then
			r.scream = cnf.scream
		end
	end
	return r
end -- Multilingual.getLang()

Multilingual.getName = function(ask, alien)
	-- Which name is assigned to this language code?
	-- Precondition:
	--	 ask	-- language code
	--	 alien  -- language of the answer
	--			   -- nil, false, "*": native
	--			   -- "!": current project
	--			   -- any valid code
	-- Postcondition:
	--	 Returns string, or false
	local r
	if ask then
		local slang   = alien
		local tLang
		if slang then
			if slang == "*" then
				slang = Multilingual.fair(ask)
			elseif slang == "!" then
				slang = favorites()[1]
			else
				slang = Multilingual.fair(slang)
			end
		else
			slang = Multilingual.fair(ask)
		end
		if not slang then
			slang = ask or "?????"
		end
		slang = slang:lower()
		tLang = fetch("Multilingual", "names")
		if tLang then
			tLang = tLang[slang]
			if tLang then
				r = tLang[ask]
			end
		end
		if not r then
			if not Multilingual.ext.tMW then
				Multilingual.ext.tMW = {}
			end
			tLang = Multilingual.ext.tMW[slang]
			if tLang == nil then
				tLang = mw.language.fetchLanguageNames(slang)
				if tLang then
					Multilingual.ext.tMW[slang] = tLang
				else
					Multilingual.ext.tMW[slang] = false
				end
			end
			if tLang then
				r = tLang[ask]
			end
		end
		if not r then
			r = mw.language.fetchLanguageName(ask:lower(), slang)
			if r == "" then
				r = false
			end
		end
	else
		r = false
	end
	return r
end -- Multilingual.getName()

Multilingual.i18n = function(available, alt, frame)
	-- Select translatable message
	-- Precondition:
	--	 available  -- table, with mapping language code ./. text
	--	 alt		-- string|nil|false, with fallback text
	--	 frame	  -- frame, if available
	--	 Returns
	--		 1. string|nil|false, with selected message
	--		 2. string|nil|false, with language code
	local r1, r2
	if type(available) == "table" then
		local codes = {}
		local trsl  = {}
		local slang
		for k, v in pairs(available) do
			if type(k) == "string" and
			   type(v) == "string" then
				slang = mw.text.trim(k:lower())
				table.insert(codes, slang)
				trsl[slang] = v
			end
		end -- for k, v
		slang = Multilingual.userLang(codes, frame)
		if slang and trsl[slang] then
			r1 = mw.text.trim(trsl[slang])
			if r1 == "" then
				r1 = false
			else
				r2 = slang
			end
		end
	end
	if not r1 and type(alt) == "string" then
		r1 = mw.text.trim(alt)
		if r1 == "" then
			r1 = false
		end
	end
	return r1, r2
end -- Multilingual.i18n()

Multilingual.int = function(access, alien, apply)
	-- Translated system message
	-- Precondition:
	--	 access  -- message ID
	--	 alien   -- language code
	--	 apply   -- nil, or sequence table with parameters $1, $2, ...
	-- Postcondition:
	--	 Returns string, or false
	local o = mw.message.new(access)
	local r
	if o:exists() then
		if type(alien) == "string" then
			o:inLanguage(alien:lower())
		end
		if type(apply) == "table" then
			o:params(apply)
		end
		r = o:plain()
	end
	return r or false
end -- Multilingual.int()

Multilingual.isLang = function(ask, additional)
	-- Could this be an ISO language code?
	-- Precondition:
	--	 ask		 -- language code
	--	 additional  -- true, if Wiki codes like "simple" permitted
	-- Postcondition:
	--	 Returns boolean
	local r, s
	if additional then
		s = ask
	else
		s = Multilingual.getBase(ask)
	end
	if s then
		r = mw.language.isKnownLanguageTag(s)
		if r then
			r = not Multilingual.fix(s)
		elseif additional then
			r = Multilingual.exotic[s] or false
		end
	else
		r = false
	end
	return r
end -- Multilingual.isLang()

Multilingual.isLangWiki = function(ask)
	-- Could this be a Wiki language version?
	-- Precondition:
	--	 ask  -- language version specifier
	-- Postcondition:
	--	 Returns boolean
	local r
	local s = Multilingual.getBase(ask)
	if s then
		r = mw.language.isSupportedLanguage(s) or
			Multilingual.exotic[ask]
	else
		r = false
	end
	return r
end -- Multilingual.isLangWiki()

Multilingual.isMinusculable = function(ask, assigned)
	-- Could this language name become downcased?
	-- Precondition:
	--	 ask	   -- language code, or nil
	--	 assigned  -- language name, or nil
	-- Postcondition:
	--	 Returns boolean
	local r = true
	if ask then
		local cnf = fetch("Multilingual", "config")
		if cnf then
			local s = string.format(" %s ", ask:lower())
			if type(cnf.stopMinusculization) == "string"
			  and cnf.stopMinusculization:find(s, 1, true) then
				r = false
			end
			if r and assigned
			  and type(cnf.seekMinusculization) == "string"
			  and cnf.seekMinusculization:find(s, 1, true)
			  and type(cnf.scanMinusculization) == "string" then
				local scan = assigned:gsub("[%(%)]", " ") .. " "
				if not scan:find(cnf.scanMinusculization) then
					r = false
				end
			end
		end
	end
	return r
end -- Multilingual.isMinusculable()

Multilingual.isRTL = function(ask)
	-- Check whether language is written right-to-left
	-- Precondition:
	--	 ask  -- string, with language (or script) code
	-- Returns true, if right-to-left
	local r
	Multilingual.rtl = Multilingual.rtl or {}
	r = Multilingual.rtl[ask]
	if type(r) ~= "boolean" then
		local bib = fetch("ISO15924")
		if type(bib) == "table" and
		   type(bib.isRTL) == "function" then
			r = bib.isRTL(ask)
		else
			r = mw.language.new(ask):isRTL()
		end
		Multilingual.rtl[ask] = r
	end
	return r
end -- Multilingual.isRTL()

Multilingual.message = function(arglist, frame)
	-- Show text in best match of user language like system message
	-- Precondition:
	--	 arglist  -- template arguments
	--	 frame	-- frame, if available
	-- Postcondition:
	--	 Returns string with appropriate text
	local r
	if type(arglist) == "table" then
		local t = {}
		local m, p, save
		for k, v in pairs(arglist) do
			if type(k) == "string" and
			   type(v) == "string" then
				v = mw.text.trim(v)
				if v ~= "" then
					if k:match("^%l%l") then
						t[k] = v
					elseif k:match("^%$%d$") and k ~= "$0" then
						p = p or {}
						k = tonumber(k:match("^%$(%d)$"))
						p[k] = v
						if not m or k > m then
							m = k
						end
					end
				end
			end
		end -- for k, v
		if type(arglist["-"]) == "string" then
			save = arglist[arglist["-"]]
		end
		r = Multilingual.i18n(t, save, frame)
		if p and r and r:find("$", 1, true) then
			t = {}
			for i = 1, m do
				t[i] = p[i] or ""
			end -- for i
			r = mw.message.newRawMessage(r, t):plain()
		end
	end
	return r or ""
end -- Multilingual.message()

Multilingual.sitelink = function(all, frame)
	-- Make link at local or other site with optimal linktext translation
	-- Precondition:
	--	 all	-- string or table or number, item ID or entity
	--	 frame  -- frame, if available
	-- Postcondition:
	--	 Returns string with any helpful internal link, or plain text
	local s = type(all)
	local object, r
	if s == "table" then
		object = all
	elseif s == "string" then
		object = mw.wikibase.getEntity(all)
	elseif s == "number" then
		object = mw.wikibase.getEntity(string.format("Q%d", all))
	end
	if type(object) == "table" then
		local collection = object.sitelinks
		local entry
		s = false
		if type(collection) == "table" then
			Multilingual.site = Multilingual.site or
								mw.wikibase.getGlobalSiteId()
			entry = collection[Multilingual.site]
			if entry then
				s = ":" .. entry.title
			elseif collection.enwiki then
				s = "w:en:" .. collection.enwiki.title
			end
		end
		r = Multilingual.wikibase(object, "labels", frame)
		if s then
			if s == ":" .. r then
				r = string.format("[[%s]]", s)
			else
				r = string.format("[[%s|%s]]", s, r)
			end
		end
	end
	return r or ""
end -- Multilingual.sitelink()

Multilingual.tabData = function(access, at, alt, frame)
	-- Retrieve translated keyword from commons:Data:****.tab
	-- Precondition:
	--	 access  -- string, with page identification on Commons
	--	 at	  -- string, with keyword
	--	 alt	 -- string|nil|false, with fallback text
	--	 frame   -- frame, if available
	--	 Returns
	--		 1. string|nil|false, with selected message
	--		 2. language code, or "error"
	local data = fetchData(access)
	local r1, r2
	if  type(data) == "table" then
		if type(at) == "string" then
			local seek = mw.text.trim(at)
			if seek == "" then
				r1 = "EMPTY Multilingual.tabData key"
			else
				local e, poly
				for i = 1, #data do
					e = data[i]
					if type(e) == "table" then
						if e[1] == seek then
							if type(e[2]) == "table" then
								poly = e[2]
							else
								r1 = "INVALID Multilingual.tabData bad #"
														 .. tostring(i)
							end
							break   -- for i
						end
					else
						break   -- for i
					end
				end   -- for i
				if poly then
					data = poly
				else
					r1 = "UNKNOWN Multilingual.tabData key: " .. seek
				end
			end
		else
			r1 = "INVALID Multilingual.tabData key"
		end
	else
		r1 = data
	end
	if r1 then
		r2 = "error"
	elseif data then
		r1, r2 = Multilingual.i18n(data, alt, frame)
		r2 = r2 or "error"
	end
	return r1, r2
end -- Multilingual.tabData()

Multilingual.userLang = function(accept, frame)
	-- Try to support user language by application
	-- Precondition:
	--	 accept  -- string or table
	--				space separated list of available ISO 639 codes
	--				Default: project language, or English
	--	 frame   -- frame, if available
	-- Postcondition:
	--	 Returns string with appropriate code
	local s = type(accept)
	local codes, r, slang
	if s == "string" then
		codes = mw.text.split(accept:lower(), "%s+")
	elseif s == "table" then
		codes = {}
		for i = 1, #accept do
			s = accept[i]
			if type(s) == "string" and
			   s ~= "" then
				table.insert(codes, s:lower())
			end
		end -- for i
	end
	slang = User.favorize(codes, frame)
	if slang then
		if feasible(slang, codes) then
			r = slang
		elseif slang:find("-", 1, true) then
			slang = Multilingual.getBase(slang)
			if feasible(slang, codes) then
				r = slang
			end
		end
		if not r then
			local others = mw.language.getFallbacksFor(slang)
			for i = 1, #others do
				slang = others[i]
				if feasible(slang, codes) then
					r = slang
					break -- for i
				end
			end -- for i
		end
	end
	if not r then
		local back = favorites()
		for i = 1, #back do
			slang = back[i]
			if feasible(slang, codes) then
				r = slang
				break -- for i
			end
		end -- for i
		if not r and codes[1] then
			r = codes[1]
		end
	end
	return r or favorites()[1]
end -- Multilingual.userLang()

Multilingual.userLangCode = function()
	-- Guess a user language code
	-- Postcondition:
	--	 Returns code of current best guess
	return User.self or favorites()[1]
end -- Multilingual.userLangCode()

Multilingual.wikibase = function(all, about, attempt, frame)
	-- Optimal translation of wikibase component
	-- Precondition:
	--	 all	  -- string or table, object ID or entity
	--	 about	-- boolean, true "descriptions" or false "labels"
	--	 attempt  -- string or not, code of preferred language
	--	 frame	-- frame, if available
	-- Postcondition:
	--	 Returns
	--		 1. string, with selected message
	--		 2. string, with language code, or not
	local s = type(all)
	local object, r, r2
	if s == "table" then
		object = all
	elseif s == "string" then
		object = mw.wikibase.getEntity(all)
	end
	if type(object) == "table" then
		if about and about ~= "labels" then
			s = "descriptions"
		else
			s = "labels"
		end
		object = object[s]
		if type(object) == "table" then
			if object[attempt] then
				r  = object[attempt].value
				r2 = attempt
			else
				local poly
				for k, v in pairs(object) do
					poly = poly or {}
					poly[k] = v.value
				end -- for k, v
				if poly then
					r, r2 = Multilingual.i18n(poly, nil, frame)
				end
			end
		end
	end
	return r or "",   r2
end -- Multilingual.wikibase()

Failsafe.failsafe = function(atleast)
	-- Retrieve versioning and check for compliance
	-- Precondition:
	--	 atleast  -- string, with required version
	--						 or wikidata|item|~|@ or false
	-- Postcondition:
	--	 Returns  string  -- with queried version/item, also if problem
	--			  false   -- if appropriate
	-- 2020-08-17
	local since = atleast
	local last	= (since == "~")
	local linked  = (since == "@")
	local link	= (since == "item")
	local r
	if last or link or linked or since == "wikidata" then
		local item = Failsafe.item
		since = false
		if type(item) == "number" and item > 0 then
			local suited = string.format("Q%d", item)
			if link then
				r = suited
			else
				local entity = mw.wikibase.getEntity(suited)
				if type(entity) == "table" then
					local seek = Failsafe.serialProperty or "P348"
					local vsn  = entity:formatPropertyValues(seek)
					if type(vsn) == "table" and
					   type(vsn.value) == "string" and
					   vsn.value ~= "" then
						if last and vsn.value == Failsafe.serial then
							r = false
						elseif linked then
							if mw.title.getCurrentTitle().prefixedText
							   ==  mw.wikibase.getSitelink(suited) then
								r = false
							else
								r = suited
							end
						else
							r = vsn.value
						end
					end
				end
			end
		end
	end
	if type(r) == "nil" then
		if not since or since <= Failsafe.serial then
			r = Failsafe.serial
		else
			r = false
		end
	end
	return r
end -- Failsafe.failsafe()

-- Export
local p = {}

p.fair = function(frame)
	-- Format language code
	--	 1  -- language code
	local s = mw.text.trim(frame.args[1] or "")
	return Multilingual.fair(s) or ""
end -- p.fair

p.fallback = function(frame)
	-- Is another language suitable as replacement?
	--	 1  -- language version specifier to be supported
	--	 2  -- language specifier of a possible replacement
	local s1 = mw.text.trim(frame.args[1] or "")
	local s2 = mw.text.trim(frame.args[2] or "")
	local r  = Multilingual.fallback(s1, s2)
	if type(r) == "table" then
		r = r[1]
	else
		r = r and "1"  or ""
	end
	return r
end -- p.fallback

p.findCode = function(frame)
	-- Retrieve language code from language name
	--	 1  -- name in current project language
	local s = mw.text.trim(frame.args[1] or "")
	return Multilingual.findCode(s) or ""
end -- p.findCode

p.fix = function(frame)
	local r = frame.args[1]
	if r then
		r = Multilingual.fix(mw.text.trim(r))
	end
	return r or ""
end -- p.fix

p.format = function(frame)
	-- Format one or more languages
	--	 1		  -- language list or item
	--	 slang	  -- language of the answer, if not native
	--				   * -- native
	--				   ! -- current project
	--				   any valid code
	--	 shift	  -- capitalize, if "c"; downcase, if "d"
	--				   capitalize first item only, if "f"
	--	 link	   -- 1 -- link items
	--	 scream	 -- category title in case of error
	--	 split	  -- split pattern, if list expected
	--	 separator -- list separator, else split
	--	 start	  -- prepend first element, if any
	local r
	local link
	if frame.args.link == "1" then
		link = true
	end
	r = Multilingual.format(frame.args[1],
							 frame.args.slang,
							 frame.args.shift,
							 link,
							 frame.args.scream,
							 frame,
							 frame.args.split,
							 frame.args.separator,
							 frame.args.start)
	return r or ""
end -- p.format

p.getBase = function(frame)
	-- Retrieve base language from possibly combined ISO language code
	--	 1  -- code
	local s = mw.text.trim(frame.args[1] or "")
	return Multilingual.getBase(s) or ""
end -- p.getBase

p.getName = function(frame)
	-- Retrieve language name from ISO language code
	--	 1  -- code
	--	 2  -- language to be used for the answer, if not native
	--		   ! -- current project
	--		   * -- native
	--		   any valid code
	local s	 = mw.text.trim(frame.args[1] or "")
	local slang = frame.args[2]
	local r
	Multilingual.frame = frame
	if slang then
		slang = mw.text.trim(slang)
	end
	r = Multilingual.getName(s, slang)
	return r or ""
end -- p.getName

p.int = function(frame)
	-- Translated system message
	--	 1			 -- message ID
	--	 lang		  -- language code
	--	 $1, $2, ...   -- parameters
	local sysMsg = frame.args[1]
	local r
	if sysMsg then
		sysMsg = mw.text.trim(sysMsg)
		if sysMsg ~= "" then
			local n	 = 0
			local slang = frame.args.lang
			local i, params, s
			if slang == "" then
				slang = false
			end
			for k, v in pairs(frame.args) do
				if type(k) == "string" then
					s = k:match("^%$(%d+)$")
					if s then
						i = tonumber(s)
						if i > n then
							n = i
						end
					end
				end
			end -- for k, v
			if n > 0 then
				local s
				params = {}
				for i = 1, n do
					s = frame.args["$" .. tostring(i)] or ""
					table.insert(params, s)
				end -- for i
			end
			r = Multilingual.int(sysMsg, slang, params)
		end
	end
	return r or ""
end -- p.int

p.isLang = function(frame)
	-- Could this be an ISO language code?
	--	 1  -- code
	local s = mw.text.trim(frame.args[1] or "")
	local lucky, r = pcall(Multilingual.isLang, s)
	return r and "1" or ""
end -- p.isLang

p.isLangWiki = function(frame)
	-- Could this be a Wiki language version?
	--	 1  -- code
	-- Returns non-empty, if possibly language version
	local s = mw.text.trim(frame.args[1] or "")
	local lucky, r = pcall(Multilingual.isLangWiki, s)
	return r and "1" or ""
end -- p.isLangWiki

p.isRTL = function(frame)
	-- Check whether language is written right-to-left
	--	 1  -- string, with language code
	-- Returns non-empty, if right-to-left
	local s = mw.text.trim(frame.args[1] or "")
	return Multilingual.isRTL(s) and "1" or ""
end -- p.isRTL()

p.message = function(frame)
	-- Translation of text element
	return Multilingual.message(fold(frame), frame)
end -- p.message

p.sitelink = function(frame)
	-- Make link at local or other site with optimal linktext translation
	--	 1  -- item ID
	local s = mw.text.trim(frame.args[1] or "")
	local r
	if s:match("^%d+$") then
		r = tonumber(s)
	elseif s:match("^Q%d+$") then
		r = s
	end
	if r then
		r = Multilingual.sitelink(r, frame)
	end
	return r or s
end -- p.sitelink

p.tabData = function(frame)
	-- Retrieve best message text from Commons Data
	--	 1	-- page identification on Commons
	--	 2	-- keyword
	--	 alt  -- fallback text
	local suite = frame.args[1]
	local seek  = frame.args[2]
	local salt  = frame.args.alt
	local r	 = Multilingual.tabData(suite, seek, salt, frame)
	return r
end -- p.tabData

p.userLang = function(frame)
	-- Which language does the current user prefer?
	--	 1  -- space separated list of available ISO 639 codes
	local s = mw.text.trim(frame.args[1] or "")
	return Multilingual.userLang(s, frame)
end -- p.userLang

p.wikibase = function(frame)
	-- Optimal translation of wikibase component
	--	 1  -- object ID
	--	 2  -- 1 for "descriptions", 0 for "labels".
	--		  or either "descriptions" or "labels"
	local r
	local s = mw.text.trim(frame.args[1] or "")
	if s ~= "" then
		local s2 = mw.text.trim(frame.args[2] or "0")
		local slang = mw.text.trim(frame.args.lang or "")
		local large = (s2 ~= "" and s2 ~= "0")
		if slang == "" then
			slang = false
		end
		r = Multilingual.wikibase(s, large, slang, frame)
	end
	return r or ""
end -- p.wikibase

p.failsafe = function(frame)
	-- Versioning interface
	local s = type(frame)
	local since
	if s == "table" then
		since = frame.args[1]
	elseif s == "string" then
		since = frame
	end
	if since then
		since = mw.text.trim(since)
		if since == "" then
			since = false
		end
	end
	return Failsafe.failsafe(since) or ""
end -- p.failsafe()

p.Multilingual = function()
	return Multilingual
end -- p.Multilingual

return p

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.