Modul:Multilingual: Unterschied zwischen den Versionen

Aus FreeWiki
Zur Navigation springen Zur Suche springen
te>PerfektesChaos
(2016-06-12)
K (36 Versionen importiert)
 
(25 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
 
local Multilingual = { suite  = "Multilingual",
 
local Multilingual = { suite  = "Multilingual",
                       serial = "2016-06-12" };
+
                       serial = "2019-06-01",
 +
                      item  = 47541920 }
 +
local User = { sniffer = "showpreview" }
  
  
  
-- local globals
+
Multilingual.correction = { -- Frequently mistaken language code
local Frame
+
      aze      = "az",
 
+
      cz        = "cs",
 
+
      deu      = "de",
 
+
      dk        = "da",
function fair( ask )
+
      ["en-UK"] = "en-GB",
    -- Format language code according to BCP 47 / RFC 4646
+
      ["en-uk"] = "en-GB",
    -- Precondition:
+
      eng      = "en",
    --    ask  -- language name, downcased
+
      ger      = "de",
    -- Postcondition:
+
      gr        = "el",
    --    nil, or string
+
      ["in"]    = "id",
    local r
+
      iw        = "he",
    if ask:find( "-", 3, true ) then
+
      jp        = "ja",
        local parts = mw.text.split( ask, "-" )
+
      lat      = "la",
        if parts[ 1 ]:match( "^%l%l%l?$" ) then
+
      se        = "sv",
            local script = parts[ 2 ]
+
      tj        = "tg"
            r = parts[ 1 ]
+
     }
            if script then
+
Multilingual.exotic = { simple = true,
                local subA2  = parts[ 3 ]
+
                        no     = true }
                if script:match( "^%l%l%l%l$" ) then
 
                    r = string.format( "%s-%s%s",
 
                                      r,
 
                                      script:sub( 1, 1 ):upper(),
 
                                      script:sub( 2 ) )
 
                else
 
                    subA2 = script
 
                end
 
                if subA2  and  subA2:match( "^%l%l$" ) then
 
                    r = string.format( "%s-%s",
 
                                      r,
 
                                      subA2:upper() )
 
                end
 
            end
 
        end
 
    elseif ask:match( "^%l%l%l?$" ) then
 
        r = ask
 
     end
 
     return r
 
end -- fair()
 
  
  
Zeile 48: Zeile 30:
 
local favorite = function ()
 
local favorite = function ()
 
     -- Postcondition:
 
     -- Postcondition:
     --    Returns code of curent project language
+
     --    Returns code of current project language
 
     if not Multilingual.self then
 
     if not Multilingual.self then
 
         Multilingual.self = mw.language.getContentLanguage():getCode()
 
         Multilingual.self = mw.language.getContentLanguage():getCode()
Zeile 58: Zeile 40:
  
  
local fetch = function ( access, allow )
+
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
Zeile 71: Zeile 74:
 
     if Multilingual.ext[ access ] == false then
 
     if Multilingual.ext[ access ] == false then
 
     elseif not Multilingual.ext[ access ] then
 
     elseif not Multilingual.ext[ access ] then
         local lucky, got = pcall( require, "Module:" .. access )
+
         local src = "Module:" .. access
         if lucky then
+
        local lucky, got
             if type( got ) == "table" then
+
         if ahead == false then
                Multilingual.ext[ access ] = got
+
            lucky, got = pcall( mw.loadData, src )
                if type( got[ access ] ) == "function" then
+
        else
                    Multilingual.ext[ access ] = got[ access ]()
+
             lucky, got = pcall( require, src )
                end
+
        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
 
         end
 
         end
         if type( Multilingual.ext[ access ] ) ~= "table" then
+
         if type( Multilingual.ext[ access ] ) ~= "table" and
            if allow then
+
          not allow then
                Multilingual.ext[ access ] = false
+
             got = string.format( "Module:%s invalid", access )
             else
+
            error( got, 0 )
                got = string.format( "Module:%s invalid", access )
 
                error( got, 0 )
 
            end
 
 
         end
 
         end
 
     end
 
     end
 
     return Multilingual.ext[ access ]
 
     return Multilingual.ext[ access ]
 
end -- fetch()
 
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()
  
  
Zeile 110: Zeile 164:
 
     end -- for k, v
 
     end -- for k, v
 
     if not r then
 
     if not r then
         r = fair( ask )
+
         r = Multilingual.fair( ask )
 
     end
 
     end
 
     return r
 
     return r
Zeile 117: Zeile 171:
  
  
function isSupported( ask, accept )
+
User.favorize = function ( accept, frame )
     -- Is ask to be supported by application?
+
    -- 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()
  
  
Zeile 146: Zeile 295:
 
         seek = mw.ustring.lower( seek )
 
         seek = mw.ustring.lower( seek )
 
         if Multilingual.isLang( seek ) then
 
         if Multilingual.isLang( seek ) then
             r = fair( seek )
+
             r = Multilingual.fair( seek )
 
         else
 
         else
 
             local slang = favorite()
 
             local slang = favorite()
Zeile 157: Zeile 306:
 
     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 181: 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 229: Zeile 389:
 
                         r = Multilingual.getName( slang, alien )
 
                         r = Multilingual.getName( slang, alien )
 
                         if active then
 
                         if active then
                             local cnf = fetch( "Multilingual/config",
+
                             slot = fill( slang, false, frame )
                                              true )
+
                            if slot then
                            if cnf  and
+
                                local wlink = fetch( "WLink" )
                              type( cnf.getLink ) == "function" then
+
                                slot = wlink.getTarget( slot )
                                if not frame then
+
                            else
                                    if not Frame then
+
                                lapsus = alert
                                        Frame = mw.getCurrentFrame()
 
                                    end
 
                                    frame = Frame
 
                                end
 
                                slot = cnf.getLink( slang, frame )
 
                                if slot then
 
                                    local wlink = fetch( "WLink" )
 
                                    slot = wlink.getTarget( slot )
 
                                else
 
                                    lapsus = alert
 
                                end
 
 
                             end
 
                             end
 
                         end
 
                         end
Zeile 265: Zeile 414:
 
                         .. mw.ustring.sub( r, 2 )
 
                         .. mw.ustring.sub( r, 2 )
 
                 elseif alter == "d" then
 
                 elseif alter == "d" then
                     if Multilingual.isMinusculable( slang or "" ) then
+
                     if Multilingual.isMinusculable( slang, r ) then
 
                         r = mw.ustring.lower( r )
 
                         r = mw.ustring.lower( r )
 
                     end
 
                     end
 
                 elseif alter == "m" then
 
                 elseif alter == "m" then
                     if Multilingual.isMinusculable( slang or "" ) then
+
                     if Multilingual.isMinusculable( slang, r ) then
 
                         r = mw.ustring.lower( mw.ustring.sub( r, 1, 1 ) )
 
                         r = mw.ustring.lower( mw.ustring.sub( r, 1, 1 ) )
 
                             .. mw.ustring.sub( r, 2 )
 
                             .. mw.ustring.sub( r, 2 )
Zeile 281: 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 311: 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 331: Zeile 574:
 
         if slang then
 
         if slang then
 
             if slang == "*" then
 
             if slang == "*" then
                 slang = ask:lower()
+
                 slang = Multilingual.fair( ask )
 
             elseif slang == "!" then
 
             elseif slang == "!" then
 
                 slang = favorite()
 
                 slang = favorite()
 
             else
 
             else
                 slang = slang:lower()
+
                 slang = Multilingual.fair( slang )
 
             end
 
             end
 
         else
 
         else
             slang = ask:lower()
+
             slang = Multilingual.fair( ask )
 
         end
 
         end
 +
        if not slang then
 +
            slang = ask or "?????"
 +
        end
 +
        slang = slang:lower()
 
         tLang = fetch( support, true )
 
         tLang = fetch( support, true )
 
         if tLang then
 
         if tLang then
Zeile 365: Zeile 612:
 
         end
 
         end
 
         if not r then
 
         if not r then
             r = mw.language.fetchLanguageName( ask, slang )
+
             r = mw.language.fetchLanguageName( ask:lower(), slang )
 
             if r == "" then
 
             if r == "" then
 
                 r = false
 
                 r = false
Zeile 378: 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 405: 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 414: Zeile 760:
  
  
Multilingual.isMinusculable = function ( ask )
+
Multilingual.isMinusculable = function ( ask, assigned )
 
     -- Could this language name become downcased?
 
     -- Could this language name become downcased?
 
     -- Precondition:
 
     -- Precondition:
     --    ask  -- language name
+
     --    ask       -- language code, or nil
     local cnf = fetch( "Multilingual/config", true )
+
    --    assigned -- language name, or nil
    local r = true
+
    -- Postcondition:
    if cnf and  type( cnf.stopMinusculization ) == "string" then
+
    --    Returns boolean
        local s = string.format( " %s ", ask:lower() )
+
    local r  = true
        if cnf.stopMinusculization:find( s, 1, true ) then
+
     if ask then
             r = false
+
        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
 
     end
 
     end
Zeile 431: Zeile 792:
  
  
Multilingual.kannDeutsch = function ( ask )
+
Multilingual.isTrans = function ( ask, assign, about )
     -- Kann man mit diesem Sprachcode deutsch verstehen?
+
     -- Check whether valid transcription for context
 
     -- Precondition:
 
     -- Precondition:
     --    ask  -- language version specifier
+
     --    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 458: 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
         if not Frame then
+
    if s == "string" then
             Frame = mw.getCurrentFrame()
+
        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
        frame = Frame
 
 
     end
 
     end
     slang = frame:callParserFunction( "int", "lang" ):lower()
+
     slang = User.favorize( codes, frame )
     if type( accept ) == "string" then
+
     if not slang then
         support = accept:lower() .. " "
+
         slang = favorite() or  "en"
    else
 
        support = favorite()
 
        if mw.language.isKnownLanguageTag( support ) then
 
            support = string.format( "%s en ", support )
 
        else
 
            support = "en "
 
        end
 
 
     end
 
     end
     if isSupported( slang, support ) then
+
     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 )
 
         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
 +
    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
+
    end
             r = support:match( "^(%S+) " )
+
    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 505: 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 511: 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 528: 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 552: 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 564: 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 578: 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 588: 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 595: 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 607: 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
  
  
  
function p.failsafe()
+
p.failsafe = function ( frame )
     return Multilingual.serial
+
    -- Versioning interface
end
+
    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, 13:54 Uhr

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", slot, r )
                   end
               end
               if lapsus and alert then
                   r = string.format( "%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