Permanently protected module
From Wikipedia, the free encyclopedia


local Multilingual = { suite   = "Multilingual",

                       serial  = "2020-12-10",

                       item    = 47541920,

                       globals = { ISO15924 = 71584769,

                                   WLink    = 19363224 }

                     }

--[=[

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

* fair()

* fallback()

* findCode()

* fix()

* format()

* getBase()

* getLang()

* getName()

* i18n()

* int()

* isLang()

* isLangWiki()

* isMinusculable()

* isRTL()

* message()

* sitelink()

* tabData()

* userLang()

* userLangCode()

* wikibase()

* failsafe()

loadData: Multilingual/config Multilingual/names

]=]

local Failsafe   = Multilingual

local GlobalMod  = Multilingual

local GlobalData = Multilingual

local User       = { sniffer = "showpreview" }

Multilingual.globals.Multilingual = Multilingual.item







Multilingual.exotic = { simple = true,

                        no     = true }

Multilingual.prefer = { cs = true,

                        de = true,

                        en = true,

                        es = true,

                        fr = true,

                        it = true,

                        nl = true,

                        pt = true,

                        ru = true,

                        sv = true }







local foreignModule = function ( access, advanced, append, alt, alert )

    -- Fetch global module

    -- Precondition:

    --     access    -- string, with name of base module

    --     advanced  -- true, for require(); else mw.loadData()

    --     append    -- string, with subpage part, if any; or false

    --     alt       -- number, of wikidata item of root; or false

    --     alert     -- true, for throwing error on data problem

    -- Postcondition:

    --     Returns whatever, probably table

    -- 2020-01-01

    local storage = access

    local finer = function ()

                      if append then

                          storage = string.format( "%s/%s",

                                                   storage,

                                                   append )

                      end

                  end

    local fun, lucky, r, suited

    if advanced then

        fun = require

    else

        fun = mw.loadData

    end

    GlobalMod.globalModules = GlobalMod.globalModules or { }

    suited = GlobalMod.globalModules access 

    if not suited then

        finer()

        lucky, r = pcall( fun,  "Module:" .. storage )

    end

    if not lucky then

        if not suited  and

           type( alt ) == "number"  and

           alt > 0 then

            suited = string.format( "Q%d", alt )

            suited = mw.wikibase.getSitelink( suited )

            GlobalMod.globalModules access  = suited or true

        end

        if type( suited ) == "string" then

            storage = suited

            finer()

            lucky, r = pcall( fun, storage )

        end

        if not lucky and alert then

            error( "Missing or invalid page: " .. storage )

        end

    end

    return r

end -- foreignModule()







local fetchData = function ( access )

    -- Retrieve translated keyword from commons:Data:****.tab

    -- Precondition:

    --     access  -- string, with page identification on Commons

    --     Returns table, with data, or string, with error message

    -- 2019-12-05

    local storage = access

    local r

    if type( storage ) == "string" then

        local s

        storage = mw.text.trim( storage )

        s = storage:lower()

        if s:sub( 1, 2 ) == "c:" then

            storage = mw.text.trim( storage:sub( 3 ) )

            s       = storage:lower()

        elseif s:sub( 1, 8 ) == "commons:" then

            storage = mw.text.trim( storage:sub( 9 ) )

            s       = storage:lower()

        end

        if s:sub( 1, 5 ) == "data:" then

            storage = mw.text.trim( storage:sub( 6 ) )

            s       = storage:lower()

        end

        if s == ""  or  s == ".tab" then

            storage = false

        elseif s:sub( -4 ) == ".tab" then

            storage = storage:sub( 1, -5 ) .. ".tab"

        else

            storage = storage .. ".tab"

        end

    end

    if type( storage ) == "string" then

        local data

        if type( GlobalData.TabDATA ) ~= "table" then

            GlobalData.TabDATA = { }

        end

        data = GlobalData.TabDATA storage 

        if data then

            r = data

        else

            local lucky

            lucky, data = pcall( mw.ext.data.get, storage, "_" )

            if type( data ) == "table" then

                data = data.data

                if type( data ) == "table" then

                    GlobalData.TabDATA storage  = data

                else

                    r = string.format( "%s [[%s%s]]",

                                       "INVALID Data:*.tab",

                                       "commons:Data:",

                                       storage )

                end

            else

                r = "BAD PAGE Data:*.tab – commons:" .. storage

            end

            if r then

                GlobalData.TabDATA storage  = r

                data = false

            else

                r = data

            end

        end

    else

        r = "BAD PAGE commons:Data:*.tab"

    end

    return r

end -- fetchData()







local favorites = function ()

    -- Provide fallback codes

    -- Postcondition:

    --     Returns table with sequence of preferred languages

    --     * ahead elements

    --     * user (not yet accessible)

    --     * page content language (not yet accessible)

    --     * page name subpage

    --     * project

    --     * en

    local r = Multilingual.polyglott

    if not r then

        local self = mw.language.getContentLanguage():getCode():lower()

        local sub  = mw.title.getCurrentTitle().subpageText

        local f    = function ( add )

                         local s = add

                         for i = 1, #r do

                             if r i  == s then

                                 s = false

                                 break -- for i

                             end

                         end -- for i

                         if s then

                             table.insert( r, s )

                         end

                     end

        r = { }

        if sub:find( "/", 2, true ) then

            sub = sub:match( "/(%l%l%l?)$" )

            if sub then

                table.insert( r, sub )

            end

        elseif sub:find( "^%l%l%l?%-?%a?%a?%a?%a?$" )  and

               mw.language.isSupportedLanguage( sub ) then

            table.insert( r, sub )

        end

        f( self )

        f( "en" )

        Multilingual.polyglott = r

    end

    return r

end -- favorites()







local feasible = function ( ask, accept )

    -- Is ask to be supported by application?

    -- Precondition:

    --     ask     -- lowercase code

    --     accept  -- sequence table, with offered lowercase codes

    -- Postcondition:

    --     nil, or true

    local r

    for i = 1, #accept do

        if accept i  == ask then

            r = true

            break -- for i

        end

    end -- for i

    return r

end -- feasible()







local fetch = function ( access, append )

    -- Attach config or library module

    -- Precondition:

    --     access  -- module title

    --     append  -- string, with subpage part of this; or false

    -- Postcondition:

    --     Returns:  table, with library, or false

    local got, sign

    if append then

        sign = string.format( "%s/%s", access, append )

    else

        sign = access

    end

    if type( Multilingual.ext ) ~= "table" then

        Multilingual.ext = { }

    end

    got = Multilingual.ext sign 

    if not got  and  got ~= false then

        local global = Multilingual.globals access 

        local lib    = ( not append  or  append == "config" )

        got = foreignModule( access, lib, append, global )

        if type( got ) == "table" then

            if lib then

                local startup = got access 

                if type( startup ) == "function" then

                    got = startup()

                end

            end

        else

            got = false

        end

        Multilingual.ext sign  = got

    end

    return got

end -- fetch()







local fetchISO639 = function ( access )

    -- Retrieve table from commons:Data:ISO639/***.tab

    -- Precondition:

    --     access  -- string, with subpage identification

    -- Postcondition:

    --     Returns table, with data, even empty

    local r

    if type( Multilingual.iso639 ) ~= "table" then

        Multilingual.iso639 = { }

    end

    r = Multilingual.iso639 access 

    if type( r ) == "nil" then

        local raw = fetchData( "ISO639/" .. access )

        if type( raw ) == "table" then

            local t

            r = { }

            for i = 1, #raw do

                t = raw i 

                if type( t ) == "table"  and

                   type( t 1  ) == "string"  and

                   type( t 2  ) == "string" then

                    r t 1   =  t 2 

                else

                    break -- for i

                end

            end -- for i

        else

            r = false

        end

        Multilingual.iso639 access  = r

    end

    return r or { }

end -- fetchISO639()







local fill = function ( access, alien, frame )

    -- Expand language name template

    -- Precondition:

    --     access  -- string, with language code

    --     alien   -- language code for which to be generated

    --     frame   -- frame, if available

    -- Postcondition:

    --     Returns string

    local template = Multilingual.tmplLang

    local r

    if type( template ) ~= "table" then

        local cnf = fetch( "Multilingual", "config" )

        if cnf then

            template = cnf.tmplLang

        end

    end

    if type( template ) == "table" then

        local source = template.title

        local f, lucky, s

        Multilingual.tmplLang = template

        if type( source ) ~= "string"  and

           type( template.namePat ) == "string"  and

           template.namePat:find( "%s", 1, true ) then

            source = string.format( template.namePat, access )

        end

        if type( source ) == "string" then

            if not Multilingual.frame then

                if frame then

                    Multilingual.frame = frame

                else

                    Multilingual.frame = mw.getCurrentFrame()

                end

            end

            f = function ( a )

                    return Multilingual.frame:expandTemplate{ title = a }

                end

            lucky, s = pcall( f, source )

            if lucky then

                r = s

            end

        end

    end

    return r

end -- fill()







local find = function ( ask, alien )

    -- Derive language code from name

    -- Precondition:

    --     ask    -- language name, downcased

    --     alien  -- language code of ask

    -- Postcondition:

    --     nil, or string

    local codes = mw.language.fetchLanguageNames( alien, "all" )

    local r

    for k, v in pairs( codes ) do

        if mw.ustring.lower( v ) == ask then

            r = k

            break -- for k, v

        end

    end -- for k, v

    if not r then

        r = Multilingual.fair( ask )

    end

    return r

end -- find()







local fold = function ( frame )

    -- Merge template and #invoke arglist

    -- Precondition:

    --     frame   -- template frame

    -- Postcondition:

    --     table, with combined arglist

    local r = { }

    local f = function ( apply )

                  if type( apply ) == "table"  and

                     type( apply.args ) == "table" then

                      for k, v in pairs( apply.args ) do

                          v = mw.text.trim( v )

                          if v ~= "" then

                              r tostring( k )  = v

                          end

                      end -- for k, v

                  end

              end -- f()

    f( frame:getParent() )

    f( frame )

    return r

end -- fold()







User.favorize = function ( accept, frame )

    -- Guess user language

    -- Precondition:

    --     accept  -- sequence table, with offered ISO 639 etc. codes

    --     frame   -- frame, if available

    -- Postcondition:

    --     Returns string with best code, or nil

    if not ( User.self or User.langs ) then

        if not User.trials then

            User.tell = mw.message.new( User.sniffer )

            if User.tell:exists() then

                User.trials = { }

                if not Multilingual.frame then

                    if frame then

                        Multilingual.frame = frame

                    else

                        Multilingual.frame = mw.getCurrentFrame()

                    end

                end

                User.sin = Multilingual.frame:callParserFunction( "int",

                                                           User.sniffer )

            else

                User.langs = true

            end

        end

        if User.sin then

            local order  = { }

            local post   = { }

            local three  = { }

            local unfold = { }

            local s, sin

            for i = 1, #accept do

                s = accept i 

                if not User.trials s  then

                    if #s > 2 then

                        if s:find( "-", 3, true ) then

                            table.insert( unfold, s )

                        else

                            table.insert( three, s )

                        end

                    else

                        if Multilingual.prefer s  then

                            table.insert( order, s )

                        else

                            table.insert( post, s )

                        end

                    end

                end

            end -- for i

            for i = 1, #post do

                table.insert( order, post i  )

            end -- for i

            for i = 1, #three do

                table.insert( order, three i  )

            end -- for i

            for i = 1, #unfold do

                table.insert( order, unfold i  )

            end -- for i

            for i = 1, #order do

                s = order i 

                sin = User.tell:inLanguage( s ):plain()

                if sin == User.sin then

                    User.self = s

                    break -- for i

                else

                    User.trials s  = true

                end

            end -- for i

        end

    end

    return User.self

end -- User.favorize()







Multilingual.fair = function ( ask )

    -- Format language specification according to RFC 5646 etc.

    -- Precondition:

    --     ask  -- string or table, as created by .getLang()

    -- Postcondition:

    --     Returns string, or false

    local s = type( ask )

    local q, r

    if s == "table" then

        q = ask

    elseif s == "string" then

        q = Multilingual.getLang( ask )

    end

    if q  and

       q.legal  and

       mw.language.isKnownLanguageTag( q.base ) then

        r = q.base

        if q.n > 1 then

            local order = { "extlang",

                            "script",

                            "region",

                            "other",

                            "extension" }

            for i = 1, #order do

                s = q order i  

                if s then

                    r =  string.format( "%s-%s", r, s )

                end

            end -- for i

        end

    end

    return r or false

end -- Multilingual.fair()







Multilingual.fallback = function ( able, another )

    -- Is another language suitable as replacement?

    -- Precondition:

    --     able     -- language version specifier to be supported

    --     another  -- language specifier of a possible replacement,

    --                 or not to retrieve a fallback table

    -- Postcondition:

    --     Returns boolean, or table with fallback codes

    local r

    if type( able ) == "string"  and  #able > 0 then

        if type( another ) == "string"  and  #another > 0 then

            if able == another then

                r = true

            else

                local s = Multilingual.getBase( able )

                if s == another then

                    r = true

                else

                    local others = mw.language.getFallbacksFor( s )

                    r = feasible( another, others )

                end

            end

        else

            local s = Multilingual.getBase( able )

            if s then

                r = mw.language.getFallbacksFor( s )

                if r 1  == "en" then

                    local d = fetchISO639( "fallback" )

                    if type( d ) == "table"  and

                       type( d s  ) == "string" then

                        r = mw.text.split( d s ], "|" )

                        table.insert( r, "en" )

                    end

                end

            end

        end

    end

    return r or false

end -- Multilingual.fallback()







Multilingual.findCode = function ( ask )

    -- Retrieve code of local (current project or English) language name

    -- Precondition:

    --     ask  -- string, with presumable language name

    --             A code itself will be identified, too.

    -- Postcondition:

    --     Returns string, or false

    local seek = mw.text.trim( ask )

    local r = false

    if #seek > 1 then

        if seek:find( "[", 1, true ) then

            local wlink = fetch( "WLink" )

            if wlink  and

               type( wlink.getPlain ) == "function" then

                seek = wlink.getPlain( seek )

            end

        end

        seek = mw.ustring.lower( seek )

        if Multilingual.isLang( seek ) then

            r = Multilingual.fair( seek )

        else

            local collection = favorites()

            for i = 1, #collection do

                r = find( seek, collection i  )

                if r then

                    break -- for i

                end

            end -- for i

        end

    end

    return r

end -- Multilingual.findCode()







Multilingual.fix = function ( attempt )

    -- Fix frequently mistaken language code

    -- Precondition:

    --     attempt  -- string, with presumable language code

    -- Postcondition:

    --     Returns string with correction, or false if no problem known

    local r = fetchISO639( "correction" )[ attempt:lower() 

    return r or false

end -- Multilingual.fix()







Multilingual.format = function ( apply, alien, alter, active, alert,

                                 frame, assembly, adjacent, ahead )

    -- Format one or more languages

    -- Precondition:

    --     apply     -- string with language list or item

    --     alien     -- language of the answer

    --                  -- nil, false, "*": native

    --                  -- "!": current project

    --                  -- "#": code, downcased, space separated

    --                  -- "-": code, mixcase, space separated

    --                  -- any valid code

    --     alter     -- capitalize, if "c"; downcase all, if "d"

    --                  capitalize first item only, if "f"

    --                  downcase every first word only, if "m"

    --     active    -- link items, if true

    --     alert     -- string with category title in case of error

    --     frame     -- if available

    --     assembly  -- string with split pattern, if list expected

    --     adjacent  -- string with list separator, else assembly

    --     ahead     -- string to prepend first element, if any

    -- Postcondition:

    --     Returns string, or false if apply empty

    local r = false

    if apply then

        local slang

        if assembly then

            local bucket = mw.text.split( apply, assembly )

            local shift = alter

            local separator

            if adjacent then

                separator = adjacent

            elseif alien == "#"  or  alien == "-" then

                separator = " "

            else

                separator = assembly

            end

            for k, v in pairs( bucket ) do

                slang = Multilingual.format( v, alien, shift, active,

                                             alert )

                if slang then

                    if r then

                        r = string.format( "%s%s%s",

                                           r, separator, slang )

                    else

                        r = slang

                        if shift == "f" then

                            shift = "d"

                        end

                    end

                end

            end -- for k, v

            if r and ahead then

                r = ahead .. r

            end

        else

            local single = mw.text.trim( apply )

            if single == "" then

                r = false

            else

                local lapsus, slot

                slang = Multilingual.findCode( single )

                if slang then

                    if alien == "-" then

                        r = slang

                    elseif alien == "#" then

                        r = slang:lower()

                    else

                        r = Multilingual.getName( slang, alien )

                        if active then

                            slot = fill( slang, false, frame )

                            if slot then

                                local wlink = fetch( "WLink" )

                                if wlink  and

                                   type( wlink.getTarget )

                                                       == "function" then

                                    slot = wlink.getTarget( slot )

                                end

                            else

                                lapsus = alert

                            end

                        end

                    end

                else

                    r = single

                    if active then

                        local title = mw.title.makeTitle( 0, single )

                        if title.exists then

                            slot = single

                        end

                    end

                    lapsus = alert

                end

                if not r then

                    r = single

                elseif alter == "c" or alter == "f" then

                    r = mw.ustring.upper( mw.ustring.sub( r, 1, 1 ) )

                        .. mw.ustring.sub( r, 2 )

                elseif alter == "d" then

                    if Multilingual.isMinusculable( slang, r ) then

                        r = mw.ustring.lower( r )

                    end

                elseif alter == "m" then

                    if Multilingual.isMinusculable( slang, r ) then

                        r = mw.ustring.lower( mw.ustring.sub( r, 1, 1 ) )

                            .. mw.ustring.sub( r, 2 )

                    end

                end

                if slot then

                    if r == slot then

                        r = string.format( "[[%s]]", r )

                    else

                        r = string.format( "[[%s|%s]]", slot, r )

                    end

                end

                if lapsus and alert then

                    r = string.format( "%s[[Category:%s]]", r, alert )

                end

            end

        end

    end

    return r

end -- Multilingual.format()







Multilingual.getBase = function ( ask )

    -- Retrieve base language from possibly combined ISO language code

    -- Precondition:

    --     ask  -- language code

    -- Postcondition:

    --     Returns string, or false

    local r

    if ask then

        local slang = ask:match( "^%s*(%a%a%a?)-?%a*%s*$" )

        if slang then

            r = slang:lower()

        else

            r = false

        end

    else

        r = false

    end

    return r

end -- Multilingual.getBase()







Multilingual.getLang = function ( ask )

    -- Retrieve components of a RFC 5646 language code

    -- Precondition:

    --     ask  -- language code with subtags

    -- Postcondition:

    --     Returns table with formatted subtags

    --             .base

    --             .region

    --             .script

    --             .suggest

    --             .year

    --             .extension

    --             .other

    --             .n

    local tags = mw.text.split( ask, "-" )

    local s    = tags 1 

    local r

    if s:match( "^%a%a%a?$" ) then

        r = { base  = s:lower(),

              legal = true,

              n     = #tags }

        for i = 2, r.n do

            s = tags i 

            if #s == 2 then

                if r.region  or  not s:match( "%a%a" ) then

                    r.legal = false

                else

                    r.region = s:upper()

                end

            elseif #s == 4 then

                if s:match( "%a%a%a%a" ) then

                    r.legal = ( not r.script )

                    r.script = s:sub( 1, 1 ):upper() ..

                               s:sub( 2 ):lower()

                elseif s:match( "20%d%d" )  or

                       s:match( "1%d%d%d" ) then

                    r.legal = ( not r.year )

                    r.year = s

                else

                    r.legal = false

                end

            elseif #s == 3 then

                if r.extlang  or  not s:match( "%a%a%a" ) then

                    r.legal = false

                else

                    r.extlang = s:lower()

                end

            elseif #s == 1 then

                s = s:lower()

                if s:match( "[tux]" ) then

                    r.extension = s

                    for k = i + 1, r.n do

                        s = tags k 

                        if s:match( "^%w+$" ) then

                            r.extension = string.format( "%s-%s",

                                                         r.extension, s )

                        else

                            r.legal = false

                        end

                    end -- for k

                else

                    r.legal = false

                end

                break -- for i

            else

                r.legal = ( not r.other )  and

                          s:match( "%a%a%a" )

                r.other = s:lower()

            end

            if not r.legal then

                break -- for i

            end

        end -- for i

        if r.legal then

            r.suggest = Multilingual.fix( r.base )

            if r.suggest then

                r.legal = false

            end

        end

    else

        r = { legal = false }

    end

    if not r.legal then

        local cnf = fetch( "Multilingual", "config" )

        if cnf  and  type( cnf.scream ) == "string" then

            r.scream = cnf.scream

        end

    end

    return r

end -- Multilingual.getLang()







Multilingual.getName = function ( ask, alien )

    -- Which name is assigned to this language code?

    -- Precondition:

    --     ask    -- language code

    --     alien  -- language of the answer

    --               -- nil, false, "*": native

    --               -- "!": current project

    --               -- any valid code

    -- Postcondition:

    --     Returns string, or false

    local r

    if ask then

        local slang   = alien

        local tLang

        if slang then

            if slang == "*" then

                slang = Multilingual.fair( ask )

            elseif slang == "!" then

                slang = favorites()[ 1 

            else

                slang = Multilingual.fair( slang )

            end

        else

            slang = Multilingual.fair( ask )

        end

        if not slang then

            slang = ask or "?????"

        end

        slang = slang:lower()

        tLang = fetch( "Multilingual", "names" )

        if tLang then

            tLang = tLang slang 

            if tLang then

                r = tLang ask 

            end

        end

        if not r then

            if not Multilingual.ext.tMW then

                Multilingual.ext.tMW = { }

            end

            tLang = Multilingual.ext.tMW slang 

            if tLang == nil then

                tLang = mw.language.fetchLanguageNames( slang )

                if tLang then

                    Multilingual.ext.tMW slang  = tLang

                else

                    Multilingual.ext.tMW slang  = false

                end

            end

            if tLang then

                r = tLang ask 

            end

        end

        if not r then

            r = mw.language.fetchLanguageName( ask:lower(), slang )

            if r == "" then

                r = false

            end

        end

    else

        r = false

    end

    return r

end -- Multilingual.getName()







Multilingual.i18n = function ( available, alt, frame )

    -- Select translatable message

    -- Precondition:

    --     available  -- table, with mapping language code ./. text

    --     alt        -- string|nil|false, with fallback text

    --     frame      -- frame, if available

    --     Returns

    --         1. string|nil|false, with selected message

    --         2. string|nil|false, with language code

    local r1, r2

    if type( available ) == "table" then

        local codes = { }

        local trsl  = { }

        local slang

        for k, v in pairs( available ) do

            if type( k ) == "string"  and

               type( v ) == "string" then

                slang = mw.text.trim( k:lower() )

                table.insert( codes, slang )

                trsl slang  = v

            end

        end -- for k, v

        slang = Multilingual.userLang( codes, frame )

        if slang  and  trsl slang  then

            r1 = mw.text.trim( trsl slang  )

            if r1 == "" then

                r1 = false

            else

                r2 = slang

            end

        end

    end

    if not r1  and  type( alt ) == "string" then

        r1 = mw.text.trim( alt )

        if r1 == "" then

            r1 = false

        end

    end

    return r1, r2

end -- Multilingual.i18n()







Multilingual.int = function ( access, alien, apply )

    -- Translated system message

    -- Precondition:

    --     access  -- message ID

    --     alien   -- language code

    --     apply   -- nil, or sequence table with parameters $1, $2, ...

    -- Postcondition:

    --     Returns string, or false

    local o = mw.message.new( access )

    local r

    if o:exists() then

        if type( alien ) == "string" then

            o:inLanguage( alien:lower() )

        end

        if type( apply ) == "table" then

            o:params( apply )

        end

        r = o:plain()

    end

    return r or false

end -- Multilingual.int()







Multilingual.isLang = function ( ask, additional )

    -- Could this be an ISO language code?

    -- Precondition:

    --     ask         -- language code

    --     additional  -- true, if Wiki codes like "simple" permitted

    -- Postcondition:

    --     Returns boolean

    local r, s

    if additional then

        s = ask

    else

        s = Multilingual.getBase( ask )

    end

    if s then

        r = mw.language.isKnownLanguageTag( s )

        if r then

            r = not Multilingual.fix( s )

        elseif additional then

            r = Multilingual.exotic s  or false

        end

    else

        r = false

    end

    return r

end -- Multilingual.isLang()







Multilingual.isLangWiki = function ( ask )

    -- Could this be a Wiki language version?

    -- Precondition:

    --     ask  -- language version specifier

    -- Postcondition:

    --     Returns boolean

    local r

    local s = Multilingual.getBase( ask )

    if s then

        r = mw.language.isSupportedLanguage( s )  or

            Multilingual.exotic ask 

    else

        r = false

    end

    return r

end -- Multilingual.isLangWiki()







Multilingual.isMinusculable = function ( ask, assigned )

    -- Could this language name become downcased?

    -- Precondition:

    --     ask       -- language code, or nil

    --     assigned  -- language name, or nil

    -- Postcondition:

    --     Returns boolean

    local r = true

    if ask then

        local cnf = fetch( "Multilingual", "config" )

        if cnf then

            local s = string.format( " %s ", ask:lower() )

            if type( cnf.stopMinusculization ) == "string"

               and  cnf.stopMinusculization:find( s, 1, true ) then

                r = false

            end

            if r  and  assigned

               and  type( cnf.seekMinusculization ) == "string"

               and  cnf.seekMinusculization:find( s, 1, true )

               and  type( cnf.scanMinusculization ) == "string" then

                local scan = assigned:gsub( "[%(%)]", " " ) .. " "

                if not scan:find( cnf.scanMinusculization ) then

                    r = false

                end

            end

        end

    end

    return r

end -- Multilingual.isMinusculable()







Multilingual.isRTL = function ( ask )

    -- Check whether language is written right-to-left

    -- Precondition:

    --     ask  -- string, with language (or script) code

    -- Returns true, if right-to-left

    local r

    Multilingual.rtl = Multilingual.rtl or { }

    r = Multilingual.rtl ask 

    if type( r ) ~= "boolean" then

        local bib = fetch( "ISO15924" )

        if type( bib ) == "table"  and

           type( bib.isRTL ) == "function" then

            r = bib.isRTL( ask )

        else

            r = mw.language.new( ask ):isRTL()

        end

        Multilingual.rtl ask  = r

    end

    return r

end -- Multilingual.isRTL()







Multilingual.message = function ( arglist, frame )

    -- Show text in best match of user language like system message

    -- Precondition:

    --     arglist  -- template arguments

    --     frame    -- frame, if available

    -- Postcondition:

    --     Returns string with appropriate text

    local r

    if type( arglist ) == "table" then

        local t = { }

        local m, p, save

        for k, v in pairs( arglist ) do

            if type( k ) == "string"  and

               type( v ) == "string" then

                v = mw.text.trim( v )

                if v ~= "" then

                    if k:match( "^%l%l" ) then

                        t k  = v

                    elseif k:match( "^%$%d$" )  and  k ~= "$0" then

                        p = p or { }

                        k = tonumber( k:match( "^%$(%d)$" ) )

                        p k  = v

                        if not m  or  k > m then

                            m = k

                        end

                    end

                end

            end

        end -- for k, v

        if type( arglist "-"  ) == "string" then

            save = arglist arglist "-"  

        end

        r = Multilingual.i18n( t, save, frame )

        if p  and  r  and  r:find( "$", 1, true ) then

            t = { }

            for i = 1, m do

                t i  = p i   or  ""

            end -- for i

            r = mw.message.newRawMessage( r, t ):plain()

        end

    end

    return r  or  ""

end -- Multilingual.message()







Multilingual.sitelink = function ( all, frame )

    -- Make link at local or other site with optimal linktext translation

    -- Precondition:

    --     all    -- string or table or number, item ID or entity

    --     frame  -- frame, if available

    -- Postcondition:

    --     Returns string with any helpful internal link, or plain text

    local s = type( all )

    local object, r

    if s == "table" then

        object = all

    elseif s == "string" then

        object = mw.wikibase.getEntity( all )

    elseif s == "number" then

        object = mw.wikibase.getEntity( string.format( "Q%d", all ) )

    end

    if type( object ) == "table" then

        local collection = object.sitelinks

        local entry

        s = false

        if type( collection ) == "table" then

            Multilingual.site = Multilingual.site  or

                                mw.wikibase.getGlobalSiteId()

            entry = collection Multilingual.site 

            if entry then

                s = ":" .. entry.title

            elseif collection.enwiki then

                s = "w:en:" .. collection.enwiki.title

            end

        end

        r = Multilingual.wikibase( object, "labels", frame )

        if s then

            if s == ":" .. r then

                r = string.format( "[[%s]]", s )

            else

                r = string.format( "[[%s|%s]]", s, r )

            end

        end

    end

    return r  or  ""

end -- Multilingual.sitelink()







Multilingual.tabData = function ( access, at, alt, frame )

    -- Retrieve translated keyword from commons:Data:****.tab

    -- Precondition:

    --     access  -- string, with page identification on Commons

    --     at      -- string, with keyword

    --     alt     -- string|nil|false, with fallback text

    --     frame   -- frame, if available

    --     Returns

    --         1. string|nil|false, with selected message

    --         2. language code, or "error"

    local data = fetchData( access )

    local r1, r2

    if  type( data ) == "table" then

        if type( at ) == "string" then

            local seek = mw.text.trim( at )

            if seek == "" then

                r1 = "EMPTY Multilingual.tabData key"

            else

                local e, poly

                for i = 1, #data do

                    e = data i 

                    if type( e ) == "table" then

                        if e 1  == seek then

                            if type( e 2  ) == "table" then

                                poly = e 2 

                            else

                                r1 = "INVALID Multilingual.tabData bad #"

                                                         .. tostring( i )

                            end

                            break   -- for i

                        end

                    else

                        break   -- for i

                    end

                end   -- for i

                if poly then

                    data = poly

                else

                    r1 = "UNKNOWN Multilingual.tabData key: " .. seek

                end

            end

        else

            r1 = "INVALID Multilingual.tabData key"

        end

    else

        r1 = data

    end

    if r1 then

        r2 = "error"

    elseif data then

        r1, r2 = Multilingual.i18n( data, alt, frame )

        r2 = r2 or "error"

    end

    return r1, r2

end -- Multilingual.tabData()







Multilingual.userLang = function ( accept, frame )

    -- Try to support user language by application

    -- Precondition:

    --     accept  -- string or table

    --                space separated list of available ISO 639 codes

    --                Default: project language, or English

    --     frame   -- frame, if available

    -- Postcondition:

    --     Returns string with appropriate code

    local s = type( accept )

    local codes, r, slang

    if s == "string" then

        codes = mw.text.split( accept:lower(), "%s+" )

    elseif s == "table" then

        codes = { }

        for i = 1, #accept do

            s = accept i 

            if type( s ) == "string"  and

               s ~= "" then

                table.insert( codes, s:lower() )

            end

        end -- for i

    end

    slang = User.favorize( codes, frame )

    if slang then

        if feasible( slang, codes ) then

            r = slang

        elseif slang:find( "-", 1, true ) then

            slang = Multilingual.getBase( slang )

            if feasible( slang, codes ) then

                r = slang

            end

        end

        if not r then

            local others = mw.language.getFallbacksFor( slang )

            for i = 1, #others do

                slang = others i 

                if feasible( slang, codes ) then

                    r = slang

                    break -- for i

                end

            end -- for i

        end

    end

    if not r then

        local back = favorites()

        for i = 1, #back do

            slang = back i 

            if feasible( slang, codes ) then

                r = slang

                break -- for i

            end

        end -- for i

        if not r  and  codes 1  then

            r = codes 1 

        end

    end

    return r  or  favorites()[ 1 

end -- Multilingual.userLang()







Multilingual.userLangCode = function ()

    -- Guess a user language code

    -- Postcondition:

    --     Returns code of current best guess

    return User.self  or  favorites()[ 1 

end -- Multilingual.userLangCode()







Multilingual.wikibase = function ( all, about, attempt, frame )

    -- Optimal translation of wikibase component

    -- Precondition:

    --     all      -- string or table, object ID or entity

    --     about    -- boolean, true "descriptions" or false "labels"

    --     attempt  -- string or not, code of preferred language

    --     frame    -- frame, if available

    -- Postcondition:

    --     Returns

    --         1. string, with selected message

    --         2. string, with language code, or not

    local s = type( all )

    local object, r, r2

    if s == "table" then

        object = all

    elseif s == "string" then

        object = mw.wikibase.getEntity( all )

    end

    if type( object ) == "table" then

        if about  and  about ~= "labels" then

            s = "descriptions"

        else

            s = "labels"

        end

        object = object s 

        if type( object ) == "table" then

            if object attempt  then

                r  = object attempt ].value

                r2 = attempt

            else

                local poly

                for k, v in pairs( object ) do

                    poly = poly or { }

                    poly k  = v.value

                end -- for k, v

                if poly then

                    r, r2 = Multilingual.i18n( poly, nil, frame )

                end

            end

        end

    end

    return r  or  "",   r2

end -- Multilingual.wikibase()







Failsafe.failsafe = function ( atleast )

    -- Retrieve versioning and check for compliance

    -- Precondition:

    --     atleast  -- string, with required version

    --                         or wikidata|item|~|@ or false

    -- Postcondition:

    --     Returns  string  -- with queried version/item, also if problem

    --              false   -- if appropriate

    -- 2020-08-17

    local since = atleast

    local last    = ( since == "~" )

    local linked  = ( since == "@" )

    local link    = ( since == "item" )

    local r

    if last  or  link  or  linked  or  since == "wikidata" then

        local item = Failsafe.item

        since = false

        if type( item ) == "number"  and  item > 0 then

            local suited = string.format( "Q%d", item )

            if link then

                r = suited

            else

                local entity = mw.wikibase.getEntity( suited )

                if type( entity ) == "table" then

                    local seek = Failsafe.serialProperty or "P348"

                    local vsn  = entity:formatPropertyValues( seek )

                    if type( vsn ) == "table"  and

                       type( vsn.value ) == "string"  and

                       vsn.value ~= "" then

                        if last  and  vsn.value == Failsafe.serial then

                            r = false

                        elseif linked then

                            if mw.title.getCurrentTitle().prefixedText

                               ==  mw.wikibase.getSitelink( suited ) then

                                r = false

                            else

                                r = suited

                            end

                        else

                            r = vsn.value

                        end

                    end

                end

            end

        end

    end

    if type( r ) == "nil" then

        if not since  or  since <= Failsafe.serial then

            r = Failsafe.serial

        else

            r = false

        end

    end

    return r

end -- Failsafe.failsafe()







-- Export

local p = { }







p.fair = function ( frame )

    -- Format language code

    --     1  -- language code

    local s = mw.text.trim( frame.args 1   or  "" )

    return Multilingual.fair( s )  or  ""

end -- p.fair







p.fallback = function ( frame )

    -- Is another language suitable as replacement?

    --     1  -- language version specifier to be supported

    --     2  -- language specifier of a possible replacement

    local s1 = mw.text.trim( frame.args 1   or  "" )

    local s2 = mw.text.trim( frame.args 2   or  "" )

    local r  = Multilingual.fallback( s1, s2 )

    if type( r ) == "table" then

        r = r 1 

    else

        r = r  and  "1"   or   ""

    end

    return r

end -- p.fallback







p.findCode = function ( frame )

    -- Retrieve language code from language name

    --     1  -- name in current project language

    local s = mw.text.trim( frame.args 1   or  "" )

    return Multilingual.findCode( s )  or  ""

end -- p.findCode







p.fix = function ( frame )

    local r = frame.args 1 

    if r then

        r = Multilingual.fix( mw.text.trim( r ) )

    end

    return r or ""

end -- p.fix







p.format = function ( frame )

    -- Format one or more languages

    --     1          -- language list or item

    --     slang      -- language of the answer, if not native

    --                   * -- native

    --                   ! -- current project

    --                   any valid code

    --     shift      -- capitalize, if "c"; downcase, if "d"

    --                   capitalize first item only, if "f"

    --     link       -- 1 -- link items

    --     scream     -- category title in case of error

    --     split      -- split pattern, if list expected

    --     separator  -- list separator, else split

    --     start      -- prepend first element, if any

    local r

    local link

    if frame.args.link == "1" then

        link = true

    end

    r = Multilingual.format( frame.args 1 ],

                             frame.args.slang,

                             frame.args.shift,

                             link,

                             frame.args.scream,

                             frame,

                             frame.args.split,

                             frame.args.separator,

                             frame.args.start )

    return r or ""

end -- p.format







p.getBase = function ( frame )

    -- Retrieve base language from possibly combined ISO language code

    --     1  -- code

    local s = mw.text.trim( frame.args 1   or  "" )

    return Multilingual.getBase( s )  or  ""

end -- p.getBase







p.getName = function ( frame )

    -- Retrieve language name from ISO language code

    --     1  -- code

    --     2  -- language to be used for the answer, if not native

    --           ! -- current project

    --           * -- native

    --           any valid code

    local s     = mw.text.trim( frame.args 1   or  "" )

    local slang = frame.args 2 

    local r

    Multilingual.frame = frame

    if slang then

        slang = mw.text.trim( slang )

    end

    r = Multilingual.getName( s, slang )

    return r or ""

end -- p.getName







p.int = function ( frame )

    -- Translated system message

    --     1             -- message ID

    --     lang          -- language code

    --     $1, $2, ...   -- parameters

    local sysMsg = frame.args 1 

    local r

    if sysMsg then

        sysMsg = mw.text.trim( sysMsg )

        if sysMsg ~= "" then

            local n     = 0

            local slang = frame.args.lang

            local i, params, s

            if slang == "" then

                slang = false

            end

            for k, v in pairs( frame.args ) do

                if type( k ) == "string" then

                    s = k:match( "^%$(%d+)$" )

                    if s then

                        i = tonumber( s )

                        if i > n then

                            n = i

                        end

                    end

                end

            end -- for k, v

            if n > 0 then

                local s

                params = { }

                for i = 1, n do

                    s = frame.args "$" .. tostring( i )   or  ""

                    table.insert( params, s )

                end -- for i

            end

            r = Multilingual.int( sysMsg, slang, params )

        end

    end

    return r or ""

end -- p.int







p.isLang = function ( frame )

    -- Could this be an ISO language code?

    --     1  -- code

    local s = mw.text.trim( frame.args 1   or  "" )

    local lucky, r = pcall( Multilingual.isLang, s )

    return r and "1" or ""

end -- p.isLang







p.isLangWiki = function ( frame )

    -- Could this be a Wiki language version?

    --     1  -- code

    -- Returns non-empty, if possibly language version

    local s = mw.text.trim( frame.args 1   or  "" )

    local lucky, r = pcall( Multilingual.isLangWiki, s )

    return r and "1" or ""

end -- p.isLangWiki







p.isRTL = function ( frame )

    -- Check whether language is written right-to-left

    --     1  -- string, with language code

    -- Returns non-empty, if right-to-left

    local s = mw.text.trim( frame.args 1   or  "" )

    return Multilingual.isRTL( s ) and "1" or ""

end -- p.isRTL()







p.message = function ( frame )

    -- Translation of text element

    return Multilingual.message( fold( frame ), frame )

end -- p.message







p.sitelink = function ( frame )

    -- Make link at local or other site with optimal linktext translation

    --     1  -- item ID

    local s = mw.text.trim( frame.args 1   or  "" )

    local r

    if s:match( "^%d+$") then

        r = tonumber( s )

    elseif s:match( "^Q%d+$") then

        r = s

    end

    if r then

        r = Multilingual.sitelink( r, frame )

    end

    return r or s

end -- p.sitelink







p.tabData = function ( frame )

    -- Retrieve best message text from Commons Data

    --     1    -- page identification on Commons

    --     2    -- keyword

    --     alt  -- fallback text

    local suite = frame.args 1 

    local seek  = frame.args 2 

    local salt  = frame.args.alt

    local r     = Multilingual.tabData( suite, seek, salt, frame )

    return r

end -- p.tabData







p.userLang = function ( frame )

    -- Which language does the current user prefer?

    --     1  -- space separated list of available ISO 639 codes

    local s = mw.text.trim( frame.args 1   or  "" )

    return Multilingual.userLang( s, frame )

end -- p.userLang







p.wikibase = function ( frame )

    -- Optimal translation of wikibase component

    --     1  -- object ID

    --     2  -- 1 for "descriptions", 0 for "labels".

    --           or either "descriptions" or "labels"

    local r

    local s = mw.text.trim( frame.args 1   or  "" )

    if s ~= "" then

        local s2    = mw.text.trim( frame.args 2   or  "0" )

        local slang = mw.text.trim( frame.args.lang  or  "" )

        local large = ( s2 ~= ""  and  s2 ~= "0" )

        if slang == "" then

            slang = false

        end

        r = Multilingual.wikibase( s, large, slang, frame )

    end

    return r or ""

end -- p.wikibase







p.failsafe = function ( frame )

    -- Versioning interface

    local s = type( frame )

    local since

    if s == "table" then

        since = frame.args 1 

    elseif s == "string" then

        since = frame

    end

    if since then

        since = mw.text.trim( since )

        if since == "" then

            since = false

        end

    end

    return Failsafe.failsafe( since )  or  ""

end -- p.failsafe()







p.Multilingual = function ()

    return Multilingual

end -- p.Multilingual



return p