Module:Multilingual
local Multilingual = { suite   = &quot;Multilingual&quot;,
                        serial  = &quot;2020-12-10&quot;,
                        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 = &quot;showpreview&quot; }
 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( &quot;%s/%s&quot;,
                                                    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,  &quot;Module:&quot; .. storage )
     end
     if not lucky then
         if not suited  and
            type( alt ) == &quot;number&quot;  and
            alt &gt; 0 then
             suited = string.format( &quot;Q%d&quot;, alt )
             suited = mw.wikibase.getSitelink( suited )
             GlobalMod.globalModules[ access ] = suited or true
         end
         if type( suited ) == &quot;string&quot; then
             storage = suited
             finer()
             lucky, r = pcall( fun, storage )
         end
         if not lucky and alert then
             error( &quot;Missing or invalid page: &quot; .. 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 ) == &quot;string&quot; then
         local s
         storage = mw.text.trim( storage )
         s = storage:lower()
         if s:sub( 1, 2 ) == &quot;c:&quot; then
             storage = mw.text.trim( storage:sub( 3 ) )
             s       = storage:lower()
         elseif s:sub( 1, 8 ) == &quot;commons:&quot; then
             storage = mw.text.trim( storage:sub( 9 ) )
             s       = storage:lower()
         end
         if s:sub( 1, 5 ) == &quot;data:&quot; then
             storage = mw.text.trim( storage:sub( 6 ) )
             s       = storage:lower()
         end
         if s == &quot;&quot;  or  s == &quot;.tab&quot; then
             storage = false
         elseif s:sub( -4 ) == &quot;.tab&quot; then
             storage = storage:sub( 1, -5 ) .. &quot;.tab&quot;
         else
             storage = storage .. &quot;.tab&quot;
         end
     end
     if type( storage ) == &quot;string&quot; then
         local data
         if type( GlobalData.TabDATA ) ~= &quot;table&quot; then
             GlobalData.TabDATA = { }
         end
         data = GlobalData.TabDATA[ storage ]
         if data then
             r = data
         else
             local lucky
             lucky, data = pcall( mw.ext.data.get, storage, &quot;_&quot; )
             if type( data ) == &quot;table&quot; then
                 data = data.data
                 if type( data ) == &quot;table&quot; then
                     GlobalData.TabDATA[ storage ] = data
                 else
                     r = string.format( &quot;%s [[%s%s]]&quot;,
                                        &quot;INVALID Data:*.tab&quot;,
                                        &quot;commons:Data:&quot;,
                                        storage )
                 end
             else
                 r = &quot;BAD PAGE Data:*.tab &amp;#8211; commons:&quot; .. storage
             end
             if r then
                 GlobalData.TabDATA[ storage ] = r
                 data = false
             else
                 r = data
             end
         end
     else
         r = &quot;BAD PAGE commons:Data:*.tab&quot;
     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( &quot;/&quot;, 2, true ) then
             sub = sub:match( &quot;/(%l%l%l?)$&quot; )
             if sub then
                 table.insert( r, sub )
             end
         elseif sub:find( &quot;^%l%l%l?%-?%a?%a?%a?%a?$&quot; )  and
                mw.language.isSupportedLanguage( sub ) then
             table.insert( r, sub )
         end
         f( self )
         f( &quot;en&quot; )
         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( &quot;%s/%s&quot;, access, append )
     else
         sign = access
     end
     if type( Multilingual.ext ) ~= &quot;table&quot; 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 == &quot;config&quot; )
         got = foreignModule( access, lib, append, global )
         if type( got ) == &quot;table&quot; then
             if lib then
                 local startup = got[ access ]
                 if type( startup ) == &quot;function&quot; 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 ) ~= &quot;table&quot; then
         Multilingual.iso639 = { }
     end
     r = Multilingual.iso639[ access ]
     if type( r ) == &quot;nil&quot; then
         local raw = fetchData( &quot;ISO639/&quot; .. access )
         if type( raw ) == &quot;table&quot; then
             local t
             r = { }
             for i = 1, #raw do
                 t = raw[ i ]
                 if type( t ) == &quot;table&quot;  and
                    type( t[ 1 ] ) == &quot;string&quot;  and
                    type( t[ 2 ] ) == &quot;string&quot; 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 ) ~= &quot;table&quot; then
         local cnf = fetch( &quot;Multilingual&quot;, &quot;config&quot; )
         if cnf then
             template = cnf.tmplLang
         end
     end
     if type( template ) == &quot;table&quot; then
         local source = template.title
         local f, lucky, s
         Multilingual.tmplLang = template
         if type( source ) ~= &quot;string&quot;  and
            type( template.namePat ) == &quot;string&quot;  and
            template.namePat:find( &quot;%s&quot;, 1, true ) then
             source = string.format( template.namePat, access )
         end
         if type( source ) == &quot;string&quot; 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, &quot;all&quot; )
     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 ) == &quot;table&quot;  and
                      type( apply.args ) == &quot;table&quot; then
                       for k, v in pairs( apply.args ) do
                           v = mw.text.trim( v )
                           if v ~= &quot;&quot; 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( &quot;int&quot;,
                                                            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 &gt; 2 then
                         if s:find( &quot;-&quot;, 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 == &quot;table&quot; then
         q = ask
     elseif s == &quot;string&quot; then
         q = Multilingual.getLang( ask )
     end
     if q  and
        q.legal  and
        mw.language.isKnownLanguageTag( q.base ) then
         r = q.base
         if q.n &gt; 1 then
             local order = { &quot;extlang&quot;,
                             &quot;script&quot;,
                             &quot;region&quot;,
                             &quot;other&quot;,
                             &quot;extension&quot; }
             for i = 1, #order do
                 s = q[ order[ i ] ]
                 if s then
                     r =  string.format( &quot;%s-%s&quot;, 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 ) == &quot;string&quot;  and  #able &gt; 0 then
         if type( another ) == &quot;string&quot;  and  #another &gt; 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 ] == &quot;en&quot; then
                     local d = fetchISO639( &quot;fallback&quot; )
                     if type( d ) == &quot;table&quot;  and
                        type( d[ s ] ) == &quot;string&quot; then
                         r = mw.text.split( d[ s ], &quot;|&quot; )
                         table.insert( r, &quot;en&quot; )
                     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 &gt; 1 then
         if seek:find( &quot;[&quot;, 1, true ) then
             local wlink = fetch( &quot;WLink&quot; )
             if wlink  and
                type( wlink.getPlain ) == &quot;function&quot; 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( &quot;correction&quot; )[ 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, &quot;*&quot;: native
     --                  -- &quot;!&quot;: current project
     --                  -- &quot;#&quot;: code, downcased, space separated
     --                  -- &quot;-&quot;: code, mixcase, space separated
     --                  -- any valid code
     --     alter     -- capitalize, if &quot;c&quot;; downcase all, if &quot;d&quot;
     --                  capitalize first item only, if &quot;f&quot;
     --                  downcase every first word only, if &quot;m&quot;
     --     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 == &quot;#&quot;  or  alien == &quot;-&quot; then
                 separator = &quot; &quot;
             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( &quot;%s%s%s&quot;,
                                            r, separator, slang )
                     else
                         r = slang
                         if shift == &quot;f&quot; then
                             shift = &quot;d&quot;
                         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 == &quot;&quot; then
                 r = false
             else
                 local lapsus, slot
                 slang = Multilingual.findCode( single )
                 if slang then
                     if alien == &quot;-&quot; then
                         r = slang
                     elseif alien == &quot;#&quot; then
                         r = slang:lower()
                     else
                         r = Multilingual.getName( slang, alien )
                         if active then
                             slot = fill( slang, false, frame )
                             if slot then
                                 local wlink = fetch( &quot;WLink&quot; )
                                 if wlink  and
                                    type( wlink.getTarget )
                                                        == &quot;function&quot; 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 == &quot;c&quot; or alter == &quot;f&quot; then
                     r = mw.ustring.upper( mw.ustring.sub( r, 1, 1 ) )
                         .. mw.ustring.sub( r, 2 )
                 elseif alter == &quot;d&quot; then
                     if Multilingual.isMinusculable( slang, r ) then
                         r = mw.ustring.lower( r )
                     end
                 elseif alter == &quot;m&quot; 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( &quot;[[%s]]&quot;, r )
                     else
                         r = string.format( &quot;[[%s|%s]]&quot;, slot, r )
                     end
                 end
                 if lapsus and alert then
                     r = string.format( &quot;%s[[Category:%s]]&quot;, 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( &quot;^%s*(%a%a%a?)-?%a*%s*$&quot; )
         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, &quot;-&quot; )
     local s    = tags[ 1 ]
     local r
     if s:match( &quot;^%a%a%a?$&quot; ) 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( &quot;%a%a&quot; ) then
                     r.legal = false
                 else
                     r.region = s:upper()
                 end
             elseif #s == 4 then
                 if s:match( &quot;%a%a%a%a&quot; ) then
                     r.legal = ( not r.script )
                     r.script = s:sub( 1, 1 ):upper() ..
                                s:sub( 2 ):lower()
                 elseif s:match( &quot;20%d%d&quot; )  or
                        s:match( &quot;1%d%d%d&quot; ) 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( &quot;%a%a%a&quot; ) then
                     r.legal = false
                 else
                     r.extlang = s:lower()
                 end
             elseif #s == 1 then
                 s = s:lower()
                 if s:match( &quot;[tux]&quot; ) then
                     r.extension = s
                     for k = i + 1, r.n do
                         s = tags[ k ]
                         if s:match( &quot;^%w+$&quot; ) then
                             r.extension = string.format( &quot;%s-%s&quot;,
                                                          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( &quot;%a%a%a&quot; )
                 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( &quot;Multilingual&quot;, &quot;config&quot; )
         if cnf  and  type( cnf.scream ) == &quot;string&quot; 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, &quot;*&quot;: native
     --               -- &quot;!&quot;: 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 == &quot;*&quot; then
                 slang = Multilingual.fair( ask )
             elseif slang == &quot;!&quot; then
                 slang = favorites()[ 1 ]
             else
                 slang = Multilingual.fair( slang )
             end
         else
             slang = Multilingual.fair( ask )
         end
         if not slang then
             slang = ask or &quot;?????&quot;
         end
         slang = slang:lower()
         tLang = fetch( &quot;Multilingual&quot;, &quot;names&quot; )
         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 == &quot;&quot; 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 ) == &quot;table&quot; then
         local codes = { }
         local trsl  = { }
         local slang
         for k, v in pairs( available ) do
             if type( k ) == &quot;string&quot;  and
                type( v ) == &quot;string&quot; 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 == &quot;&quot; then
                 r1 = false
             else
                 r2 = slang
             end
         end
     end
     if not r1  and  type( alt ) == &quot;string&quot; then
         r1 = mw.text.trim( alt )
         if r1 == &quot;&quot; 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 ) == &quot;string&quot; then
             o:inLanguage( alien:lower() )
         end
         if type( apply ) == &quot;table&quot; 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 &quot;simple&quot; 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( &quot;Multilingual&quot;, &quot;config&quot; )
         if cnf then
             local s = string.format( &quot; %s &quot;, ask:lower() )
             if type( cnf.stopMinusculization ) == &quot;string&quot;
                and  cnf.stopMinusculization:find( s, 1, true ) then
                 r = false
             end
             if r  and  assigned
                and  type( cnf.seekMinusculization ) == &quot;string&quot;
                and  cnf.seekMinusculization:find( s, 1, true )
                and  type( cnf.scanMinusculization ) == &quot;string&quot; then
                 local scan = assigned:gsub( &quot;[%(%)]&quot;, &quot; &quot; ) .. &quot; &quot;
                 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 ) ~= &quot;boolean&quot; then
         local bib = fetch( &quot;ISO15924&quot; )
         if type( bib ) == &quot;table&quot;  and
            type( bib.isRTL ) == &quot;function&quot; 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 ) == &quot;table&quot; then
         local t = { }
         local m, p, save
         for k, v in pairs( arglist ) do
             if type( k ) == &quot;string&quot;  and
                type( v ) == &quot;string&quot; then
                 v = mw.text.trim( v )
                 if v ~= &quot;&quot; then
                     if k:match( &quot;^%l%l&quot; ) then
                         t[ k ] = v
                     elseif k:match( &quot;^%$%d$&quot; )  and  k ~= &quot;$0&quot; then
                         p = p or { }
                         k = tonumber( k:match( &quot;^%$(%d)$&quot; ) )
                         p[ k ] = v
                         if not m  or  k &gt; m then
                             m = k
                         end
                     end
                 end
             end
         end -- for k, v
         if type( arglist[ &quot;-&quot; ] ) == &quot;string&quot; then
             save = arglist[ arglist[ &quot;-&quot; ] ]
         end
         r = Multilingual.i18n( t, save, frame )
         if p  and  r  and  r:find( &quot;$&quot;, 1, true ) then
             t = { }
             for i = 1, m do
                 t[ i ] = p[ i ]  or  &quot;&quot;
             end -- for i
             r = mw.message.newRawMessage( r, t ):plain()
         end
     end
     return r  or  &quot;&quot;
 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 == &quot;table&quot; then
         object = all
     elseif s == &quot;string&quot; then
         object = mw.wikibase.getEntity( all )
     elseif s == &quot;number&quot; then
         object = mw.wikibase.getEntity( string.format( &quot;Q%d&quot;, all ) )
     end
     if type( object ) == &quot;table&quot; then
         local collection = object.sitelinks
         local entry
         s = false
         if type( collection ) == &quot;table&quot; then
             Multilingual.site = Multilingual.site  or
                                 mw.wikibase.getGlobalSiteId()
             entry = collection[ Multilingual.site ]
             if entry then
                 s = &quot;:&quot; .. entry.title
             elseif collection.enwiki then
                 s = &quot;w:en:&quot; .. collection.enwiki.title
             end
         end
         r = Multilingual.wikibase( object, &quot;labels&quot;, frame )
         if s then
             if s == &quot;:&quot; .. r then
                 r = string.format( &quot;[[%s]]&quot;, s )
             else
                 r = string.format( &quot;[[%s|%s]]&quot;, s, r )
             end
         end
     end
     return r  or  &quot;&quot;
 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 &quot;error&quot;
     local data = fetchData( access )
     local r1, r2
     if  type( data ) == &quot;table&quot; then
         if type( at ) == &quot;string&quot; then
             local seek = mw.text.trim( at )
             if seek == &quot;&quot; then
                 r1 = &quot;EMPTY Multilingual.tabData key&quot;
             else
                 local e, poly
                 for i = 1, #data do
                     e = data[ i ]
                     if type( e ) == &quot;table&quot; then
                         if e[ 1 ] == seek then
                             if type( e[ 2 ] ) == &quot;table&quot; then
                                 poly = e[ 2 ]
                             else
                                 r1 = &quot;INVALID Multilingual.tabData bad #&quot;
                                                          .. tostring( i )
                             end
                             break   -- for i
                         end
                     else
                         break   -- for i
                     end
                 end   -- for i
                 if poly then
                     data = poly
                 else
                     r1 = &quot;UNKNOWN Multilingual.tabData key: &quot; .. seek
                 end
             end
         else
             r1 = &quot;INVALID Multilingual.tabData key&quot;
         end
     else
         r1 = data
     end
     if r1 then
         r2 = &quot;error&quot;
     elseif data then
         r1, r2 = Multilingual.i18n( data, alt, frame )
         r2 = r2 or &quot;error&quot;
     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 == &quot;string&quot; then
         codes = mw.text.split( accept:lower(), &quot;%s+&quot; )
     elseif s == &quot;table&quot; then
         codes = { }
         for i = 1, #accept do
             s = accept[ i ]
             if type( s ) == &quot;string&quot;  and
                s ~= &quot;&quot; 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( &quot;-&quot;, 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 &quot;descriptions&quot; or false &quot;labels&quot;
     --     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 == &quot;table&quot; then
         object = all
     elseif s == &quot;string&quot; then
         object = mw.wikibase.getEntity( all )
     end
     if type( object ) == &quot;table&quot; then
         if about  and  about ~= &quot;labels&quot; then
             s = &quot;descriptions&quot;
         else
             s = &quot;labels&quot;
         end
         object = object[ s ]
         if type( object ) == &quot;table&quot; 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  &quot;&quot;,   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 == &quot;~&quot; )
     local linked  = ( since == &quot;@&quot; )
     local link    = ( since == &quot;item&quot; )
     local r
     if last  or  link  or  linked  or  since == &quot;wikidata&quot; then
         local item = Failsafe.item
         since = false
         if type( item ) == &quot;number&quot;  and  item &gt; 0 then
             local suited = string.format( &quot;Q%d&quot;, item )
             if link then
                 r = suited
             else
                 local entity = mw.wikibase.getEntity( suited )
                 if type( entity ) == &quot;table&quot; then
                     local seek = Failsafe.serialProperty or &quot;P348&quot;
                     local vsn  = entity:formatPropertyValues( seek )
                     if type( vsn ) == &quot;table&quot;  and
                        type( vsn.value ) == &quot;string&quot;  and
                        vsn.value ~= &quot;&quot; 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 ) == &quot;nil&quot; then
         if not since  or  since &lt;= 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  &quot;&quot; )
     return Multilingual.fair( s )  or  &quot;&quot;
 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  &quot;&quot; )
     local s2 = mw.text.trim( frame.args[ 2 ]  or  &quot;&quot; )
     local r  = Multilingual.fallback( s1, s2 )
     if type( r ) == &quot;table&quot; then
         r = r[ 1 ]
     else
         r = r  and  &quot;1&quot;   or   &quot;&quot;
     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  &quot;&quot; )
     return Multilingual.findCode( s )  or  &quot;&quot;
 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 &quot;&quot;
 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 &quot;c&quot;; downcase, if &quot;d&quot;
     --                   capitalize first item only, if &quot;f&quot;
     --     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 == &quot;1&quot; 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 &quot;&quot;
 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  &quot;&quot; )
     return Multilingual.getBase( s )  or  &quot;&quot;
 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  &quot;&quot; )
     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 &quot;&quot;
 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 ~= &quot;&quot; then
             local n     = 0
             local slang = frame.args.lang
             local i, params, s
             if slang == &quot;&quot; then
                 slang = false
             end
             for k, v in pairs( frame.args ) do
                 if type( k ) == &quot;string&quot; then
                     s = k:match( &quot;^%$(%d+)$&quot; )
                     if s then
                         i = tonumber( s )
                         if i &gt; n then
                             n = i
                         end
                     end
                 end
             end -- for k, v
             if n &gt; 0 then
                 local s
                 params = { }
                 for i = 1, n do
                     s = frame.args[ &quot;$&quot; .. tostring( i ) ]  or  &quot;&quot;
                     table.insert( params, s )
                 end -- for i
             end
             r = Multilingual.int( sysMsg, slang, params )
         end
     end
     return r or &quot;&quot;
 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  &quot;&quot; )
     local lucky, r = pcall( Multilingual.isLang, s )
     return r and &quot;1&quot; or &quot;&quot;
 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  &quot;&quot; )
     local lucky, r = pcall( Multilingual.isLangWiki, s )
     return r and &quot;1&quot; or &quot;&quot;
 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  &quot;&quot; )
     return Multilingual.isRTL( s ) and &quot;1&quot; or &quot;&quot;
 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  &quot;&quot; )
     local r
     if s:match( &quot;^%d+$&quot;) then
         r = tonumber( s )
     elseif s:match( &quot;^Q%d+$&quot;) 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  &quot;&quot; )
     return Multilingual.userLang( s, frame )
 end -- p.userLang
 
 
 
 p.wikibase = function ( frame )
     -- Optimal translation of wikibase component
     --     1  -- object ID
     --     2  -- 1 for &quot;descriptions&quot;, 0 for &quot;labels&quot;.
     --           or either &quot;descriptions&quot; or &quot;labels&quot;
     local r
     local s = mw.text.trim( frame.args[ 1 ]  or  &quot;&quot; )
     if s ~= &quot;&quot; then
         local s2    = mw.text.trim( frame.args[ 2 ]  or  &quot;0&quot; )
         local slang = mw.text.trim( frame.args.lang  or  &quot;&quot; )
         local large = ( s2 ~= &quot;&quot;  and  s2 ~= &quot;0&quot; )
         if slang == &quot;&quot; then
             slang = false
         end
         r = Multilingual.wikibase( s, large, slang, frame )
     end
     return r or &quot;&quot;
 end -- p.wikibase
 
 
 
 p.failsafe = function ( frame )
     -- Versioning interface
     local s = type( frame )
     local since
     if s == &quot;table&quot; then
         since = frame.args[ 1 ]
     elseif s == &quot;string&quot; then
         since = frame
     end
     if since then
         since = mw.text.trim( since )
         if since == &quot;&quot; then
             since = false
         end
     end
     return Failsafe.failsafe( since )  or  &quot;&quot;
 end -- p.failsafe()
 
 
 
 p.Multilingual = function ()
     return Multilingual
 end -- p.Multilingual