FANDOM


-- <nowiki>
-- I18n storage module for FANDOM.
-- @module      i18n
-- @version     1.3.3
-- @usage       require("Dev:I18n")
-- @author      Speedit
-- @author      KockaAdmiralac
-- @release     alpha; untested
-- @todo        Fallbacks and unit testing.
local i18n = {}
 
-- Module variables & dependencies.
local title = mw.title.getCurrentTitle()
local fallbacks = require('Dev:Fallbacklist')
 
-- I18n datastore class.
-- @section     i18nd
local i18nd = {}
 
-- Datastore language getter.
-- @name        i18nd:getLang
-- @return      {string} Default language (datastore messages).
function i18nd:getLang()
    return self.defaultLang
end
 
-- Datastore language setter to user language.
-- @name        i18nd:useUserLang
-- @return      {self} Self instance.
function i18nd:useUserLang()
    self.defaultLang = i18n.getLang() or self.defaultLang
    return self
end
 
-- Datastore language setter to user language.
-- @name        i18nd:useContentLang
-- @return      {self} Self instance.
function i18nd:useContentLang()
    self.defaultLang = mw.language.getContentLanguage():getCode()
    return self
end
 
-- Datastore language setter to specificed language.
-- @name        i18nd:useLang
-- @param       {string} code Language code to use.
-- @return      {self} Self instance.
function i18nd:useLang(code)
    self.defaultLang = isValidCode(code)
        and code
        or  self.defaultLang
    return self
end
 
-- Datastore temporary language setter to user language.
-- @name        i18nd:inUserLang
-- @return      {self} Self instance.
function i18nd:inUserLang()
    self.tempLang = i18n.getLang() or i18nd.tempLang
    return self
end
 
-- Datastore temporary language setter to user language.
-- @name        i18nd:inContentLang
-- @return      {self} Self instance.
function i18nd:inContentLang()
    self.tempLang = mw.language.getContentLanguage():getCode()
    return self
end
 
-- Datastore temporary language setter to specificed language.
-- @name        i18nd:inLang
-- @param       {string} code Language code to use.
-- @return      {self} Self instance.
function i18nd:inLang(code)
    self.tempLang = isValidCode(code)
        and code
        or  self.tempLang
    return self
end
 
-- Datastore temporary source setter to specificed datastore.
-- @name        i18nd:fromSource
-- @param       {string} ... Source name(s) to use.
-- @return      {self} Self instance.
function i18nd: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 message utility.
-- @name        i18nd:msg
-- @param       {string|table} opts Message configuration or key.
-- @param[opt]  {string} opts.key Message key to return.
-- @param[opt]  {table} opts.args Arguments to substitute.
-- @param[opt]  {table} opts.sources Source names to limit to.
-- @param[opt]  {table} opts.lang Temporary language to use.
-- @param[opt]  {string} ... Arguments to substitute.
-- @return      {string} Message key or '<key>'.
-- @todo        Better fallback system with [[Dev:Fallbacklist]].
function i18nd:msg(opts, ...)
    local frame = mw.getCurrentFrame()
    -- Argument normalization.
    if not self or not opts then
        error('missing arguments in i18nd: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 = handleArgs(msg, args)
        end
        if msg and source_i[i] and lang ~= 'qqx' then
            return frame
                and frame:preprocess(mw.text.trim(msg))
                or  mw.text.trim(msg)
        end
    end
    return mw.text.nowiki('<' .. key .. '>')
end
 
-- Argument substitution as $n where n > 0.
-- @param       {string} msg Message to substitute arguments into.
-- @param       {table} args Arguments table to substitute.
-- @return      {string} Resulting message.
function handleArgs(msg, args)
    for i, a in ipairs(args) do
        msg = (string.gsub(msg, '%$' .. i, a))
    end
    return msg
end
 
-- Checks whether a language code is valid.
-- @param       {string} code Language code to check
-- @return      {bool} Whether the language code is valid.
function isValidCode(code)
    return type(code) == 'string' and #mw.language.fetchLanguageName(code) ~= 0
end
 
-- Language code function.
-- @usage       {{#invoke:i18n|getLang}}
-- @return      {string} code Language code.
function i18n.getLang()
    local code = mw.language.getContentLanguage():getCode()
    local frame = mw.getCurrentFrame()
    local subPage = title.subpageText
    local uselang
    -- Language argument test.
    if isValidCode( ( (frame or {}).args or {}).uselang or '') then
        code = frame.args.uselang
    elseif isValidCode( ( (frame and frame.getParent(frame) or {}).args or {}).uselang or '') then
        code = frame:getParent().args.uselang
    -- Subpage language test.
    elseif title.isSubpage and isValidCode(subPage) then
        code = isValidCode(subPage) and subPage or code
    -- User language test.
    elseif frame then
        uselang = frame:preprocess('{{int:lang}}')
        code = mw.text.decode(uselang) == '<lang>'
            and code
            or  uselang
    end
    return code
end
 
-- I18n message datastore loader.
-- @param       {string} source ROOTPAGENAME/path of target i18n submodule.
-- @usage       require('Dev:Install').loadMessages(source)
-- @raise       'no source supplied to i18n.loadMessages'
-- @return      {table} i18n I18n datastore instance.
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 = {}
                i18nd.__index = i18nd
                setmetatable(ds, i18nd)
                -- Set default language.
                ds.defaultLang = i18n.getLang()
                ds._messages = {}
            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
 
-- I18n message function.
-- @param       {table} frame Frame table from invocation.
-- @param       {string} frame.args[1] ROOTPAGENAME of i18n submodule.
-- @param       {string} frame.args[2] Key of i18n message.
-- @param       {string} frame.args.lang Default language of message (optional).
-- @usage       {{#invoke:i18n|getMsg|source|key}}
-- @raise       'missing arguments in i18n.getMsg'
-- @return      {string} msg I18n message in localised language.
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 i18nd = i18n.loadMessages(source)
    -- Pass through language argument.
    i18nd:inLang(frame.args.lang)
    -- Return message.
    return i18nd:msg { key = key, args = repl }
end
 
-- Template wrapper for [[Template:I18n]].
-- @param               {table} frameChild Frame invocation object.
-- @usage               {{#invoke:i18n|main}}
function i18n.main(frameChild)
    local frame = frameChild:getParent()
    local args = {}
    for p, v in pairs(frame.args) do args[p] = v end
    -- Extract function name as first argument.
    local fn_name = args[1] and table.remove(args, 1)
    -- Check for function argument.
    if fn_name == nil then
        error((mw.ustring.gsub(mw.ustring.match(mw.message.new('scribunto-common-nofunction'):plain(), ':%s(.*)%p$'), '^.', mw.ustring.lower)))
    end
    -- Check function exists.
    if i18n[fn_name] == nil then
        error((mw.ustring.gsub(mw.ustring.match(mw.message.new('scribunto-common-nosuchfunction'):plain(), ':%s(.*)%p$'), '^.', mw.ustring.lower)))
    end
    -- Execute function if it does.
    frame.args = args
    return i18n[fn_name](frame)
end
 
return i18n
-- </nowiki>
Community content is available under CC-BY-SA unless otherwise noted.

Fandom may earn an affiliate commission on sales made from links on this page.

Stream the best stories.

Fandom may earn an affiliate commission on sales made from links on this page.

Get Disney+