local Multilingual = --[=[ 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 = Multilinguallocal GlobalMod = Multilinguallocal GlobalData = Multilinguallocal User = Multilingual.globals.Multilingual = Multilingual.item
Multilingual.exotic = Multilingual.prefer =
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)
"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 rend -- 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)
"c:" then storage = mw.text.trim(storage:sub(3)) s = storage:lower elseif s:sub(1, 8)
"data:" then storage = mw.text.trim(storage:sub(6)) s = storage:lower end if s
".tab" then storage = false elseif s:sub(-4)
"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 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 rend -- 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]
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]
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
"config") got = foreignModule(access, lib, append, global) if type(got)
"function" then got = startup end end else got = false end Multilingual.ext[sign] = got end return gotend -- 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)
"table" then local t r = for i = 1, #raw do t = raw[i] if type(t)
"string" and type(t[2])
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)
"string" and template.namePat:find("%s", 1, true) then source = string.format(template.namePat, access) end if type(source)
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)
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" 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 rend -- 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
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
"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 = 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 falseend -- 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 #another > 0 then if able
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]
"table" and type(d[s])
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 rend -- 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 falseend -- 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
"-" 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
"" then r = false else local lapsus, slot slang = Multilingual.findCode(single) if slang then if 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)
"c" or alter
"d" then if Multilingual.isMinusculable(slang, r) then r = mw.ustring.lower(r) end elseif alter
slot then r = string.format("%s", r) else r = string.format("%s", slot, r) end end if lapsus and alert then r = string.format("%s", r, alert) end end end end return rend -- 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 rend -- 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 = for i = 2, r.n do s = tags[i] if #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
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 = end if not r.legal then local cnf = fetch("Multilingual", "config") if cnf and type(cnf.scream)
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 = 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
"" then r = false end end else r = false end return rend -- 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)
"string" and type(v)
"" then r1 = false else r2 = slang end end end if not r1 and type(alt)
"" then r1 = false end end return r1, r2end -- 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)
"table" then o:params(apply) end r = o:plain end return r or falseend -- 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 rend -- 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 rend -- 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.seekMinusculization:find(s, 1, true) and type(cnf.scanMinusculization)
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)
"function" then r = bib.isRTL(ask) else r = mw.language.new(ask):isRTL end Multilingual.rtl[ask] = r end return rend -- 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)
"string" and type(v)
"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
"string" then object = mw.wikibase.getEntity(all) elseif s
"table" then local collection = object.sitelinks local entry s = false if type(collection)
":" .. r then r = string.format("%s", s) else r = string.format("%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)
"string" then local seek = mw.text.trim(at) if seek
"table" then if e[1]
"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, r2end -- 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
"table" then codes = for i = 1, #accept do s = accept[i] if type(s)
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
"string" then object = mw.wikibase.getEntity(all) end 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 "", r2end -- 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 link = (since
"wikidata" then local item = Failsafe.item since = false if type(item)
"table" then local seek = Failsafe.serialProperty or "P348" local vsn = entity:formatPropertyValues(seek) if type(vsn)
"string" and vsn.value ~= "" then if last and vsn.value
mw.wikibase.getSitelink(suited) then r = false else r = suited end else r = vsn.value end end end end end end if type(r)
-- Exportlocal 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)
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
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
"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 send -- 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 rend -- 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
p.failsafe = function(frame) -- Versioning interface local s = type(frame) local since if s
"string" then since = frame end if since then since = mw.text.trim(since) if since
p.Multilingual = function return Multilingualend -- p.Multilingual
return p