Modul:Multilingual: Unterschied zwischen den Versionen

Aus FreeWiki
Wechseln zu: Navigation, Suche
te>PerfektesChaos
(+ format|start=)
K (36 Versionen importiert)
 
(33 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
--[=[ 2014-10-16
+
local Multilingual = { suite  = "Multilingual",
Multilingual
+
                      serial = "2019-06-01",
]=]
+
                      item  = 47541920 }
 +
local User = { sniffer = "showpreview" }
  
  
  
local Multilingual = { }
+
Multilingual.correction = { -- Frequently mistaken language code
local Frame
+
      aze      = "az",
local Got = { }
+
      cz        = "cs",
local SelfLang
+
      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 fetch = function ( access, allow )
+
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
 
     -- Attach config or library module
 
     -- Precondition:
 
     -- Precondition:
 
     --    access  -- module title
 
     --    access  -- module title
 
     --    allow  -- permit non-existence
 
     --    allow  -- permit non-existence
 +
    --    ahead  -- name of startup procedure, if not access;
 +
    --                false for mw.loadData
 
     -- Postcondition:
 
     -- Postcondition:
 
     --    Returns  table or false, with library
 
     --    Returns  table or false, with library
 
     --    Throws error, if not available
 
     --    Throws error, if not available
     if Got[ access ] == false then
+
     if type( Multilingual.ext ) ~= "table" then
     elseif not Got[ access ] then
+
        Multilingual.ext = { }
         local lucky, got = pcall( require, "Module:" .. access )
+
    end
         if lucky then
+
    if Multilingual.ext[ access ] == false then
             if type( got ) == "table" then
+
     elseif not Multilingual.ext[ access ] then
                Got[ access ] = got
+
         local src = "Module:" .. access
                if type( got[ access ] ) == "function" then
+
        local lucky, got
                    Got[ access ] = got[ access ]()
+
         if ahead == false then
                end
+
            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
            got = "Module" .. access .. " invalid"
 
 
         end
 
         end
         if type( Got[ access ] ) ~= "table" then
+
         if type( Multilingual.ext[ access ] ) ~= "table" and
 +
          not allow then
 +
            got = string.format( "Module:%s invalid", access )
 
             error( got, 0 )
 
             error( got, 0 )
 
         end
 
         end
 
     end
 
     end
     return Got[ access ]
+
     return Multilingual.ext[ access ]
 
end -- fetch()
 
end -- fetch()
  
  
  
function isSupported( ask, accept )
+
local function fill( access, alien, frame )
     -- Is ask to supported by application?
+
    -- 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:
 
     -- Precondition:
     --    ask     -- lowercase code
+
     --    able     -- language version specifier to be supported
     --    accept -- space separated/terminated list of lowercase codes
+
     --    another -- language specifier of a possible replacement
 
     -- Postcondition:
 
     -- Postcondition:
     --    nil, or else
+
     --    Returns boolean
     local seek = string.format( " %s ", ask )
+
     local r
    local supported = string.format( " %s", accept )
+
    if type( able ) == "string"  and
     return supported:find( seek, 1, true )
+
      type( another ) == "string" then
end -- isSupported()
+
        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 )
 
Multilingual.findCode = function ( ask )
     -- Retrieve code of local (current project) language name
+
     -- Retrieve code of local (current project or English) language name
 
     -- Precondition:
 
     -- Precondition:
 
     --    ask  -- string, with presumable language name
 
     --    ask  -- string, with presumable language name
Zeile 70: Zeile 295:
 
         seek = mw.ustring.lower( seek )
 
         seek = mw.ustring.lower( seek )
 
         if Multilingual.isLang( seek ) then
 
         if Multilingual.isLang( seek ) then
             r = seek
+
             r = Multilingual.fair( seek )
 
         else
 
         else
             local codes
+
             local slang = favorite()
             if not SelfLang then
+
            r = find( seek, slang )
                 SelfLang = mw.language.getContentLanguage():getCode()
+
             if not r  and  slang ~= "en" then
 +
                 r = find( seek, "en" )
 
             end
 
             end
            codes = mw.language.fetchLanguageNames( SelfLang, "all" )
 
            for k, v in pairs( codes ) do
 
                if mw.ustring.lower( v ) == seek then
 
                    r = k
 
                    break -- for k, v
 
                end
 
            end -- for k, v
 
 
         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
 +
    return Multilingual.correction[ attempt:lower() ]  or  false
 +
end -- Multilingual.fix()
  
  
Zeile 98: Zeile 328:
 
     --                  -- nil, false, "*": native
 
     --                  -- nil, false, "*": native
 
     --                  -- "!": current project
 
     --                  -- "!": current project
 +
    --                  -- "#": code, downcased, space separated
 +
    --                  -- "-": code, mixcase, space separated
 
     --                  -- any valid code
 
     --                  -- any valid code
     --    alter    -- capitalize, if "c"; downcase, if "d"
+
     --    alter    -- capitalize, if "c"; downcase all, if "d"
 
     --                  capitalize first item only, if "f"
 
     --                  capitalize first item only, if "f"
 +
    --                  downcase every first word only, if "m"
 
     --    active    -- link items, if true
 
     --    active    -- link items, if true
 
     --    alert    -- string with category title in case of error
 
     --    alert    -- string with category title in case of error
Zeile 108: Zeile 341:
 
     --    ahead    -- string to prepend first element, if any
 
     --    ahead    -- string to prepend first element, if any
 
     -- Postcondition:
 
     -- Postcondition:
     --    Returns string, or false
+
     --    Returns string, or false if apply empty
 
     local r = false
 
     local r = false
 
     if apply then
 
     if apply then
Zeile 118: Zeile 351:
 
             if adjacent then
 
             if adjacent then
 
                 separator = adjacent
 
                 separator = adjacent
 +
            elseif alien == "#"  or  alien == "-" then
 +
                separator = " "
 
             else
 
             else
 
                 separator = assembly
 
                 separator = assembly
Zeile 147: Zeile 382:
 
                 slang = Multilingual.findCode( single )
 
                 slang = Multilingual.findCode( single )
 
                 if slang then
 
                 if slang then
                     r = Multilingual.getName( slang, alien )
+
                     if alien == "-" then
                     if active then
+
                        r = slang
                         local cnf = fetch( "Multilingual/config", true )
+
                     elseif alien == "#" then
                         if cnf then
+
                         r = slang:lower()
                            if not frame then
+
                    else
                                if not Frame then
+
                         r = Multilingual.getName( slang, alien )
                                    Frame = mw.getCurrentFrame()
+
                        if active then
                                end
+
                             slot = fill( slang, false, frame )
                                frame = Frame
 
                            end
 
                             slot = cnf.getLink( slang, frame )
 
 
                             if slot then
 
                             if slot then
                                 slot = fetch( "WLink" ).getTarget( slot )
+
                                 local wlink = fetch( "WLink" )
 +
                                slot = wlink.getTarget( slot )
 
                             else
 
                             else
 
                                 lapsus = alert
 
                                 lapsus = alert
Zeile 175: Zeile 408:
 
                     lapsus = alert
 
                     lapsus = alert
 
                 end
 
                 end
                 if alter == "c" or alter == "f" then
+
                 if not r then
 +
                    r = single
 +
                elseif alter == "c" or alter == "f" then
 
                     r = mw.ustring.upper( mw.ustring.sub( r, 1, 1 ) )
 
                     r = mw.ustring.upper( mw.ustring.sub( r, 1, 1 ) )
 
                         .. mw.ustring.sub( r, 2 )
 
                         .. mw.ustring.sub( r, 2 )
 
                 elseif alter == "d" then
 
                 elseif alter == "d" then
                     r = mw.ustring.lower( r )
+
                     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
 
                 end
 
                 if slot then
 
                 if slot then
Zeile 188: Zeile 430:
 
                     end
 
                     end
 
                 end
 
                 end
                 if lapsus then
+
                 if lapsus and alert then
 
                     r = string.format( "%s[[Category:%s]]", r, alert )
 
                     r = string.format( "%s[[Category:%s]]", r, alert )
 
                 end
 
                 end
Zeile 218: Zeile 460:
 
     return r
 
     return r
 
end -- Multilingual.getBase()
 
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()
  
  
Zeile 233: Zeile 569:
 
     local r
 
     local r
 
     if ask then
 
     if ask then
         local slang = alien
+
         local slang   = alien
 +
        local support = "Multilingual/names"
 +
        local tLang
 
         if slang then
 
         if slang then
 
             if slang == "*" then
 
             if slang == "*" then
                 slang = nil
+
                 slang = Multilingual.fair( ask )
 
             elseif slang == "!" then
 
             elseif slang == "!" then
                 if not SelfLang then
+
                 slang = favorite()
                    SelfLang = mw.language.getContentLanguage():getCode()
+
            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
                 slang = SelfLang
+
            end
             else
+
            if tLang then
                slang = slang:lower()
+
                 r = tLang[ ask ]
 +
             end
 +
        end
 +
        if not r then
 +
            r = mw.language.fetchLanguageName( ask:lower(), slang )
 +
            if r == "" then
 +
                r = false
 
             end
 
             end
 
         end
 
         end
        r = mw.language.fetchLanguageName( ask, slang )
 
 
     else
 
     else
 
         r = false
 
         r = false
Zeile 255: Zeile 625:
  
  
Multilingual.isLang = function ( ask )
+
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?
 
     -- 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 282: Zeile 750:
 
     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 291: Zeile 760:
  
  
Multilingual.kannDeutsch = function ( ask )
+
Multilingual.isMinusculable = function ( ask, assigned )
     -- Kann man mit diesem Sprachcode deutsch verstehen?
+
     -- Could this language name become downcased?
 
     -- Precondition:
 
     -- Precondition:
     --    ask  -- language version specifier
+
     --    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:
 
     -- Postcondition:
 
     --    Returns boolean
 
     --    Returns boolean
     local r
+
     local r = false
     local s = Multilingual.getBase( ask )
+
     local t
    if s then
+
    if type( Multilingual.trans ) ~= "table" then
         local support = [=[ de als bar dsb frr gsw hsb ksh |
+
         t = fetch( "Multilingual/scripts", true, false )
                            lb nds pdc pdt pfl sli stq vmf ]=]
+
         if type( t ) == "table" then
         if support:find( string.format( " %s ", s ),  1,  true ) then
+
             Multilingual.trans = t.trans  or  { }
             r = true
 
 
         else
 
         else
             r = false
+
             Multilingual.trans = { }
 
         end
 
         end
     else
+
     end
         r = false
+
    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
 
     end
 
     return r
 
     return r
end -- Multilingual.kannDeutsch()
+
end -- Multilingual.isTrans()
  
  
Zeile 318: Zeile 830:
 
     -- 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
         frame = mw.getCurrentFrame()
+
     if s == "string" then
     end
+
         codes = mw.text.split( accept:lower(), " " )
    slang = frame:callParserFunction( "int", "lang" ):lower()
+
     elseif s == "table" then
    if type( accept ) == "string" then
+
        codes = { }
        support = accept:lower() .. " "
+
        for i = 1, #accept do
 +
            s = accept[ i ]
 +
            if type( s ) == "string" then
 +
                table.insert( codes, s:lower() )
 +
            end
 +
        end -- for i
 
     else
 
     else
         support = mw.language.getContentLanguage():getCode()
+
         codes = { }
         if mw.language.isKnownLanguageTag( support ) then
+
        slang = favorite()
             support = string.format( "%s en ", support )
+
         if mw.language.isKnownLanguageTag( slang ) then
        else
+
             table.insert( codes, slang )
            support = "en "
 
 
         end
 
         end
 
     end
 
     end
     if isSupported( slang, support ) then
+
    slang = User.favorize( codes, frame )
 +
    if not slang then
 +
        slang = favorite()  or  "en"
 +
    end
 +
     if feasible( slang, codes ) then
 
         r = slang
 
         r = slang
 
     elseif slang:find( "-", 1, true ) then
 
     elseif slang:find( "-", 1, true ) then
         slang = Multilingual.getBase()
+
         slang = Multilingual.getBase( slang )
         if isSupported( slang, support ) then
+
         if feasible( slang, codes ) then
 
             r = slang
 
             r = slang
 
         end
 
         end
 
     end
 
     end
 
     if not r then
 
     if not r then
         if Multilingual.kannDeutsch( slang ) and
+
         local others = mw.language.getFallbacksFor( slang )
          isSupported( "de", support ) then
+
        for i = 1, #others do
             r = "de"
+
            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
         if not r then
+
    end
             r = support:match( "^(%S+) " )
+
    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
 
     end
 
     end
 
     return r
 
     return r
end -- Multilingual.userLang()
+
end -- Multilingual.failsafe()
  
  
Zeile 362: Zeile 939:
 
-- Export
 
-- Export
 
local p = { }
 
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
  
  
Zeile 368: Zeile 965:
 
     -- 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 385: Zeile 993:
 
     --    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 409: Zeile 1.017:
 
     -- 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 421: Zeile 1.030:
 
     --          * -- 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.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
  
  
Zeile 435: Zeile 1.103:
 
     -- 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 445: Zeile 1.113:
 
     -- Could this be a Wiki language version?
 
     -- Could this be a Wiki language version?
 
     --    1  -- code
 
     --    1  -- code
     local lucky, r = pcall( Multilingual.isLangWiki,
+
    local s = mw.text.trim( frame.args[ 1 ]  or  "" )
                            frame.args[ 1 ] )
+
     local lucky, r = pcall( Multilingual.isLangWiki, s )
 
     return r and "1" or ""
 
     return r and "1" or ""
 
end -- p.isLangWiki
 
end -- p.isLangWiki
Zeile 452: Zeile 1.120:
  
  
p.kannDeutsch = function ( frame )
+
p.isTrans = function ( frame )
     -- Kann man mit diesem Sprachcode deutsch verstehen?
+
     -- Check whether valid transcription for context
     --    1  -- code
+
     --    1     -- string, with transcription key
     local r = Multilingual.kannDeutsch( frame.args[ 1 ] )
+
    --    2    -- string, with language or scripting code
     return r and "1" or ""
+
    --    site -- string or nil, with site scripting code
end -- p.kannDeutsch
+
     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
  
  
Zeile 464: Zeile 1.136:
 
     -- 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.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()
  
  

Aktuelle Version vom 6. September 2019, 12:54 Uhr

Die Dokumentation für dieses Modul kann unter Modul:Multilingual/Doku erstellt werden

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