มอดูล:CS1 translator

--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;
------------------------------------------------------------------------------------------
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
------------------------------------------------------------------------------------------
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_tk or ( == v) or (v:match ('^%s$'))) then	-- skip when  already present, skip when  is empty-string, skip when  is whitespace
					args_tk = v;												-- save k/v pair to in 
					count = count + 1;
				end
			end
		end
	end
	return count;																-- return the number of parameters in 
end
--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_tv;													-- 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_tv;													-- 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_tv;													-- 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_tv;													-- 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_ti then											-- if not nil
					table.insert (date_t, date_parts_ti);						-- 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_tkey then												-- if there is a matching table
			for _, param in ipairs (params_dates_tkey) do						-- get each parameter name and
				args_tparam = 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
--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_tlang) do
			if args_tparam then												-- if the non-English parameter has a value
				misc_date = _month_xlate ({args_tparam});						-- translate the date
				table.insert (cite_args_t, table.concat ({en_param, '=', misc_date}));	-- make the english parameter
				args_tparam = nil;											-- unset, consumed no longer needed
			end
		end
	end
end
--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_tparam then
			table.insert (series_t, args_tparam);								-- add to temp sequence table
			args_tparam = 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
--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_tv then
			isxn = args_tv;
		end
		args_tv = 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_tv then
			ignore_value = args_tv;
		end
		args_tv = 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
--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_talias then
			table.insert (cite_args_t, table.concat ({prefix, args_talias}));
		end
		args_talias = nil;													-- unset, no longer needed
	end
end
--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
--------------------------------------------------------------------------------------------
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_tidentifier_t1 then											-- if this identifier parameter has a value
			local value_t = {}
			if identifier_t2 then												-- if there is a label (all except |id= should have a label)
				table.insert (value_t, identifier_t2);						-- the label
				table.insert (value_t, ': ');								-- the necessary punctuation and spacing
				if identifier_t3 then											-- if an extlink prefix
					table.insert (value_t, );								-- open extlink markup
					table.insert (value_t, identifier_t3);					-- the link prefix
					table.insert (value_t, args_tidentifier_t1);			-- the identifier value
					if identifier_t4 then										-- postfix?
						table.insert (value_t, identifier_t4);				-- the link postfix
					end
					table.insert (value_t, ' ');								-- require space between url and label
					table.insert (value_t, args_tidentifier_t1);			-- the identifier value as label
					table.insert (value_t, );								-- close extlink markup
				else
					table.insert (value_t, args_tidentifier_t1);			-- the identifier value
				end
			else
				table.insert (value_t, args_tidentifier_t1);				-- no label so value only
			end
			table.insert (id_t, table.concat (value_t));						-- add to temp sequence table
			args_tidentifier_t1 = 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
--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
--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
--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_talias and no_values_targs_talias then					-- if the alias has a value and the value equates to 'no'
			table.insert (cite_args_t, 'url-status=live')
		end
		args_talias = nil;													-- unset because no longer necessary
	end
end
--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_talias, values_t) or (args_talias 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_talias = nil;													-- unset because no longer necessary
	end
end
--------------------------------------------------------------------------
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
--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_tin_lang) do								-- loop through the list of 'language' parameters supported at .wiki
		if args_tv then														-- if this parameter has a value
			lang_param = v;														-- save the parameter name
			lang_param_val = args_tv;											-- save the parameter value
		end
		args_tv = 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_tlang_param = nil;													-- unset because no longer needed
end
--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_tlang_param then
			language = args_tlang_param;
			args_tlang_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_tlang_param or args_tlang_param..i then				-- if non-enumerated or its enumerated alias
					table.insert (langs_t, (args_tlang_param or args_tlang_param..i));	-- prefer non-enumerated
					args_tlang_param = nil;									-- unset as no longer needed
					args_tlang_param..i = nil;
				else
					break;														-- no  or  parameters; break out of while
				end
			elseif args_tlang_param..i then
				table.insert (langs_t, args_tlang_param..i);
				args_tlang_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_ti = 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
------------------------------------------------------------------
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_talias and (values_targs_talias or values_t'*') then	-- if the alias has a recognized value
			table.insert (cite_args_t, table.concat ({'name-list-style=', values_targs_talias or values_t'*'}));
		end
		args_talias = nil;													-- unset because no longer necessary
	end
end
--2 template
local function render (frame, args_t, cite_args_t, params_main_t, template, language)
	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_tparam_key then									-- if params_main_t maps to a cs1|2 template parameter
				local en_param = params_main_tparam_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 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 ({), });	-- 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_tparam = val;														-- and put the results in the output table
	end
	return table.concat ({frame:expandTemplate ({title=template, args=out_t}), xlated_msg});	-- render  template with translated parameters
end
----------------------------------------------------------------------
date and language parameter functions utilized for almost all translations (pl and pt support enumerated language
parameters)
local function dates_and_language (args_t, cite_args_t, in_lang)
	date_make (args_t, cite_args_t, params_dates_tin_lang);					-- assemble and translate |date=
	misc_date_make (args_t, cite_args_t, in_lang);
	language_make (args_t, cite_args_t, in_lang, params_language_t);			-- translate language from German to appropriate language tag
end
--=========================>=====================================**
------------------------------------------------------------------------------------------
Common function to implement:
	 (ar:قالب:استشهاد بكتاب)
	 (ar:قالب:استشهاد بدورية محكمة)