|
|
| Line 1: |
Line 1: |
| local this = {}
| | -- |
| | -- INTRO: (!!! DO NOT RENAME THIS PAGE !!!) |
| | -- This module allows any template or module to be copy/pasted between |
| | -- wikis without any translation changes. All translation text is stored |
| | -- in the global Data:*.tab pages on Commons, and used everywhere. |
| | -- |
| | -- SEE: https://www.mediawiki.org/wiki/Multilingual_Templates_and_Modules |
| | -- |
| | -- ATTENTION: |
| | -- Please do NOT rename this module - it has to be identical on all wikis. |
| | -- This code is maintained at https://www.mediawiki.org/wiki/Module:TNT |
| | -- Please do not modify it anywhere else, as it may get copied and override your changes. |
| | -- Suggestions can be made at https://www.mediawiki.org/wiki/Module_talk:TNT |
| | -- |
| | -- DESCRIPTION: |
| | -- The "msg" function uses a Commons dataset to translate a message |
| | -- with a given key (e.g. source-table), plus optional arguments |
| | -- to the wiki markup in the current content language. |
| | -- Use lang=xx to set language. Example: |
| | -- |
| | -- {{#invoke:TNT | msg |
| | -- | I18n/Template:Graphs.tab <!-- https://commons.wikimedia.org/wiki/Data:I18n/Template:Graphs.tab --> |
| | -- | source-table <!-- uses a translation message with id = "source-table" --> |
| | -- | param1 }} <!-- optional parameter --> |
| | -- |
| | -- |
| | -- The "doc" function will generate the <templatedata> parameter documentation for templates. |
| | -- This way all template parameters can be stored and localized in a single Commons dataset. |
| | -- NOTE: "doc" assumes that all documentation is located in Data:Templatedata/* on Commons. |
| | -- |
| | -- {{#invoke:TNT | doc | Graph:Lines }} |
| | -- uses https://commons.wikimedia.org/wiki/Data:Templatedata/Graph:Lines.tab |
| | -- if the current page is Template:Graph:Lines/doc |
| | -- |
|
| |
|
| function this.checkLanguage(subpage, default) | | local config = (function() |
| --[[Check first if there's an any invalid character that would cause the
| | local ok, res = pcall(mw.loadData, "Module:TNT/config"); |
| mw.language.isKnownLanguageTag function() to throw an exception:
| | return ok and res or {}; |
| - all ASCII controls in [\000-\031\127],
| | end)(); |
| - double quote ("), sharp sign (#), ampersand (&), apostrophe ('),
| | |
| - slash (/), colon (:), semicolon (;), lower than (<), greater than (>),
| | local p = {} |
| - brackets and braces ([, ], {, }), pipe (|), backslash (\\)
| | local i18nDataset = 'I18n/Module:TNT.tab' |
| All other characters are accepted, including space and all non-ASCII
| | |
| characters (including \192, which is invalid in UTF-8).
| | -- Forward declaration of the local functions |
| --]]
| | local sanitizeDataset, loadData, link, formatMessage |
| if mw.language.isValidCode(subpage) and mw.language.isKnownLanguageTag(subpage)
| |
| --[[However "SupportedLanguages" are too restrictive, as they discard many
| |
| valid BCP47 script variants (only because MediaWiki still does not
| |
| define automatic transliterators for them, e.g. "en-dsrt" or
| |
| "fr-brai" for French transliteration in Braille), and country variants,
| |
| (useful in localized data, even if they are no longer used for
| |
| translations, such as zh-cn, also useful for legacy codes).
| |
| We want to avoid matching subpagenames containing any uppercase letter,
| |
| (even if they are considered valid in BCP 47, in which they are
| |
| case-insensitive; they are not "SupportedLanguages" for MediaWiki, so
| |
| they are not "KnownLanguageTags" for MediaWiki).
| |
| To be more restrictive, we exclude tags
| |
| * for specific uses in template subpages and unusable as language tags;
| |
| * that is not ASCII and not a lowercase letter, minus-hyphen, or digit,
| |
| or does not start by a letter or does not finish by a letter or digit;
| |
| * or that has subtags with more than 8 characters between hyphens;
| |
| * or that has two hyphens.
| |
| --]]
| |
| or subpage ~= "doc"
| |
| and subpage ~= "layout"
| |
| and subpage ~= "button"
| |
| and subpage ~= "buttons"
| |
| and subpage ~= "sandbox"
| |
| and subpage ~= "testcase"
| |
| and subpage ~= "testcases"
| |
| and string.find(subpage, "^[%l][%-%d%l]*[%d%l]$") ~= nil
| |
| and string.find(subpage, "[%d%l][%d%l][%d%l][%d%l][%d%l][%d%l][%d%l][%d%l][%d%l]") == nil
| |
| and string.find(subpage, "%-%-") == nil then
| |
| return subpage
| |
| end
| |
| -- Otherwise there's currently no known language subpage
| |
| return default
| |
| end
| |
|
| |
|
| --[[Get the last subpage of an arbitrary page if it is a translation.
| | function p.msg(frame) |
| To be used from templates.
| | local dataset, id |
| ]]
| | local params = {} |
| function this.getLanguageSubpage(frame) | | local lang = nil |
| local title = frame and frame.args[1] | | for k, v in pairs(frame.args) do |
| if not title or title == '' then
| | if k == 1 then |
| title = mw.title.getCurrentTitle()
| | dataset = mw.text.trim(v) |
| | elseif k == 2 then |
| | id = mw.text.trim(v) |
| | elseif type(k) == 'number' then |
| | params[k - 2] = mw.text.trim(v) |
| | elseif k == 'lang' and v ~= '_' then |
| | lang = mw.text.trim(v) |
| | end |
| end | | end |
| return this._getLanguageSubpage(title) | | return formatMessage(dataset, id, params, lang) |
| end | | end |
|
| |
|
| --[[Get the last subpage of an arbitrary page if it is a translation. | | -- Identical to p.msg() above, but used from other lua modules |
| To be used from Lua.
| | -- Parameters: name of dataset, message key, optional arguments |
| ]]
| | -- Example with 2 params: format('I18n/Module:TNT', 'error_bad_msgkey', 'my-key', 'my-dataset') |
| function this._getLanguageSubpage(title)
| | function p.format(dataset, key, ...) |
| if type(title) == 'string' then
| | local checkType = require('libraryUtil').checkType |
| title = mw.title.new(title)
| | checkType('format', 1, dataset, 'string') |
| end
| | checkType('format', 2, key, 'string') |
| if not title then
| | return formatMessage(dataset, key, {...}) |
| -- invalid title
| |
| return nil
| |
| end
| |
| --[[This code does not work in all namespaces where the Translate tool works.
| |
| -- It works in the main namespace on Meta because it allows subpages there
| |
| -- It would not work in the main namespace of English Wikipedia (but the
| |
| -- articles are monolignual on that wiki).
| |
| -- On Meta-Wiki the main space uses subpages and its pages are translated.
| |
| -- The Translate tool allows translatng pages in all namespaces, even if
| |
| -- the namespace officially does not have subpages.
| |
| -- On Meta-Wiki the Category namespace still does not have subpages enabled,
| |
| -- even if they would be very useful for categorizing templates, that DO have
| |
| -- subpages (for documentatio and tstboxes pages). This is a misconfiguration
| |
| -- bug of Meta-Wiki. The work-around is to split the full title and then
| |
| -- get the last titlepart.
| |
| local subpage = title.subpageText | |
| --]] | |
| local titleparts = mw.text.split(title.fullText, '/') | |
| local subpage = titleparts[#titleparts]
| |
| return this.checkLanguage(subpage, '') | |
| end | | end |
|
| |
|
| --[[Get the last subpage of the current page if it is a translation. | | |
| ]]
| | -- Identical to p.msg() above, but used from other lua modules with the language param |
| function this.getCurrentLanguageSubpage() | | -- Parameters: language code, name of dataset, message key, optional arguments |
| return this._getLanguageSubpage(mw.title.getCurrentTitle()) | | -- Example with 2 params: formatInLanguage('es', I18n/Module:TNT', 'error_bad_msgkey', 'my-key', 'my-dataset') |
| | function p.formatInLanguage(lang, dataset, key, ...) |
| | local checkType = require('libraryUtil').checkType |
| | checkType('formatInLanguage', 1, lang, 'string') |
| | checkType('formatInLanguage', 2, dataset, 'string') |
| | checkType('formatInLanguage', 3, key, 'string') |
| | return formatMessage(dataset, key, {...}, lang) |
| end | | end |
|
| |
|
| --[[Get the first part of the language code of the subpage, before the '-'. | | -- Obsolete function that adds a 'c:' prefix to the first param. |
| --]] | | -- "Sandbox/Sample.tab" -> 'c:Data:Sandbox/Sample.tab' |
| function this.getMainLanguageSubpage() | | function p.link(frame) |
| parts = mw.text.split(this.getCurrentLanguageSubpage(), '-') | | return link(frame.args[1]) |
| return parts[1]
| |
| end | | end |
|
| |
|
| --[[Get the last subpage of the current frame if it is a translation.
| | local implGetTemplateData; |
| Not used locally.
| | function p.doc(frame) |
| --]]
| | local dataset = sanitizeDataset(frame.args[1]) |
| function this.getFrameLanguageSubpage(frame)
| | local json, dataPage, categories = implGetTemplateData(nil, dataset, frame.args) |
| return this._getLanguageSubpage(frame:getParent():getTitle()) | | return frame:extensionTag('templatedata', json) .. |
| | formatMessage(i18nDataset, 'edit_doc', {link(dataPage)}) .. |
| | (categories or ""); |
| end | | end |
|
| |
|
| --[[Get the language of the current page. Not used locally.
| | function p.getTemplateData(dataset) |
| --]]
| | local data = implGetTemplateData(true, dataset); |
| function this.getLanguage() | | return data; |
| local subpage = mw.title.getCurrentTitle().subpageText
| |
| return this.checkLanguage(subpage, mw.language.getContentLanguage():getCode())
| |
| end | | end |
|
| |
|
| --[[Get the language of the current frame. Not used locally.
| | function p.getTemplateDataNew(...) |
| --]]
| | return implGetTemplateData(nil, ...); |
| function this.getFrameLanguage(frame) | |
| local titleparts = mw.text.split(frame:getParent():getTitle(), '/')
| |
| local subpage = titleparts[#titleparts]
| |
| return this.checkLanguage(subpage, mw.language.getContentLanguage():getCode())
| |
| end | | end |
|
| |
|
| function this.title(namespace, basepagename, subpage) | | function implGetTemplateData(legacy, dataset, args) |
| local message, title
| | -- TODO: add '_' parameter once lua starts reindexing properly for "all" languages |
| local pagename = basepagename
| | local data, dataPage, categories = loadData( |
| if (subpage or '') ~= '' then
| | dataset, nil, not legacy and 'TemplateData' or nil); |
| pagename = pagename .. '/' .. subpage
| | local names = {} |
| end
| | for _, field in ipairs(data.schema.fields) do |
| local valid, title = xpcall(function()
| | table.insert(names, field.name) |
| return mw.title.new(pagename, namespace) -- costly
| | end |
| end, function(msg) -- catch undocumented exception (!?)
| | |
| -- thrown when namespace does not exist. The doc still
| | local numOnly = true |
| -- says it should return a title, even in that case...
| | local params = {} |
| message = msg
| | local paramOrder = {} |
| end)
| | for _, row in ipairs(data.data) do |
| if valid and title ~= nil and (title.id or 0) ~= 0 then
| | local newVal = {} |
| return title
| | local name = nil |
| end
| | for pos, columnName in ipairs(names) do |
| return { -- "pseudo" mw.title object with id = nil in case of error
| | if columnName == 'name' then |
| prefixedText = pagename, -- the only property we need below
| | name = row[pos] |
| message = message -- only for debugging
| | else |
| }
| | newVal[columnName] = row[pos] |
| | end |
| | end |
| | if name then |
| | if ( |
| | (type(name) ~= "number") |
| | and ( |
| | (type(name) ~= "string") |
| | or not string.match(name, "^%d+$") |
| | ) |
| | ) then |
| | numOnly = false |
| | end |
| | params[name] = newVal |
| | table.insert(paramOrder, name) |
| | 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.description, |
| | -- TODO: Store this in a dataset: |
| | format = (args and args.format or nil), |
| | }) |
| | |
| | if numOnly then |
| | json = string.gsub(json,'"zzz123":"",?', "") |
| | end |
| | |
| | return json, dataPage, categories; |
| end | | end |
|
| |
|
| --[[If on a translation subpage (like Foobar/de), this function returns | | -- Local functions |
| a given template in the same language, if the translation is available.
| |
| Otherwise, the template is returned in its default language, without
| |
| modification.
| |
| This is aimed at replacing the current implementation of Template:TNTN.
| |
|
| |
|
| This version does not expand the returned template name: this solves the
| | sanitizeDataset = function(dataset) |
| problem of self-recursion in TNT when translatable templates need themselves
| | if not dataset then |
| to transclude other translable templates (such as Tnavbar).
| | return nil |
| --]]
| | end |
| function this.getTranslatedTemplate(frame, withStatus) | | dataset = mw.text.trim(dataset) |
| local args = frame.args
| | if dataset == '' then |
| local pagename = args['template']
| | return nil |
| --[[Check whether the pagename is actually in the Template namespace, or
| | elseif string.sub(dataset,-4) ~= '.tab' then |
| if we're transcluding a main-namespace page.
| | return dataset .. '.tab' |
| (added for backward compatibility of Template:TNT)
| | else |
| ]]
| | return dataset |
| local namespace, title = args['tntns'] or ''
| | end |
| if namespace ~= '' then -- Checks for tntns parameter for custom ns.
| |
| title = this.title(namespace, pagename) -- Costly
| |
| else -- Supposes that set page is in ns10.
| |
| namespace = 'Template'
| |
| title = this.title(namespace, pagename) -- Costly
| |
| if title.id == nil then -- not found in the Template namespace, assume the main namespace (for backward compatibility)
| |
| namespace = ''
| |
| title = this.title(namespace, pagename) -- Costly
| |
| end
| |
| end
| |
| -- Get the last subpage and check if it matches a known language code.
| |
| local subpage = args['uselang'] or ''
| |
| if subpage == '' then
| |
| subpage = this.getCurrentLanguageSubpage()
| |
| end
| |
| if subpage == '' then
| |
| -- Check if a translation of the pagename exists in English
| |
| local newtitle = this.title(namespace, pagename, 'en') -- Costly
| |
| -- Use the translation when it exists
| |
| if newtitle.id ~= nil then
| |
| title = newtitle
| |
| end
| |
| else
| |
| -- Check if a translation of the pagename exists in that language
| |
| local newtitle = this.title(namespace, pagename, subpage) -- Costly
| |
| if newtitle.id == nil then
| |
| -- Check if a translation of the pagename exists in English
| |
| newtitle = this.title(namespace, pagename, 'en') -- Costly
| |
| end
| |
| -- Use the translation when it exists
| |
| if newtitle.id ~= nil then
| |
| title = newtitle
| |
| end
| |
| end
| |
| -- At this point the title should exist
| |
| if withStatus then
| |
| -- status returned to Lua function below
| |
| return title.prefixedText, title.id ~= nil
| |
| else
| |
| -- returned directly to MediaWiki
| |
| return title.prefixedText
| |
| end
| |
| end | | end |
|
| |
|
| --[[If on a translation subpage (like Foobar/de), this function renders
| | loadData = function(dataset, lang, dataType) |
| a given template in the same language, if the translation is available.
| | dataset = sanitizeDataset(dataset) |
| Otherwise, the template is rendered in its default language, without
| | if not dataset then |
| modification.
| | error(formatMessage(i18nDataset, 'error_no_dataset', {})) |
| This is aimed at replacing the current implementation of Template:TNT.
| | end |
|
| | |
| Note that translatable templates cannot transclude themselves other
| | -- Give helpful error to thirdparties who try and copy this module. |
| translatable templates, as it will recurse on TNT. Use TNTN instead
| | if not mw.ext or not mw.ext.data or not mw.ext.data.get then |
| to return only the effective template name to expand externally, with
| | error(string.format([['''Missing JsonConfig extension, or not properly configured; |
| template parameters also provided externally.
| | Cannot load https://commons.wikimedia.org/wiki/Data:%s. |
| --]]
| | See https://www.mediawiki.org/wiki/Extension:JsonConfig#Supporting_Wikimedia_templates''']], dataset)) |
| function this.renderTranslatedTemplate(frame)
| | end |
| local title, found = this.getTranslatedTemplate(frame, true)
| | |
| -- At this point the title should exist prior to performing the expansion
| | local dataPage = dataset; |
| -- of the template, otherwise render a red link to the missing page
| | local data, categories; |
| -- (resolved in its assumed namespace). If we don't tet this here, a
| | if dataType == 'TemplateData' then |
| -- script error would be thrown. Returning a red link is consistant with
| | dataPage = 'TemplateData/' .. dataset; |
| -- MediaWiki behavior when attempting to transclude inexistant templates.
| | data = mw.ext.data.get(dataPage, lang); |
| if not found then | | if data == false then |
| return '[[' .. title .. ']]' | | data = mw.ext.data.get('Templatedata/' .. dataset, lang); |
| | if data ~= false then |
| | local legacyTemplateDataCategoryName = config.legacyTemplateDataCategoryName; |
| | if legacyTemplateDataCategoryName ~= false then |
| | categories = string.format( |
| | '[[Category:%s%s]]', |
| | legacyTemplateDataCategoryName or "Templates using legacy global TemplateData table name", |
| | config.translatableCategoryLink and mw.getCurrentFrame():callParserFunction("#translation:") or "" |
| | ); |
| | end |
| | dataPage = 'Templatedata/' .. dataset; |
| | end |
| | end |
| | else |
| | data = mw.ext.data.get(dataset, lang) |
| | end |
| | |
| | if data == false then |
| | if dataset == i18nDataset then |
| | -- Prevent cyclical calls |
| | error('Missing Commons dataset ' .. i18nDataset) |
| | else |
| | error(formatMessage(i18nDataset, 'error_bad_dataset', {link(dataPage)})) |
| | end |
| end | | end |
| -- Copy args pseudo-table to a proper table so we can feed it to expandTemplate.
| | return data, dataPage, categories; |
| -- Then render the pagename.
| |
| local args = frame.args
| |
| local pargs = (frame:getParent() or {}).args
| |
| local arguments = {}
| |
| if (args['noshift'] or '') == '' then
| |
| for k, v in pairs(pargs) do
| |
| local n = tonumber(k) or 0
| |
| if n <= 0 then -- unnumbered args
| |
| arguments[k] = v
| |
| elseif n >= 2 then -- numbered args >= 2 need to be shifted
| |
| arguments[n - 1] = v
| |
| end
| |
| end
| |
| else -- special case where TNT is used as autotranslate
| |
| -- (don't shift again what is shifted in the invokation)
| |
| for k, v in pairs(pargs) do
| |
| arguments[k] = v
| |
| end
| |
| end
| |
| arguments['template'] = title -- override the existing parameter of the base template name supplied with the full name of the actual template expanded
| |
| arguments['tntns'] = nil -- discard the specified namespace override
| |
| arguments['uselang'] = args['uselang'] -- argument forwarded into parent frame
| |
| arguments['noshift'] = args['noshift'] -- argument forwarded into parent frame
| |
| return frame:expandTemplate{title = ':' .. title, args = arguments}
| |
| end | | end |
|
| |
|
| --[[A helper for mocking TNT in Special:TemplateSandbox. TNT breaks | | -- Given a dataset name, convert it to a title with the 'commons:data:' prefix |
| TemplateSandbox; mocking it with this method means templates won't be
| | link = function(dataset) |
| localized but at least TemplateSandbox substitutions will work properly.
| | return 'c:Data:' .. mw.text.trim(dataset or '') |
| Won't work with complex uses.
| | end |
| --]]
| | |
| function this.mockTNT(frame) | | formatMessage = function(dataset, key, params, lang) |
| local pargs = (frame:getParent() or {}).args
| | for _, row in pairs(loadData(dataset, lang).data) do |
| local arguments = {}
| | local id, msg = unpack(row) |
| for k, v in pairs(pargs) do
| | if id == key then |
| local n = tonumber(k) or 0
| | local result = mw.message.newRawMessage(msg, unpack(params or {})) |
| if n <= 0 then -- unnumbered args
| | return result:plain() |
| arguments[k] = v
| | end |
| elseif n >= 2 then -- numbered args >= 2 need to be shifted
| | end |
| arguments[n - 1] = v
| | if dataset == i18nDataset then |
| end
| | -- Prevent cyclical calls |
| end
| | error('Invalid message key "' .. key .. '"') |
| if not pargs[1] then
| | else |
| return ''
| | error(formatMessage(i18nDataset, 'error_bad_msgkey', {key, link(dataset)})) |
| end | | end |
| return frame:expandTemplate{title = 'Template:' .. pargs[1], args = arguments}
| |
| end | | end |
|
| |
|
| return this | | return p |