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 – 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, 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

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.frame then

if frame then

Multilingual.frame = frame

else

Multilingual.frame = mw.getCurrentFrame()

end

end

f = function ( a )

return Multilingual.frame: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 ( 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

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,

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