Diferencia entre revisiones de «Módulo:I18n»

De Wikimedia Small Projects
(Página creada con «-- <nowiki> return { ["_metadata"] = { ["order"] = { "aside", "bgcolor", "bordercolor", "class", "collapsed", "comment", "dismiss", "header", "id", "image", "imagelink", "imagewidth", "style", "text", "type" } }, ["en"] = { ["aside"] = "aside", ["b…»)
 
Sin resumen de edición
Línea 1: Línea 1:
-- <nowiki>
--- I18n library for message storage in Lua datastores.
return {
--  The module is designed to enable message separation from modules &
    ["_metadata"] = {
--  templates. It has support for handling language fallbacks. This
        ["order"] = {
--  module is a Lua port of [[I18n-js]] and i18n modules that can be loaded
            "aside",
--  by it are editable through [[I18nEdit]].
            "bgcolor",
-- 
            "bordercolor",
--  @module        i18n
            "class",
--  @version        1.4.0
            "collapsed",
--  @require        Module:Entrypoint
            "comment",
--  @require        Module:Fallbacklist
            "dismiss",
--  @author         [[User:KockaAdmiralac|KockaAdmiralac]]
            "header",
--  @author         [[User:Speedit|Speedit]]
            "id",
--  @attribution    [[User:Cqm|Cqm]]
            "image",
--  @release        stable
            "imagelink",
--  @see            [[I18n|I18n guide]]
            "imagewidth",
--  @see            [[I18n-js]]
            "style",
--  @see            [[I18nEdit]]
            "text",
--  <nowiki>
            "type"
local i18n, _i18n = {}, {}
         }
 
    },
--  Module variables & dependencies.
    ["en"] = {
local title = mw.title.getCurrentTitle()
         ["aside"] = "aside",
local fallbacks = require('Dev:Fallbacklist')
        ["bgcolor"] = "bgcolor",
local entrypoint = require('Dev:Entrypoint')
        ["bordercolor"] = "bordercolor",
local uselang
        ["class"] = "class",
 
        ["collapsed"] = "collapsed",
--- Argument substitution as $n where n > 0.
        ["comment"] = "comment",
--  @function          _i18n.handleArgs
        ["dismiss"] = "Dismiss",
--  @param              {string} msg Message to substitute arguments into.
        ["header"] = "header",
--  @param              {table} args Arguments table to substitute.
        ["id"] = "id",
--  @return            {string} Resulting message.
        ["image"] = "image",
--  @local
        ["imagelink"] = "imagelink",
function _i18n.handleArgs(msg, args)
        ["imagewidth"] = "imagewidth",
    for i, a in ipairs(args) do
         ["style"] = "style",
         msg = (string.gsub(msg, '%$' .. tostring(i), tostring(a)))
        ["text"] = "text",
    end
        ["type"] = "type"
     return msg
     },
end
     ["de"] = {
 
         ["aside"] = "nebenbemerkung",
--- Checks whether a language code is valid.
         ["bgcolor"] = "hintergrundfarbe",
--  @function          _i18n.isValidCode
        ["bordercolor"] = "randfarbe",
--  @param              {string} code Language code to check.
        ["class"] = "klasse",
--  @return            {boolean} Whether the language code is valid.
        ["collapsed"] = "eingeklappt",
--  @local
        ["comment"] = "Kommentar",
function _i18n.isValidCode(code)
        ["dismiss"] = "Verstecken",
     return type(code) == 'string' and #mw.language.fetchLanguageName(code) ~= 0
         ["header"] = "Titel",
end
        ["id"] = "id",
 
        ["image"] = "bild",
--- Checks whether a message contains unprocessed wikitext.
        ["imagelink"] = "bildlink",
--  Used to optimise message getter by not preprocessing pure text.
        ["imagewidth"] = "bildbreite",
--  @function          _i18n.isWikitext
        ["style"] = "style",
--  @param              {string} msg Message to check.
        ["text"] = "Text",
--  @return            {boolean} Whether the message contains wikitext.
        ["type"] = "typ"
function _i18n.isWikitext(msg)
    },
    return
    ["et"] = {
         type(msg) == 'string' and
        ["aside"] = "kõrval",
         (
        ["bgcolor"] = "taustavärv",
            msg:find('%-%-%-%-') or
        ["bordercolor"] = "piirivärv",
            msg:find('%f[^\n%z][;:*#] ') or
        ["class"] = "klass",
            msg:find('%f[^\n%z]==* *[^\n|]+ =*=%f[\n]') or
        ["comment"] = "kommentaar",
            msg:find('%b<>') or msg:find('\'\'') or
        ["dismiss"] = "Loobu",
            msg:find('%[%b[]%]') or msg:find('{%b{}}')
         ["id"] = "id",
         )
        ["image"] = "pilt",
end
         ["imagelink"] = "pildilink",
 
         ["imagewidth"] = "pildisuurus",
--- I18n datastore class.
        ["style"] = "stiil",
--  This is used to control language translation and access to individual
         ["text"] = "tekst",
--  messages. The datastore instance provides language and message
        ["type"] = "tüüp"
--  getter-setter methods, which can be used to internationalize Lua modules.
     },
--  The language methods (any ending in `Lang`) are all **chainable**.
     ["hi"] = {
--  @type            Data
        ["aside"] = "अलग",
local Data = {}
         ["bgcolor"] = "bgcolor",
Data.__index = Data
         ["bordercolor"] = "bordercolour",
 
         ["class"] = "क्लास",
--- Datastore message getter utility.
        ["collapsed"] = "छोटा",
--  This method returns localized messages from the datastore corresponding
        ["comment"] = "टिप्पणी",
--  to a `key`. These messages may have `$n` parameters, which can be
         ["dismiss"] = "हटाएँ",
--  replaced by optional argument strings supplied by the `msg` call.
         ["header"] = "हैडर",
-- 
         ["id"] = "id",
--  This function supports [[Lua reference manual#named_arguments|named
         ["image"] = "चित्र",
--  arguments]]. The named argument syntax is more versatile despite its
         ["imagelink"] = "imagelink",
--  verbosity; it can be used to select message language & source(s).
         ["imagewidth"] = "imagewidth",
--  @function          Data:msg
         ["style"] = "स्टाइल",
--  @usage
        ["text"] = "टेक्स्ट",
-- 
         ["type"] = "प्रकार"
--      ds:msg{
     },
--          key = 'message-name',
     ["ja"] = {
--          lang = '',
        ["aside"] = "アサイド",
--          args = {...},
        ["bgcolor"] = "背景色",
--          sources = {}
        ["bordercolor"] = "境界線の色",
--      }
         ["class"] = "クラス",
-- 
         ["collapsed"] = "折り畳む",
--  @usage
        ["comment"] = "コメント",
-- 
         ["dismiss"] = "後で見る",
--      ds:msg('message-name', ...)
        ["header"] = "ヘッダー",
-- 
         ["id"] = "ID",
--  @param              {string|table} opts Message configuration or key.
         ["image"] = "画像",
--  @param[opt]         {string} opts.key Message key to return from the
        ["imagelink"] = "画像リンク",
--                      datastore.
        ["imagewidth"] = "画像の幅",
--  @param[opt]         {table} opts.args Arguments to substitute into the
        ["style"] = "スタイル",
--                      message (`$n`).
        ["text"] = "テキスト",
--  @param[opt]         {table} opts.sources Source names to limit to (see
        ["type"] = "種類"
--                      `Data:fromSources`).
    },
--  @param[opt]         {table} opts.lang Temporary language to use (see
    ["pl"] = {
--                      `Data:inLang`).
         ["aside"] = "aside",
--  @param[opt]         {string} ... Arguments to substitute into the message
         ["bgcolor"] = "tło",
--                      (`$n`).
         ["bordercolor"] = "krawędź",
--  @error[115]         {string} 'missing arguments in Data:msg'
         ["class"] = "klasa",
--  @return            {string} Localised datastore message or `'<key>'`.
        ["collapsed"] = "zwinięty",
function Data:msg(opts, ...)
         ["comment"] = "komentarz",
    local frame = mw.getCurrentFrame()
        ["dismiss"] = "Odrzuć",
    -- Argument normalization.
        ["header"] = "nagłówek",
    if not self or not opts then
         ["id"] = "id",
         error('missing arguments in Data:msg')
         ["image"] = "obraz",
    end
        ["imagelink"] = "link obrazu",
    local key = type(opts) == 'table' and opts.key or opts
        ["imagewidth"] = "szerokość obrazu",
    local args = opts.args or {...}
        ["style"] = "style",
    -- Configuration parameters.
        ["text"] = "tekst",
    if opts.sources then
         ["type"] = "typ"
         self:fromSources(unpack(opts.sources))
     },
    end
     ["pt-br"] = {
    if opts.lang then
        ["aside"] = "de lado",
         self:inLang(opts.lang)
        ["bgcolor"] = "bgcolor",
    end
        ["bordercolor"] = "cor da borda",
    -- Source handling.
         ["class"] = "class",
    local source_n = self.tempSources or self._sources
         ["collapsed"] = "recolher",
    local source_i = {}
        ["comment"] = "comentário",
    for n, i in pairs(source_n) do
        ["dismiss"] = "Dispensar",
         source_i[i] = n
        ["header"] = "cabeçalho",
    end
        ["id"] = "id",
    self.tempSources = nil
        ["image"] = "imagem",
     -- Language handling.
        ["imagelink"] = "link imagem",
     local lang = self.tempLang or self.defaultLang
        ["imagewidth"] = "largura da imagem",
    self.tempLang = nil
        ["style"] = "estilo",
    -- Message fetching.
        ["text"] = "texto",
    local msg
        ["type"] = "tipo"
    for i, messages in ipairs(self._messages) do
     },
        -- Message data.
     ["ru"] = {
         local msg = (messages[lang] or {})[key]
        ["aside"] = "текст справа",
         -- Fallback support (experimental).
         ["bgcolor"] = "bgcolor",
         for _, l in ipairs((fallbacks[lang] or {})) do
         ["bordercolor"] = "bordercolor",
            if msg == nil then
        ["class"] = "class",
                msg = (messages[l] or {})[key]
        ["collapsed"] = "collapsed",
            end
        ["comment"] = "дополнение",
         end
         ["dismiss"] = "dismiss",
         -- Internal fallback to 'en'.
         ["header"] = "заголовок",
         msg = msg ~= nil and msg or messages.en[key]
         ["id"] = "id",
         -- Handling argument substitution from Lua.
         ["image"] = "изображение",
         if msg and source_i[i] and #args > 0 then
         ["imagelink"] = "ссылка в изображении",
            msg = _i18n.handleArgs(msg, args)
        ["imagewidth"] = "ширина изображения",
         end
        ["style"] = "style",
         if msg and source_i[i] and lang ~= 'qqx' then
        ["text"] = "текст",
            return frame and _i18n.isWikitext(msg)
        ["type"] = "type"
                and frame:preprocess(mw.text.trim(msg))
     },
                or  mw.text.trim(msg)
     ["tr"] = {
         end
        ["aside"] = "kenar",
     end
         ["bgcolor"] = "aprengi",
     return mw.text.nowiki('<' .. key .. '>')
        ["bordercolor"] = "kenarlıkrengi",
end
         ["class"] = "sınıf",
 
        ["collapsed"] = "daratılmış",
--- Datastore template parameter getter utility.
        ["comment"] = "yorum",
--  This method, given a table of arguments, tries to find a parameter's
        ["dismiss"] = "Kapat",
--  localized name in the datastore and returns its value, or nil if
        ["header"] = "başlık",
--  not present.
        ["id"] = "kimlik",
--
        ["image"] = "resim",
--  This method always uses the wiki's content language.
         ["imagelink"] = "resimbağlantısı",
--  @function          Data:parameter
        ["imagewidth"] = "resimgenişliği",
--  @param              {string} parameter Parameter's key in the datastore
        ["style"] = "stil",
--  @param              {table} args Arguments to find the parameter in
        ["text"] = "metin",
--  @error[176]         {string} 'missing arguments in Data:parameter'
        ["type"] = "tür"
--  @return            {string|nil} Parameter's value or nil if not present
     },
function Data:parameter(key, args)
     ["vi"] = {
    -- Argument normalization.
        ["aside"] = "phía bên",
    if not self or not key or not args then
         ["bgcolor"] = "màu nền",
         error('missing arguments in Data:parameter')
         ["bordercolor"] = "màu viền",
    end
        ["class"] = "lớp",
    local contentLang = mw.language.getContentLanguage():getCode()
        ["collapsed"] = "đã đóng",
    -- Message fetching.
        ["comment"] = "bình luận",
    for i, messages in ipairs(self._messages) do
        ["dismiss"] = "Bỏ qua",
         local msg = (messages[contentLang] or {})[key]
        ["header"] = "tiêu đề",
         if msg ~= nil and args[msg] ~= nil then
        ["id"] = "id",
            return args[msg]
        ["image"] = "hình ảnh",
         end
        ["imagelink"] = "liên kết ảnh",
         for _, l in ipairs((fallbacks[contentLang] or {})) do
        ["imagewidth"] = "độ rộng ảnh",
            if msg == nil or args[msg] == nil then
        ["style"] = "phong cách",
                -- Check next fallback.
        ["text"] = "văn bản",
                msg = (messages[l] or {})[key]
         ["type"] = "loại"
            else
     },
                -- A localized message was found.
     ["zh"] = {
                return args[msg]
         ["aside"] = "侧栏",
            end
        ["bgcolor"] = "背景颜色",
         end
         ["bordercolor"] = "边框颜色",
         -- Fallback to English.
         ["class"] = "类",
         msg = messages.en[key]
         ["collapsed"] = "折叠",
         if msg ~= nil and args[msg] ~= nil then
         ["comment"] = "备注",
            return args[msg]
        ["dismiss"] = "关闭",
         end
        ["header"] = "标题",
    end
        ["id"] = "id",
end
        ["image"] = "图像",
 
        ["imagelink"] = "图像链接",
--- Datastore temporary source setter to a specificed subset of datastores.
        ["imagewidth"] = "图像宽度",
--  By default, messages are fetched from the datastore in the same
        ["style"] = "样式",
--  order of priority as `i18n.loadMessages`.
        ["text"] = "文字",
--  @function          Data:fromSource
        ["type"] = "类型"
--  @param              {string} ... Source name(s) to use.
     },
--  @return            {Data} Datastore instance.
     ["zh-tw"] = {
function Data:fromSource(...)
        ["aside"] = "邊界",
    local c = select('#', ...)
        ["bgcolor"] = "背景色彩",
    if c ~= 0 then
        ["bordercolor"] = "邊框色彩",
         self.tempSources = {}
         ["class"] = "屬性",
         for i = 1, c do
         ["collapsed"] = "摺疊",
            local n = select(i, ...)
         ["comment"] = "評論",
            if type(n) == 'string' and type(self._sources[n]) == 'number' then
         ["dismiss"] = "忽略",
                self.tempSources[n] = self._sources[n]
         ["header"] = "標頭",
            end
         ["id"] = "ID",
         end
         ["image"] = "圖片",
    end
        ["imagelink"] = "圖片連結",
     return self
         ["imagewidth"] = "圖片長度",
end
        ["style"] = "樣式",
 
        ["text"] = "文字",
--- Datastore default language getter.
        ["type"] = "類型"
--  @function          Data:getLang
    }
--  @return            {string} Default language to serve datastore messages in.
}
function Data:getLang()
     return self.defaultLang
end
 
--- Datastore language setter to `wgUserLanguage`.
--  @function          Data:useUserLang
--  @return            {Data} Datastore instance.
--  @note              Scribunto only registers `wgUserLanguage` when an
--                     invocation is at the top of the call stack.
function Data:useUserLang()
    self.defaultLang = i18n.getLang() or self.defaultLang
    return self
end
 
--- Datastore language setter to `wgContentLanguage`.
--  @function          Data:useContentLang
--  @return            {Data} Datastore instance.
function Data:useContentLang()
    self.defaultLang = mw.language.getContentLanguage():getCode()
    return self
end
 
--- Datastore language setter to specificed language.
--  @function          Data:useLang
--  @param              {string} code Language code to use.
--  @return            {Data} Datastore instance.
function Data:useLang(code)
    self.defaultLang = _i18n.isValidCode(code)
         and code
         or  self.defaultLang
    return self
end
 
--- Temporary datastore language setter to `wgUserLanguage`.
--  The datastore language reverts to the default language in the next
--  @{Data:msg} call.
--  @function          Data:inUserLang
--  @return            {Data} Datastore instance.
function Data:inUserLang()
    self.tempLang = i18n.getLang() or self.tempLang
     return self
end
 
--- Temporary datastore language setter to `wgContentLanguage`.
--  Only affects the next @{Data:msg} call.
--  @function          Data:inContentLang
--  @return            {Data} Datastore instance.
function Data:inContentLang()
     self.tempLang = mw.language.getContentLanguage():getCode()
    return self
end
 
--- Temporary datastore language setter to a specificed language.
--  Only affects the next @{Data:msg} call.
--  @function          Data:inLang
--  @param              {string} code Language code to use.
--  @return            {Data} Datastore instance.
function Data:inLang(code)
    self.tempLang = _i18n.isValidCode(code)
         and code
         or  self.tempLang
    return self
end
 
--  Package functions.
 
--- Localized message getter by key.
--  Can be used to fetch messages in a specific language code through `uselang`
--  parameter. Extra numbered parameters can be supplied for substitution into
--  the datastore message.
--  @function          i18n.getMsg
--  @param              {table} frame Frame table from invocation.
--  @param              {table} frame.args Metatable containing arguments.
--  @param              {string} frame.args[1] ROOTPAGENAME of i18n submodule.
--  @param              {string} frame.args[2] Key of i18n message.
--  @param[opt]         {string} frame.args.lang Default language of message.
--  @error[271]         'missing arguments in i18n.getMsg'
--  @return            {string} I18n message in localised language.
--  @usage              {{i18n|getMsg|source|key|arg1|arg2|uselang {{=}} code}}
function i18n.getMsg(frame)
    if
         not frame or
         not frame.args or
         not frame.args[1] or
         not frame.args[2]
    then
         error('missing arguments in i18n.getMsg')
    end
    local source = frame.args[1]
    local key = frame.args[2]
     -- Pass through extra arguments.
     local repl = {}
    for i, a in ipairs(frame.args) do
         if i >= 3 then
            repl[i-2] = a
         end
    end
    -- Load message data.
    local ds = i18n.loadMessages(source)
    -- Pass through language argument.
    ds:inLang(frame.args.uselang)
    -- Return message.
    return ds:msg { key = key, args = repl }
end
--- I18n message datastore loader.
--  @function          i18n.loadMessages
--  @param              {string} ... ROOTPAGENAME/path for target i18n
--                      submodules.
--  @error[322]        {string} 'no source supplied to i18n.loadMessages'
--  @return            {table} I18n datastore instance.
--  @usage              require('Dev:I18n').loadMessages('1', '2')
function i18n.loadMessages(...)
    local ds
     local i = 0
     local s = {}
    for j = 1, select('#', ...) do
         local source = select(j, ...)
         if type(source) == 'string' and source ~= '' then
            i = i + 1
            s[source] = i
            if not ds then
                -- Instantiate datastore.
                ds = {}
                ds._messages = {}
                -- Set default language.
                setmetatable(ds, Data)
                ds:useUserLang()
            end
            source = string.gsub(source, '^.', mw.ustring.upper)
            source = mw.ustring.find(source, ':')
                and source
                or  'Dev:' .. source .. '/i18n'
            ds._messages[i] = mw.loadData(source)
         end
     end
     if not ds then
         error('no source supplied to i18n.loadMessages')
    else
         -- Attach source index map.
         ds._sources = s
         -- Return datastore instance.
         return ds
    end
end
 
--- Language code getter.
--  Can validate a template's language code through `uselang` parameter.
--  @function          i18n.getLang
--  @usage              {{i18n|getLang|uselang {{=}} code}}
--  @return            {string} Language code.
function i18n.getLang()
     local frame = mw.getCurrentFrame() or {}
     local parentFrame = frame.getParent and frame:getParent() or {}
 
    local code = mw.language.getContentLanguage():getCode()
    local subPage = title.subpageText
 
    -- Language argument test.
    local langOverride =
         (frame.args or {}).uselang or
         (parentFrame.args or {}).uselang
    if _i18n.isValidCode(langOverride) then
         code = langOverride
 
    -- Subpage language test.
    elseif title.isSubpage and _i18n.isValidCode(subPage) then
         code = _i18n.isValidCode(subPage) and subPage or code
 
    -- User language test.
    elseif parentFrame.preprocess or frame.preprocess then
         uselang = uselang
            or  parentFrame.preprocess
                and parentFrame:preprocess('{{int:lang}}')
                or  frame:preprocess('{{int:lang}}')
         local decodedLang = mw.text.decode(uselang)
         if decodedLang ~= '<lang>' and decodedLang ~= '⧼lang⧽' then
            code = decodedLang == '(lang)'
                and 'qqx'
                or  uselang
         end
    end
 
    return code
end
 
--- Template wrapper for [[Template:I18n]].
--  @function          i18n.main
--  @param              {table} frame Frame invocation object.
--  @return            {string} Module output in template context.
--  @usage              {{#invoke:i18n|main}}
i18n.main = entrypoint(i18n)
 
return i18n
-- </nowiki>
-- </nowiki>

Revisión del 23:02 1 nov 2023

La documentación para este módulo puede ser creada en Módulo:I18n/doc

--- I18n library for message storage in Lua datastores.
--  The module is designed to enable message separation from modules &
--  templates. It has support for handling language fallbacks. This
--  module is a Lua port of [[I18n-js]] and i18n modules that can be loaded
--  by it are editable through [[I18nEdit]].
--  
--  @module         i18n
--  @version        1.4.0
--  @require        Module:Entrypoint
--  @require        Module:Fallbacklist
--  @author         [[User:KockaAdmiralac|KockaAdmiralac]]
--  @author         [[User:Speedit|Speedit]]
--  @attribution    [[User:Cqm|Cqm]]
--  @release        stable
--  @see            [[I18n|I18n guide]]
--  @see            [[I18n-js]]
--  @see            [[I18nEdit]]
--  <nowiki>
local i18n, _i18n = {}, {}

--  Module variables & dependencies.
local title = mw.title.getCurrentTitle()
local fallbacks = require('Dev:Fallbacklist')
local entrypoint = require('Dev:Entrypoint')
local uselang

--- Argument substitution as $n where n > 0.
--  @function           _i18n.handleArgs
--  @param              {string} msg Message to substitute arguments into.
--  @param              {table} args Arguments table to substitute.
--  @return             {string} Resulting message.
--  @local
function _i18n.handleArgs(msg, args)
    for i, a in ipairs(args) do
        msg = (string.gsub(msg, '%$' .. tostring(i), tostring(a)))
    end
    return msg
end

--- Checks whether a language code is valid.
--  @function           _i18n.isValidCode
--  @param              {string} code Language code to check.
--  @return             {boolean} Whether the language code is valid.
--  @local
function _i18n.isValidCode(code)
    return type(code) == 'string' and #mw.language.fetchLanguageName(code) ~= 0
end

--- Checks whether a message contains unprocessed wikitext.
--  Used to optimise message getter by not preprocessing pure text.
--  @function           _i18n.isWikitext
--  @param              {string} msg Message to check.
--  @return             {boolean} Whether the message contains wikitext.
function _i18n.isWikitext(msg)
    return
        type(msg) == 'string' and
        (
            msg:find('%-%-%-%-') or
            msg:find('%f[^\n%z][;:*#] ') or
            msg:find('%f[^\n%z]==* *[^\n|]+ =*=%f[\n]') or
            msg:find('%b<>') or msg:find('\'\'') or
            msg:find('%[%b[]%]') or msg:find('{%b{}}')
        )
end

--- I18n datastore class.
--  This is used to control language translation and access to individual
--  messages. The datastore instance provides language and message
--  getter-setter methods, which can be used to internationalize Lua modules.
--  The language methods (any ending in `Lang`) are all **chainable**.
--  @type            Data
local Data = {}
Data.__index = Data

--- Datastore message getter utility.
--  This method returns localized messages from the datastore corresponding
--  to a `key`. These messages may have `$n` parameters, which can be
--  replaced by optional argument strings supplied by the `msg` call.
--  
--  This function supports [[Lua reference manual#named_arguments|named
--  arguments]]. The named argument syntax is more versatile despite its
--  verbosity; it can be used to select message language & source(s).
--  @function           Data:msg
--  @usage
--  
--      ds:msg{
--          key = 'message-name',
--          lang = '',
--          args = {...},
--          sources = {}
--      }
--  
--  @usage
--  
--      ds:msg('message-name', ...)
--  
--  @param              {string|table} opts Message configuration or key.
--  @param[opt]         {string} opts.key Message key to return from the
--                      datastore.
--  @param[opt]         {table} opts.args Arguments to substitute into the
--                      message (`$n`).
--  @param[opt]         {table} opts.sources Source names to limit to (see
--                      `Data:fromSources`).
--  @param[opt]         {table} opts.lang Temporary language to use (see
--                      `Data:inLang`).
--  @param[opt]         {string} ... Arguments to substitute into the message
--                      (`$n`).
--  @error[115]         {string} 'missing arguments in Data:msg'
--  @return             {string} Localised datastore message or `'<key>'`.
function Data:msg(opts, ...)
    local frame = mw.getCurrentFrame()
    -- Argument normalization.
    if not self or not opts then
        error('missing arguments in Data:msg')
    end
    local key = type(opts) == 'table' and opts.key or opts
    local args = opts.args or {...}
    -- Configuration parameters.
    if opts.sources then
        self:fromSources(unpack(opts.sources))
    end
    if opts.lang then
        self:inLang(opts.lang)
    end
    -- Source handling.
    local source_n = self.tempSources or self._sources
    local source_i = {}
    for n, i in pairs(source_n) do
        source_i[i] = n
    end
    self.tempSources = nil
    -- Language handling.
    local lang = self.tempLang or self.defaultLang
    self.tempLang = nil
    -- Message fetching.
    local msg
    for i, messages in ipairs(self._messages) do
        -- Message data.
        local msg = (messages[lang] or {})[key]
        -- Fallback support (experimental).
        for _, l in ipairs((fallbacks[lang] or {})) do
            if msg == nil then
                msg = (messages[l] or {})[key]
            end
        end
        -- Internal fallback to 'en'.
        msg = msg ~= nil and msg or messages.en[key]
        -- Handling argument substitution from Lua.
        if msg and source_i[i] and #args > 0 then
            msg = _i18n.handleArgs(msg, args)
        end
        if msg and source_i[i] and lang ~= 'qqx' then
            return frame and _i18n.isWikitext(msg)
                and frame:preprocess(mw.text.trim(msg))
                or  mw.text.trim(msg)
        end
    end
    return mw.text.nowiki('<' .. key .. '>')
end

--- Datastore template parameter getter utility.
--  This method, given a table of arguments, tries to find a parameter's
--  localized name in the datastore and returns its value, or nil if
--  not present.
--
--  This method always uses the wiki's content language.
--  @function           Data:parameter
--  @param              {string} parameter Parameter's key in the datastore
--  @param              {table} args Arguments to find the parameter in
--  @error[176]         {string} 'missing arguments in Data:parameter'
--  @return             {string|nil} Parameter's value or nil if not present
function Data:parameter(key, args)
    -- Argument normalization.
    if not self or not key or not args then
        error('missing arguments in Data:parameter')
    end
    local contentLang = mw.language.getContentLanguage():getCode()
    -- Message fetching.
    for i, messages in ipairs(self._messages) do
        local msg = (messages[contentLang] or {})[key]
        if msg ~= nil and args[msg] ~= nil then
            return args[msg]
        end
        for _, l in ipairs((fallbacks[contentLang] or {})) do
            if msg == nil or args[msg] == nil then
                -- Check next fallback.
                msg = (messages[l] or {})[key]
            else
                -- A localized message was found.
                return args[msg]
            end
        end
        -- Fallback to English.
        msg = messages.en[key]
        if msg ~= nil and args[msg] ~= nil then
            return args[msg]
        end
    end
end

--- Datastore temporary source setter to a specificed subset of datastores.
--  By default, messages are fetched from the datastore in the same
--  order of priority as `i18n.loadMessages`.
--  @function           Data:fromSource
--  @param              {string} ... Source name(s) to use.
--  @return             {Data} Datastore instance.
function Data:fromSource(...)
    local c = select('#', ...)
    if c ~= 0 then
        self.tempSources = {}
        for i = 1, c do
            local n = select(i, ...)
            if type(n) == 'string' and type(self._sources[n]) == 'number' then
                self.tempSources[n] = self._sources[n]
            end
        end
    end
    return self
end

--- Datastore default language getter.
--  @function           Data:getLang
--  @return             {string} Default language to serve datastore messages in.
function Data:getLang()
    return self.defaultLang
end

--- Datastore language setter to `wgUserLanguage`.
--  @function           Data:useUserLang
--  @return             {Data} Datastore instance.
--  @note               Scribunto only registers `wgUserLanguage` when an
--                      invocation is at the top of the call stack.
function Data:useUserLang()
    self.defaultLang = i18n.getLang() or self.defaultLang
    return self
end

--- Datastore language setter to `wgContentLanguage`.
--  @function           Data:useContentLang
--  @return             {Data} Datastore instance.
function Data:useContentLang()
    self.defaultLang = mw.language.getContentLanguage():getCode()
    return self
end

--- Datastore language setter to specificed language.
--  @function           Data:useLang
--  @param              {string} code Language code to use.
--  @return             {Data} Datastore instance.
function Data:useLang(code)
    self.defaultLang = _i18n.isValidCode(code)
        and code
        or  self.defaultLang
    return self
end

--- Temporary datastore language setter to `wgUserLanguage`.
--  The datastore language reverts to the default language in the next
--  @{Data:msg} call.
--  @function           Data:inUserLang
--  @return             {Data} Datastore instance.
function Data:inUserLang()
    self.tempLang = i18n.getLang() or self.tempLang
    return self
end

--- Temporary datastore language setter to `wgContentLanguage`.
--  Only affects the next @{Data:msg} call.
--  @function           Data:inContentLang
--  @return             {Data} Datastore instance.
function Data:inContentLang()
    self.tempLang = mw.language.getContentLanguage():getCode()
    return self
end

--- Temporary datastore language setter to a specificed language.
--  Only affects the next @{Data:msg} call.
--  @function           Data:inLang
--  @param              {string} code Language code to use.
--  @return             {Data} Datastore instance.
function Data:inLang(code)
    self.tempLang = _i18n.isValidCode(code)
        and code
        or  self.tempLang
    return self
end

--  Package functions.

--- Localized message getter by key.
--  Can be used to fetch messages in a specific language code through `uselang`
--  parameter. Extra numbered parameters can be supplied for substitution into
--  the datastore message.
--  @function           i18n.getMsg
--  @param              {table} frame Frame table from invocation.
--  @param              {table} frame.args Metatable containing arguments.
--  @param              {string} frame.args[1] ROOTPAGENAME of i18n submodule.
--  @param              {string} frame.args[2] Key of i18n message.
--  @param[opt]         {string} frame.args.lang Default language of message.
--  @error[271]         'missing arguments in i18n.getMsg'
--  @return             {string} I18n message in localised language.
--  @usage              {{i18n|getMsg|source|key|arg1|arg2|uselang {{=}} code}}
function i18n.getMsg(frame)
    if
        not frame or
        not frame.args or
        not frame.args[1] or
        not frame.args[2]
    then
        error('missing arguments in i18n.getMsg')
    end
    local source = frame.args[1]
    local key = frame.args[2]
    -- Pass through extra arguments.
    local repl = {}
    for i, a in ipairs(frame.args) do
        if i >= 3 then
            repl[i-2] = a
        end
    end
    -- Load message data.
    local ds = i18n.loadMessages(source)
    -- Pass through language argument.
    ds:inLang(frame.args.uselang)
    -- Return message.
    return ds:msg { key = key, args = repl }
end
 
--- I18n message datastore loader.
--  @function           i18n.loadMessages
--  @param              {string} ... ROOTPAGENAME/path for target i18n
--                      submodules.
--  @error[322]         {string} 'no source supplied to i18n.loadMessages'
--  @return             {table} I18n datastore instance.
--  @usage              require('Dev:I18n').loadMessages('1', '2')
function i18n.loadMessages(...)
    local ds
    local i = 0
    local s = {}
    for j = 1, select('#', ...) do
        local source = select(j, ...)
        if type(source) == 'string' and source ~= '' then
            i = i + 1
            s[source] = i
            if not ds then
                -- Instantiate datastore.
                ds = {}
                ds._messages = {}
                -- Set default language.
                setmetatable(ds, Data)
                ds:useUserLang()
            end
            source = string.gsub(source, '^.', mw.ustring.upper)
            source = mw.ustring.find(source, ':')
                and source
                or  'Dev:' .. source .. '/i18n'
            ds._messages[i] = mw.loadData(source)
        end
    end
    if not ds then
        error('no source supplied to i18n.loadMessages')
    else
        -- Attach source index map.
        ds._sources = s
        -- Return datastore instance.
        return ds
    end
end

--- Language code getter.
--  Can validate a template's language code through `uselang` parameter.
--  @function           i18n.getLang
--  @usage              {{i18n|getLang|uselang {{=}} code}}
--  @return             {string} Language code.
function i18n.getLang()
    local frame = mw.getCurrentFrame() or {}
    local parentFrame = frame.getParent and frame:getParent() or {}

    local code = mw.language.getContentLanguage():getCode()
    local subPage = title.subpageText

    -- Language argument test.
    local langOverride =
        (frame.args or {}).uselang or
        (parentFrame.args or {}).uselang
    if _i18n.isValidCode(langOverride) then
        code = langOverride

    -- Subpage language test.
    elseif title.isSubpage and _i18n.isValidCode(subPage) then
        code = _i18n.isValidCode(subPage) and subPage or code

    -- User language test.
    elseif parentFrame.preprocess or frame.preprocess then
        uselang = uselang
            or  parentFrame.preprocess
                and parentFrame:preprocess('{{int:lang}}')
                or  frame:preprocess('{{int:lang}}')
        local decodedLang = mw.text.decode(uselang) 
        if decodedLang ~= '<lang>' and decodedLang ~= '⧼lang⧽' then
            code = decodedLang == '(lang)'
                and 'qqx'
                or  uselang
        end
    end

    return code
end

--- Template wrapper for [[Template:I18n]].
--  @function           i18n.main
--  @param              {table} frame Frame invocation object.
--  @return             {string} Module output in template context.
--  @usage              {{#invoke:i18n|main}}
i18n.main = entrypoint(i18n)

return i18n
-- </nowiki>