Modul:Multilingual: Unterschied zwischen den Versionen

Aus skandinavien-wiki.net
w>PerfektesChaos
(2016-11-29)
K (43 Versionen von wikivoyage:Modul:Multilingual importiert)
 
(30 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
local Multilingual = { suite = "Multilingual",
local Multilingual = { suite   = "Multilingual",
                       serial = "2016-11-29" }
                       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






-- local globals
Multilingual.exotic = { simple = true,
local Frame
                        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 favorite = function ()
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:
     -- Postcondition:
     --    Returns code of curent project language
     --    Returns whatever, probably table
     if not Multilingual.self then
     -- 2020-01-01
         Multilingual.self = mw.language.getContentLanguage():getCode()
    local storage = access
                                                            :lower()
    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
     end
     return Multilingual.self
    if type( storage ) == "string" then
end -- favorite()
        local data
        if type( GlobalData.TabDATA ) ~= "table" then
            GlobalData.TabDATA = { }
        end
        data = GlobalData.TabDATA[ storage ]
        if data then
            r = data
        else
            local lucky
            lucky, data = pcall( mw.ext.data.get, storage, "_" )
            if type( data ) == "table" then
                data = data.data
                if type( data ) == "table" then
                    GlobalData.TabDATA[ storage ] = data
                else
                    r = string.format( "%s [[%s%s]]",
                                      "INVALID Data:*.tab",
                                      "commons:Data:",
                                      storage )
                end
            else
                r = "BAD PAGE Data:*.tab – commons:" .. storage
            end
            if r then
                GlobalData.TabDATA[ storage ] = r
                data = false
            else
                r = data
            end
        end
    else
        r = "BAD PAGE commons:Data:*.tab"
    end
     return r
end -- fetchData()
 
 
 
local favorites = function ()
    -- Provide fallback codes
    -- Postcondition:
    --    Returns table with sequence of preferred languages
    --    * ahead elements
    --    * user (not yet accessible)
    --    * page content language (not yet accessible)
    --    * page name subpage
    --    * project
    --    * en
    local r = Multilingual.polyglott
    if not r then
        local self = mw.language.getContentLanguage():getCode():lower()
        local sub  = mw.title.getCurrentTitle().subpageText
        local f    = function ( add )
                        local s = add
                        for i = 1, #r do
                            if r[ i ] == s then
                                s = false
                                break -- for i
                            end
                        end -- for i
                        if s then
                            table.insert( r, s )
                        end
                    end
        r = { }
        if sub:find( "/", 2, true ) then
            sub = sub:match( "/(%l%l%l?)$" )
            if sub then
                table.insert( r, sub )
            end
        elseif sub:find( "^%l%l%l?%-?%a?%a?%a?%a?$" )  and
              mw.language.isSupportedLanguage( sub ) then
            table.insert( r, sub )
        end
        f( self )
        f( "en" )
        Multilingual.polyglott = r
    end
    return r
end -- favorites()
 
 
 
local feasible = function ( ask, accept )
    -- Is ask to be supported by application?
    -- Precondition:
    --    ask    -- lowercase code
    --    accept  -- sequence table, with offered lowercase codes
    -- Postcondition:
    --    nil, or true
    local r
    for i = 1, #accept do
        if accept[ i ] == ask then
            r = true
            break -- for i
        end
    end -- for i
    return r
end -- feasible()






local fetch = function ( access, allow )
local fetch = function ( access, append )
     -- Attach config or library module
     -- Attach config or library module
     -- Precondition:
     -- Precondition:
     --    access  -- module title
     --    access  -- module title
     --    allow  -- permit non-existence
     --    append  -- string, with subpage part of this; or false
     -- Postcondition:
     -- Postcondition:
     --    Returns  table or false, with library
     --    Returns: table, with library, or false
     --     Throws error, if not available
     local got, sign
     if append then
        sign = string.format( "%s/%s", access, append )
    else
        sign = access
    end
     if type( Multilingual.ext ) ~= "table" then
     if type( Multilingual.ext ) ~= "table" then
         Multilingual.ext = { }
         Multilingual.ext = { }
     end
     end
     if Multilingual.ext[ access ] == false then
     got = Multilingual.ext[ sign ]
    elseif not Multilingual.ext[ access ] then
    if not got  and  got ~= false then
         local lucky, got = pcall( require, "Module:" .. access )
        local global = Multilingual.globals[ access ]
         if lucky then
         local lib    = ( not append  or  append == "config" )
            if type( got ) == "table" then
        got = foreignModule( access, lib, append, global )
                 Multilingual.ext[ access ] = got
         if type( got ) == "table" then
                 if type( got[ access ] ) == "function" then
            if lib then
                     Multilingual.ext[ access ] = got[ access ]()
                 local startup = got[ access ]
                 if type( startup ) == "function" then
                     got = startup()
                 end
                 end
             end
             end
        else
            got = false
         end
         end
         if type( Multilingual.ext[ access ] ) ~= "table" then
         Multilingual.ext[ sign ] = got
             if allow then
    end
                 Multilingual.ext[ access ] = false
    return got
             else
end -- fetch()
                got = string.format( "Module:%s invalid", access )
 
                 error( got, 0 )
 
 
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
         end
     end
     end
     return Multilingual.ext[ access ]
     return r
end -- fetch()
end -- fill()






function find( ask, alien )
local find = function ( ask, alien )
     -- Derive language code from name
     -- Derive language code from name
     -- Precondition:
     -- Precondition:
Zeile 80: Zeile 384:




function isSupported( ask, accept )
local fold = function ( frame )
     -- Is ask to be supported by application?
     -- Merge template and #invoke arglist
     -- Precondition:
     -- Precondition:
     --    ask    -- lowercase code
     --    frame  -- template frame
    --    accept  -- space separated/terminated list of lowercase codes
     -- Postcondition:
     -- Postcondition:
     --    nil, or else
     --    table, with combined arglist
     local seek = string.format( " %s ", ask )
    local r = { }
     local supported = string.format( " %s", accept )
     local f = function ( apply )
    return supported:find( seek, 1, true )
                  if type( apply ) == "table"  and
end -- isSupported()
                    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()




Zeile 127: Zeile 517:
     return r or false
     return r or false
end -- Multilingual.fair()
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()




Zeile 141: Zeile 573:
     if #seek > 1 then
     if #seek > 1 then
         if seek:find( "[", 1, true ) then
         if seek:find( "[", 1, true ) then
             seek = fetch( "WLink" ).getPlain( seek )
             local wlink = fetch( "WLink" )
            if wlink  and
              type( wlink.getPlain ) == "function" then
                seek = wlink.getPlain( seek )
            end
         end
         end
         seek = mw.ustring.lower( seek )
         seek = mw.ustring.lower( seek )
Zeile 147: Zeile 583:
             r = Multilingual.fair( seek )
             r = Multilingual.fair( seek )
         else
         else
             local slang = favorite()
             local collection = favorites()
             r = find( seek, slang )
             for i = 1, #collection do
            if not r and  slang ~= "en" then
                r = find( seek, collection[ i ] )
                 r = find( seek, "en" )
                if r then
             end
                    break -- for i
                 end
             end -- for i
         end
         end
     end
     end
     return r
     return r
end -- Multilingual.findCode()
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()




Zeile 228: Zeile 678:
                         r = Multilingual.getName( slang, alien )
                         r = Multilingual.getName( slang, alien )
                         if active then
                         if active then
                             local cnf = fetch( "Multilingual/config",
                             slot = fill( slang, false, frame )
                                              true )
                            if slot then
                            if cnf and
                                local wlink = fetch( "WLink" )
                              type( cnf.getLink ) == "function" then
                                if wlink and
                                if not frame then
                                  type( wlink.getTarget )
                                    if not Frame then
                                                      == "function" then
                                        Frame = mw.getCurrentFrame()
                                    end
                                    frame = Frame
                                end
                                slot = cnf.getLink( slang, frame )
                                if slot then
                                    local wlink = fetch( "WLink" )
                                     slot = wlink.getTarget( slot )
                                     slot = wlink.getTarget( slot )
                                else
                                    lapsus = alert
                                 end
                                 end
                            else
                                lapsus = alert
                             end
                             end
                         end
                         end
Zeile 264: Zeile 707:
                         .. mw.ustring.sub( r, 2 )
                         .. mw.ustring.sub( r, 2 )
                 elseif alter == "d" then
                 elseif alter == "d" then
                     if Multilingual.isMinusculable( slang or "" ) then
                     if Multilingual.isMinusculable( slang, r ) then
                         r = mw.ustring.lower( r )
                         r = mw.ustring.lower( r )
                     end
                     end
                 elseif alter == "m" then
                 elseif alter == "m" then
                     if Multilingual.isMinusculable( slang or "" ) then
                     if Multilingual.isMinusculable( slang, r ) then
                         r = mw.ustring.lower( mw.ustring.sub( r, 1, 1 ) )
                         r = mw.ustring.lower( mw.ustring.sub( r, 1, 1 ) )
                             .. mw.ustring.sub( r, 2 )
                             .. mw.ustring.sub( r, 2 )
Zeile 322: Zeile 765:
     --            .region
     --            .region
     --            .script
     --            .script
    --            .suggest
     --            .year
     --            .year
     --            .extension
     --            .extension
Zeile 385: Zeile 829:
             end
             end
         end -- for i
         end -- for i
        if r.legal then
            r.suggest = Multilingual.fix( r.base )
            if r.suggest then
                r.legal = false
            end
        end
     else
     else
         r = { legal = false }
         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
     end
     return r
     return r
Zeile 406: Zeile 862:
     if ask then
     if ask then
         local slang  = alien
         local slang  = alien
        local support = "Multilingual/names"
         local tLang
         local tLang
         if slang then
         if slang then
Zeile 412: Zeile 867:
                 slang = Multilingual.fair( ask )
                 slang = Multilingual.fair( ask )
             elseif slang == "!" then
             elseif slang == "!" then
                 slang = favorite()
                 slang = favorites()[ 1 ]
             else
             else
                 slang = Multilingual.fair( slang )
                 slang = Multilingual.fair( slang )
Zeile 423: Zeile 878:
         end
         end
         slang = slang:lower()
         slang = slang:lower()
         tLang = fetch( support, true )
         tLang = fetch( "Multilingual", "names" )
         if tLang then
         if tLang then
             tLang = tLang[ slang ]
             tLang = tLang[ slang ]
Zeile 461: Zeile 916:




Multilingual.isLang = function ( ask )
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?
     -- Could this be an ISO language code?
     -- Precondition:
     -- Precondition:
     --    ask -- language code
     --    ask         -- language code
    --    additional  -- true, if Wiki codes like "simple" permitted
     -- Postcondition:
     -- Postcondition:
     --    Returns boolean
     --    Returns boolean
     local r
     local r, s
     local s = Multilingual.getBase( ask )
    if additional then
        s = ask
     else
        s = Multilingual.getBase( ask )
    end
     if s then
     if s then
         r = mw.language.isKnownLanguageTag( s )
         r = mw.language.isKnownLanguageTag( s )
        if r then
            r = not Multilingual.fix( s )
        elseif additional then
            r = Multilingual.exotic[ s ] or false
        end
     else
     else
         r = false
         r = false
Zeile 488: Zeile 1.020:
     local s = Multilingual.getBase( ask )
     local s = Multilingual.getBase( ask )
     if s then
     if s then
         r = mw.language.isSupportedLanguage( s )
         r = mw.language.isSupportedLanguage( s ) or
            Multilingual.exotic[ ask ]
     else
     else
         r = false
         r = false
Zeile 497: Zeile 1.030:




Multilingual.isMinusculable = function ( ask )
Multilingual.isMinusculable = function ( ask, assigned )
     -- Could this language name become downcased?
     -- Could this language name become downcased?
     -- Precondition:
     -- Precondition:
     --    ask  -- language name
     --    ask       -- language code, or nil
     local cnf = fetch( "Multilingual/config", true )
    --    assigned -- language name, or nil
     -- Postcondition:
    --    Returns boolean
     local r = true
     local r = true
     if cnf and  type( cnf.stopMinusculization ) == "string" then
     if ask then
        local s = string.format( " %s ", ask:lower() )
        local cnf = fetch( "Multilingual", "config" )
        if cnf.stopMinusculization:find( s, 1, true ) then
        if cnf then
             r = false
            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
     end
     end
Zeile 514: Zeile 1.062:




Multilingual.kannDeutsch = function ( ask )
Multilingual.isRTL = function ( ask )
     -- Kann man mit diesem Sprachcode deutsch verstehen?
     -- Check whether language is written right-to-left
     -- Precondition:
     -- Precondition:
     --    ask  -- language version specifier
     --    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:
     -- Postcondition:
     --    Returns boolean
     --    Returns string with appropriate text
     local r
     local r
     local s = Multilingual.getBase( ask )
     if type( arglist ) == "table" then
     if s then
        local t = { }
         local support = [=[ de als bar dsb frr gsw hsb ksh |
        local m, p, save
                            lb nds pdc pdt pfl sli stq vmf ]=]
        for k, v in pairs( arglist ) do
         if support:find( string.format( " %s ", s ),  1,  true ) then
            if type( k ) == "string"  and
             r = true
              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
         else
             r = false
             r1 = "INVALID Multilingual.tabData key"
         end
         end
     else
     else
         r = false
         r1 = data
    end
    if r1 then
        r2 = "error"
    elseif data then
        r1, r2 = Multilingual.i18n( data, alt, frame )
        r2 = r2 or "error"
     end
     end
     return r
     return r1, r2
end -- Multilingual.kannDeutsch()
end -- Multilingual.tabData()




Zeile 541: Zeile 1.236:
     -- Try to support user language by application
     -- Try to support user language by application
     -- Precondition:
     -- Precondition:
     --    accept  -- space separated list of available ISO 639 codes
     --    accept  -- string or table
    --                space separated list of available ISO 639 codes
     --                Default: project language, or English
     --                Default: project language, or English
     --    frame  -- frame, if available
     --    frame  -- frame, if available
     -- Postcondition:
     -- Postcondition:
     --    Returns string with appropriate code
     --    Returns string with appropriate code
     local r, slang, support
     local s = type( accept )
     if not frame then
    local codes, r, slang
         if not Frame then
    if s == "string" then
             Frame = mw.getCurrentFrame()
        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
        frame = Frame
     end
     end
    slang = frame:callParserFunction( "int", "lang" ):lower()
     if not r then
     if type( accept ) == "string" then
         local back = favorites()
         support = accept:lower() .. " "
        for i = 1, #back do
    else
            slang = back[ i ]
        support = favorite()
            if feasible( slang, codes ) then
        if mw.language.isKnownLanguageTag( support ) then
                r = slang
             support = string.format( "%s en ", support )
                break -- for i
         else
             end
             support = "en "
        end -- for i
         if not r  and  codes[ 1 ] then
             r = codes[ 1 ]
         end
         end
     end
     end
     if isSupported( slang, support ) then
     return r  or  favorites()[ 1 ]
        r = slang
end -- Multilingual.userLang()
     elseif slang:find( "-", 1, true ) then
 
         slang = Multilingual.getBase( slang )
 
        if isSupported( slang, support ) then
 
            r = slang
Multilingual.userLangCode = function ()
         end
    -- 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
     end
     if not r then
     if type( object ) == "table" then
         if Multilingual.kannDeutsch( slang ) and
         if about and about ~= "labels" then
          isSupported( "de", support ) then
            s = "descriptions"
             r = "de"
        else
             s = "labels"
         end
         end
         if not r then
        object = object[ s ]
             r = support:match( "^(%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
     end
     end
     return r
     return r or  "",  r2
end -- Multilingual.userLang()
end -- Multilingual.wikibase()






function Multilingual.failsafe( assert )
Failsafe.failsafe = function ( atleast )
     -- Retrieve versioning and check for compliance
     -- Retrieve versioning and check for compliance
     -- Precondition:
     -- Precondition:
     --    assert -- string, with required version, or false
     --    atleast -- string, with required version
    --                        or wikidata|item|~|@ or false
     -- Postcondition:
     -- Postcondition:
     --    Returns  string with appropriate version, or false
     --    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
     local r
     if assert and  assert > Multilingual.serial then
     if last  or  link  or  linked  or  since == "wikidata" then
        r = false
        local item = Failsafe.item
     else
        since = false
         r = Multilingual.serial
        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
     end
     return r
     return r
end -- Multilingual.failsafe()
end -- Failsafe.failsafe()




Zeile 611: Zeile 1.416:
     -- Format language code
     -- Format language code
     --    1  -- language code
     --    1  -- language code
     return Multilingual.fair( frame.args[ 1 ] )  or  ""
     local s = mw.text.trim( frame.args[ 1 ] or  "" )
    return Multilingual.fair( s )  or  ""
end -- p.fair
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




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




Zeile 636: Zeile 1.470:
     --    scream    -- category title in case of error
     --    scream    -- category title in case of error
     --    split      -- split pattern, if list expected
     --    split      -- split pattern, if list expected
     --    separator  -- list separator, else assembly
     --    separator  -- list separator, else split
     --    start      -- prepend first element, if any
     --    start      -- prepend first element, if any
     local r
     local r
Zeile 660: Zeile 1.494:
     -- Retrieve base language from possibly combined ISO language code
     -- Retrieve base language from possibly combined ISO language code
     --    1  -- code
     --    1  -- code
     return Multilingual.getBase( frame.args[ 1 ] )  or  ""
     local s = mw.text.trim( frame.args[ 1 ] or  "" )
    return Multilingual.getBase( s )  or  ""
end -- p.getBase
end -- p.getBase


Zeile 672: Zeile 1.507:
     --          * -- native
     --          * -- native
     --          any valid code
     --          any valid code
    local s    = mw.text.trim( frame.args[ 1 ]  or  "" )
     local slang = frame.args[ 2 ]
     local slang = frame.args[ 2 ]
     local r
     local r
    Multilingual.frame = frame
     if slang then
     if slang then
         slang = mw.text.trim( slang )
         slang = mw.text.trim( slang )
     end
     end
     r = Multilingual.getName( frame.args[ 1 ], slang )
     r = Multilingual.getName( s, slang )
     return r or ""
     return r or ""
end -- p.getName
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




Zeile 686: Zeile 1.566:
     -- Could this be an ISO language code?
     -- Could this be an ISO language code?
     --    1  -- code
     --    1  -- code
     local lucky, r = pcall( Multilingual.isLang,
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
                            frame.args[ 1 ] )
     local lucky, r = pcall( Multilingual.isLang, s )
     return r and "1" or ""
     return r and "1" or ""
end -- p.isLang
end -- p.isLang
Zeile 696: Zeile 1.576:
     -- Could this be a Wiki language version?
     -- Could this be a Wiki language version?
     --    1  -- code
     --    1  -- code
     local lucky, r = pcall( Multilingual.isLangWiki,
    -- Returns non-empty, if possibly language version
                            frame.args[ 1 ] )
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
     local lucky, r = pcall( Multilingual.isLangWiki, s )
     return r and "1" or ""
     return r and "1" or ""
end -- p.isLangWiki
end -- p.isLangWiki
Zeile 703: Zeile 1.584:




p.kannDeutsch = function ( frame )
p.isRTL = function ( frame )
     -- Kann man mit diesem Sprachcode deutsch verstehen?
     -- Check whether language is written right-to-left
     --    1  -- code
     --    1  -- string, with language code
     local r = Multilingual.kannDeutsch( frame.args[ 1 ] )
    -- Returns non-empty, if right-to-left
     return r and "1" or ""
     local s = mw.text.trim( frame.args[ 1 ] or  "" )
end -- p.kannDeutsch
     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




Zeile 715: Zeile 1.636:
     -- Which language does the current user prefer?
     -- Which language does the current user prefer?
     --    1  -- space separated list of available ISO 639 codes
     --    1  -- space separated list of available ISO 639 codes
  return Multilingual.userLang( frame.args[ 1 ], frame )
    local s = mw.text.trim( frame.args[ 1 ] or  "" )
    return Multilingual.userLang( s, frame )
end -- p.userLang
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 )
p.failsafe = function ( frame )
     local since = frame.args[ 1 ]
    -- 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
     if since then
         since = mw.text.trim( since )
         since = mw.text.trim( since )
Zeile 728: Zeile 1.678:
         end
         end
     end
     end
     return Multilingual.failsafe( since )  or  ""
     return Failsafe.failsafe( since )  or  ""
end
end -- p.failsafe()





Aktuelle Version vom 27. Januar 2023, 14:51 Uhr

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:


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