Modul:Multilingual

Aus skandinavien-wiki.net
Version vom 2. Juni 2019, 21:24 Uhr von w>PerfektesChaos (2019-06-01)

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 = "2019-06-01",
                       item   = 47541920 }
local User = { sniffer = "showpreview" }



Multilingual.correction = { -- Frequently mistaken language code
      aze       = "az",
      cz        = "cs",
      deu       = "de",
      dk        = "da",
      ["en-UK"] = "en-GB",
      ["en-uk"] = "en-GB",
      eng       = "en",
      ger       = "de",
      gr        = "el",
      ["in"]    = "id",
      iw        = "he",
      jp        = "ja",
      lat       = "la",
      se        = "sv",
      tj        = "tg"
    }
Multilingual.exotic = { simple = true,
                        no     = true }



local favorite = function ()
    -- Postcondition:
    --     Returns code of current project language
    if not Multilingual.self then
        Multilingual.self = mw.language.getContentLanguage():getCode()
                                                            :lower()
    end
    return Multilingual.self
end -- favorite()



function feasible( 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, ahead )
    -- Attach config or library module
    -- Precondition:
    --     access  -- module title
    --     allow   -- permit non-existence
    --     ahead   -- name of startup procedure, if not access;
    --                false for mw.loadData
    -- Postcondition:
    --     Returns  table or false, with library
    --     Throws error, if not available
    if type( Multilingual.ext ) ~= "table" then
        Multilingual.ext = { }
    end
    if Multilingual.ext[ access ] == false then
    elseif not Multilingual.ext[ access ] then
        local src = "Module:" .. access
        local lucky, got
        if ahead == false then
            lucky, got = pcall( mw.loadData, src )
        else
            lucky, got = pcall( require, src )
        end
        Multilingual.ext[ access ] = false
        if type( got ) == "table" then
            local startup = ahead or access
            Multilingual.ext[ access ] = got
            if type( got[ startup ] ) == "function" then
                Multilingual.ext[ access ] = got[ startup ]()
            end
        end
        if type( Multilingual.ext[ access ] ) ~= "table"  and
           not allow then
            got = string.format( "Module:%s invalid", access )
            error( got, 0 )
        end
    end
    return Multilingual.ext[ access ]
end -- fetch()



local function fill( 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", true, true )
        if type( cnf ) == "table" 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" then
            if type( template.namePat ) == "string"  and
               template.namePat:find( "%s", 1, true ) then
                source = string.format( template.namePat, access )
            end
        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()



function find( 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()



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 s, sin
            for i = 1, #accept do
                s = accept[ i ]
                if not User.trials[ s ] then
                    sin = User.tell:inLanguage( s ):plain()
                    if sin == User.sin then
                        User.self = s
                        break -- for i
                    else
                        User.trials[ s ] = true
                    end
                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
    -- Postcondition:
    --     Returns boolean
    local r
    if type( able ) == "string"  and
       type( another ) == "string" 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
    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
            seek = fetch( "WLink" ).getPlain( seek )
        end
        seek = mw.ustring.lower( seek )
        if Multilingual.isLang( seek ) then
            r = Multilingual.fair( seek )
        else
            local slang = favorite()
            r = find( seek, slang )
            if not r  and  slang ~= "en" then
                r = find( seek, "en" )
            end
        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
    return Multilingual.correction[ attempt:lower() ]  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" )
                                slot = wlink.getTarget( slot )
                            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", true, true )
        if type( cnf ) == "table"  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 support = "Multilingual/names"
        local tLang
        if slang then
            if slang == "*" then
                slang = Multilingual.fair( ask )
            elseif slang == "!" then
                slang = favorite()
            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( support, true )
        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.getScriptName = function ( assigned, alien, add )
    -- Retrieve script name, hopefully linked
    -- Precondition:
    --     assigned  -- string, with ISO 15924 script code
    --     alien     -- string, with ISO language code, or not
    --     add       -- arbitrary additional information
    -- Postcondition:
    --     Returns string
    local r   = assigned
    local src = "Multilingual/scripting"
    if not Multilingual[ src ] then
        Multilingual[ src ] = fetch( src, true, "MultiScript" )
    end
    if Multilingual[ src ] then
        r = Multilingual[ src ].Text.scriptName( assigned, alien, add )
    end
    return r
end -- Multilingual.getScriptName()



Multilingual.i18n = function ( available, alt, frame )
    -- Select translatable message
    -- Precondition:
    --     available  -- table, with mapping language code ./. text
    --     alt        -- string|nil|false, with fallback
    --     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", true, true )
        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.isTrans = function ( ask, assign, about )
    -- Check whether valid transcription for context
    -- Precondition:
    --     ask     -- string, with transcription key
    --     assign  -- string, with language or scripting code
    --     about   -- string or nil, with site scripting code
    -- Postcondition:
    --     Returns boolean
    local r = false
    local t
    if type( Multilingual.trans ) ~= "table" then
        t = fetch( "Multilingual/scripts", true, false )
        if type( t ) == "table" then
            Multilingual.trans = t.trans  or  { }
        else
            Multilingual.trans = { }
        end
    end
    t = Multilingual.trans[ assign ]
    if type( t ) == "table" then
        for k, v in pairs( t ) do
            if v == ask then
                r = true
                break    -- for i
            end
        end -- for k, v
    end
    if not r  and  about == "Latn" then
        r = ( ask == "BGN-PCGN"  or  ask == "ALA-LC" )
    end
    return r
end -- Multilingual.isTrans()



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(), " " )
    elseif s == "table" then
        codes = { }
        for i = 1, #accept do
            s = accept[ i ]
            if type( s ) == "string"  then
                table.insert( codes, s:lower() )
            end
        end -- for i
    else
        codes = { }
        slang = favorite()
        if mw.language.isKnownLanguageTag( slang ) then
            table.insert( codes, slang )
        end
    end
    slang = User.favorize( codes, frame )
    if not slang then
        slang = favorite()  or  "en"
    end
    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
        if not r then
            if feasible( "en", codes ) then
                r = "en"
            elseif #codes > 1  and
                   codes[ 1 ]  and
                   codes[ 1 ]~= "" then
                r = codes[ 1 ]
            end
        end
    end
    return r or favorite() or "en"
end -- Multilingual.userLang()



Multilingual.userLangCode = function ()
    -- Guess a user language code
    -- Postcondition:
    --     Returns code of current best guess
    return User.self  or  favorite()  or  "en"
end -- Multilingual.userLangCode()



Multilingual.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version or "wikidata",
    --                or false
    -- Postcondition:
    --     Returns  string with appropriate version, or false
    local since = atleast
    local r
    if since == "wikidata" then
        local item = Multilingual.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                 item ) )
            if type( entity ) == "table" then
                local vsn = entity:formatPropertyValues( "P348" )
                if type( vsn ) == "table"  and
                   type( vsn.value ) == "string" and
                   vsn.value ~= "" then
                    r = vsn.value
                end
            end
        end
    end
    if not r then
        if not since  or  since <= Multilingual.serial then
            r = Multilingual.serial
        else
            r = false
        end
    end
    return r
end -- Multilingual.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  "" )
    return Multilingual.fallback( s1, s2 )  and  "1"   or   ""
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.getScriptName = function ( frame )
    -- Retrieve script name from ISO 15924 script code, hopefully linked
    --     1  -- code
    --     2  -- optional additional key
    local s1 = mw.text.trim( frame.args[ 1 ]  or  "????" )
    local s2 = frame.args[ 2 ]
    if s2 then
        s2 = mw.text.trim( s2 )
    end
    return Multilingual.getScriptName( s1, false, s2 )
end -- p.getScriptName



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
    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.isTrans = function ( frame )
    -- Check whether valid transcription for context
    --     1     -- string, with transcription key
    --     2     -- string, with language or scripting code
    --     site  -- string or nil, with site scripting code
    local s1   = mw.text.trim( frame.args[ 1 ]  or  "" )
    local s2   = mw.text.trim( frame.args[ 2 ]  or  "" )
    local site = mw.text.trim( frame.args.site  or  "" )
    return Multilingual.isTrans( s1, s2, site )  and  "1"   or   ""
end -- p.isTrans



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.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 Multilingual.failsafe( since )  or  ""
end -- p.failsafe()



p.Multilingual = function ()
    return Multilingual
end -- p.Multilingual

return p