Modul:Multilingual

Aus FreeWiki
Version vom 1. Juni 2019, 19:34 Uhr von te>PerfektesChaos (2019-05-24)
Zur Navigation springen Zur Suche springen

local Multilingual = { suite = "Multilingual",

                      serial = "2019-05-24",
                      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 slang
       for k, v in pairs( available ) do
           if type( k ) == "string"  and
              type( v ) == "string" then
               table.insert( codes,  mw.text.trim( k:lower() ) )
           end
       end -- for k, v
       slang = Multilingual.userLang( codes, frame )
       if slang then
           for k, v in pairs( available ) do
               if type( k ) == "string"  and
                  mw.text.trim( k:lower() ) == slang then
                   r1 = mw.text.trim( v )
                   if r1 == "" then
                       r1 = false
                   else
                       r2 = slang
                   end
               end
           end -- for k, v
       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