Module:Template translation
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