Dokumentation für das Modul Multilingual[Ansicht] [Bearbeiten] [Versionsgeschichte] [Aktualisieren]

Utilities for multilingual texts and ISO 639 (BCP47) issues etc.

Verwendung in anderen Modulen

Dieses Modul ist notwendig für die Ausführung folgender Module. Bei Anpassungen sollte die Funktionstüchtigkeit der folgenden Module geprüft werden. Benutze dazu auch diese Tracking-Kategorie um Fehler zu finden, die sich dann auf Artikel auswirken:

Hinweise
local Multilingual = { suite   = "Multilingual",                        serial  = "2020-12-10",                        item    = 47541920,                        globals = { ISO15924 = 71584769,                                    WLink    = 19363224 }                      } --[=[ Utilities for multilingual texts and ISO 639 (BCP47) issues etc. * fair() * fallback() * findCode() * fix() * format() * getBase() * getLang() * getName() * i18n() * int() * isLang() * isLangWiki() * isMinusculable() * isRTL() * message() * sitelink() * tabData() * userLang() * userLangCode() * wikibase() * failsafe() loadData: Multilingual/config Multilingual/names ]=] local Failsafe   = Multilingual local GlobalMod  = Multilingual local GlobalData = Multilingual local User       = { sniffer = "showpreview" } Multilingual.globals.Multilingual = Multilingual.item    Multilingual.exotic = { simple = true,                         no     = true } Multilingual.prefer = { cs = true,                         de = true,                         en = true,                         es = true,                         fr = true,                         it = true,                         nl = true,                         pt = true,                         ru = true,                         sv = true }    local foreignModule = function ( access, advanced, append, alt, alert )     -- Fetch global module     -- Precondition:     --     access    -- string, with name of base module     --     advanced  -- true, for require(); else mw.loadData()     --     append    -- string, with subpage part, if any; or false     --     alt       -- number, of wikidata item of root; or false     --     alert     -- true, for throwing error on data problem     -- Postcondition:     --     Returns whatever, probably table     -- 2020-01-01     local storage = access     local finer = function ()                       if append then                           storage = string.format( "%s/%s",                                                    storage,                                                    append )                       end                   end     local fun, lucky, r, suited     if advanced then         fun = require     else         fun = mw.loadData     end     GlobalMod.globalModules = GlobalMod.globalModules or { }     suited = GlobalMod.globalModules[ access ]     if not suited then         finer()         lucky, r = pcall( fun,  "Module:" .. storage )     end     if not lucky then         if not suited  and            type( alt ) == "number"  and            alt > 0 then             suited = string.format( "Q%d", alt )             suited = mw.wikibase.getSitelink( suited )             GlobalMod.globalModules[ access ] = suited or true         end         if type( suited ) == "string" then             storage = suited             finer()             lucky, r = pcall( fun, storage )         end         if not lucky and alert then             error( "Missing or invalid page: " .. storage )         end     end     return r end -- foreignModule()    local fetchData = function ( access )     -- Retrieve translated keyword from commons:Data:****.tab     -- Precondition:     --     access  -- string, with page identification on Commons     --     Returns table, with data, or string, with error message     -- 2019-12-05     local storage = access     local r     if type( storage ) == "string" then         local s         storage = mw.text.trim( storage )         s = storage:lower()         if s:sub( 1, 2 ) == "c:" then             storage = mw.text.trim( storage:sub( 3 ) )             s       = storage:lower()         elseif s:sub( 1, 8 ) == "commons:" then             storage = mw.text.trim( storage:sub( 9 ) )             s       = storage:lower()         end         if s:sub( 1, 5 ) == "data:" then             storage = mw.text.trim( storage:sub( 6 ) )             s       = storage:lower()         end         if s == ""  or  s == ".tab" then             storage = false         elseif s:sub( -4 ) == ".tab" then             storage = storage:sub( 1, -5 ) .. ".tab"         else             storage = storage .. ".tab"         end     end     if type( storage ) == "string" then         local data         if type( GlobalData.TabDATA ) ~= "table" then             GlobalData.TabDATA = { }         end         data = GlobalData.TabDATA[ storage ]         if data then             r = data         else             local lucky             lucky, data = pcall( mw.ext.data.get, storage, "_" )             if type( data ) == "table" then                 data = data.data                 if type( data ) == "table" then                     GlobalData.TabDATA[ storage ] = data                 else                     r = string.format( "%s [[%s%s]]",                                        "INVALID Data:*.tab",                                        "commons:Data:",                                        storage )                 end             else                 r = "BAD PAGE Data:*.tab &#8211; commons:" .. storage             end             if r then                 GlobalData.TabDATA[ storage ] = r                 data = false             else                 r = data             end         end     else         r = "BAD PAGE commons:Data:*.tab"     end     return r end -- fetchData()    local favorites = function ()     -- Provide fallback codes     -- Postcondition:     --     Returns table with sequence of preferred languages     --     * ahead elements     --     * user (not yet accessible)     --     * page content language (not yet accessible)     --     * page name subpage     --     * project     --     * en     local r = Multilingual.polyglott     if not r then         local self = mw.language.getContentLanguage():getCode():lower()         local sub  = mw.title.getCurrentTitle().subpageText         local f    = function ( add )                          local s = add                          for i = 1, #r do                              if r[ i ] == s then                                  s = false                                  break -- for i                              end                          end -- for i                          if s then                              table.insert( r, s )                          end                      end         r = { }         if sub:find( "/", 2, true ) then             sub = sub:match( "/(%l%l%l?)$" )             if sub then                 table.insert( r, sub )             end         elseif sub:find( "^%l%l%l?%-?%a?%a?%a?%a?$" )  and                mw.language.isSupportedLanguage( sub ) then             table.insert( r, sub )         end         f( self )         f( "en" )         Multilingual.polyglott = r     end     return r end -- favorites()    local feasible = function ( ask, accept )     -- Is ask to be supported by application?     -- Precondition:     --     ask     -- lowercase code     --     accept  -- sequence table, with offered lowercase codes     -- Postcondition:     --     nil, or true     local r     for i = 1, #accept do         if accept[ i ] == ask then             r = true             break -- for i         end     end -- for i     return r end -- feasible()    local fetch = function ( access, append )     -- Attach config or library module     -- Precondition:     --     access  -- module title     --     append  -- string, with subpage part of this; or false     -- Postcondition:     --     Returns:  table, with library, or false     local got, sign     if append then         sign = string.format( "%s/%s", access, append )     else         sign = access     end     if type( Multilingual.ext ) ~= "table" then         Multilingual.ext = { }     end     got = Multilingual.ext[ sign ]     if not got  and  got ~= false then         local global = Multilingual.globals[ access ]         local lib    = ( not append  or  append == "config" )         got = foreignModule( access, lib, append, global )         if type( got ) == "table" then             if lib then                 local startup = got[ access ]                 if type( startup ) == "function" then                     got = startup()                 end             end         else             got = false         end         Multilingual.ext[ sign ] = got     end     return got end -- fetch()    local fetchISO639 = function ( access )     -- Retrieve table from commons:Data:ISO639/***.tab     -- Precondition:     --     access  -- string, with subpage identification     -- Postcondition:     --     Returns table, with data, even empty     local r     if type( Multilingual.iso639 ) ~= "table" then         Multilingual.iso639 = { }     end     r = Multilingual.iso639[ access ]     if type( r ) == "nil" then         local raw = fetchData( "ISO639/" .. access )         if type( raw ) == "table" then             local t             r = { }             for i = 1, #raw do                 t = raw[ i ]                 if type( t ) == "table"  and                    type( t[ 1 ] ) == "string"  and                    type( t[ 2 ] ) == "string" then                     r[ t[ 1 ] ] =  t[ 2 ]                 else                     break -- for i                 end             end -- for i         else             r = false         end         Multilingual.iso639[ access ] = r     end     return r or { } end -- fetchISO639()    local fill = function ( access, alien, 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|%s]]", slot, r )                     end                 end                 if lapsus and alert then                     r = string.format( "%s[[Category:%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]]", 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