Module:Template translation

From Center for Integrated Circuits and Devices Research (CIDR)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Template translation/doc

  1 local this = {}
  2 
  3 function this.checkLanguage(subpage, default)
  4     --[[Check first if there's an any invalid character that would cause the
  5         mw.language.isKnownLanguageTag function() to throw an exception:
  6         - all ASCII controls in [\000-\031\127],
  7         - double quote ("), sharp sign (#), ampersand (&), apostrophe ('),
  8         - slash (/), colon (:), semicolon (;), lower than (<), greater than (>),
  9         - brackets and braces ([, ], {, }), pipe (|), backslash (\\)
 10         All other characters are accepted, including space and all non-ASCII
 11         characters (including \192, which is invalid in UTF-8).
 12     --]]
 13     if mw.language.isValidCode(subpage) and mw.language.isKnownLanguageTag(subpage)
 14     --[[However "SupportedLanguages" are too restrictive, as they discard many
 15         valid BCP47 script variants (only because MediaWiki still does not
 16         define automatic transliterators for them, e.g. "en-dsrt" or
 17         "fr-brai" for French transliteration in Braille), and country variants,
 18         (useful in localized data, even if they are no longer used for
 19         translations, such as zh-cn, also useful for legacy codes).
 20         We want to avoid matching subpagenames containing any uppercase letter,
 21         (even if they are considered valid in BCP 47, in which they are
 22         case-insensitive; they are not "SupportedLanguages" for MediaWiki, so
 23         they are not "KnownLanguageTags" for MediaWiki).
 24         To be more restrictive, we exclude any character
 25         * that is not ASCII and not a lowercase letter, minus-hyphen, or digit,
 26           or does not start by a letter or does not finish by a letter or digit;
 27         * or that has more than 8 characters between hyphens;
 28         * or that has two hyphens;
 29         * or with specific uses in template subpages and unusable as languages.
 30     --]]
 31     or  string.find(subpage, "^[%l][%-%d%l]*[%d%l]$") ~= nil
 32     and string.find(subpage, "[%d%l][%d%l][%d%l][%d%l][%d%l][%d%l][%d%l][%d%l][%d%l]") == nil
 33     and string.find(subpage, "%-%-") == nil
 34     and subpage ~= "doc"
 35     and subpage ~= "layout"
 36     and subpage ~= "sandbox"
 37     and subpage ~= "testcases"
 38     and subpage ~= "init"
 39     and subpage ~= "preload"
 40     then
 41         return subpage
 42     end
 43     -- Otherwise there's currently no known language subpage
 44     return default
 45 end
 46 
 47 --[[Get the last subpage of an arbitrary page if it is a translation.
 48     To be used from templates.
 49     ]]
 50 function this.getLanguageSubpage(frame)
 51 	local title = frame and frame.args[1]
 52 	if not title or title == '' then
 53 		title = mw.title.getCurrentTitle()
 54 	end
 55 	return this._getLanguageSubpage(title)
 56 end
 57 
 58 --[[Get the last subpage of an arbitrary page if it is a translation.
 59     To be used from Lua.
 60     ]]
 61 function this._getLanguageSubpage(title)
 62 	if type(title) == 'string' then
 63 		title = mw.title.new(title)
 64 	end
 65 	if not title then
 66 		-- invalid title
 67 		return mw.language.getContentLanguage():getCode()
 68 	end
 69 	--[[This code does not work in all namespaces where the Translate tool works.
 70 	--  It works in the main namespace on Meta because it allows subpages there
 71 	--  It would not work in the main namespace of English Wikipedia (but the
 72 	--  articles are monolignual on that wiki).
 73 	--  On Meta-Wiki the main space uses subpages and its pages are translated.
 74 	--  The Translate tool allows translatng pages in all namespaces, even if
 75 	--  the namespace officially does not have subpages.
 76 	--  On Meta-Wiki the Category namespace still does not have subpages enabled,
 77 	--  even if they would be very useful for categorizing templates, that DO have
 78 	--  subpages (for documentatio and tstboxes pages). This is a misconfiguration
 79 	--  bug of Meta-Wiki. The work-around is to split the full title and then
 80 	--  get the last titlepart.
 81 	local subpage = title.subpageText
 82 	--]]
 83 	local titleparts = mw.text.split(title.fullText, '/')
 84 	local subpage = titleparts[#titleparts]
 85 	return this.checkLanguage(subpage, mw.language.getContentLanguage():getCode())
 86 end
 87 
 88 --[[Get the last subpage of the current page if it is a translation.
 89     ]]
 90 function this.getCurrentLanguageSubpage()
 91 	return this._getLanguageSubpage(mw.title.getCurrentTitle())
 92 end
 93 
 94 --[[Get the first part of the language code of the subpage, before the '-'.
 95     ]]
 96 function this.getMainLanguageSubpage()
 97 	parts = mw.text.split( this.getCurrentLanguageSubpage(), '-' )
 98 	return parts[1]
 99 end
100 
101 --[[Get the last subpage of the current frame if it is a translation.
102     Not used locally.
103     ]]
104 function this.getFrameLanguageSubpage(frame)
105 	return this._getLanguageSubpage(frame:getParent():getTitle())
106 end
107 
108 --[[Get the language of the current page.
109     Not used locally.
110     ]]
111 function this.getLanguage()
112     local subpage = mw.title.getCurrentTitle().subpageText
113     return this.checkLanguage(subpage, mw.language.getContentLanguage():getCode())
114 end
115 
116 --[[Get the language of the current frame.
117     Not used locally.
118     ]]
119 function this.getFrameLanguage(frame)
120     local titleparts = mw.text.split(frame:getParent():getTitle(), '/')
121     local subpage = titleparts[#titleparts]
122     return this.checkLanguage(subpage, mw.language.getContentLanguage():getCode())
123 end
124 
125 function this.title(namespace, basepagename, subpage)
126     local message, title
127     local pagename = basepagename
128     if (subpage or '') ~= ''
129     then
130         pagename = pagename .. '/' .. subpage
131     end
132     local valid, title = xpcall(function()
133             return mw.title.new(pagename, namespace) -- costly
134         end, function(msg) -- catch undocumented exception (!?)
135             -- thrown when namespace does not exist. The doc still
136             -- says it should return a title, even in that case...
137             message = msg
138         end)
139     if valid and title ~= nil and (title.id or 0) ~= 0
140     then
141         return title
142     end
143     return { -- "pseudo" mw.title object with id = nil in case of error
144         prefixedText = pagename, -- the only property we need below
145         message = message -- only for debugging
146     }
147 end
148 
149 --[[If on a translation subpage (like Foobar/de), this function returns
150     a given template in the same language, if the translation is available.
151     Otherwise, the template is returned in its default language, without
152     modification.
153     This is aimed at replacing the current implementation of Template:TNTN.
154 
155     This version does not expand the returned template name: this solves the
156     problem of self-recursion in TNT when translatable templates need themselves
157     to transclude other translable templates (such as Tnavbar).
158     ]]
159 function this.getTranslatedTemplate(frame, withStatus)
160     local args = frame.args
161     local pagename = args['template']
162     
163     --[[Check whether the pagename is actually in the Template namespace, or
164         if we're transcluding a main-namespace page.
165         (added for backward compatibility of Template:TNT)
166         ]]
167     local title
168     local namespace = args['tntns'] or ''
169     if (namespace ~= '') -- Checks for tntns parameter for custom ns.
170     then
171         title = this.title(namespace, pagename) -- Costly
172     else -- Supposes that set page is in ns10.
173     	namespace = 'Template'
174         title = this.title(namespace, pagename) -- Costly
175         if title.id == nil
176         then -- not found in the Template namespace, assume the main namespace (for backward compatibility)
177     	    namespace = ''
178             title = this.title(namespace, pagename) -- Costly
179         end
180     end
181     
182     -- Get the last subpage and check if it matches a known language code.
183     local subpage = args['uselang'] or ''
184     if (subpage == '')
185     then
186         subpage = this.getCurrentLanguageSubpage()
187     end
188     if (subpage == '')
189     then
190         -- Check if a translation of the pagename exists in English
191         local newtitle = this.title(namespace, pagename, 'en') -- Costly
192         -- Use the translation when it exists
193         if newtitle.id ~= nil
194         then
195             title = newtitle
196         end
197     else
198         -- Check if a translation of the pagename exists in that language
199         local newtitle = this.title(namespace, pagename, subpage) -- Costly
200         if newtitle.id == nil
201         then
202             -- Check if a translation of the pagename exists in English
203             newtitle = this.title(namespace, pagename, 'en') -- Costly
204         end
205         -- Use the translation when it exists
206         if newtitle.id ~= nil
207         then
208             title = newtitle
209         end
210     end
211     -- At this point the title should exist
212     if withStatus then
213     	-- status returned to Lua function below
214         return title.prefixedText, title.id ~= nil
215     else
216     	-- returned directly to MediaWiki
217         return title.prefixedText
218     end
219 end
220 
221 --[[If on a translation subpage (like Foobar/de), this function renders
222     a given template in the same language, if the translation is available.
223     Otherwise, the template is rendered in its default language, without
224     modification.
225     This is aimed at replacing the current implementation of Template:TNT.
226     
227     Note that translatable templates cannot transclude themselves other
228     translatable templates, as it will recurse on TNT. Use TNTN instead
229     to return only the effective template name to expand externally, with
230     template parameters also provided externally.
231     ]]
232 function this.renderTranslatedTemplate(frame)
233 	local title, found = this.getTranslatedTemplate(frame, true)
234     -- At this point the title should exist prior to performing the expansion
235     -- of the template, otherwise render a red link to the missing page
236     -- (resolved in its assumed namespace). If we don't tet this here, a
237     -- script error would be thrown. Returning a red link is consistant with
238     -- MediaWiki behavior when attempting to transclude inexistant templates.
239 	if not found then
240 		return '[[' .. title .. ']]'
241 	end
242 
243     -- Copy args pseudo-table to a proper table so we can feed it to expandTemplate.
244     -- Then render the pagename.
245     local args = frame.args
246     local pargs = (frame:getParent() or {}).args
247     local arguments = {}
248     if (args['noshift'] or '') == ''
249     then
250         for k, v in pairs(pargs) do
251             -- numbered args >= 1 need to be shifted
252             local n = tonumber(k) or 0
253             if (n > 0)
254             then
255                 if (n >= 2)
256                 then
257                     arguments[n - 1] = v
258                 end
259             else
260                 arguments[k] = v
261             end
262         end
263     else -- special case where TNT is used as autotranslate
264     	-- (don't shift again what is shifted in the invokation)
265         for k, v in pairs(pargs) do
266             arguments[k] = v
267         end
268     end
269     arguments['template'] = title -- override the existing parameter of the base template name supplied with the full name of the actual template expanded
270     arguments['tntns'] = nil -- discard the specified namespace override
271     arguments['uselang'] = args['uselang'] -- argument forwarded into parent frame
272     arguments['noshift'] = args['noshift'] -- argument forwarded into parent frame
273     
274     return frame:expandTemplate{title = ':' .. title, args = arguments}
275 end
276 
277 --[[A helper for mocking TNT in Special:TemplateSandbox. TNT breaks
278     TemplateSandbox; mocking it with this method means templates won't be
279     localized but at least TemplateSandbox substitutions will work properly.
280     Won't work with complex uses.
281     ]]
282 function this.mockTNT(frame)
283     local pargs = (frame:getParent() or {}).args
284     local arguments = {}
285     for k, v in pairs(pargs) do
286         -- numbered args >= 1 need to be shifted
287         local n = tonumber(k) or 0
288         if (n > 0)
289         then
290             if (n >= 2)
291             then
292                 arguments[n - 1] = v
293             end
294         else
295             arguments[k] = v
296         end
297     end
298     if not pargs[1]
299     then
300     	return ''
301 	end
302     return frame:expandTemplate{title = 'Template:' .. pargs[1], args = arguments}
303 end
304 
305 return this