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", slot, r)

end

end

if lapsus and alert then

r = string.format("%sCategory:%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, 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