Module:I18n
Appearance
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 wikia:dev:I18n-js and i18n modules that can be loaded by it are editable through wikia:dev:I18nEdit.
On Wikimedia projects, i18n messages are editable through Data:i18n/ subpages on Wikimedia Commons.
Documentation
Package items
i18n.getMsg(frame)(function)- Localized message getter by key.
- Parameters:
- Error: 'missing arguments in i18n.getMsg' (string; line 271)
- Returns: Localized message. (string)
i18n.loadMessages(...)(function)- I18n message datastore loader.
- Parameter:
...ROOTPAGENAME/path for i18n submodules. (string) - Error: 'no source supplied to i18n.loadMessages' (string; line 322)
- Returns: I18n datastore instance. (table)
i18n.getLang()(function)- Language code getter.
- Returns: Language code. (string)
i18n.loadI18n(name, i18n_arg)(function)- Given an i18n table instantiates the values (deprecated).
- Parameters:
i18n.loadI18nFrame(name, i18n_arg)(function)- Loads an i18n for a specific frame (deprecated).
- Parameters:
i18n.main(frame)(function)- Wrapper for the module.
- Parameter:
frameFrame invocation object. (table) - Returns: Module output. (string)
_i18n.isWikitext(msg)(function)- Checks whether a message contains unprocessed wikitext. Used to optimise message getter by not preprocessing pure text.
- Parameter:
msgMessage to check. (string) - Returns: Whether the message contains wikitext. (boolean)
Data
I18n datastore class.
Data:msg(opts, ...)(function)- Datastore message getter utility.
- Parameters:
- Error: 'missing arguments in Data:msg' (string; line 115)
- Returns: Localised message or
'<key>'. (string) Data:parameter(key, args)(function)- Datastore template parameter getter utility.
- Parameters:
- Error: 'missing arguments in Data:parameter' (string; line 176)
- Returns: Parameter value or nil. (string|nil)
Data:fromSource(...)(function)- Datastore temporary source setter.
- Parameter:
...Source name(s). (string) - Returns: Datastore instance. (Data)
Data:getLang()(function)- Datastore default language getter.
- Returns: Default language. (string)
Data:useUserLang()(function)- Datastore language setter to wgUserLanguage.
- Returns: Datastore instance. (Data)
Data:useContentLang()(function)- Datastore language setter to wgContentLanguage.
- Returns: Datastore instance. (Data)
Data:useLang(code)(function)- Datastore language setter to specified language.
- Parameter:
codeLanguage code. (string) - Returns: Datastore instance. (Data)
Data:inUserLang()(function)- Temporary datastore language setter to wgUserLanguage.
- Returns: Datastore instance. (Data)
Data:inContentLang()(function)- Temporary datastore language setter to wgContentLanguage.
- Returns: Datastore instance. (Data)
Data:inLang(code)(function)- Temporary datastore language setter to specified language.
- Parameter:
codeLanguage code. (string) - Returns: Datastore instance. (Data)
See also
--- 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 [[wikia:dev:I18n-js]] and i18n modules that can be loaded
-- by it are editable through [[wikia:dev:I18nEdit]].
--
-- On Wikimedia projects, i18n messages are editable
-- through [[c:Special:PrefixIndex/Data:i18n/|Data:i18n/]] subpages on
-- Wikimedia Commons.
--
-- @module i18n
-- @version 1.4.1
-- @require Module:Entrypoint
-- @require Module:Fallbacklist
-- @author [[wikia:dev:User:KockaAdmiralac|KockaAdmiralac]] (original Fandom implementation)
-- @author [[wikia:dev:User:Speedit|Speedit]] (original Fandom implementation)
-- @author [[User:Awesome Aasim|Awesome Aasim]] (Wikimedia port)
-- @attribution [[wikia:dev:User:Cqm|Cqm]]
-- @release beta
-- @see [[wikia:dev:I18n|I18n guide]]
-- @see [[wikia:dev:I18n-js]]
-- @see [[wikia:dev:I18nEdit]]
-- <nowiki>
local i18n, _i18n = {}, {}
-- Module variables & dependencies.
local title = mw.title.getCurrentTitle()
local fallbacks = require('Module:Fallbacklist')
local entrypoint = require('Module: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.
-- @type Data
local Data = {}
Data.__index = Data
--- Datastore message getter utility.
-- @function Data:msg
-- @param {string|table} opts Message configuration or key.
-- @param[opt] {string} opts.key Message key.
-- @param[opt] {table} opts.args Arguments for `$n` substitution.
-- @param[opt] {table} opts.sources Source names to limit to.
-- @param[opt] {table} opts.lang Temporary language.
-- @param[opt] {string} ... Arguments for `$n` substitution.
-- @error[115] {string} 'missing arguments in Data:msg'
-- @return {string} Localised message or `'<key>'`.
function Data:msg(opts, ...)
local frame = mw.getCurrentFrame()
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 {...}
if opts.sources then
self:fromSource(unpack(opts.sources))
end
if opts.lang then
self:inLang(opts.lang)
end
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
local lang = self.tempLang or self.defaultLang
self.tempLang = nil
local msg
for i, messages in ipairs(self._messages) do
msg = (messages[lang] or {})[key]
for _, l in ipairs(fallbacks[lang] or {}) do
if msg == nil then
msg = (messages[l] or {})[key]
end
end
msg = msg or messages.en[key]
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.
-- @function Data:parameter
-- @param {string} key Parameter 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 value or nil.
function Data:parameter(key, args)
if not self or not key or not args then
error('missing arguments in Data:parameter')
end
local contentLang = mw.language.getContentLanguage():getCode()
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
msg = (messages[l] or {})[key]
else
return args[msg]
end
end
msg = messages.en[key]
if msg ~= nil and args[msg] ~= nil then
return args[msg]
end
end
end
--- Datastore temporary source setter.
-- @function Data:fromSource
-- @param {string} ... Source name(s).
-- @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.
function Data:getLang()
return self.defaultLang
end
--- Datastore language setter to wgUserLanguage.
-- @function Data:useUserLang
-- @return {Data} Datastore instance.
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 specified language.
-- @function Data:useLang
-- @param {string} code Language code.
-- @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.
-- @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.
-- @function Data:inContentLang
-- @return {Data} Datastore instance.
function Data:inContentLang()
self.tempLang = mw.language.getContentLanguage():getCode()
return self
end
--- Temporary datastore language setter to specified language.
-- @function Data:inLang
-- @param {string} code Language code.
-- @return {Data} Datastore instance.
function Data:inLang(code)
self.tempLang = _i18n.isValidCode(code) and code or self.tempLang
return self
end
--- Localized message getter by key.
-- @function i18n.getMsg
-- @param {table} frame Frame table.
-- @param {string} frame.args[1] ROOTPAGENAME of i18n submodule.
-- @param {string} frame.args[2] Message key.
-- @param[opt] {string} frame.args.lang Default language.
-- @error[271] {string} 'missing arguments in i18n.getMsg'
-- @return {string} Localized message.
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]
local repl = {}
for i, a in ipairs(frame.args) do
if i >= 3 then
repl[i-2] = a
end
end
local ds = i18n.loadMessages(source)
ds:inLang(frame.args.uselang)
return ds:msg { key = key, args = repl }
end
--- I18n message datastore loader.
-- @function i18n.loadMessages
-- @param {string} ... ROOTPAGENAME/path for i18n submodules.
-- @error[322] {string} 'no source supplied to i18n.loadMessages'
-- @return {table} 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
ds = {}
ds._messages = {}
setmetatable(ds, Data)
ds:useUserLang()
end
source = string.gsub(source, '^.', mw.ustring.upper)
local fullSource = mw.ustring.find(source, ':') and source or 'Module:' .. source .. '/i18n'
local success, messages = pcall(mw.loadData, fullSource)
if success then
-- Handle legacy { i18n = {...} } structure
if messages.i18n then
messages = { en = messages.i18n }
end
local msgCopy = {}
local langSecond = nil
for lang_id, msgtbl in pairs(messages) do
if langSecond == nil then
if lang_id == "qqq" or fallbacks[lang_id] then
langSecond = false
else
langSecond = true
end
end
for id_lang, msg in pairs(msgtbl) do
if langSecond then
msgCopy[id_lang] = msgCopy[id_lang] or {}
msgCopy[id_lang][lang_id] = msg
else
msgCopy[lang_id] = msgCopy[lang_id] or {}
msgCopy[lang_id][id_lang] = msg
end
end
end
ds._messages[i] = msgCopy
end
local tab = mw.ext.data.get('I18n/' .. source .. '.tab', '_')
local T = {}
if not success and not tab then
mw.log("Warning: i18n for " .. source .. " is missing")
ds._messages[i] = {}
else
if tab and tab.data then
for _, row in pairs(tab.data) do
local id, t = unpack(row)
for lang, msg in pairs(t) do
if not T[lang] then T[lang] = {} end
T[lang][id] = msg
end
end
end
if not success then
ds._messages[i] = T
else
for lang, msgTbl in pairs(T) do
ds._messages[i][lang] = ds._messages[i][lang] or msgTbl
end
end
end
end
end
if not ds then
error('no source supplied to i18n.loadMessages')
end
ds._sources = s
return ds
end
--- Language code getter.
-- @function i18n.getLang
-- @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
local langOverride = (frame.args or {}).uselang or (parentFrame.args or {}).uselang
if _i18n.isValidCode(langOverride) then
code = langOverride
elseif title.isSubpage and _i18n.isValidCode(subPage) then
code = _i18n.isValidCode(subPage) and subPage or code
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
--- Merges two tables.
-- @function tableMerge
-- @param {table} t1 Target table.
-- @param {table} t2 Source table.
-- @param {boolean} overwrite Overwrite existing keys.
-- @return {table} Merged table.
-- @local
local function tableMerge(t1, t2, overwrite)
for k, v in pairs(t2) do
if type(v) == "table" and type(t1[k]) == "table" then
tableMerge(t1[k], v, overwrite)
else
if overwrite or t1[k] == nil then t1[k] = v end
end
end
return t1
end
--- Given an i18n table instantiates the values (deprecated).
-- @function i18n.loadI18n
-- @param {string} name Name of module with i18n.
-- @param {table} i18n_arg Existing i18n table.
function i18n.loadI18n(name, i18n_arg)
local exist, res = pcall(require, name)
if exist and next(res) ~= nil then
if i18n_arg then
tableMerge(i18n_arg, res.i18n, true)
end
end
end
--- Loads an i18n for a specific frame (deprecated).
-- @function i18n.loadI18nFrame
-- @param {string} name Name of module with i18n.
-- @param {table} i18n_arg Existing i18n table.
function i18n.loadI18nFrame(frame, i18n_arg)
return i18n.loadI18n(frame:getTitle() .. "/i18n", i18n_arg)
end
--- Wrapper for the module.
-- @function i18n.main
-- @param {table} frame Frame invocation object.
-- @return {string} Module output.
i18n.main = entrypoint(i18n)
return require("Module:Deprecated")(i18n,
{
["loadI18n"] = {
deprecated = true,
replacement = "use <code>i18n.loadMessages</code>"
},
["loadI18nFrame"] = {
deprecated = true,
replacement = "use <code>i18n.loadMessages</code>"
}
}
)
-- </nowiki>