# Module:Multilingual

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 &#8211; 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 not got and got ~= false 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, )  
-- Expand language name template  
-- Precondition:  
-- access -- string, with language code  
-- alien -- language code for which to be generated  
-- -- , if available  
-- Postcondition:  
-- Returns string  
local template = Multilingual.tmplLang  
local r  
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. then  
if then  
Multilingual. =  
else  
Multilingual. = mw.getCurrentFrame  
end  
end  
f = function ( a )  
return Multilingual.:expandTemplate{ title = a }  
end  
lucky, s = pcall( f, source )  
if lucky then  
r = s  
end  
end  
end  
return r  
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  
-- Merge template and #invoke arglist  
-- Precondition:  
-- -- template  
-- 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( :getParent )  
f  
return r  
end -- fold  

User.favorize = function ( accept, )  
-- Guess user language  
-- Precondition:  
-- accept -- sequence table, with offered ISO 639 etc. codes  
-- -- , 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. then  
if then  
Multilingual. =  
else  
Multilingual. = mw.getCurrentFrame  
end  
end  
User.sin = Multilingual.: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  
else  
if Multilingual.prefer[ s ] then  
table.insert( order, s )  
else  
table.insert( post, s )  
end  
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,  
, 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  
-- -- 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, )  
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( "%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, )  
-- Select translatable message  
-- Precondition:  
-- available -- table, with mapping language code ./. text  
-- alt -- string|nil|false, with fallback text  
-- -- , 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, )  
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 -to-  
-- Precondition:  
-- ask -- string, with language (or script) code  
-- Returns true, if -to-  
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, )  
-- Show text in best match of user language like system message  
-- Precondition:  
-- arglist -- template arguments  
-- -- , 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, )  
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, )  
-- Make link at local or other site with optimal linktext translation  
-- Precondition:  
-- all -- string or table or number, item ID or entity  
-- -- , 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", )  
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, )  
-- 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  
-- -- , 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, )  
r2 = r2 or "error"  
end  
return r1, r2  
end -- Multilingual.tabData  

Multilingual.userLang = function ( accept, )  
-- 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  
-- -- , 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, )  
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, )  
-- 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  
-- -- , 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, )  
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  
-- Format language code  
-- 1 -- language code  
local s = mw.text.trim( .args[ 1 ] or "" )  
return Multilingual.fair( s ) or ""  
end -- p.fair  

p.fallback = function  
-- 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( .args[ 1 ] or "" )  
local s2 = mw.text.trim( .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  
-- Retrieve language code from language name  
-- 1 -- name in current project language  
local s = mw.text.trim( .args[ 1 ] or "" )  
return Multilingual.findCode( s ) or ""  
end -- p.findCode  

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

p.format = function  
-- 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 .args.link == "1" then  
link = true  
end  
r = Multilingual.format( .args[ 1 ],  
.args.slang,  
.args.shift,  
link,  
.args.scream,  
,  
.args.split,  
.args.separator,  
.args.start )  
return r or ""  
end -- p.format  

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

p.getName = function  
-- 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( .args[ 1 ] or "" )  
local slang = .args[ 2 ]  
local r  
Multilingual. =  
if slang then  
slang = mw.text.trim( slang )  
end  
r = Multilingual.getName( s, slang )  
return r or ""  
end -- p.getName  

p.int = function  
-- Translated system message  
-- 1 -- message ID  
-- lang -- language code  
-- $1, $2, ... -- parameters  
local sysMsg = .args[ 1 ]  
local r  
if sysMsg then  
sysMsg = mw.text.trim( sysMsg )  
if sysMsg ~= "" then  
local n = 0  
local slang = .args.lang  
local i, params, s  
if slang == "" then  
slang = false  
end  
for k, v in pairs( .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 = .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  
-- Could this be an ISO language code?  
-- 1 -- code  
local s = mw.text.trim( .args[ 1 ] or "" )  
local lucky, r = pcall( Multilingual.isLang, s )  
return r and "1" or ""  
end -- p.isLang  

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

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

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

p.sitelink = function  
-- Make link at local or other site with optimal linktext translation  
-- 1 -- item ID  
local s = mw.text.trim( .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, )  
end  
return r or s  
end -- p.sitelink  

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

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

p.wikibase = function  
-- 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( .args[ 1 ] or "" )  
if s ~= "" then  
local s2 = mw.text.trim( .args[ 2 ] or "0" )  
local slang = mw.text.trim( .args.lang or "" )  
local large = ( s2 ~= "" and s2 ~= "0" )  
if slang == "" then  
slang = false  
end  
r = Multilingual.wikibase( s, large, slang, )  
end  
return r or ""  
end -- p.wikibase  

p.failsafe = function  
-- Versioning interface  
local s = type  
local since  
if s == "table" then  
since = .args[ 1 ]  
elseif s == "string" then  
since =  
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