Module:I18n/consolefriendly
Jump to navigation
Jump to search
Documentation for this module may be created at Module:I18n/consolefriendly/doc
-- <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 = {}
-- local keeper of locality
-- keeps local functions local and defends them against nasty globals
local _i18n = {}
-- Module variables & dependencies.
local title = mw.title.getCurrentTitle()
local fallbacks = require('Dev:Fallbacklist')
-- I18n datastore class.
-- @section i18nd
local i18nd = {}
-- converts things to human-frienldy strings
-- https://stackoverflow.com/a/27028488
-- @param {object} o thing to dump
-- @returns {string} dumped thing
function dump(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end-- dump
-- 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 = _i18n.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 = _i18n.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
-- debug
--mw.log('i18n.msg pre-handleargs msg&args', dump(msg), dump(args))
-- not debug
msg = _i18n.handleArgs(msg, args)
-- debug
--mw.log('i18n.msg post-handleargs msg&args', dump(msg), dump(args))
end
if msg and source_i[i] and lang ~= 'qqx' then
-- debug
--mw.log('i18n.msg', msg, dump(msg))
return frame
and frame.preprocess 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 _i18n.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 _i18n.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() or {}
local subPage = title.subpageText
local uselang
-- Language argument test.
if _i18n.isValidCode( ( (frame or {}).args or {}).uselang or '') then
code = frame.args.uselang
elseif _i18n.isValidCode( ( (frame and (frame.getParent and frame:getParent(frame) or {}) or {}).args or {}).uselang or '') then
code = frame:getParent().args.uselang
-- Subpage language test.
elseif title.isSubpage then
code = _i18n.isValidCode(subPage) and subPage or code
-- User language test.
elseif frame then
-- use en if no frame found
uselang = frame.preprocess and frame:preprocess('{{int:lang}}') or 'en'
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)
local args = frame and frame.args or frame
if
not frame or
not args or
not args[1] or
not args[2]
then
error('missing arguments in i18n.getMsg')
end
local source = args[1]
local key = args[2]
-- Pass through extra arguments.
local repl = {}
for i, a in ipairs(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(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)
-- anti-console defense
local frame = frameChild.getParent and frameChild:getParent() or (frameChild or {})
local args = {}
-- there is no .parents and .args in the console
local frameChildArgs = frameChild.args and frameChild.args or frameChild
for p, v in pairs(frameChildArgs) 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>