This module allows templates and modules to be easily translated as part of the multilingual templates and modules project. Instead of storing English text in a module or a template, Translate module allows modules to be designed language-neutral, and store multilingual text in the /i18n.json
subpage. This way your module or template will use those translated strings (messages), or if the message has not yet been translated, will fallback to English. When someone updates the translation table, your page will automatically update (might take some time, or you can purge it), but no change in the template or module is needed on any of the wikis. This process is very similar to MediaWiki's localisation, and supports all standard localization conventions such as {{PLURAL|...}}
and other parameters.
See also
- Module:TNT on Wikipedia
require( 'strict' )
local Translate = {}
local metatable = {}
local methodtable = {}
metatable.__index = methodtable
local libraryUtil = require( 'libraryUtil' )
local checkType = libraryUtil.checkType
local i18n = require( 'Module:i18n' ):new()
--- Cache table containing i18n data at the 'data' key, and a key map at the 'keys' key
--- The table is keyed by the i18n.json module name
local cache = {}
local i18nDataset = 'Module:Translate/i18n.json'
--- Uses the current title as the base and appends '/i18n.json'
---
--- @param dataset table
--- @return string|nil
local function guessDataset( dataset )
if mw.ustring.find( dataset, ':', 1, true ) then
return dataset
elseif type( dataset ) == 'string' then
return mw.ustring.format( 'Module:%s/i18n.json', dataset )
end
return nil
end
--- Loads a dataset and saves it to the cache
---
--- @param dataset string
--- @return table { data = "The dataset", keys = "Translation key mapped to index" }
local function load( dataset )
if cache[ dataset ] ~= nil then
return cache[ dataset ]
end
local data = mw.loadJsonData( dataset )
local keys = {}
for index, row in ipairs( data.data ) do
keys[ row[ 1 ] ] = index
end
cache[ dataset ] = {
data = data,
keys = keys
}
return cache[ dataset ]
end
--- Retrieves a message from a dataset and formats it according to parameters and language
---
--- @param dataset string
--- @param key string
--- @param params table
--- @param lang string
local function formatMessage( dataset, key, params, lang )
local data = load( dataset )
if data.keys[ key ] == nil then
error( formatMessage( i18nDataset, 'error_bad_msgkey', { key, dataset }, mw.getContentLanguage():getCode() ) )
end
local msg = data.data.data[ data.keys[ key ] ][ 2 ]
if msg == nil then
error( formatMessage( i18nDataset, 'error_bad_msgkey', { key, dataset }, mw.getContentLanguage():getCode() ) )
end
msg = msg[ lang ] or msg[ 'en' ] or error( mw.ustring.format( 'Language "%s" not found for key "%s"', lang, key ) )
local result = mw.message.newRawMessage( msg, unpack( params or {} ) )
return result:plain()
end
--- Translates a message
---
--- @param dataset string
--- @param key string
--- @return string
function methodtable.format( dataset, key, ... )
dataset = guessDataset( dataset )
checkType('format', 1, dataset, 'string')
checkType('format', 2, key, 'string')
local lang = mw.getContentLanguage():getCode()
return formatMessage( dataset, key, {...}, lang )
end
--- Translates a message in a given language
---
--- @param lang string
--- @param dataset string
--- @param key string
--- @return string
function methodtable.formatInLanguage( lang, dataset, key, ... )
dataset = guessDataset( dataset )
checkType('formatInLanguage', 1, lang, 'string')
checkType('formatInLanguage', 2, dataset, 'string')
checkType('formatInLanguage', 3, key, 'string')
return formatMessage( dataset, key, {...}, lang )
end
--- Wrapper function for Translate.getTemplateData that wraps the output in a <templatedata> tag
---
--- @param frame table Current MW Frame
--- @return string
function Translate.doc( frame )
local dataset = frame.args[ 1 ] or frame.args[ 'dataset' ] or ( mw.title.getCurrentTitle().prefixedText .. '/i18n.json' )
return frame:extensionTag( 'templatedata', Translate.getTemplateData( dataset ) )
end
--- Base logic taken from Module:TNT
--- Iterates through all user settable arguments and outputs json usable in a <templatedata> tag
---
--- @param dataset string The data.json page from which the arguments are taken
--- @return string Json
function Translate.getTemplateData( dataset )
local data = load( guessDataset( dataset ) )
local instance = Translate:new( dataset )
local names = {}
for _, field in ipairs( data.data.schema.fields ) do
table.insert( names, field.name )
end
local numOnly = true
local params = {}
local paramOrder = {}
for _, row in ipairs( data.data.data ) do
local newVal = {}
local name
if row[ 1 ]:sub( 1, 3 ) == 'ARG' then
for pos, columnName in ipairs( names ) do
if columnName == 'id' then
name = instance.format( dataset, row[ pos ] )
elseif columnName ~= 'message' then
newVal[ columnName ] = row[ pos ]
-- Allow to share examples and label
if ( columnName == 'example' or columnName == 'label' ) and type( row[ pos ] ) == 'string' then
newVal[ columnName ] = {
de = row[ pos ],
en = row[ pos ],
}
end
end
end
if name and newVal[ 'type' ] ~= nil then
if type( name ) ~= "number" and ( type( name ) ~= "string" or not mw.ustring.match( name, "^%d+$" ) ) then
numOnly = false
end
params[ name ] = newVal
table.insert( paramOrder, name )
-- TODO: Limit this to a specified subset of url args
if row[ 1 ]:sub( -3 ) == 'Url' then
for i = 1, 4 do
local tmp = {}
for k, v in pairs( newVal ) do
if type( v ) == 'table' then
tmp[ k ] = {}
for k1, v1 in pairs( v ) do
tmp[ k ][ k1 ] = v1
end
else
tmp[ k ] = v
end
end
local nameI = name .. tostring( i )
tmp[ 'required' ] = false
tmp[ 'suggested' ] = false
params[ nameI ] = tmp
table.insert( paramOrder, nameI )
end
end
end
end
end
-- Work around json encoding treating {"1":{...}} as an [{...}]
if numOnly then
params['zzz123']=''
end
local json = mw.text.jsonEncode({
params = params,
paramOrder = paramOrder,
description = data.template_description,
})
if numOnly then
json = mw.ustring.gsub( json,'"zzz123":"",?', "" )
end
return json
end
--- Calls TNT with the given key
---
--- @param dataset string The i18n.json page
--- @param config table The calling module's config
--- @param key string The translation key
--- @param addSuffix boolean|nil Adds a language suffix if config.smw_multilingual_text is true
--- @return string If the key was not found in the .tab page, the key is returned
function methodtable.translate( self, dataset, config, key, addSuffix, ... )
checkType( 'Module:Translate.translate', 1, self, 'table' )
checkType( 'Module:Translate.translate', 2, dataset, 'string' )
checkType( 'Module:Translate.translate', 3, config, 'table' )
checkType( 'Module:Translate.translate', 4, key, 'string' )
checkType( 'Module:Translate.translate', 5, addSuffix, 'boolean', true )
-- Temporary fallback function
local prefix = string.match( key, '([^_]*)' )
if prefix == 'SMW' or prefix == 'label' then
return i18n:translate( key )
end
addSuffix = addSuffix or false
local success, translation
local function multilingualIfActive( input )
if addSuffix and config.smw_multilingual_text == true then
return mw.ustring.format( '%s@%s', input, config.module_lang or mw.getContentLanguage():getCode() )
end
return input
end
if config.module_lang ~= nil then
success, translation = pcall( self.formatInLanguage, config.module_lang, dataset, key or '', ... )
else
success, translation = pcall( self.format, dataset, key or '', ... )
end
if not success or translation == nil then
local title = mw.title.new( guessDataset( dataset ) )
if not title.exists then
error( mw.ustring.format( 'I18N table "%s" does not exist!', dataset ), 3 )
end
return nil
end
return multilingualIfActive( translation )
end
--- New Instance
---
--- @return table Translate
function Translate.new( self, dataset )
local instance = {
dataset = dataset or nil
}
setmetatable( instance, metatable )
return instance
end
return Translate