Module:CS1 translator#Supported templates

--[[

TODO: |título=Copia archivada -> |title=Archive copy? Other languages?

TODO: make sure that citation bot doesn't improperly rename translatable templates; see $fix_it currently at https://github.com/ms609/citation-bot/blob/master/Template.php#L85

]]

require ('strict');

local _month_xlate = require ('Module:Month translator')._month_xlate;

local data = mw.loadData ('Module:CS1 translator/data');

local params_main_t = data.params_main_t;

local params_dates_t = data.params_dates_t;

local params_misc_dates_t = data.params_misc_dates_t;

local params_identifiers_t = data.params_identifiers_t;

local params_language_t = data.params_language_t;

local content_lang = mw.getContentLanguage().code; -- this wiki's language tag

--[[--------------------------< I N _ A R R A Y >--------------------------------------------------------------

Whether needle is in haystack

]]

local function in_array (needle, haystack)

if needle == nil then

return false;

end

for n, v in ipairs (haystack) do

if v == needle then

return n;

end

end

return false;

end

--[[--------------------------< A R G S _ G E T >--------------------------------------------------------------

get parameter names and values into an associative table from (the parameters in the #invoke) and from

the parent frame (the parameters in the calling template); set all parameter names (keys) to lower case, skip

parameters with empty-string values, skip parameters with whitespace-only values, skip duplicate parameter names

(parameter names in the #invoke frame have precedence over the same parameter name in the template frame).

This replaces Module:Arguments.getArgs() because this allows us to set parameter names to lowercase; something

that can't be done with getArgs()

returns (the number of parameters added to ) because #args_t doesn't always work

]]

local function args_get (frame, args_t)

local count = 0;

for _, frame_t in ipairs ({frame, frame:getParent()}) do -- invoke frame first, then template frame

for k, v in pairs (frame_t.args) do -- for each parameter in the associative table

if 'string' == type (k) then -- ignore positional parameters

k = mw.ustring.lower (k); -- set lower here so we only do it once; ustring because ru.wiki

if v and not (args_t[k] or ('' == v) or (v:match ('^%s$'))) then -- skip when already present, skip when is empty-string, skip when is whitespace

args_t[k] = v; -- save k/v pair to in

count = count + 1;

end

end

end

end

return count; -- return the number of parameters in

end

--[[--------------------------< D A T E _ M A K E >------------------------------------------------------------

- table of k/v pairs where k is the non-English parameter name and v is the assigned value from frame

- table of k/v_t pairs where k is the date part and v_t is a sequence table of non-English date-holding parameter names

- a sequence table that holds parameter=value strings (without pipes); for sorting

if args_t[] is set, translate month name (if necessary) else assemble and translate date from date

parts args_t[], args_t[], args_t[]. Resulting format is always dmy. Unset

all date values after date created. add translated date as text string to sequence table

month names in non-English |year= parameters that hold more than the year portion of a data (French |année=8 mars 2007 for example)

are NOT transla

]]

local function date_make (args_t, cite_args_t, params_dates_t)

local date, year, month, day;

if params_dates_t.date_t then -- TODO: is there a better way to do this?

for _, v in ipairs (params_dates_t.date_t) do -- loop through the date-holding parameters

date = args_t[v]; -- will be nil if not set

if date then break end -- if set, we're done

end

end

if params_dates_t.year_t then

for _, v in ipairs (params_dates_t.year_t) do -- loop through the year-holding parameters -- may also hold month and or day

year = args_t[v]; -- will be nil if not set

if year then break end -- if set, we're done

end

end

if params_dates_t.month_t then

for _, v in ipairs (params_dates_t.month_t) do -- loop through the month-holding parameters

month = args_t[v]; -- will be nil if not set

if month then break end -- if set, we're done

end

end

if params_dates_t.day_t then

for _, v in ipairs (params_dates_t.day_t) do -- loop through the day-holding parameters

day = args_t[v]; -- will be nil if not set

if day then break end -- if set, we're done

end

end

if date then

date = _month_xlate ({date}); -- attempt translation

elseif year then -- if 'year'; without year, any spurious 'month' and/or 'day' params meaningless; pass on as-is to cs1|2 for error handling

if month then

month = _month_xlate ({month}); -- if there is a month parameter, translate its value

local date_parts_t = {day, month, year};

local date_t = {}

for i=1, 3 do -- done this way because members of might be nil

if date_parts_t[i] then -- if not nil

table.insert (date_t, date_parts_t[i]); -- add to a temporary table

end

end

date = table.concat (date_t, ' '); -- make the dmy date string

else

-- date = year; -- no date so make |date=

date = _month_xlate ({year}); -- attempt translation if non-English |year= has a month name

end

year = nil; -- unset no longer needed

end

local keys_t = {'date_t', 'year_t', 'month_t', 'day_t'};

for _, key in ipairs (keys_t) do -- loop through the keys_t sequence table

if params_dates_t[key] then -- if there is a matching table

for _, param in ipairs (params_dates_t[key]) do -- get each parameter name and

args_t[param] = nil; -- unset because no longer needed

end

end

end

if date then

table.insert (cite_args_t, table.concat ({'date=', date})); -- create and save parameter-like string (without pipe)

if year then

table.insert (cite_args_t, table.concat ({'year=', year})); -- do the same here; year because both |date= and |year= are allowed in cs1|2

end

end

end

--[[--------------------------< M I S C _ D A T E _ M A K E >--------------------------------------------------

- table of k/v pairs where k is the non-English parameter name and v is the assigned value from frame

- a sequence table that holds parameter=value strings (without pipes); for sorting

- language code index into the non-English parameter names table

TODO: translate |orig-date=? can have a translatable date but can also have extraneous text ... At this writing,

_month_xlate() expects only a date.

]]

local function misc_date_make (args_t, cite_args_t, in_lang)

local misc_date;

for _, lang in ipairs ({in_lang, 'en'}) do -- first do non-English names then, because they might be present, look for English parameter names

for param, en_param in pairs (data.params_misc_dates_t[lang]) do

if args_t[param] then -- if the non-English parameter has a value

misc_date = _month_xlate ({args_t[param]}); -- translate the date

table.insert (cite_args_t, table.concat ({en_param, '=', misc_date})); -- make the english parameter

args_t[param] = nil; -- unset, consumed no longer needed

end

end

end

end

--[[--------------------------< S E R I E S _ M A K E >--------------------------------------------------------

assemble the various 'series' parts into |series=

series={{{Reihe|}}} {{{NummerReihe|}}} {{{BandReihe|}}} {{{HrsgReihe|}}}

TODO: should this function be German only or does it need to allow other languages? is German the only language

that combines multiple elements into |series=?

]]

local function series_make (args_t, cite_args_t)

local series_t = {};

local params = {'reihe', 'nummerreihe', 'bandreihe', 'hrsgreihe'};

for _, param in ipairs (params) do

if args_t[param] then

table.insert (series_t, args_t[param]); -- add to temp sequence table

args_t[param] = nil; -- unset, no longer needed

end

end

if 0 ~= #series_t then

local series = table.concat (series_t, ', '); -- concatenate whichever parameters are present

table.insert (cite_args_t, table.concat ({'series=', series})); -- and make a parameter

end

end

--[[--------------------------< I S X N _ M A K E >------------------------------------------------------------

make an |isbn= or |issn= parameter. This function applies ((accept-as-written)) markup when there is some sort

of parameter equivalent to cs1|2's (now deprecated) |ignore-isbn-error=

]]

local function isxn_make (args_t, cite_args_t, type, aliases_t, ignore_params_t, ignore_values_t)

local isxn;

local ignore_value;

for _, v in ipairs (aliases_t) do -- loop through the aliases_t sequence table

if args_t[v] then

isxn = args_t[v];

end

args_t[v] = nil; -- unset because no longer needed

end

for _, v in ipairs (ignore_params_t) do -- loop through the ignor_params_t sequence table

if args_t[v] then

ignore_value = args_t[v];

end

args_t[v] = nil; -- unset because no longer needed

end

if isxn and ignore_value and (in_array (ignore_value, ignore_values_t) or ignore_values_t['*']) then -- is a table values that evaluate to 'yes' or wildcard

table.insert (cite_args_t, table.concat ({type, '=((', isxn, '))'})); -- make parameter but accept this isxn as written

end

end

--[[--------------------------< A T _ M A K E >----------------------------------------------------------------

makes |at= for those templates that have things like |column=. Not qualified by |page= or |pages= so that cs1|2

will emit an error message when both |at= and |page(s)= present.

is a sequence table of non-English parameter names

- text string that prefixes the value in ; 'col. ' for example

]]

local function at_make (args_t, cite_args_t, aliases_t, prefix)

for _, alias in ipairs (aliases_t) do

if args_t[alias] then

table.insert (cite_args_t, table.concat ({prefix, args_t[alias]}));

end

args_t[alias] = nil; -- unset, no longer needed

end

end

--[[--------------------------< C H A P T E R _ M A K E _ F R >------------------------------------------------

make |chapter= from concatenation of chapter number (if present) and chapter name; chapter name else

]]

local function chapter_make_fr (args_t, cite_args_t)

local chapter = args_t['titre chapitre'] or args_t['chapitre'];

if chapter and (args_t['numéro chapitre'] or args_t['numéro']) then -- when chapter numbers, concatenate number with chapter name

chapter = (args_t['numéro chapitre'] or args_t['numéro']) .. ' ' .. chapter;

table.insert (cite_args_t, 'chapter=' .. chapter);

elseif chapter then

table.insert (cite_args_t, 'chapter=' .. chapter); -- here when chapter without number

end

args_t['titre chapitre'] = nil; -- unset as no longer needed

args_t['chapitre'] = nil;

args_t['numéro chapitre'] = nil;

args_t['numéro'] = nil;

end

--[[--------------------------< I D _ M A K E >----------------------------------------------------------------

make a comma separated list of identifiers to be included in |id=

is a sequence table of sequence tables where:

[1] is the non-English parameter name normalized to lower case

[2] is the associated wikitext label to be used in the rendering

[3] is the url prefix to be attached to the identifier value from the template parameter

[4] is the url postfix to be attached to the identifier value

]]

local function id_make (frame, args_t, cite_args_t, params_identifiers_t)

local id_t = {};

local value;

for _, identifier_t in ipairs (params_identifiers_t) do

if args_t[identifier_t[1]] then -- if this identifier parameter has a value

local value_t = {}

if identifier_t[2] then -- if there is a label (all except |id= should have a label)

table.insert (value_t, identifier_t[2]); -- the label

table.insert (value_t, ': '); -- the necessary punctuation and spacing

if identifier_t[3] then -- if an extlink prefix

table.insert (value_t, '['); -- open extlink markup

table.insert (value_t, identifier_t[3]); -- the link prefix

table.insert (value_t, args_t[identifier_t[1]]); -- the identifier value

if identifier_t[4] then -- postfix?

table.insert (value_t, identifier_t[4]); -- the link postfix

end

table.insert (value_t, ' '); -- require space between url and label

table.insert (value_t, args_t[identifier_t[1]]); -- the identifier value as label

table.insert (value_t, ']'); -- close extlink markup

else

table.insert (value_t, args_t[identifier_t[1]]); -- the identifier value

end

else

table.insert (value_t, args_t[identifier_t[1]]); -- no label so value only

end

table.insert (id_t, table.concat (value_t)); -- add to temp sequence table

args_t[identifier_t[1]] = nil; -- unset, no longer needed

end

end

if 0 ~= #id_t then

local id = table.concat (id_t, ', '); -- concatenate whichever parameters are present into a comma-separated list

table.insert (cite_args_t, table.concat ({'id=', id})); -- and make a parameter

end

end

--[[--------------------------< T I T L E _ M A K E _ F R >----------------------------------------------------

join |title= with |subtitle= to make new |title=

]]

local function title_make_fr (args_t, cite_args_t)

local title = args_t['titre']; -- get the 'required' title parameter

args_t['titre'] = nil; -- unset as no longer needed

if not title then

title = args_t['titre original'] or args_t['titre vo']; -- if |titre= empty or missing use one of these 'aliases'

args_t['titre original'] = nil; -- unset as no longer needed

args_t['titre vo'] = nil;

end

if title then -- only when there is a title

if args_t['sous-titre'] then -- add subtitle if present

title = title .. ': ' .. args_t['sous-titre'];

args_t['sous-titre'] = nil; -- unset as no longer needed

end

table.insert (cite_args_t, 'title=' .. (title or '')); -- add to cite_args_t

end

end

--[[--------------------------< T I T L E _ M A K E _ P T >----------------------------------------------------

join |title= with |subtitle= to make new |title=

]]

local function title_make_pt (args_t, cite_args_t)

local title = args_t['título'] or args_t['titulo'] or args_t['titlo']; -- get the 'required' title parameter

args_t['título'] = nil; -- unset as no longer needed

args_t['titulo'] = nil;

args_t['titlo'] = nil;

if not title then

return

end

if args_t['subtítulo'] or args_t['subtitulo'] then -- add subtitle if present

title = title .. ': ' .. (args_t['subtítulo'] or args_t['subtitulo']);

args_t['subtítulo'] = nil; -- unset as no longer needed

args_t['subtitulo'] = nil;

end

table.insert (cite_args_t, 'title=' .. (title or '')); -- add to cite_args_t

end

--[[--------------------------< U R L _ S T A T U S _ M A K E >------------------------------------------------

is a sequence table of |dead-url= parameter aliases

is a table of k/v pairs where k is a valid parameter value that means 'no' as in |dead-url=no (ie |url-status=live)

]]

local function url_status_make (args_t, cite_args_t, aliases_t, no_values_t)

for _, alias in ipairs (aliases_t) do -- loop through the aliases_t sequence table

if args_t[alias] and no_values_t[args_t[alias]] then -- if the alias has a value and the value equates to 'no'

table.insert (cite_args_t, 'url-status=live')

end

args_t[alias] = nil; -- unset because no longer necessary

end

end

--[[--------------------------< U R L _ A C C E S S _ M A K E >------------------------------------------------

is a sequence table of |subscription= or |registration= parameter aliases

is a table of k/v pairs where k is a valid parameter value that means 'yes' as in |subscription=yes (ie |url-access=subscription)

can have a wildcard ('*'=true) which indicates that anything assigned to |subscription= or |registration= is sufficient

]]

local function url_access_make (args_t, cite_args_t, type, aliases_t, values_t)

for _, alias in ipairs (aliases_t) do -- loop through the aliases_t sequence table

if in_array (args_t[alias], values_t) or (args_t[alias] and values_t['*']) then -- if the alias has a value and the value equates to 'yes' or the wildcard

if 'subscription' == type then

table.insert (cite_args_t, 'url-access=subscription')

else

table.insert (cite_args_t, 'url-access=registration')

end

end

args_t[alias] = nil; -- unset because no longer necessary

end

end

--[[--------------------------< L A N G U A G E _ T A G _ G E T >----------------------------------------------

Test to see if it is a known language tag. If it is, return .

When not a known language tag, search through for as a language name; if found

return the associated language tag; else.

]]

local function language_tag_get (known_langs_t, lang)

if mw.language.isKnownLanguageTag (lang) then

return lang; -- is a known language tag ('en', 'da', etc); return the tag

end

local lang_lc = lang:lower(); -- make lowercase copy for comparisons

for tag, name in pairs (known_langs_t) do -- loop through

if lang_lc == name:lower() then -- looking for

return tag; -- found it, return the associated language tag

end

end

return lang; -- not a known language or tag; return as is

end

--[[--------------------------< L A N G U A G E _ M A K E >----------------------------------------------------

this function looks at to see if it is a language tag known by WikiMedia. If it is a known

language tag, adds |language= to cite_args_t and unsets args_t[].

when is not a known language tag, fetches a k/v table of known language tags and names from MediaWiki

for the language (a language tag) where 'k' is a language tag and 'v' is the associated language name.

searches that table for . If found, adds |language= to cite_args_t and unsets args_t[]

this function takes no action and returns nothing when is not known.

language names are expected to be properly capitalized and spelled according to the rules of the source wiki so

the match must be exact.

]]

local function language_make (args_t, cite_args_t, in_lang, lang_params_t)

local lang_param;

local lang_param_val;

for _, v in ipairs (lang_params_t[in_lang]) do -- loop through the list of 'language' parameters supported at .wiki

if args_t[v] then -- if this parameter has a value

lang_param = v; -- save the parameter name

lang_param_val = args_t[v]; -- save the parameter value

end

args_t[v] = nil; -- unset not needed; if multiple 'language' parameters set, we use the 'last' one

end

if not lang_param_val then

return; -- no 'language' parameter in this template so done

end

local known_langs_t = mw.language.fetchLanguageNames (in_lang, 'all'); -- get k/v table of language names from MediaWiki for language; (k/v pair is ['tag'] = 'name')

local tag = language_tag_get (known_langs_t, lang_param_val); -- returns valid language tag or content of

table.insert (cite_args_t, table.concat ({'language=', tag})); -- prefer the 'tag' because that is more translatable

args_t[lang_param] = nil; -- unset because no longer needed

end

--[[--------------------------< L A N G U A G E _ M A K E _ P T >----------------------------------------------

Special case for pt which supports some 'language' parameters that are not enumerated and some 'language' parameters

that are enumerated.

Inspects the non-enumerated parameters |codling=, |in=, and |ling=; if any set, return the value.

If none of the non-enumerated parameters are set, inspect the enumeratable parameters |idioma=, |língua=, |lingua=

in that order.

Collects the value from one of the non-enumerated parameters and returns that value or collects all of the values

from one set of the enumeratable parameters and returns only that set of languages.

If other non-enumerated parameters have values or other enumeratable parameter sets have values, they are ignored

so that the template will emit unrecognized parameter errors for the improper mixture of parameter names.

TODO: possible to share this with pl?

]]

local function language_make_pt (args_t, cite_args_t)

local known_langs_t = mw.language.fetchLanguageNames ('pt', 'all'); -- get a table of known language names and tags for Portuguese

local language;

for _, lang_param in ipairs ({'codling', 'in', 'ling'}) do -- non-enumerated language parameters

if args_t[lang_param] then

language = args_t[lang_param];

args_t[lang_param] = nil;

return language_tag_get (known_langs_t, language); -- attempt to get a language tag; return tag or

end

end

local langs_t = {};

for _, lang_param in ipairs ({'idioma', 'língua', 'lingua'}) do -- for each set of enumeratable language parameters

local i=1; -- make an enumerator

while 1 do -- loop forever

if i == 1 then

if args_t[lang_param] or args_t[lang_param..i] then -- if non-enumerated or its enumerated alias

table.insert (langs_t, (args_t[lang_param] or args_t[lang_param..i])); -- prefer non-enumerated

args_t[lang_param] = nil; -- unset as no longer needed

args_t[lang_param..i] = nil;

else

break; -- no or parameters; break out of while

end

elseif args_t[lang_param..i] then

table.insert (langs_t, args_t[lang_param..i]);

args_t[lang_param..i] = nil; -- unset as no longer needed

elseif 0 ~= #langs_t then -- here when but no

for i, lang in ipairs (langs_t) do -- loop through and

langs_t[i] = language_tag_get (known_langs_t, lang); -- attempt to get a language tag; returns tag or

end

return table.concat (langs_t, ', '); -- make a string and done

else

break; -- no parameter; break out of while; should not get here

end

i = i + 1; -- bump the enumerator

end

end

end

--[[--------------------------< N A M E _ L I S T _ S T Y L E _ M A K E >--------------------------------------

is a sequence table of |name-list-style= parameter aliases

is a table of k/v pairs where 'k' is is the non-English parameter value and 'v' is its translation

for parameters that are |last-author-amp= translations, in the function call write: {['*'] = 'amp'}

]]

local function name_list_style_make (args_t, cite_args_t, aliases_t, values_t)

for _, alias in ipairs (aliases_t) do -- loop through the aliases_t sequence table

if args_t[alias] and (values_t[args_t[alias]] or values_t['*']) then -- if the alias has a recognized value

table.insert (cite_args_t, table.concat ({'name-list-style=', values_t[args_t[alias]] or values_t['*']}));

end

args_t[alias] = nil; -- unset because no longer necessary

end

end

--[[--------------------------< R E N D E R >------------------------------------------------------------------

common renderer that translates parameters (after special case translations) and then renders the cs1|2 template

]]

local function render (frame, args_t, cite_args_t, params_main_t, template, lang_tag)

local out_t = {} -- associative table for frame:expandTemplate

local expand = args_t.expand; -- save content of |expand= to render a nowiki'd version of the translated template

args_t.expand = nil; -- unset so we don't pass to cs1|2

for param, val in pairs (args_t) do -- for each parameter in the template

if val and ('subst' ~= param) then -- when it has an assigned value; skip '|subst=subst:' (from AnomieBOT when it substs the cs1 template)

local enum = param:match ('%d+'); -- get the enumerator if is enumerated; nil else

local param_key = param:gsub ('%d+', '#'); -- replace enumerator with '#' if is enumerated

if params_main_t[param_key] then -- if params_main_t[] maps to a cs1|2 template parameter

local en_param = params_main_t[param_key];

if enum then

en_param = en_param:gsub ('#', enum); -- replace '#' in enumerated cs1|2 parameter with enumerator from non-English parameter

end

table.insert (cite_args_t, table.concat ({en_param, '=', val})); -- use the cs1|2 parameter with enumerator if present

else

table.insert (cite_args_t, table.concat ({param, '=', val})); -- use non-English param

end

end

end

local language = mw.language.fetchLanguageName (lang_tag, content_lang);

local xlated_msg = table.concat ({

''

});

if expand then -- to see the translation as a raw template, create a nowiki'd version

table.sort (cite_args_t); -- sort so that the nowiki rendering is pretty

local xlation = table.concat ({'{{', template, ' |', table.concat (cite_args_t, ' |'), '}}'}); -- the template string

return frame:preprocess (table.concat ({'', xlation, xlated_msg, ''}));

end

for _, arg in ipairs (cite_args_t) do -- spin through the sequence table of parameters

local param, val = arg:match ('^([^=]+)=(.+)'); -- split parameter string

if nil == param then error (mw.dumpObject (cite_args_t)) end

out_t[param] = val; -- and put the results in the output table

end

return table.concat ({frame:expandTemplate ({title=template, args=out_t}), xlated_msg}); -- render {{