Modul:Central-w-hu

A Wikipédiából, a szabad enciklopédiából

Central-w-hu[mi ez?] • [dokumentáció: mutat, szerkeszt] • [tesztek: létrehozás]

--	Tests pages using this module, to help to change the rigth page :
--	Utilisateur:Rical/Central modules reference manual
--	Utilisateur:Rical/Victor Hugo			{{#invoke:p.v.version|read}}	fr = French = Français = Français
--	Module:Author3/Documentation			{{#invoke:p.v.version|read}}	fr = French = Français = Français
--	Module:Central/Documentation			{{#invoke:Central|read}}		fr = French = Français = Français

--	Usual pages for Translations
--	Modulenn:Central-s-br/doc				{{#invoke:Central-s-br|read}}	br = Breton = Brezhoneg
--	Modul:Central-s-de/Doku					{{#invoke:Central-s-de|read}}	de = German = Deutsch
--	Module:Central-s-en/Documentation		{{#invoke:Central-s-en|read}}	en = English = English
--	Module:Central-s-es/Documentación		{{#invoke:Central-s-es|read}}	es = Spanish = español
--	Module:Central-s-fr/Documentation		{{#invoke:Central-s-fr|read}}	fr = French = Français
--	Modul:Central-w-hu/Dokumentáció			{{#invoke:Central-w-hu|read}}	hu = Hungarian = Magyar
--	Mô đun:Central-w-vi/tài liệu			{{#invoke:Central-w-vi|read}}	vi = Vietnamese = Tiếng việt
--	MediaWiki:Scribunto/Central modules reference manual

local p = {}
p.v = {} -- Version descriptor for central modules updated by versioning.getModuleAndTime() -- Try to load a module and forms a descriptor of one loaded object.
p.v.version = "p.v.version" -- The main module select this version, from its p.versions{}, to replace its main version.
p.v.version = "Central-w-hu" -- The main module select this version, from its p.versions{}, to replace its main version.
p.v.main_version = "Central" -- Central modules share the main version from the central repositoty to any projects.
-- Later, versioning.getModuleAndTime() will try to load a module and form a descriptor of one loaded module or library.

local Central = p
-- p.Central_x_y = "<Central_x_y>" -- To adapt the version of Module:Central in any translated text.
p.Module_Central_version = "p.v.version" -- To adapt the version of Module:Central in any translated text.

--		traductions perturbe par un test sans memo ?

p.ModuleNS = mw.site.namespaces.Module.name .. ":"

p.versions = { -- Modules dependencies. Dependencias del módulo. Dépendances du module.
	versionName = "p.v.version", versionNumber = "30 22:22", versionDate = "2017-01-30 22:22",
	sought = "p.v.version", -- Sought module and submodules versions
	known = "Central;p.v.version", -- Known module and submodules versions
--	loaded_list = ";Central;p.v.version;",-- Module:Central
--	currentVersion = "", -- show if the mediawiki version has change ( for debug )
	-- Central.fr.ws is in conflict with LUA syntax table.intable.subtable and results in overflow at runtime.
	-- Then Rical renames Central.fr.ws in Central-s-fr at 16 decembre 2016-12-16 18:03.
}

-- Todo: Modèle:DP-ONU-Auteur, see: https://fr.wikisource.org/wiki/Mod%C3%A8le:DP-ONU
	-- rights_values_box_70
	-- Todo: Tpt 20121005 19:48 : J'ai intégré le microformat hcard dans le modèle auteur afin de pouvoir y ajouter l'image.
	-- Todo: Tpt 20121005 19:48 : Par contre, j'ai ajouté deux lignes incluant de manière caché les dates de naissance et de décès directement dans le modèle auteur.
	-- Todo: Tpt 20121005 19:48 : Voici la diff. Pourrais-tu les intégrer dans un des sous-modèles que tu as créés pour la manipulation des dates avec la structure suivante :
	-- Todo: Tpt 20121005 19:48 : <span class="(b|d)day" title="DATE AU FORMAT YYYY">AFFICHAGE DE LA DATE</span> Tpt (d) 5 septembre 2012 à 19:48 (UTC)

-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- Development guide line for modules
--
-- For an easier and faster work, group several modules and libraries and their tests simulations in the same true module.
-- Whith several modules and libraries in 1 module, we can move and change variables and functions, several at once.
-- Simulate libraries in _G space, waiting a true implementation.
-- There is a bug report by Rical: T122752 : #invoke do not record the main module in package.loaded.
-- https://phabricator.wikimedia.org/T122752 Rical created this task.Via Web ·Sat, Jan 2, 2016-01-02 23:08
-- Simulate Modules in _G space, waiting the DEBUG of T122752#invoke.
-- Simulate old and new versions of modules and libraries to satisfy the main module calls until need to change it.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------

-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	Development guide line for i18n translations
--
--	A central module could need a translation service and a versioning service to enhance its functions or its stability without disturbing the preview normal use.
--	A main module can define its sought sub-modules versions names and its known sub-modules versions names.
--	The Library:versioning installs other libraries, supports versions, binds modules, libraries and i18n translations.
--	A central module and its translations can inpedendently change. Its p.versions{} identifications change also.
--	For this goal a module contains at least one i18n tranlations table. Its /I18N sub-modules contains translations in several languages.
--	Central Libraries, like modules, contain their own p.versions{} identifications and i18n translations in Module:Library/library_name/I18N.
--	The adaptation to any number of languages is automatic.
--	The highest level is the main module. The lowest level is the libraries level.
--
--	Any module or library can contain i18n tables.
--	i18n tables are mixed from the lowest to the highest level, to permit at upper module to adapt the meaning of texts to a new use.
--	Each library is independent of other ones and they can be mixed in any order.
--	To avoid conflict of translations keys between libraries, all translations keys names start with the library name.
--
--	The library testsCases contains internal auto tests cases to verify or to understand the functions and the use cases.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------

--	The central system based on MediaWiki makes it easy to embed wikicontent.
--	The central system permit to convert any module as central. It is build on Mediawiki, Scribunto and Wikibase.
--	Here the Module:Central declares some libraries, waiting stable Scribunto central libraries:
begin = {}			--	The Library:begin begins to support other tests wikis. It translates tests pages names for these wikis.
datas = {}			--	The Library:datas form some viewers for tables(in lines and columns), dropboxes, luatables...
events = {}			--	The Library:events form events like erros, warnings and categories.
luaTable = {}		--	The Library:luaTable enhances previous table library, to avoid ambiguities. To enhance, we could write here : luaTable = table.
modes = {}			--	The Library:modes support modes and their options, like p.read(frame).
--	string = {}		--	The Library:string come from Scribunto. The Module:Central add some functions to it.
testsCases = {}		--	The Library:testsCases implements recursive mediawiki tests cases and reports them for Lua coders.
tools = {}			--	The Library:tools supports micenalous functions for arguments, translations, reports on other processes...
translate = {}		--	The Library:translate supports i18n translations.
versioning = {}		--	The Library:versioning installs other libraries, bind modules and libraries, bind i18n translations, manage versions.
viewer = {}			--	The Library:viewer form some viewers for tables(in lines and columns), dropboxes, luatables...
--
mathroman = {}		--	The Library:mathroman is here as an example of very small central library. It becomes central using the central system.


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:begin begins to support other tests wikis. It translates tests pages names for these wikis.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- begin = {} -- already declared by Scribunto, else in _G space.
-- Translations for datas library
begin.i18n = {}
begin.i18n.br = { -- br = Breton = Brezhoneg
--	Modulenn:Central-s-br/doc				{{#invoke:Central-s-br|read}}	br = Breton = Brezhoneg
	begin_translate_texts_guide				= "Pour traduire, conserver exactement sans traduction les parties comme celles-ci : <b>%1</b> ; <b>%1 = %2</b> (%3) ; <code>Q535</code> ; <br/> ; \n* ; _G.",
	begin_Module_Central_central_version	= "Modulenn:Central",
	begin_Module_Central_local_version		= "Modulenn:p.v.version",
	begin_Module_Central_local_document		= "Modulenn:p.v.version/doc",
	begin_Library_translate_I18N			= "Modulenn:Library/translate/I18N",
	begin_Central_modules_reference_manual	= "Scribunto/Lua reference manual", -- Présenter le manuel au brouillon dans le wiki local
	begin_follow_central_modules_title		= "begin.follow_central_modules() Follow the begin of use of central modules.",
	begin_follow_central_modules_headers	= "Project; Language; Admins; Assets; Main module; Start on; Used from; User",
	begin_central_count_admins_active_users	= "This site, br.wikisource.org, count %1 administrators for %2 active users.";
	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Links to beginnings of central modules.",
	begin_links_to_central_modules_headers	= "Project; Admins; Active users; Language; Code; Version; Documentation";
} -- begin.i18n.br
begin.i18n.de = { -- de = German = Deutsche
--	Modul:Central-s-de/Doku					{{#invoke:Central-s-de|read}}	de = German = Deutsch
	begin_translate_texts_guide				= "Pour traduire, conserver exactement sans traduction les parties comme celles-ci : <b>%1</b> ; <b>%1 = %2</b> (%3) ; <code>Q535</code> ; <br/> ; \n* ; _G.",
	begin_Module_Central_central_version	= "Modul:Central",
	begin_Module_Central_local_version		= "Modul:p.v.version",
	begin_Module_Central_local_document		= "Modul:p.v.version/Doku",
	begin_Library_translate_I18N			= "Modul:Library/translate/I18N",
	begin_Central_modules_reference_manual	= "Scribunto/Central modules reference manual", -- Trình bày dự thảo sổ tay trong wiki địa phương
	begin_follow_central_modules_title		= "begin.follow_central_modules() Folgen Sie den Beginn der Verwendung von zentralen Module.",
	begin_follow_central_modules_headers	= "Projekt; Sprache; Admins; Vermögenswerte; Hauptmodul ; Starten; verwendet; Benutzer";
	begin_central_count_admins_active_users	= "Für diese Seite, br.wikisource.org, Konto 1% Direktoren für %2 aktive Nutzer.";
	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Links zu zentralen Module Debüt.",
	begin_links_to_central_modules_headers	= "Projekt; Direktoren; Vermögenswerte; Sprache; Code; Version; Dokumentation";
	
} -- begin.i18n.de
begin.i18n.en = { -- en = English = English
	begin_translate_texts_guide				= "Để dịch, giữ chính xác mà không cần phần dịch như thế này : <b>%1</b> ; <b>%1 = %2</b> (%3) ; <code>Q535</code> ; <br/> ; \n* ; _G.",
	begin_Module_Central_central_version	= "Mô đun:Central",
	begin_Module_Central_local_version		= "Mô đun:p.v.version",
	begin_Module_Central_local_document		= "Mô đun:p.v.version/Documentation",
	begin_Library_translate_I18N			= "Mô đun:Library/translate/I18N",
	begin_Central_modules_reference_manual	= "Scribunto/Central modules reference manual", -- Trình bày dự thảo sổ tay trong wiki địa phương
	begin_follow_central_modules_title		= "begin.follow_central_modules() Follow the begin of use of central modules.",
	begin_follow_central_modules_headers	= "Project; Language; Admins; Assets; Main module; Start on; Used from; User",
	begin_central_count_admins_active_users	= "This site, br.wikisource.org, count %1 administrators for %2 active users.";
	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Links to beginnings of central modules.",
	begin_links_to_central_modules_headers	= "Project; Admins; Active users; Language; Code; Version; Documentation";
} -- begin.i18n.en
begin.i18n.es = { -- es = Spanish = español
--	Módulo:Central-s-es/Documentación		{{#invoke:Central-s-es|read}}	es = Spanish = español
	begin_translate_texts_guide				= "Para traducir, sólo mantener partes sin traducir como estos : <b>%1</b> ; <b>%1 = %2</b> (%3) ; <code>Q535</code> ; <br/> ; \n* ; _G.",
	begin_Module_Central_central_version	= "Módulo:Central",
	begin_Module_Central_local_version		= "Módulo:p.v.version",
	begin_Module_Central_local_document		= "Módulo:p.v.version/Documentación",
	begin_Library_translate_I18N			= "Módulo:Library/translate/I18N",
	begin_Central_modules_reference_manual	= "Scribunto/Central modules reference manual", -- Trình bày dự thảo sổ tay trong wiki địa phương
	begin_follow_central_modules_title		= "begin.follow_central_modules() Sigir el comienzo del uso de los módulos centrales.",
	begin_follow_central_modules_headers	= "Proyecto; Lenguaje; Admins; Activos; Módulo principal; Iniciar en; Utilizado desde; Usuario",
	begin_central_count_admins_active_users	= "Este sitio, br.wikisource.org, cuenta %1 administradores para %2 usuarios activos.";
	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Enlaces a de inicios de módulos centrales.",
	begin_links_to_central_modules_headers	= "Proyecto; Administradores; Usuarios activos; Idioma; Código; Versión; Documentación";
} -- begin.i18n.es
begin.i18n.fr = { -- fr = French = Français
	begin_translate_texts_guide				= "Pour traduire, conserver exactement sans traduction les parties comme celles-ci : <b>%1</b> ; <b>%1 = %2</b> (%3) ; <code>Q535</code> ; <br/> ; \n* ; _G.",
	begin_Module_Central_central_version	= "Module:Central",
	begin_Module_Central_local_version		= "Module:p.v.version",
	begin_Module_Central_local_document		= "Module:p.v.version/Documentation",
	begin_Library_translate_I18N			= "Module:Library/translate/I18N",
	begin_Central_modules_reference_manual	= "Scribunto/Lua reference manual", -- Présenter le manuel au brouillon dans le wiki local
	begin_follow_central_modules_title		= "begin.follow_central_modules() Suivre le début de l'utilisation des modules centraux.",
	begin_follow_central_modules_headers	= "Projet; Langue; Admins; Actifs; Module principal; Démarrer; Utilisé; Utilisateur";
	begin_central_count_admins_active_users	= "Ce site, br.wikisource.org, compte %1 administrateurs pour %2 utilisateurs actifs.";
	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Liens vers des débuts de modules centraux.",
	begin_links_to_central_modules_headers	= "Projet; Administrateurs; Utilisateurs actifs; Langue; Code; Version; Documentation";
} -- begin.i18n.fr
begin.i18n.vi = { -- vi = Vietnamese = Tiếng việt
	begin_translate_texts_guide				= "Để dịch, giữ chính xác mà không cần phần dịch như thế này : <b>%1</b> ; <b>%1 = %2</b> (%3) ; <code>Q535</code> ; <br/> ; \n* ; _G.",
	begin_Module_Central_central_version	= "Mô đun:Central",
	begin_Module_Central_local_version		= "Mô đun:p.v.version",
	begin_Module_Central_local_document		= "Mô đun:p.v.version/Documentation",
	begin_Library_translate_I18N			= "Mô đun:Library/translate/I18N",
	begin_Central_modules_reference_manual	= "Scribunto/Central modules reference manual", -- Trình bày dự thảo sổ tay trong wiki địa phương
	begin_follow_central_modules_title		= "begin.follow_central_modules() Thực hiện theo các đầu việc sử dụng các module Trung ương.",
	begin_follow_central_modules_headers	= "Dự án; Ngôn ngữ; Quản trị viên; Tài sản; Module chính; Bắt đầu; Dược sử dụng; Người sử dụng",
	begin_central_count_admins_active_users	= "Trang web này, Br.wikisource.org có %1 quản trị viên cho %2 người dùng hoạt động.",
	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Liên kết đến sự khởi đầu của module trung tâm.",
	begin_links_to_central_modules_headers	= "dự án; quản trị; tài sản; ngôn ngữ; mã; phiên bản; tài liệu",
	-- --	Mô đun:Central-s-vi/tài liệu {{#invoke:Central-s-vi|read}}
} -- begin.i18n.vi

function begin.links_to_central_modules(hostPort) -- Form links for standard central modules version in any wiki
--	local vers, url, urldoc, link = begin.links_to_central_modules() -- Form links for standard central modules version in any wiki
--	See: $wmincProjectDatabases in https://www.mediawiki.org/wiki/Extension:WikimediaIncubator
--	translate.Central_x_y = p.Central_x_y -- To adapt the version of Module:Central in any translated text.
--	translate.Module_Central_version = p.Module_Central_version -- To adapt the version of Module:Central in any translated text.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>begin.links_to_central_modules()</b> : Form links for standard central modules version in any wiki."
	t = t .. "\n* " .. viewer.form9user("begin_central_count_admins_active_users", mw.site.stats.admins, mw.site.stats.activeUsers)
--	begin_links_to_central_modules_title	= "begin.links_to_central_modules() Links to beginnings of central modules.",
--	begin_links_to_central_modules_headers	= "Project; Admins; Active users; Language; Code; Version; Documentation";
	local wikis = {
		cm = 'commons',
		test2 = 'test2',
		p = 'wiki',
		b = 'wikibooks',
		wm = 'wikimedia',
		meta = 'meta',
		n = 'wikinews',
		q = 'wikiquote',
		s = 'wikisource',
		species = 'species',
		t = 'wiktionary',
		v = 'wikiversity',
		y = 'wikivoyage',
	}
	local kiwis = {
		commons = 'cm',
		test2 = 'test2',
		wiki = 'p',
		wikibooks = 'b',
		wikimedia = 'wm',
		meta = 'meta',
		wikinews = 'n',
		wikiquote = 'q',
		wikisource = 's',
		species = 'species',
		wiktionary = 't',
		wikiversity = 'v',
		wikivoyage = 'y',
	}
	local Central_connections = { -- español(es=Spanish), français(fr=French), Tiếng Việt(vi=Vietnamese),
		--
		--	Modulenn:Central-s-br/doc			{{#invoke:Central-s-br|read}}	br = Breton = Brezhoneg
		{ hostPort = 'br.wikipedia.org',	lang = "br", project = "w", moduleSpace = "Modulenn", doc = "doc",
			vers = "Central-w-br",	centralmodule = "Modulenn:Central-w-br", centraldoc = "Modulenn:Central-w-br/doc",
			admins = "16 ?", assets = "236 ?", starton = "20170215", usedfrom = "20170304 ?", user = "VIGNERON", },
		--
		--	Modul:Central-s-de/Doku				{{#invoke:Central-s-de|read}}	de = German = Deutsch
		{ hostPort = 'de.wikipedia.org',	lang = "de", project = "s", moduleSpace = "Modul",	doc = "Doku",
			vers = "Central-s-de",	centralmodule = "Modul:Central-s-de",		centraldoc = "Modul:Central-s-de/Doku",
			admins = "16 ?", assets = "236 ?", starton = "20170215 ?", usedfrom = "20170304 ?", user = "Wer?", },
		--
		--	Module:Central-s-en/Documentation	{{#invoke:Central-s-en|read}}	en = English = English
		{ hostPort = 'en.wikipedia.org',	lang = "en", project = "w", moduleSpace = "Module",	doc = "Documentation",
			vers = "Central-w-en",	centralmodule = "Module:Central-w-en",		centraldoc = "Module:Central-w-en/Documentation",
			admins = "16 ?", assets = "236 ?", starton = "20170215 ?", usedfrom = "20170304 ?", user = "Who?", },
		--
		--	Module:Central-s-es/Documentación	{{#invoke:Central-s-es|read}}	es = Spanish = español
		{ hostPort = 'es.wikipedia.org',	lang = "es", project = "w", moduleSpace = "Módulo",	doc = "Documentación",
			vers = "Central-w-es",	centralmodule = "Módulo:Central-w-es",		centraldoc = "Módulo: Central-w-es/Documentación",
			admins = "16 ?", assets = "236 ?", starton = "20170215 ?", usedfrom = "20170304 ?", user = "Who?", },
		--
		--	Module:Central-s-fr/Documentation	{{#invoke:Central-s-fr|read}}	fr = French = français
		{ hostPort = 'fr.wikipedia.org',	lang = "fr", project = "w", moduleSpace = "Module",	doc = "Documentation",
			vers = "Central-w-fr",	centralmodule = "Module:Central-w-fr",		centraldoc = "Module:Central-w-fr/Documentation",
			admins = "16 ?", assets = "236 ?", starton = "20170215 ?", usedfrom = "20170304 ?", user = "Rical", },
		--
		--	Modul:Central-w-hu/Dokumentáció		{{#invoke:Central-w-hu|read}}	hu = Hungarian = Magyar
		{ hostPort = 'hu.wikipedia.org',	lang = "hu", project = "w", moduleSpace = "Modul",	doc = "Dokumentáció",
			vers = "Central-w-hu", centralmodule = "Modul:Central-w-hu",		centraldoc = "Modul:Central-w-hu/Dokumentáció",
			admins = "16 ?", assets = "236 ?", starton = "20170215 ?", usedfrom = "20170304 ?", user = "Rical", },
		--
		--	Mô đun:Central-s-vi/tài liệu		{{#invoke:Central-s-vi|read}}	vi = Vietnamese = Tiếng Việt
		{ hostPort = 'vi.wikipedia.org',	lang = "vi", project = "w", moduleSpace = "Mô đun",	doc = "tài liệu",
			vers = "Central-w-vi",	centralmodule = "Mô đun:Central-w-vi",		centraldoc = "Mô đun:Central-w-vi/tài liệu",
			admins = "16 ?", assets = "236 ?", starton = "20170215 ?", usedfrom = "20170304 ?", user = "Rical", },
	}
--	local version, main_version = vers) -- Form the version and the main version of a module or a library.
	local vers, vers_memo, link = nil, nil, nil
	local url = "https://fr.wikisource.org/wiki/Module:p.v.version"
	local urldoc = "https://fr.wikisource.org/wiki/Module:p.v.version"
	local mwuri = mw.uri.new()
	local hostPort = host or mw.uri.hostPort -- Actual local hostPort, or wanted link to other wikis, or test case.
	local WikiSelect = nil
	for i, wiki in pairs(Central_connections) do
		local t3 = (t3 or "") .. viewer.ta( wiki.lang, wiki.centraldoc )
		if wiki.hostPort == hostPort then
			vers = wiki.vers
			url = string.gsub(url, "fr.wikisource.org", wiki.hostPort )
			url = string.gsub(url, "Module:p.v.version", wiki.centralmodule )
			urldoc = string.gsub(urldoc, "fr.wikisource.org", wiki.hostPort )
			urldoc = string.gsub(urldoc, "Module:p.v.version", wiki.centraldoc )
			link = string.gsub("[[:wiki:lang:Module:p.v.version|Module:p.v.version]]", ":lang:", ":" .. wiki.lang .. ":" )
			link = string.gsub(link, ":wiki:", ":" .. wiki.project .. ":" )
			WikiSelect = wiki
		end
	end
--	hostPort = "fr.wikisource.org"
--	translate.Central_x_y = "<Central_x_y>" -- To adapt the version of Module:Central in any translated text.
	translate.Module_Central_version = vers -- To adapt the version of Module:Central in any translated text.
	-- Server mw.uri.new: , protocol = nil , user = nil , password = nil , host = fr.wikisource.org ,
	-- port = nil , path = /wiki/Module:Central-s-fr/Documentation , query = nil ,
	-- fragment = nil , userInfo = nil , hostPort = fr.wikisource.org , authority = fr.wikisource.org , queryString = nil ,
	-- relativePath = /wiki/Module:Central-s-fr/Documentation
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return WikiSelect -- begin.links_to_central_modules(t)
end -- function begin.links_to_central_modules(hostPort)

function begin.follow_central_modules(t) -- Tests of main central modules
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>Tests of main central modules:</b> :"
	t = t .. "\n* " .. viewer.form9user("begin_central_count_admins_active_users", mw.site.stats.admins, mw.site.stats.activeUsers)
	--[[ Table holding site statistics. Available statistics are:
	mw.site.stats{}
    pages: Number of pages in the wiki.
    articles: Number of articles in the wiki.
    files: Number of files in the wiki.
    edits: Number of edits in the wiki.
    views: Number of views in the wiki. Not available if $wgDisableCounters is set.
    users: Number of users in the wiki.
    activeUsers: Number of active users in the wiki.
    admins: Number of users in group 'sysop' in the wiki.
    --]]
	local tabView = {
		testGroup = {
		--	Modulenn:Central-s-br/doc				{{#invoke:Central-s-br|read}}	fr = Breton = Breton = Breitz
			{ hostPort = "br.wikisource.org", project = "s", lang = "br", admins = "16", assets = "236", -- 16 administrateurs pour 236
				moduleSpace = "Modulenn",	modulename = "Central-s-br", starton = "20170305", usedfrom = "20170330 ?", user = "VIGNERON", },
		--	Modul:Central-s-de/Documentation		{{#invoke:Central-s-de|read}}	de = German = Deutsch
			{ hostPort = "de.wikisource.org", project = "s", lang = "de", admins = "16 ?", assets = "236 ?", -- 16 administrateurs pour 236
				moduleSpace = "Modul",	modulename = "Central-s-de", starton = "20170405 ?", usedfrom = "20170430 ?", user = "Wer?", }, -- es = ¿Quién?
		--	Module:Central-s-fr/Documentation		{{#invoke:Central-s-fr|read}}	fr = French = Français = Français
			{ hostPort = "fr.wikisource.org", project = "s", lang = "fr", admins = "16 ?", assets = "236 ?", -- 16 administrateurs pour 236
				moduleSpace = "Module",	modulename = "Central-s-fr", starton = "20170215", usedfrom = "20170304 ?", user = "Rical", },
				
		--	Modul:Central-w-hu/Dokumentáció		{{#invoke:Central-w-hu|read}}	hu = Hungarian = Magyar
		--	{ hostPort = 'hu.wikipedia.org', project = "w", lang = "hu", code = "w", vers = "Central-w-hu",
		--		centralmodule = "Modul:Central-w-fr",		centraldoc = "Modul:Central-w-hu/Dokumentáció", },
				
		--	Modul:Central-w-hu/Dokumentáció		{{#invoke:Central-w-hu|read}}	hu = Hungarian = Magyar
			{ hostPort = "hu.wikipedia.org", project = "w", lang = "hu", admins = "23", assets = "1513", -- 23 administrateurs pour 1513
				moduleSpace = "Modul",	modulename = "Central-w-hu", starton = "20170505 ?", usedfrom = "20170530 ?", user = "Ki?", },
				
		--	Mô đun:Central-w-vi/tài liệu			{{#invoke:Central-w-vi|read}}	fr = Vietnamese = Vietnamien = Tiếng Việt
			{ hostPort = "vi.wikipedia.org", project = "w", lang = "vi", admins = "23", assets = "1513", -- 23 administrateurs pour 1513
				moduleSpace = "Mô đun",	modulename = "Central-w-vi", starton = "20170505 ?", usedfrom = "20170530 ?", user = "Ai?", },
		},
		headers = "begin_follow_central_modules_headers",
	--	headers	= "Project; Language; Main module; Start on; Used from; User",
	}
	tabView.t = (tabView.t or "") .. viewer.ta("follow_central_modules: ", "start")
	tabView.t = tabView.t .. viewer.ta("#testGroup: ", #tabView.testGroup)
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		tabView.t = tabView.t .. viewer.ta("begin.tableView_form_one_case: ", #case)
		local vers, url, urldoc, link = begin.links_to_central_modules(hostPort) -- Form links for standard central modules version in any wiki
		local WikiSelect = begin.links_to_central_modules(hostPort) -- Form links for standard central modules version in any wiki
		local wiki_Module_space = mw.site.namespaces.Module.name -- name: Local namespace name.
		local mwtitle = mw.title.getCurrentTitle()
		local page = (case.moduleSpace or "moduleSpace") .. ":" .. (case.modulename or "modulename") -- nsText = Module , baseText = p.v.version
		case.module = "[[ :" .. case.project .. ":" .. case.lang .. ":" .. page .. " | " .. page .. " ]]"
		return { case.project, case.lang, case.admins, case.assets, case.module, case.starton, case.usedfrom, case.user, }
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	local vers, url, urldoc, link = begin.links_to_central_modules("br.wikisource.org") -- Form links for standard central modules version in any wiki
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
--	t = t .. tabView.t -- internal track to debug
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function begin.follow_central_modules(t)


-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------
--	The Library:datas imports datas of Wikidata from mw.wikibase
--	function datas.import_wikidata(args_known, id) -- Import datas of Wikidata from mw.wikibase
--	see Extension:Wikibase Client/Lua
--	see Extension:Scribunto/Lua reference manual : class nameTest extends Scribunto_LuaEngineTestBase
-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------


-- datas = {} -- already declared by Scribunto, else in _G space.
-- Translations for datas library
datas.i18n = {}
datas.i18n.en = {
	-- Wikidata
	datas_wikidata_wikibase_err			= "Error: Wikibase is not available.",
	datas_wikidata_getEntity_err		= "Error: getEntity Wikidata is not available.",
	datas_wikidata_getEntityObject_err	= "Error: Element Wikidata <b>%1</b> is not found.",
	datas_wikidata_property_err			= "Error: Wikidata property <b>%1</b> is not found.",
	datas_wikidata_error_err			= "Error Wikidata: <b>%1</b> ",
--	datas_wikidata_cat_err				= "Error Wikidata",
	datas_wikidata_cat_err				= "Module with internal error",	 -- versioning_module_internal_error_cat
	datas_structured_data_txt			= 'Datas from Wikidata',
	datas_wikidata_any_page_title		= "Wikidata for any page:",
	datas_wikidata_details_test_title	= "Tests and imported datas from wikidata",
	datas_wikidata_arbitrary_tests_title = "datas.import_wikidata() Test of Wikidata arbitrary access",
	datas_wikidata_arbitrary_access_text = "Wikidata for another Title: ",
	datas_wikidata_time_details_title	= "datas.wikidata_time_details{} Wikidata time properties for more details",

	-- Miscellaneous warnings and errors
	datas_no_args_wikidata_err			= "Error: Module without wikidata arguments table.",
	datas_sources_of_datas				= "Informations from: /Wikidata, /template or module, /other, /warning, /error",
} -- datas.i18n.en
datas.i18n.es = {
	-- Wikidata
	datas_wikidata_wikibase_err			= "Error: Wikibase no está disponible.",
	datas_wikidata_getEntity_err		= "Error: getEntity Wikidata no está disponible.",
	datas_wikidata_getEntityObject_err	= "Error: Elemento <b>%1</b> de Wikidata no se encuentra.",
	datas_wikidata_property_err			= "Error: La propiedad <b>%1</b> de Wikidata no se encuentra.",
	datas_wikidata_error_err			= "Error Wikidata : <b>%1</b> ",
	datas_wikidata_cat_err				= "Módulo con error interno",	 -- versioning_module_internal_error_cat
	datas_structured_data_txt			= 'Datos de Wikidata',
	datas_wikidata_any_page_title		= "Wikidata para cualquier página:",
	datas_wikidata_details_test_title	= "Pruebas y datos importados de wikidata",
	datas_wikidata_arbitrary_tests_title = "datas.import_wikidata() Prueba de Wikidata acceso arbitrario",
	datas_wikidata_arbitrary_access_text = "Wikidata por otro título: ",
	datas_wikidata_time_details_title	= "datas.wikidata_time_details{} Wikidata propiedades de tiempo para más detalles",

	-- Diversos mensajes y errores
	datas_no_args_wikidata_err			= "Error: Módulo sin argumentos wikidata tabla.",
	datas_sources_of_datas				= "Informaciones de: /Wikidata, /template o module, /other, /warning, /error",
} -- datas.i18n.es
datas.i18n.fr = {
	-- Wikidata
	datas_wikidata_wikibase_err			= "Erreur : Wikibase n'est pas disponible.",
	datas_wikidata_getEntity_err		= "Erreur : getEntity Wikidata n'est pas disponible.",
	datas_wikidata_getEntityObject_err	= "Erreur : L'élément <b>%1</b> de Wikidata n'est pas trouvé.",
	datas_wikidata_property_err			= "Erreur : La propriété <b>%1</b> de Wikidata n'est pas trouvée.",
	datas_wikidata_error_err			= "Erreur Wikidata : <b>%1</b> ",
	datas_wikidata_cat_err				= "versioning_module_internal_error_cat",
--	datas_wikidata_cat_err				= "Module with internal error",	 -- versioning_module_internal_error_cat
	datas_structured_data_txt			= 'Données de Wikidata',
	datas_wikidata_any_page_title		= "Wikidata pour une autre page :",
	datas_wikidata_details_test_title	= "Tests et données importées de wikidata",
	datas_wikidata_arbitrary_tests_title = "datas.import_wikidata() Test de Wikidata accès arbitraire",
	datas_wikidata_arbitrary_access_text = "Wikidata pour une autre page: ",
	datas_wikidata_time_details_title	= "datas.wikidata_time_details{} Wikidata propriétés de temps pour plus de détails",

	-- Messages et erreurs divers
	datas_no_args_wikidata_err			= "Erreur interne : Module sans table d'arguments wikidata.",
	datas_sources_of_datas				= "Informations de : /Wikidata, /modèle ou module, /autres, /warning, /erreur",
} -- datas.i18n.fr

function datas.import_wikidata_new(args_known, wd_id) -- Import datas of Wikidata from mw.wikibase
--	wikidata structure args_known.father = mw.wikibase.label( "Q" .. entity.claims.p107[0].mainsnak.datavalue.value["numeric-id"])
	if type(args_known) ~= "table" then args_known = modes.args_known end
	if type(wd_id) ~= "string" then wd_id = nil end
	local t, adr, val, proplabel = "", nil, nil, nil
	local wd = {}
	local wd_mng = {} -- wikidata manager
	wd_mng.wd_base = mw.wikibase
	if not wd_mng.wd_base then -- Wikibase available ?
		wd_mng.wd_error = "wd_base"
		t = t .. viewer.ta("wd_error", wd_mng.wd_error)
		wd_mng.t = t
		datas.args_wikidata = wd
		return wd, t, wd_mng
	end
	local function getEntityObject_pcall( wd_id ) -- wikidata arbitrary access must not fail and block the page.
		local success, entity = pcall( mw.wikibase.getEntity, wd_id ) -- pcall or xpcall can run any function without blocking page.
		if success then return entity else return nil end
		-- T49930 Allow accessing data from a Wikidata item not connected to the current page - arbitrary access (tracking)
	end
	local pcall_wd_id = getEntityObject_pcall( wd_id )
	if pcall_wd_id then -- Wikidata item available ?
		wd_mng.wd_entity = getEntityObject_pcall( wd_id )
	else
		wd_mng.wd_id = mw.wikibase.getEntityIdForCurrentPage() -- Returns the Item id as string, like "Q42"
		if wd_mng.wd_id then -- Wikidata item available ?
			wd_mng.wd_entity = mw.wikibase.getEntityObject(wd_mng.wd_id)
			wd_mng.wd_entity.id = mw.wikibase.getEntityIdForCurrentPage() -- Returns the Item id as string, like "Q42"
		end
	--	wd_mng.wd_entity = mw.wikibase.getEntity() -- ex getEntityObject
	end
	if wd_mng.wd_entity then -- Page Wikidata disponible ?
		if wd_mng.wd_entity.claimRanks then
			wd_mng.wd_claimRanks = wd_mng.wd_entity.claimRanks
			wd_mng.wd_RANK_TRUTH = wd_mng.wd_entity.claimRanks.RANK_TRUTH
			wd_mng.wd_RANK_PREFERRED = wd_mng.wd_entity.claimRanks.RANK_PREFERRED
			wd_mng.wd_RANK_NORMAL = wd_mng.wd_entity.claimRanks.RANK_NORMAL
			wd_mng.wd_RANK_DEPRECATED = wd_mng.wd_entity.claimRanks.RANK_DEPRECATED
			-- https://www.mediawiki.org/wiki/Extension:WikibaseClient/Lua#mw.wikibase.entity.claimRanks
			-- Return the normal ranked claims with the property id P5
			-- entity:formatPropertyValues( 'P5', { mw.wikibase.entity.claimRanks.RANK_NORMAL } )
			-- Return all claims with id P123 (as the table passed contains all possible claim ranks)
			-- entity:formatPropertyValues( 'P123', mw.wikibase.entity.claimRanks )
			-- mw.wikibase.entity.claimRanks = RANK_TRUTH, RANK_PREFERRED, RANK_NORMAL, RANK_DEPRECATED
		end
		wd_mng.sitelink = wd_mng.wd_entity:getSitelink( )
		wd_mng.label = wd_mng.wd_entity:getLabel( ) -- Returns a string, like "Berlin" with 'de'
		wd_mng.props = wd_mng.wd_entity:getProperties() -- or {} Returns a table like: { "P123", "P1337" }
		wd_mng.props_maxn = tostring(table.maxn( wd_mng.props ) )
		wd_mng.props_txt = "T " .. mw.text.listToText( wd_mng.props )
		--
		wd_mng.props_list = " "
		for i, pp in ipairs(wd_mng.props) do -- Properties list
			wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues(  pp )
			if wd_mng.wd_claimRanks then
				wd_mng.formatPropertyValues_claimRanks = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_claimRanks } )
			end
			if wd_mng.wd_RANK_TRUTH then
				wd_mng.formatPropertyValues_RANK_TRUTH = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_TRUTH } )
			end
			if wd_mng.wd_RANK_PREFERRED then
				wd_mng.formatPropertyValues_RANK_PREFERRED = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_PREFERRED } )
			end
			if wd_mng.wd_RANK_TRUTH then
				wd_mng.formatPropertyValues_wd_RANK_NORMAL = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_NORMAL } )
			end
			if wd_mng.wd_RANK_TRUTH then
				wd_mng.formatPropertyValues_RANK_DEPRECATED = wd_mng.wd_entity:formatPropertyValues(  pp, { wd_mng.wd_RANK_DEPRECATED } )
			end
			val = wd_mng.formatPropertyValues.value
			proplabel = wd_mng.formatPropertyValues.label
			wd_mng.props_list = wd_mng.props_list .. " , " .. i .. "=" .. pp .. "=" .. viewer.ta(proplabel, val)
			-- .. "/" .. viewer.ta(proplabel, wd_mng.formatPropertyNORMAL)
			if pp == "P569" then -- birthyear P569 for test DEBUG
			tools.TimeName = pp .. ".1.mainsnak" ; datas.wikidata_time_details = wd_mng.wd_entity.claims[pp][1].mainsnak end
			if pp == "P570" then -- deathyear P570 for test DEBUG
			tools.TimeName = pp .. ".1.mainsnak" ; datas.wikidata_time_details = wd_mng.wd_entity.claims[pp][1].mainsnak end
			-- https://www.wikidata.org/wiki/Wikidata:Bistro#Date de décès inconnue
			-- Dans la propriete :Il y a trois petits rectangles à gauche qui permettent de dire si il n'y a pas de valeur (donc pas mort) où si la date est inconnue, le modèle ici ne tient pas compte de ces éléments. --[[User:Pino~eowiki|Pino~eowiki]] ([[User talk:Pino~eowiki|<span class="signature-talk">{{int:Talkpagelinktext}}</span>]]) 16:40, 27 January 2016 (UTC)
		end
	else -- if not wd_mng.wd_entity then entity unknown for the page. entity introuvable pour la page.
		wd_mng.wd_error = "wd_entity"
		t = t .. viewer.ta("wd_error", wd_mng.wd_error)
		wd_mng.t = t
		datas.args_wikidata = wd
		return wd, t, wd_mng
	end
	if wd_mng.wd_error then -- Show an error, Signaler une erreur
		if wd_mng.wd_error == "wikidata_props" then err = viewer.form9user("datas_wikidata_error_err", "wikidata_props") end
		if wd_mng.wd_error == "wd_base" then err = viewer.form9user("datas_wikidata_wikibase_err") end
		if wd_mng.wd_error == "wd_entity" then err = viewer.form9user("datas_wikidata_getEntity_err", wd_mng.wd_entity) end
		if wd_mng.wd_error == "wd_property" then err = viewer.form9user("datas_wikidata_property_err", wd_mng.wd_property) end
		-- elem -> id
		if wd_mng.wd_error == "wd_id" then err = viewer.form9user("datas_wikidata_getEntityObject_err", wd_mng.wd_id) end
		err = events.add_err(err) .. ";"
		t = t .. err
		local datas_wikidata_cat_err = events.add_cat("datas_wikidata_cat_err")
	else
		if type(args_known) ~= "table" then args_known = {} end
		for key, pp in pairs(args_known) do -- Pour tous les paramètres connus
			if pp.prop then
				if pp.prop == "label" then val = wd_mng.label
				elseif pp.prop == "sitelink" then val = wd_mng.sitelink
				elseif pp.prop == "itemid" then val = wd_mng.wd_entity.id
				elseif pp.prop == "description" then val = wd_mng.wd_base.description( wd_mng.wd_id ) -- wikibase.description( id )
				elseif pp.prop == "claims" then val = wd_mng.wd_base.renderSnak( wd_mng.wd_entity['claims'] ) -- Returns the given Snaks formatted as wiki text.
			--	local entity = mw.wikibase.getEntityObject()
			--	local snaks = entity['claims']['P342'][1]['qualifiers']
			--	mw.wikibase.renderSnaks( snaks ) -- Returns the given Snaks formatted as wiki text.
				else
					wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues( pp.prop ) -- "P" .. pp.prop
					-- Returns a table like: { value = "Formatted claim value", label = "Label of the Property" }
					if wd_mng.wd_RANK_TRUTH then
						wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues(  pp, { wd_mng.wd_RANK_TRUTH } )
					end
					if wd_mng.wd_claimRanks then
						wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues(  pp, { wd_mng.wd_claimRanks } )
					end
					val = wd_mng.formatPropertyValues.value
					proplabel = wd_mng.formatPropertyValues.label
				end
				if pp.format == "year" then
					val = mw.ustring.sub( val, -4, -1 )
				end
				wd[key] = val
				t = t .. viewer.ta(key, val) -- .. viewer.ta(key, wd_mng.formatPropertyNORMAL)
			end
		end
	end
--	wd.lastname = wd.label
	wd_mng.t = t
	datas.args_wikidata = wd
	return wd, t, wd_mng
end -- function datas.import_wikidata_new(args_known, id)

function datas.import_wikidata(args_known, id)
--	wikidata structure args_known.father = mw.wikibase.label( "Q" .. entity.claims.p107[0].mainsnak.datavalue.value["numeric-id"])
	if type(args_known) ~= "table" then args_known = tools.args_known end
	local t, adr, val, proplabel = "", nil, nil, nil
	local wd = {}
	local wd_mng = {} -- wikidata manager
	if type(id) == "string" then -- Le 2013-06-19 acces restreint par Lua pour un autre élément
		-- https://bugzilla.wikimedia.org/show_bug.cgi?id=49805
		-- T49930 Allow accessing data from a Wikidata item not connected to the current page - arbitrary access (tracking)
		-- Reported: 2013-06-19 10:52 UTC by Rical
		wd_mng.wd_id = id
	--	t = t .. viewer.ta("wd_error", wd.wd_error)
	--	t = t .. "\n* " .. viewer.ta("wd_id", wd_mng.wd_id)
	--	t = t .. " see [https://phabricator.wikimedia.org/T49930 bug T49930]" -- DEBUG
		wd_mng.t = t or ""
	--	wd_mng.wd_error = "wd_id"
--		wd_mng.wd_id = nil -- anti-bug 47930
--		tools.args_wikidata = wd
--		return wd, t, wd_mng
	end
	wd_mng.wd_base = mw.wikibase
	if not wd_mng.wd_base then -- Wikidata disponible ?
		wd_mng.wd_error = "wd_base"
		t = t .. viewer.ta("wd_error", wd_mng.wd_error)
		wd_mng.t = t
		tools.args_wikidata = wd
		return wd, t, wd_mng
	end
	--
	local function getEntityObject_xpcall( wd_id ) -- wikidata arbitrary access must not fail and block the page.
	--	local entity = mw.wikibase.getEntityObject( wd_id )
	--	if type(wd_id) ~= "string" then wd_id = nil end
		local success, entity = pcall( mw.wikibase.getEntity, wd_id ) -- pcall or xpcall can run any function without blocking page.
		if success then return entity else return nil end
		-- T49930 Allow accessing data from a Wikidata item not connected to the current page - arbitrary access (tracking)
	end
--	wd_mng.wd_entity = getEntityObject_xpcall( wd_mng.wd_id )
	if getEntityObject_xpcall( wd_mng.wd_id ) then -- Page Wikidata disponible ?
		wd_mng.wd_entity = mw.wikibase.getEntity( wd_mng.wd_id ) -- ex getEntityObject
	end
	--
--	wd_mng.wd_entity = mw.wikibase.getEntityObject( ) -- anti-bug 47930
	if wd_mng.wd_entity then -- Page Wikidata disponible ?
		if wd_mng.wd_entity.claimRanks then
			wd_mng.wd_claimRanks = wd_mng.wd_entity.claimRanks
			wd_mng.wd_RANK_TRUTH = wd_mng.wd_entity.claimRanks.RANK_TRUTH
			wd_mng.wd_RANK_PREFERRED = wd_mng.wd_entity.claimRanks.RANK_PREFERRED
			wd_mng.wd_RANK_NORMAL = wd_mng.wd_entity.claimRanks.RANK_NORMAL
			wd_mng.wd_RANK_DEPRECATED = wd_mng.wd_entity.claimRanks.RANK_DEPRECATED
			-- https://www.mediawiki.org/wiki/Extension:WikibaseClient/Lua#mw.wikibase.entity.claimRanks
			-- Return the normal ranked claims with the property id P5
			-- entity:formatPropertyValues( 'P5', { mw.wikibase.entity.claimRanks.RANK_NORMAL } )
			-- Return all claims with id P123 (as the table passed contains all possible claim ranks)
			-- entity:formatPropertyValues( 'P123', mw.wikibase.entity.claimRanks )
			-- mw.wikibase.entity.claimRanks = RANK_TRUTH, RANK_PREFERRED, RANK_NORMAL, RANK_DEPRECATED
		end
		wd_mng.sitelink = wd_mng.wd_entity:getSitelink( )
		wd_mng.label = wd_mng.wd_entity:getLabel( ) -- Returns a string, like "Berlin" with 'de'
		wd_mng.props = wd_mng.wd_entity:getProperties() -- or {} Returns a table like: { "P123", "P1337" }
		wd_mng.props_maxn = tostring(table.maxn( wd_mng.props ) )
		wd_mng.props_txt = "T " .. mw.text.listToText( wd_mng.props )
		--
		wd_mng.props_list = " "
		for i, pp in ipairs(wd_mng.props) do -- Properties list
			wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues(  pp )
			if wd_mng.wd_claimRanks then
				wd_mng.formatPropertyValues_claimRanks = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_claimRanks } )
			end
			if wd_mng.wd_RANK_TRUTH then
				wd_mng.formatPropertyValues_RANK_TRUTH = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_TRUTH } )
			end
			if wd_mng.wd_RANK_PREFERRED then
				wd_mng.formatPropertyValues_RANK_PREFERRED = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_PREFERRED } )
			end
			if wd_mng.wd_RANK_TRUTH then
				wd_mng.formatPropertyValues_wd_RANK_NORMAL = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_NORMAL } )
			end
			if wd_mng.wd_RANK_TRUTH then
				wd_mng.formatPropertyValues_RANK_DEPRECATED = wd_mng.wd_entity:formatPropertyValues(  pp, { wd_mng.wd_RANK_DEPRECATED } )
			end
			val = wd_mng.formatPropertyValues.value
			proplabel = wd_mng.formatPropertyValues.label
			wd_mng.props_list = wd_mng.props_list .. " , " .. i .. "=" .. pp .. "=" .. viewer.ta(proplabel, val)
			-- .. "/" .. viewer.ta(proplabel, wd_mng.formatPropertyNORMAL)
			if pp == "P569" then -- birthyear P569 for test DEBUG
			tools.TimeName = pp .. ".1.mainsnak" ; tools.TimeTest = wd_mng.wd_entity.claims[pp][1].mainsnak end
			if pp == "P570" then -- deathyear P570 for test DEBUG
			tools.TimeName = pp .. ".1.mainsnak" ; tools.TimeTest = wd_mng.wd_entity.claims[pp][1].mainsnak end
			-- https://www.wikidata.org/wiki/Wikidata:Bistro#Date de décès inconnue
			-- Dans la propriete :Il y a trois petits rectangles à gauche qui permettent de dire si il n'y a pas de valeur (donc pas mort) où si la date est inconnue, le modèle ici ne tient pas compte de ces éléments. --[[User:Pino~eowiki|Pino~eowiki]] ([[User talk:Pino~eowiki|<span class="signature-talk">{{int:Talkpagelinktext}}</span>]]) 16:40, 27 January 2016 (UTC)
		end
	else -- entity unknown for the page. entity introuvable pour la page.
		wd_mng.wd_error = "wd_entity"
		t = t .. viewer.ta("wd_error", wd_mng.wd_error)
		wd_mng.t = t
		tools.args_wikidata = wd
		return wd, t, wd_mng
	end
	if wd_mng.wd_error then -- Show an error, Signaler une erreur
		if wd_mng.wd_error == "wikidata_props" then err = viewer.form9user("tools_wikidata_error_err", "wikidata_props") end
		if wd_mng.wd_error == "wd_base" then err = viewer.form9user("tools_wikidata_wikibase_err") end
		if wd_mng.wd_error == "wd_entity" then err = viewer.form9user("tools_wikidata_getEntity_err", wd_mng.wd_entity) end
		if wd_mng.wd_error == "wd_property" then err = viewer.form9user("tools_wikidata_property_err", wd_mng.wd_property) end
		-- elem -> id
		if wd_mng.wd_error == "wd_id" then err = viewer.form9user("tools_wikidata_getEntityObject_err", wd_mng.wd_id) end
		err = tools.err_add(err)
		t = t .. err
		local tools_wikidata_cat_err = tools.cat_add("tools_wikidata_cat_err")
	else
		for key, pp in pairs(args_known) do -- Pour tous les paramètres connus
			if pp.prop then
				if pp.prop == "label" then val = wd_mng.label
				elseif pp.prop == "sitelink" then val = wd_mng.sitelink
				elseif pp.prop == "entityid" then val = wd_mng.wd_entity.id
				elseif pp.prop == "description" then val = wd_mng.wd_base.description( wd_mng.wd_id ) -- wikibase.description( id )
				elseif pp.prop == "claims" then val = wd_mng.wd_base.renderSnak( wd_mng.wd_entity['claims'] ) -- Returns the given Snaks formatted as wiki text.
			--	local entity = mw.wikibase.getEntityObject()
			--	local snaks = entity['claims']['P342'][1]['qualifiers']
			--	mw.wikibase.renderSnaks( snaks ) -- Returns the given Snaks formatted as wiki text.
				else
					wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues( pp.prop ) -- "P" .. pp.prop
					-- Returns a table like: { value = "Formatted claim value", label = "Label of the Property" }
					if wd_mng.wd_RANK_TRUTH then
						wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues(  pp, { wd_mng.wd_RANK_TRUTH } )
					end
					if wd_mng.wd_claimRanks then
						wd_mng.formatPropertyValues = wd_mng.wd_entity:formatPropertyValues(  pp, { wd_mng.wd_claimRanks } )
					end
					val = wd_mng.formatPropertyValues.value
					proplabel = wd_mng.formatPropertyValues.label
				end
				if pp.format == "year" then
					val = mw.ustring.sub( val, -4, -1 )
				end
				wd[key] = val
				t = t .. viewer.ta(key, val) -- .. viewer.ta(key, wd_mng.formatPropertyNORMAL)
			end
		end
	end
--	wd.lastname = wd.label
	wd_mng.t = t
	tools.args_wikidata = wd
	return wd, t, wd_mng
end -- function datas.import_wikidata(args_known, id)

function datas.wikidata_details_test(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>wikidata_details_test</b> :"
	t = t .. "<br>" .. luaTable.formSubCounts("datas.i18n")
	local wd, tw, wd_mng
	--	t = t .. datas.wikidata_arbitrary_tests()
	wd, tw, wd_mng = datas.import_wikidata( modes.args_known ) -- for present page
	if wd_mng and wd_mng.wd_entity and wd_mng.wd_entity.id then
		t = t .. "\n* Entity.id: " .. (wd_mng.wd_entity.id or "") -- itemid
		local datas_structured_data_txt = viewer.form9user("datas_structured_data_txt")
		t = t .. ' <span style="color:#232388; font-size:140%; line-height:120%; ">⦁&nbsp;&nbsp;</span>[[d:' .. wd_mng.wd_entity.id .. '|' .. datas_structured_data_txt .. ']]<br>'
	end
	t = t .. "\n* Sought properties: " .. tw
	t = t .. "\n* Properties number maxn: " .. (wd_mng.props_maxn or "")
--	t = t .. "\n* Properties txt: " .. (wd_mng.props_txt or "")
	t = t .. "\n* Properties list: " .. (wd_mng.props_list or "")
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t -- .init_configs(=14 -- .save_configs(=48 -- .restore_configs(=58 -- 
end -- function datas.wikidata_details_test(t)

function datas.wikidata_arbitrary_tests(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
-- T49930 Allow accessing data from a Wikidata item not connected to the current page - arbitrary access (tracking)
	local t = t or "\n* wikidata_arbitrary_tests: "
	local function one_arbitrary_access(id)
		local t = ""
		local wd, tw, wd_mng = datas.import_wikidata(modes.args_known, id) -- example Q535 = Victor Hugo
		if wd_mng and wd_mng.wd_entity and wd_mng.wd_entity.id then
			t = t .. "\n* " .. viewer.form9user("datas_wikidata_arbitrary_access_text")
			.. (wd.itemid or "") .. ", " .. (wd.label or "") .. " ( " .. (wd.birthyear or "") .. " - " .. (wd.deathyear or "") .. " ) "
			local datas_structured_data_txt = viewer.form9user("datas_structured_data_txt")
			t = t .. ' <span style="color:#232388; font-size:140%; line-height:120%; ">⦁&nbsp;&nbsp;</span>[[d:' .. wd_mng.wd_entity.id .. '|' .. datas_structured_data_txt .. ']]'
			t = t .. ", " .. (wd_mng.props_maxn or "") .. " properties. " -- "\n* Properties number maxn: "
			t = t .. "\n* Sought properties: " .. tw
		else
			t = "<br>* No arbitrary access for: " .. tostring(id)
		end
		return t
	end
	t = t .. one_arbitrary_access("Q535") -- Q535 = Victor Hugo = Victor Marie Hugo
--	t = t .. one_arbitrary_access("Victor Hugo") -- Q535 = Victor Hugo = Victor Marie Hugo
	t = t .. one_arbitrary_access("Q8023") -- Q899264 = Nelson Mandela
--	t = t .. one_arbitrary_access("Q899264") -- Q899264 = Martin Fleischmann
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function datas.wikidata_arbitrary_tests(t)


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:events support events of types erros, warnings and categories.
--	events.init_configs( base, tabOptions, group ) -- Init a new events group
--	events.add_err(key, ... ) -- Add an error in the actual table of events.
--	events.catGroup(groupCat, groupList) -- Add some categories in the actual table of events.
--	events.form(evt) -- Format events types
--	events.categ_Test(t) -- tester les categories
-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------


--	events = {} -- already declared by Scribunto, else in _G space.
-- Translations for Library:events
events.i18n = {}
events.i18n.en = {
	events_test_tests_title					= "List a table, test with limits:",
	events_categ_Test_title					= "events.categ_Test() Test: Creation of categories in some languages",
	events_test_resTest_title				= "List a table, test with limits:",
	events_test_projects_title				= "Test and display tests events for projects",
	events_test_projects_headers			= "Type;Key;Name test and values;result of the test",
	events_test_mediawiki_title				= "Test and display tests events for mediawiki",
	events_test_mediawiki_headers			= "Type;Key;Name test and values;result of the test",
	events_test_categ_Test_title			= "categ_Test test : classes in some languages",
	events_test_categ_Test_headers			= "Test type;user language;wiki language;Categories",
	events_all_kinds_tests_title			= "events.all_kinds_test() Test: all kinds of events (err, wng, cat)",
	events_close_without_memo_err			= "events.restore_configs(memo) without <b>memo</b> in module <b>%1</b>.",
	events_selectLang_tests_title			= "events.selectLang() Tests: Select a language for tests or other",
	events_selectLang_test_headers			= "Wiki language; User language; Sought language; Selected language; Example",
	events_selectLang_test_example			= "Language: wiki = <b>%1</b>, user = <b>%2</b>, selected = <b>%3</b>.",
	events_selectLang_test_err				= "Internal error : events.selectLang(lang) has no tests table.",
	events_selectLang_test_cat				= "Module with internal error",
} -- events.i18n.en
events.i18n.es = {
	events_test_tests_title					= "List a table, test with limits:",
	events_categ_Test_title					= "events.categ_Test() Prueba: Creación de categorías en algunos idiomas",
	events_test_resTest_title				= "List a table, test with limits:",
	events_test_projects_title				= "Test and display tests events for projects",
	events_test_projects_headers			= "Type;Key;Name test and values;result of the test",
	events_test_mediawiki_title				= "Test and display tests events for mediawiki",
	events_test_mediawiki_headers			= "Type;Key;Name test and values;result of the test",
	events_test_categ_Test_title			= "Prueba categ_Test: clases en algunos lenguajes",
	events_test_categ_Test_headers			= "Tipo de prueba;Lenguaje usuario;Lenguaje wiki;Categorías",
	events_all_kinds_tests_title			= "events.all_kinds_test() Prueba: todo tipo de eventos (err, wng, cat)",
	events_add_cat_deprecated				= "Función obsoleto en módulo principal.",
	events_close_without_memo_err			= "events.restore_configs(memo) sin <b>memo</b> en el módulo <b>%1</b>.",
	events_selectLang_tests_title			= "events.selectLang() Pruebas: Seleccione un idioma para pruebas u otro",
	events_selectLang_test_headers			= "Lengua del Wiki; Idioma del usuario; Lengua buscada; Idioma seleccionado; Ejemplo",
	events_selectLang_test_example			= "Idioma: wiki = <b>%1</b>, usuario = <b>%2</b>, seleccionada = <b>%3</b>.",
	events_selectLang_test_err				= "Error interno: events.selectLang(lang) no tiene tabla de pruebas.",
	events_selectLang_test_cat				= "Módulo con error interno",
	versioning_defaultCrashResult_ref_err	= "La función antiCrash() protege esta página contra un colapso completo debido a un error interna: %1.",
} -- events.i18n.es
events.i18n.fr = {
	events_test_tests_title					= "List a table, test with limits:",
	events_categ_Test_title					= "events.categ_Test() Test: Création des catégories dans quelques langues",
	events_test_resTest_title				= "List a table, test with limits:",
	events_test_projects_title				= "Test and display tests events for projects",
	events_test_projects_headers			= "Type;Key;Name test and values;result of the test",
	events_test_mediawiki_title				= "Test and display tests events for mediawiki",
	events_test_mediawiki_headers			= "Type;Key;Name test and values;result of the test",
	events_test_categ_Test_title			= "Tester categ_Test : catégories dans quelques langues",
	events_test_categ_Test_headers			= "Type de test;Langue utilisateur;Langue wiki;Catégories",
	events_all_kinds_tests_title			= "events.all_kinds_test() Test: toutes les sortes d'événements (err, wng, cat)",
	events_add_cat_deprecated				= "Fonction obsolète dans le module principal.",
	events_close_without_memo_err			= "events.restore_configs(memo) sans <b>memo</b> dans le module <b>%1</b>.",
	events_selectLang_tests_title			= "events.selectLang() Tests: Sélectionnez une langue pour les tests ou autres",
	events_selectLang_test_headers			= "Langue du wiki; Langue utilisateur; Langue cherchée; Langue choisie; Exemple",
	events_selectLang_test_example			= "Langues : wiki = <b>%1</b>, utilisateur = <b>%2</b>, choisie = <b>%3</b>.",
	events_selectLang_test_err				= "Erreur interne: events.selectLang(lang) n'a pas de table de tests.",
	events_selectLang_test_cat				= "Module avec erreur interne",
} -- events.i18n.fr

events.testGroup = {}
events.rowGroup = {}
events.tabOptions = {}
events.tabOptions.testGroup = {}
events.tabOptions.rowGroup = {}

function events.init_configs(base, tabOptions, group) -- Initialize global configurations and save them before eventual changes.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	-- actual init
	if type(base) ~= "table" then base = {} end
	if type(group) ~= "table" then group = nil end
	if type(tabOptions) ~= "table" then
		tabOptions = events.tabOptions or {}
	end
	base.events = events -- A new events process/object come from the events process/object in this module.
	base.events.tabOptions		= tabOptions				or base.events.tabOptions or events.tabOptions
	base.events.catview			= tabOptions.catview		or base.events.catview or modes.catView
	base.events.default_group	= tabOptions.default_group	or base.events.group_all_types_test
	base.events.dropboxStyle	= tabOptions.dropboxStyle	or base.events.dropboxStyle
	base.events.testGroup		= group or tabOptions.testGroup or base.events.testGroup or events.testGroup or {}
	base.events.headers			= tabOptions.headers		or base.events.headers
	base.events.kind			= tabOptions.kind			or base.events.kind
	base.events.tableViewStyle	= tabOptions.tableViewStyle or base.events.dropboxStyle
	base.events.testsFunction	= tabOptions.testsFunction	or base.events.testsFunction
	base.events.typ				= tabOptions.typ			or base.events.typ
	base.events.user_lang		= tabOptions.user_lang		or base.events.user_lang
	base.events.wiki_lang		= tabOptions.wiki_lang		or base.events.wiki_lang
	base.events.recursiveLevel	= tabOptions.recursiveLevel or base.events.recursiveLevel
	base.events.recursiveLimit	= tabOptions.recursiveLimit or base.events.recursiveLimit or 11111
	base.events.testGroup		= events.testGroup			or base.events.testGroup or {}
	translate.init_wiki_user_page_lang(base.events.wiki_lang, base.events.user_lang)
	return memo, base.events
end -- function events.init_configs(base, tabOptions, group)

function tools.initCatsErrs() -- Initialize new cats and errors tables.
	versioning.deprecatedFunction("tools.initCatsErrs", "events.memorize")
	return events.save_configs() -- Save global configuration before eventual changes.
end
--	How to use initCatsErrs and closeCatsErrs:
--	tools.initCatsErrs() -- Initialize new cats and errors tables.
--	Any tests which must not disturb other ones, nor main process.
--	tools.closeCatsErrs() -- Close a cats and errors table.
function events.save_configs() -- Save global configuration before eventual changes.
	local memo = {}
	memo.memo_group = events.testGroup
	memo.memo_tabOptions = events.tabOptions
	memo.memo_versioning_main_i18n = versioning.main_i18n
	memo.memo_translate_main_i18n = translate.main_i18n
	memo.memo_wiki_lang = translate.wiki_lang
	memo.memo_user_lang = translate.user_lang
	memo.memo_page_lang = translate.page_lang
	memo.memo_categories_list = events.categories_list
	memo.memo_catView = modes.catView
	memo.memo_recursiveLimit = modes.recursiveLimit
	memo.memo_mode_name = modes.mode_name
	memo.memo_mode_options = modes.mode_options
	memo.memo_invoke_options = modes.invoke_options
	memo.memo_options_for_modes = modes.options_for_modes
	memo.memo_report_trackOptions = modes.report_trackOptions
	return memo
end -- function events.save_configs()

function tools.closeCatsErrs(memo) -- Close a cats and errors table.
	versioning.deprecatedFunction("tools.closeCatsErrs", "events.close")
	return events.restore_configs(memo) -- Restore global configurations after eventual changes.
end
--	function events.init_configs(=3 -- function events.save_configs(=3 -- function events.restore_configs(=2 -- 
--	.init_configs(=16-3=13 -- .save_configs(=50-3-init-deprecated=45 -- .restore_configs(=60-2=58 -- OK : 16+50-3-1-1 = 13+45 = 58 = 60-2
function events.restore_configs(memo) -- Restore global configurations after eventual changes.
	-- Close categories and errors tables for one test or a group of tests.
	if type(memo) ~= "table" then
		events.add_err("events_close_without_memo_err", versioning.main_versions.versionName) -- Add an error in the actual table of events.
		return
	end
	events.testGroup = memo.memo_group
	events.tabOptions = memo.memo_tabOptions
	versioning.main_i18n = memo.memo_versioning_main_i18n
	translate.main_i18n = memo.memo_translate_main_i18n
	translate.wiki_lang = memo.memo_wiki_lang
	translate.user_lang = memo.memo_user_lang
	translate.page_lang = memo.memo_page_lang
	translate.init_wiki_user_page_lang(translate.wiki_lang, translate.user_lang)
	events.categories_list = memo.memo_categories_list
	modes.catView = memo.memo_catView
	modes.recursiveLimit = memo.memo_recursiveLimit
	modes.mode_name = memo.memo_mode_name
	modes.mode_options = memo.memo_mode_options
	modes.invoke_options = memo.memo_invoke_options
	modes.options_for_modes = memo.memo_options_for_modes
	modes.report_trackOptions = memo.memo_report_trackOptions
end -- function events.restore_configs(memo)

function events.select_typ(typ) -- Select events by one type
	local is = {}
	for i, evt in pairs(events.testGroup) do
		if evt.typ == typ then table.insert( is, evt.idargs ) end
	end
	return is
end

function events.sort_typ() -- Sort events by type in wng, err, cat.
	local wng, err, cat = {}, {}, {}
	for i, evt in pairs(events.testGroup) do
		if evt.typ == "wng" then table.insert( wng, evt ) end
		if evt.typ == "err" then table.insert( err, evt ) end
		if evt.typ == "cat" then table.insert( cat, evt ) end
	end
	return wng, err, cat
end -- function events.sort_typ()

function events.form(evt) -- Format events types
	if type(evt) ~= "table" then return end
	evt.tableStyle = evt.tableStyle or "" -- Could change the style of the wikitext. Not available on 20160916
	if type(evt.name) ~= "string" then evt.name = ""
	elseif evt.name ~= "" then evt.name = evt.name .. "_" end
	evt.idargs = evt.name .. tostring( viewer.form9user( tostring(evt.key), luaTable.toList( evt.v ) ) )
	-- Each type of event forms its own type of wikitext
	evt.user_wkt = translate.trans9vars(translate.user_translations, evt.key, luaTable.toList( evt.v ) )
	evt.wiki_wkt = translate.trans9vars(translate.wiki_translations, evt.key, luaTable.toList( evt.v ) )
	evt.res = evt.wkt or ""
	if evt.typ == "err" then evt.res = viewer.errorColor(evt.user_wkt) end
	if evt.typ == "wng" then evt.res = viewer.warningColor(evt.user_wkt) end
	local c = events.catview or ":"
	local wiki_catspace = mw.site.namespaces.Category.name -- name: Local namespace name.
	if evt.typ == "cat" then -- Format a category to display or activate
		evt.res = " [[" .. c .. wiki_catspace .. ":" .. evt.wiki_wkt ..	 "|" .. evt.user_wkt .. "]] "
	end
	viewer.trc = viewer.trc or "? form " .. translate.formTestCase(".form evt.key=%1, evt.res=%2, ", evt.key, evt.res )
	return evt.res, evt
end -- function events.form(evt)

function events.add(typ, key, ...) -- Add an event to a group.
	local evt = {} -- actual event
	if type(typ) == "table" then evt = typ else evt = {} end -- actual event
	evt.typ = evt.typ or typ -- Type of event: err, wng, cat
	evt.key = evt.key or key -- Translation key, simple or extended by name
	if type(typ) ~= "string" then typ = "notyp" end -- To debug abnormal type of event
	if type(key) ~= "string" then key = "nokey" end -- To debug abnormal translation key
	evt.name = evt.name or "" -- Translation key, simple or extended by name
	evt.v = evt.v or { ... } -- table of detail values
	evt.res, evt = events.form( evt )
	-- Insert only new events
	local new_evt = translate.formTestCase( tostring(evt.key), luaTable.toList( evt.v ) )
	for i, event in pairs(events.testGroup) do
		local old_evt = translate.formTestCase( tostring(event.key), luaTable.toList( event.v ) )
		if new_evt == old_evt then new_evt = nil end -- delete the new event if it already exists.
	end
	if new_evt then table.insert( events.testGroup, evt ) end
--	table.insert( events.testGroup, evt ) -- { evt.typ, evt.key, evt.name, evt.wkt } )
	return evt.res, evt
end -- function events.add(typ, key, ...)

function events.add_err(key, ... ) -- Add an error in the actual table of events.
	local res, evt = events.add("err", key, ...) -- Add an event to a group.
	viewer.trc = viewer.trc .. translate.formTestCase("\n* err_add evt.typ=%1, evt.key=%2, ", evt.typ, evt.key)
	return res, evt
end

function events.add_wng(key, ... ) -- Add a warning in the actual table of events.
	local res, evt = events.add("wng", key, ...) -- Add an event to a group.
	viewer.trc = viewer.trc .. translate.formTestCase("\n* msg_add evt.typ=%1, evt.key=%2, ", evt.typ, evt.key)
	return res, evt
end

function events.add_cat(key, ... ) -- Add a category in the actual table of events.
	local res, evt = events.add("cat", key, ...) -- Add an event to a group.
	viewer.trc = viewer.trc .. translate.formTestCase("\n* add_cat evt.typ=%1, evt.key=%2, ", evt.typ, evt.key)
	return res, evt
end

function events.catGroup(groupCat, groupList) -- Add some categories in the actual table of events.
--	catGroup("Country %1", "France,Italia") -> [[Category:Country France]] [[Category:Country Italia]]
	if type(groupCat) ~= "string" then groupCat = "%1" end
	if type(groupList) ~= "string" then return "" end
	local cats = ""
--	local t = ""
--	t = t .. viewer.ta("groupList", groupList)
	for i, str in mw.text.gsplit(groupList, ";", true) do
		local wkt = viewer.form9user(groupCat, str)
		cats = cats .. events.add_cat(groupCat, wkt)
	end
	local res, evt = events.add("catGroup", groupCat, cats) -- Add an event to a group.
--	viewer.trc = viewer.trc .. viewer.form9user("\n* add_cat evt.typ=%1, evt.key=%2, ", evt.typ, evt.key)
	return res, evt
end -- function events.catGroup(groupCat, groupList)

function events.selector( tabOptions, evt )
	if type(evt) ~= "table" then
		evt = { -- options for viewer.tableView() -- Form a table with lines and columns.
			headers = "typ;key;selector-name;wkt",
			style = "",
			typ = "err",
			rowGroup = {},
		}
	else
		evt.headers = evt.headers or "typ;key;else-name;wkt"
		evt.tableStyle = evt.tableStyle or ""
		evt.name = evt.name or "_"
		evt.typ = evt.typ or "nil"
	end
	if	   (type(tabOptions) == "table") and (type(tabOptions.typ) == "string") and (tabOptions.typ == evt.typ) then
		table.insert( tabOptions.testGroup, { evt.typ, evt.key, evt.name, evt.wkt } )
	elseif (type(tabOptions) == "table") and (type(tabOptions.typ) == "string") and (tabOptions.typ == "v") then
		table.insert( tabOptions.testGroup, { evt.typ, evt.key, evt.name, evt.wkt } )
	else
		tabOptions = {}
		table.insert( tabOptions.testGroup, { evt.typ, evt.key, evt.name, evt.wkt } )
	end
end -- function events.selector( tabOptions, evt )

function events.categ_Test(t) -- test of categories
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	translate.init_wiki_user_page_lang() -- restore previous langages
	local t = t or "\n* These categ_Test test the function <b>add_cat</b> : " .. viewer.ta("user_lang", translate.user_lang) .. viewer.ta("wiki_lang", translate.wiki_lang)
	t = t .. "<br>" .. luaTable.formSubCounts("events.i18n")
	local tabView = {
		testGroup = { -- events.testGroup
			{ typ = "cat", funcname = "add_cat",	 user_lang = nil, wiki_lang = nil, key = "datas_no_args_wikidata_err", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = nil, wiki_lang = nil, key = "versioning_module_usage_error_cat", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "en", wiki_lang = nil, key = "versioning_module_internal_error_cat", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "es", wiki_lang = nil, key = "modes_no_source_arguments_err", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "en", wiki_lang = "es", key = "datas_no_args_wikidata_err", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "fr", wiki_lang = "en", key = "versioning_module_internal_error_cat", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "es", wiki_lang = "en", key = "versioning_module_internal_error_cat", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "en", wiki_lang = "x-y-z", key = "versioning_module_internal_error_cat", },
			{ typ = "cat", funcname = "add_cat",	 user_lang = "x-y-z", wiki_lang = "en", key = "versioning_module_internal_error_cat", },
		},
		headers = "events_test_categ_Test_headers",
	--	headers = "Test type;user language;wiki language;Categories",
	}
	modes.catView = ":"
	local cat_test = {}
--	cat_test.memo, cat_test.events = events.init_configs(cat_test, tabOptions, add_cat_tests) -- Init a new events group
	function tabView.tableView_form_one_case(test) -- For each case: ac_opt = testOptions.testGroup[i]
		if test.funcname == "add_cat" then
			test.catext = events.form(test) --, luaTable.toList(test.vals) )
			return { (test.funcname or "-"), (test.user_lang or "-"), (test.wiki_lang or "-"), (test.catext or "-") }
		elseif test.funcname == "catGroup" then
			test.catext = events.catGroup(test.key) --, luaTable.toList(test.vals) )
			return { (test.funcname or "-"), (test.user_lang or "-"), (test.wiki_lang or "-"), (test.catext or "-") }
		end
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function events.categ_Test(t)

function events.all_kinds_test(t) -- Test: all kinds of events (err, wng, cat)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* Test the function <b>add_cat</b> : " .. viewer.ta("user_lang", translate.user_lang) .. viewer.ta("wiki_lang", translate.wiki_lang)
	local tabView = {
		testGroup = { -- events_all_kinds_tests_title
			{	typ = "err",	 key= "mathroman_char_X_in_N_err", v = { [1] = "Y", [2] = 5 },
				name = "mathroman_roman2int", wkt = "character Y in 5", },
			{	typ = "cat", key= "versioning_module_internal_error_cat",
				name = "math_ro_man", wkt = "Module with internal error", },
			{	typ = "err", key= "modes_is_undefined_err", v = { [1] = "birthyear", [2] = 1989 },
				name = "xyz", wkt = "The argument birth:<b>1789</b> is not defined.", },
			{	typ = "XXXXX", key= "tools_strange_XXXXX", v = { [1] = "blabla", },
				name = "xyz", wkt = "The argument birth:<b>1789</b> is not defined.", },
			{	typ = "wng", key= "translate_user_wiki_lang_msg", v = { [1] = "Spanish", [2] = "French" },
				name = "versioning_module_internal_error_cat", wkt = "Languages: user: Polish, wiki: Greek.", },
			{	typ = "cat", key= "modes_language_cat", v = { [1] = "Spanish", },
			wkt = "Speaking Spanish", },
		},
		headers = "typ;key;idargs;result",
	}
	function tabView.tableView_form_one_case(evt) -- Generally to define: Convert a case from testGroup to rowGroup.
		local res, evt = events.form(evt)
		return { evt.typ or "t", evt.key or "k", evt.idargs or "i", evt.res or "r" }
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function events.all_kinds_test(t)

function events.selectLang( lang ) -- Select a language for tests or other purpose.
	if type(lang) ~= "string" then lang = "user_lang" end -- default is user language
	-- Select between : user_lang wiki_lang tests en es fr or any available in i18n.
	if lang == "tests" then lang = "tests"
	elseif translate.main_i18n and translate.main_i18n[lang] then lang = lang -- available in i18n
	elseif lang == "user_lang" then lang = translate.user_lang
	elseif lang == "wiki_lang" then lang = translate.wiki_lang
	else lang = translate.user_lang end -- default is user language
	return lang
end -- function events.selectLang( lang )

function events.selectLang_test(t) -- Tests: Select a language for tests or other purpose.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* Test <b>events.selectLang()</b> :" -- events_selectLang_tests_title
	local tabView = {
		testGroup = { -- events.testGroup
			{ wiki_lang = "en", user_lang = "fr",	sought = "wiki_lang", },
			{ wiki_lang = "en", user_lang = "es",	sought = "user_lang", },
			{ wiki_lang = "en", user_lang = nil ,	sought = "wiki_lang", },
			{ wiki_lang = nil , user_lang = "es",	sought = "user_lang", },
			{ wiki_lang = "fr", user_lang = "es",	sought = "tests", },
		},
		headers = "events_selectLang_test_headers"
	--	headers = "Wiki language; User language; Sought language; Selected language; Example",
	}
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		local selected = events.selectLang( lang )
		local example = viewer.form9user("events_selectLang_test_example")
		if selected == "tests" then example = translate.formTestCase("selectLang", case.wiki_lang, case.user_lang, case.sought) end
		return { tostring(case.wiki_lang), tostring(case.user_lang), tostring(case.sought), tostring(selected), tostring(example), }
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- events.selectLang_test( lang )

function events.tableView_form_all_cases(testOptions) -- Run tableView_form_one_case() for all cases in testGroup{}. Can reject some cases as nil.
	if type(testOptions) ~= "table" then
		testOptions = { -- options for viewer.tableView() -- Form a table with lines and columns.
			headers = "typ;key;idargs;result",
			kind = "projects", -- mediawiki or projects
			typ = "err",
			testGroup = {}, -- tests or testOptions.testGroup or
			rowGroup = {}, -- tests or testOptions.testGroup or
		}
	end
	if type(testOptions.testGroup) ~= "table" then
		testOptions.testGroup = { -- cases for tests
		{ typ = "err",	key = "events_err", idargs = "arg1", },
		{ typ = "wng",	key = "events_wng", idargs = "arg1", },
		{ typ = "cat",	key = "events_cat", idargs = "arg1", },
		}
	end

	function test1(evt) -- For each case: ac_opt = testOptions.testGroup[i]
		local res, evt = events.form(evt)
	--	evt.res, evt = events.form(evt)
		return { evt.typ or "t", evt.key or "k", evt.idargs or "i", evt.res or "r" }
	end
	if type(testOptions.tableView_form_one_case) ~= "function" then
		testOptions.tableView_form_one_case = test1
	end
	testOptions.rowGroup = testOptions.rowGroup or {}
	local j = 0
	for i, case_opt in ipairs(testOptions.testGroup) do
		local case_result = testOptions.tableView_form_one_case(case_opt) -- A case can be nil and rejected
		if case_result then
			j = j + 1
			table.insert( testOptions.rowGroup, case_result )
		end
	--	table.insert( testOptions.rowGroup, testOptions.tableView_form_one_case(case_opt) )
	end
	return testOptions.rowGroup, j
end -- function events.tableView_form_all_cases(testOptions)

events.errors_list = {} -- Deprecated: Table to collect errors and warnings
events.erron = true -- Activated or not errors. Errores activado o no. Erreurs activées ou non.
events.categories_list = {} -- Table to collect all categories to generate in wikitext
modes.catView = "" -- = ":" to document a category rather than truly categorize it

function events.error_add(typ, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9) -- Add a warning, error or category to le lists.
	--	events.add_err("modes_value_re_defined_err", k, key, xyz)
	if not events.erron then return "" end
	local wng = { ["typ"] = typ, ["ref"] = ref, ["v1"] = v1, ["v2"] = v2, ["v3"] = v3, ["v4"] = v4, ["v5"] = v5, ["v6"] = v6, ["v7"] = v7, ["v8"] = v8, ["v9"] = v9 }
	local str = viewer.form9user(wng.ref, wng.v1, wng.v2, wng.v3, wng.v4, wng.v5, wng.v6, wng.v7, wng.v8, wng.v9)
	local do_it = true
	for k, reg in ipairs(events.errors_list) do -- If the new error was previously registered, do not add it.
		local reg_txt = viewer.form9user(reg.ref, reg.v1, reg.v2, reg.v3, reg.v4, reg.v5, reg.v6, reg.v7, reg.v8, reg.v9)
		if str == reg_txt then do_it = false end
	end
	if do_it then table.insert(events.errors_list, wng) end
	local res = ""
	if wng.typ == "wng" then
		res = '<br>⦁ ' .. viewer.warningColor(str)
	end
	if wng.typ == "err" then
		res = '<br>⦁ ' .. viewer.errorColor(str)
	end
	return tostring(res)
end -- function events.error_add(typ, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)

function events.add_err(key, v1, v2, v3, v4, v5, v6, v7, v8, v9)
--	return events.error_add("err", ref, v1, v2, v3, v4, v5, v6, v7, v8, v9) .. ";"
--	return viewer.form9user(key, v1, v2, v3, v4, v5, v6, v7, v8, v9)
--	events.error_add("err", ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
--	events.add_err(key, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	return events.error_add("err", v1, v2, v3, v4, v5, v6, v7, v8, v9)
end

function events.add_wng(key, v1, v2, v3, v4, v5, v6, v7, v8, v9)
--	return viewer.form9user(key, v1, v2, v3, v4, v5, v6, v7, v8, v9)
--	events.error_add("wng", ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
--	events.add_err(key, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	return events.error_add("wng", v1, v2, v3, v4, v5, v6, v7, v8, v9)
end

function events.errors_lister(title, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	local res, msgref = "", ""
	local mwtitle = mw.title.getCurrentTitle()
	local page = tostring(mwtitle.nsText) .. ":" .. tostring(mwtitle.text)
	if type(title) ~= "string" then title = "modes_error_list_header_err" end
	res = res .. '\n*' .. viewer.form9user(title, page, v1, v2, v3, v4, v5, v6, v7, v8, v9) -- .. ' - ' .. page
	local n = 0
	for k, wng in ipairs(events.errors_list) do
		msgref = viewer.form9user(wng.ref, wng.v1, wng.v2, wng.v3, wng.v4, wng.v5, wng.v6, wng.v7, wng.v8, wng.v9) -- texte traduit ou direct
		if wng.typ == "wng" then
			res = res .. '<br>⦁ ' .. viewer.warningColor(msgref)
		end
	end
	for k, wng in ipairs(events.errors_list) do
		msgref = viewer.form9user(wng.ref, wng.v1, wng.v2, wng.v3, wng.v4, wng.v5, wng.v6, wng.v7, wng.v8, wng.v9) -- texte traduit ou direct
		if wng.typ == "err" then
			res = res .. '<br>⦁ ' .. viewer.errorColor(msgref)
			n = n + 1
		end
	end
	if n > 0 then
		events.add_cat("versioning_module_with_error_err")
	end
	return res
end -- function events.errors_lister(title, v1, v2, v3, v4, v5, v6, v7, v8, v9)

function events.gener_categories(args_final) -- Only for this library, form categories without activate them
	if type(args_final) ~= "table" then args_final = modes.args_final end
	if args_final.birthyear then
		events.add_cat("p_authors_birthyear_cat", args_final.birthyear)
	end
	if args_final.deathyear then
		events.add_cat("p_authors_deathyear_cat", args_final.deathyear)
	end
	if args_final.description then
	--	events.add_cat("p_authors_deathyear_cat", args_final.description)
	end
	return
end -- function events.gener_categories(args_final)

-- - - - ------------------ - - - - ---------------------------------
-- Manage categories. Administrar categorías. Gérer les catégories.
-- - - - ------------------ - - - - ---------------------------------

-- Record in categories_list and generate the wikitext of a category
function events.add_cat(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	-- add a category to the categories_list
	local cat = { ["typ"] = "cat", ["ref"] = ref, ["v1"] = v1, ["v2"] = v2, ["v3"] = v3, ["v4"] = v4, ["v5"] = v5, ["v6"] = v6, ["v7"] = v7, ["v8"] = v8, ["v9"] = v9 }
	local str = viewer.form9user(cat.ref, cat.v1, cat.v2, cat.v3, cat.v4, cat.v5, cat.v6, cat.v7, cat.v8, cat.v9)
	local do_it = true
	for k, reg in ipairs(events.categories_list) do -- If the new category was previously registered, do not add it.
		local reg_txt = viewer.form9user(reg.ref, reg.v1, reg.v2, reg.v3, reg.v4, reg.v5, reg.v6, reg.v7, reg.v8, reg.v9)
		if str == reg_txt then do_it = false end
	end
	if do_it then table.insert(events.categories_list, cat) end
	--
	local c = modes.catView or modes.catView or ""
--	if modes.catView == true or modes.catView == true then c = ":" else c = "" end
	local wiki_catspace = mw.site.namespaces.Category.name -- translate.wiki_translations.category or
	local user_catspace = mw.site.namespaces.Category.name -- translate.user_translations.category or
	local user_cat = translate.trans9vars(translate.user_translations, cat.ref, cat.v1, cat.v2, cat.v3, cat.v4, cat.v5, cat.v6, cat.v7, cat.v8, cat.v9)
	local wiki_cat = translate.trans9vars(translate.wiki_translations, cat.ref, cat.v1, cat.v2, cat.v3, cat.v4, cat.v5, cat.v6, cat.v7, cat.v8, cat.v9)
	--
	user_cat = (user_cat or "")
	local user_verif = string.match( user_cat, "([%w_])") -- verif content with all alphanumeric characters.
	if not user_verif then user_verif = ":" .. user_cat .. ":" end
--	user_cat = user_verif or ("<" .. user_cat .. ">")
	--
	user_catspace = "" -- without "Category" word
	local wiki_catspace = mw.site.namespaces.Category.name -- name: Local namespace name.
	local res = " [[" .. c .. wiki_catspace .. ":" .. wiki_cat ..  "|" .. user_catspace .. " " .. user_cat .. "]] "
	local link = " [[:" .. wiki_catspace .. ":" .. wiki_cat ..	"|" .. user_catspace .. " " .. user_cat .. "]] "
	return res, link
end -- function events.add_cat(ref, v1, v2, v3, v4, v5)

-- - - - ------------------ - - - - --------------------------------- tools

-- generate the wikitext of the list of categories to only display them
function events.categories_lister(c)
	local res = "" -- "\n* Catégories : "
	local wiki_cat = ""
	local user_cat = ""
	modes.options_to_catView()
	-- c can replace catView to enforce the documentation or the categorisation
	-- c peut remplacer catView pour forcer la documentation ou la catégorisation
	c = c or modes.catView or ""
	local user_catspace = mw.site.namespaces.Category.name -- Local namespace name.
	local wiki_catspace = mw.site.namespaces.Category.name -- Local namespace name.
	for k, cat in ipairs(events.categories_list) do
		user_cat = translate.trans9vars(translate.user_translations, cat.ref, cat.v1, cat.v2, cat.v3, cat.v4, cat.v5, cat.v6, cat.v7, cat.v8, cat.v9)
		wiki_cat = translate.trans9vars(translate.wiki_translations, cat.ref, cat.v1, cat.v2, cat.v3, cat.v4, cat.v5, cat.v6, cat.v7, cat.v8, cat.v9)
		user_catspace = "" -- without "Category" word
		user_cat = (user_cat or "") -- prize
		if user_cat == " " then user_cat = "" end
		if user_cat == "  " then user_cat = "" end
		if user_cat == "	" then user_cat = "" end
	--	local user_verif = string.match( user_cat, "([%w_])") -- verif content with all alphanumeric characters.
	--	user_verif = mw.ustring.gsub( user_cat, "(%s)", "") -- verif content with all alphanumeric characters.
		local user_verif = mw.ustring.gsub( user_cat, "%s", "") -- verif content with all alphanumeric characters.
		if not user_verif then user_verif = "1" .. user_cat .. "2" end
	--	user_cat = user_verif or ("<" .. user_cat .. ">")
		res = res .. " [[" .. c .. wiki_catspace .. ":" .. wiki_cat ..	"|" .. user_catspace .. user_cat .. "]] "
	end
	-- Category namespaces = 14 Category
	-- https://en.wikipedia.org/wiki/IETF_language_tag
	-- https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
	return res
end -- function events.categories_lister(c)


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:mathroman is here as an example of very small central library. It becomes central using the central system.
--	function mathroman.roman2int(rm, testcase) -- Convert a roman number to decimal
--	function mathroman.int2roman(i, testcase) -- Convert a integer number to roman
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- mathroman = {} -- already declared by Scribunto, else in _G space.
mathroman.i18n = {} -- translations tables known in the module
mathroman.i18n.en = {
	mathroman_errors_head_err				= "Error: ",
	mathroman_roman_err						= "Roman error: ",
	mathroman_value_error_err				= "Value error: ",
	mathroman_J_before_end_err				= "character J before the end",
	mathroman_char_X_in_N_err				= "character %1 in %2",
	mathroman_char_increase_err				= "3 increasing characters",
	mathroman_greater_4999_err				= "value > 4999",
	mathroman_null_value_err				= "null value",
	mathroman_rom2dig_value_err				= "roman value error",
	mathroman_dig2rom_value_err				= "digital value error",
	mathroman_roman2int_test_title			= "mathroman.roman2int() Test roman to digital numbers",
	mathroman_roman2int_test_headers		= "Roman number; Digital value; Correct; Error(s)",
	mathroman_int2roman_test_title			= "mathroman.int2roman() Test digital numbers to roman numbers",
	mathroman_int_is_not_integer_err		= "The not integer number <b>%1</b> is not convertible to Roman numeral",
	mathroman_int_is_not_number_err			= "The value <b>%1</b> is not a convertible number to Roman numeral",
	mathroman_is_not_string_err				= "The value <b>%1</b> is not a text convertible to number"
} -- mathroman.i18n.en
mathroman.i18n.es = {
	mathroman_errors_head_err				= "Error: ",
	mathroman_roman_err						= "Error de romano: ",
	mathroman_value_error_err				= "Error de valor: ",
	mathroman_J_before_end_err				= "carácter J antes del fin",
	mathroman_char_X_in_N_err				= "carácter 1% en %2",
	mathroman_char_increase_err				= "3 crecientes caracteres",
	mathroman_greater_4999_err				= "valor > 4999",
	mathroman_null_value_err				= "valor null",
	mathroman_rom2dig_value_err				= "romano valor error",
	mathroman_dig2rom_value_err				= "decimale valor error",
	mathroman_roman2int_test_title			= "mathroman.roman2int() Prueba de números romanos a números decimales",
	mathroman_roman2int_test_headers		= "Número romano; Valor digital; Corregido; Error(s)",
	mathroman_int2roman_test_title			= "mathroman.int2roman() Prueba de números decimales a números romanos",
	mathroman_int_is_not_integer_err		= "El no entero <b>%1</b> no se puede convertir en número romano",
	mathroman_int_is_not_number_err			= "El valor <b>%1</b> no es un número y no se puede convertir en número romano",
	mathroman_is_not_string_err				= "El valor <b>%1</b> no es un texto y no se puede convertir en número",
} -- mathroman.i18n.es
mathroman.i18n.fr = {
	mathroman_errors_head_err				= "Erreur : ",
	mathroman_roman_err						= "Erreur de romain : ",
	mathroman_value_error_err				= "Erreur de valeur : ",
	mathroman_J_before_end_err				= "caractère J avant la fin",
	mathroman_char_X_in_N_err				= "caractère %1 en %2",
	mathroman_char_increase_err				= "3 caractères croissants",
	mathroman_greater_4999_err				= "valeur > 4999",
	mathroman_null_value_err				= "valeur nulle",
	mathroman_rom2dig_value_err				= "erreur de valeur de romain",
	mathroman_dig2rom_value_err				= "erreur de valeur de décimal",
	mathroman_roman2int_test_title			= "mathroman.roman2int() Test des nombres romains en nombres décimaux",
	mathroman_roman2int_test_headers		= "Nombre romain; Valeur décimale; Correct; Erreur(s)",
	mathroman_int2roman_test_title			= "mathroman.int2roman() Test des nombres décimaux en nombres romains",
	mathroman_int_is_not_integer_err		= "Le nombre non entier <b>%1</b> n'est pas convertible en nombre romain",
	mathroman_int_is_not_number_err			= "La valeur <b>%1</b> n'est pas un nombre convertible en nombre romain",
	mathroman_is_not_string_err				= "La valeur <b>%1</b> n'est pas un texte convertible en nombre",
} -- mathroman.i18n.fr

--	vueRomains : XIJ=12 MCXI=1111 MCDXLIV=1444 MDCLXVI=1666 MCMXCIX=1999 MMCCXXII=2222 MMMMCMXCIX=4999 ERREURS=0 erreur caractere S en 7. XIA=11 erreur caractere A en 3. XJI=12 erreur caractere J avant la fin. IXC=89 erreur caracteres croissants. VLD=445 erreur caracteres croissants. MMMMM=5000 erreur > 4999. MMMMMYJXC=5089 erreur > 4999. erreur caractere Y en 6. erreur caractere J avant la fin.

function mathroman.pcall_int2roman(val, errs) -- DEBUG : mathroman.int2roman() can fail without blocking page.
--	EBUG: Without this patch the mathroman.int2roman() get the parser error "attempt to call field 'int2roman' ". Patched by Rical 2015-04-20.
--	local event_century_roman = ""
--	word, errx = mathroman.int2roman(val, errs)
	-- See Maniphest T27845 Support loading wiki pages through mediaWiki.loader.load()
	-- DEBUG code:
	local success, word, errs = pcall(mathroman.int2roman, val, errs) -- pcall or xpcall can run any function without blocking page.
	if success then return word, errs else return nil, errs end
end

function mathroman.pcall_roman2int(word, errs) -- DEBUG : mathroman.roman2int() can fail without blocking page.
--	DEBUG: Without this patch the mathroman.int2roman() get the parser error "attempt to call field 'roman2int' ". Patched by Rical 2015-04-20.
--	val, errs = mathroman.roman2int(word, errs)
	local success, val, errs = pcall(mathroman.roman2int, word, errs) -- pcall or xpcall can run any function without blocking page.
	if success then return val, errs else return nil, errs end
end

--	Romans view : XIJ=12 MCXI=1111 MCDXLIV=1444 MDCLXVI=1666 MCMXCIX=1999 MMCCXXII=2222 MMMMCMXCIX=4999 ERREURS=0 erreur caractere S en 7. XIA=11 erreur caractere A en 3. XJI=12 erreur caractere J avant la fin. IXC=89 erreur caracteres croissants. VLD=445 erreur caracteres croissants. MMMMM=5000 erreur > 4999. MMMMMYJXC=5089 erreur > 4999. erreur caractere Y en 6. erreur caractere J avant la fin.

function mathroman.roman2int(rm) -- Convert a roman number to integer
	local v = 0 -- valeur totale
	local v1 = 0 -- valeur de derniere lettre
	local v2 = 0 -- valeur de lettre precedente
	local v3 = 0 -- valeur de lettre precedente
	local x = '-' -- caractere en cours d'evaluation
	local i = 1 -- numero du caractere en cours d'evaluation
	local j = 0 -- numero du caractere de reference courant (debut en Lua)
	local k = 0 -- numero du caractere de reference courant (fin en Lua)
	local errs, errtab = "", {}
	if type(rm) ~= "string" then
	--	errs = errs .. events.add_err("mathroman_is_not_string_err", tostring(rm) )
		table.insert(errtab, tostring(events.add_err("mathroman_is_not_string_err", tostring(rm) ) ) )
		return 0, errs
	end
	if rm == "" then
		errs = errs .. events.add_err("mathroman_is_not_string_err", '""')
		table.insert(errtab, tostring(events.add_err("mathroman_is_not_string_err", '""') ) )
		return 0, errs
	end
	if type(rm) ~= "string" then rm = "-" end
	local lst = '-MDCLXVIJ' -- caracteres autorises
	x = string.sub(rm, i, i) or ''
	while (x ~= '') do
		v3 = v2
		v2 = v1
		v1 = 0
	--	x = string.gsub(rm, i, i)
		if ( x == 'M' ) then v1 = 1000 end
		if ( x == 'D' ) then v1 = 500 end
		if ( x == 'C' ) then v1 = 100 end
		if ( x == 'L' ) then v1 = 50 end
		if ( x == 'X' ) then v1 = 10 end
		if ( x == 'V' ) then v1 = 5 end
		if ( x == 'I' ) then v1 = 1 end
		if ( x == 'J' ) then v1 = 1 end
		if ( x == 'J' ) and ( i < string.len(rm) ) then
			errs = errs .. events.add_err("mathroman_J_before_end_err") -- e4 = 'character J before the end'
			table.insert(errtab, tostring(events.add_err("mathroman_J_before_end_err") ) ) -- e4 = 'character J before the end'
		end
		if ( v1 == 0 ) then
			errs = errs .. events.add_err("mathroman_char_X_in_N_err", x, i) -- e3 = "character K in 3"
			table.insert(errtab, tostring(events.add_err("mathroman_char_X_in_N_err", x, i) ) )
		end
		v = v + v1
		if ( (v1 == 5*v2) or (v1 == 10*v2) ) then v = v - (2*v2) end -- adjust 4, 9, 40, 90 ...
		j, k = string.find(lst, x)
		if ( j == nil ) then j = -1 end
		if ( k == nil ) then k = -1 end
		if (v1 > v2) and (v2 > v3) and (v3 > 0) then
			errs = errs .. events.add_err("mathroman_char_increase_err") -- e2 = ' increasing chars.'
			table.insert(errtab, tostring(events.add_err("mathroman_char_increase_err") ) )
		end
		i = i + 1
		x = string.sub(rm, i, i) or ''
	end
--	if ( v == 0 ) then --	e0 = ' valeur nulle.'
	if ( v < 1 ) then --	e0 = ' valeur nulle.'
		errs = errs .. events.add_err("mathroman_null_value_err")
		table.insert(errtab, tostring(events.add_err("mathroman_null_value_err") ) )
	--	v = 1
	end
	if ( v > 4999 ) then -- e1 = ' valeur > 4999.'
		errs = errs .. events.add_err("mathroman_greater_4999_err")
		table.insert(errtab, tostring(events.add_err("mathroman_greater_4999_err") ) )
	--	v = 4999
	end
--	{ name = "mathroman.roman2int (4)', func = mathroman.roman2int, args = { "VI", }, expect = { 123 } },
--	errs = errs .. ":" .. tostring(v) .. ":" .. tostring(rm) .. ":" -- runOneTest
	errs = table.concat(errtab, " ; ")
	return v, errs -- with or without errors
end -- function mathroman.roman2int(rm)

function mathroman.romani2r(i, j)
	if ( j == nil ) then j = '' end
	local rm=''
	if ( i == 1000 ) then rm = 'M' end
	if ( i == 500 ) then rm = 'D' end
	if ( i == 100 ) then rm = 'C' end
	if ( i == 50 ) then rm = 'L' end
	if ( i == 10 ) then rm = 'X' end
	if ( i == 5 ) then rm = 'V' end
	if ( i == 1 ) then
		rm = 'I'
		if ( j == 'J' ) then  rm = 'J' end
	end
	return rm
end -- function mathroman.romani2r(i, j)

function mathroman.int2roman(int) -- Convert an integer to a roman number, also if int is a string
	local errs = "" -- local collection of errors
	local n = tonumber(int)
	--[ [
	if n then
		local floor = math.floor(n)
		if n ~= floor then
			errs = errs .. events.add_err("mathroman_int_is_not_integer_err", tostring(int) )
			roman = ""
			return roman, errs -- with error
		end
	elseif int == "" then
		errs = errs .. events.add_err("mathroman_is_not_string_err", '""' )
		return "", errs
	else
		errs = errs .. events.add_err("mathroman_int_is_not_number_err", tostring(int) )
		roman = ""
		return roman, errs -- with error
	end
	--] ]
	if type(n) ~= "number" then n = 0 end
	n = math.floor(n)
	n = n or 0 -- delete after debug
	local v100 = 100
	local v500 = v100*5
	local v1000 = v100*10 -- roman cycle romain 1000, 100, 10, 1
	local v, v1, v2, v3 = 0, 0, 0, 0 -- Total value, last, and previous. Valeur totale, derniere, et precedentes.
	local reste, reduction = 0, 0 -- Rest to convert, last reduction. Reste a convertir, derniere reduction.
	local rm = ''
	local roman = '' -- chiffre et nombre romain resultant
	reste = n
	if ( n > 4999 ) then
		errs = errs .. events.add_err("mathroman_greater_4999_err") -- e1 = ' valeur > 4999.'
		reste = 0
		roman = "" -- 'ERROR'
	end
	if ( n < 1 ) then
	--	n = 0
		errs = errs .. events.add_err("mathroman_null_value_err") -- e2 = ' valeur < 1.'
		reste = 0
		roman = "" -- 'ERROR'
	end
	while (reste > 0) do
		v3 = v2
		v2 = v1
		v1 = reste
		reduction = 0
		if ( reste >= v1000 ) then
			reduction = v1000
		elseif ( reste >= v100*9 ) then
			reduction = v100
			reste = reste + v100*2
		elseif ( reste >= v500 ) then
			reduction = v500
		elseif ( reste >= v100*4 ) then
			reduction = v100
			reste = reste + v100*2
		elseif ( reste >= v100 ) then
			reduction = v100
		elseif ( reste >= 1000 ) then
			v100 = 100
			v1000 = 1000
			reduction = v1000
		end
		rm = mathroman.romani2r(reduction)
		roman = roman .. rm
		reste = reste - reduction
		if ( reste < v100 ) then
			if ( v100 >= 10 ) then
			v100 = v100/10
			v500 = v100*5
			v1000 = v100*10
			end
		end
	end
--	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { int = 444 }, expect = { "CDXLIV" } },
--	errs = errs .. ":" .. tostring(roman) .. ":" .. tostring(int) .. ":" -- runOneTest
	return roman, errs -- with or without errors
end -- function mathroman.int2roman(int)

function mathroman.roman2int_test(t) -- Tests of main central modules
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* " .. viewer.form9user("mathroman_roman2int_test_title")
	t = t .. "<br>" .. luaTable.formSubCounts("mathroman.i18n")
	local tabView = {
		testGroup = {
			{ 123, },
			{ 2.78, },
			{ "-X", },
			{ "", },
			{ "0", },
			{ "MCXI", },
			{ "XIJ", },
			{ "XJI", },
			{ "XIA", },
			{ "VLD", },
			{ "IXC", },
			{ "MMMMCMXCIX", },
			{ "MMMMM", },
			{ "MMMMMYJXC", },
			{ {x}, },
			{ function()end, },
		},
		headers = "mathroman_roman2int_test_headers", -- mathroman_roman2int_test_title
--	mathroman_roman2int_test_headers		= "Roman number; Digital value; Correct; Error(s)",
--	mathroman_roman2int_test_headers		= "Número romano; Valor digital; Corregido; Error(s)",
--	mathroman_roman2int_test_headers		= "Nombre romain; Valeur décimale; Correct; Erreur(s)",
	}
	tabView.t = (tabView.t or "") .. viewer.ta("roman2int_test: ", "start")
	tabView.t = tabView.t .. viewer.ta("#testGroup: ", #tabView.testGroup)
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		local word = case[1] -- DEBUG : mathroman.roman2int() can fail without blocking page.
		local valX, errs = mathroman.roman2int(word) -- DEBUG : mathroman.roman2int() can fail without blocking page.
		local wordX, errsX = mathroman.int2roman(valX) -- DEBUG : mathroman.roman2int() can fail without blocking page.
--		local case = { word, valX, wordX, errs .. " " .. errsX, }
		return { word, valX, wordX, errs .. " " .. errsX, }
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
--	t = t .. tabView.t -- internal track to debug
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function mathroman.roman2int_test(t)

function mathroman.int2roman_test(t, val) -- Unitary tests of mathroman.int2roman
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "int2roman_test"
	t = "\n:*<b>" .. t .. "</b>:"
	t = t .. "<br>" .. luaTable.formSubCounts("mathroman.i18n")
	t = t .. viewer.Th() .. viewer.Tc("Digital value") .. viewer.Tc("Roman number") .. viewer.Tc("Error")
	local function int2roman_test1( t, val)
	--	local word, errs = mathroman.pcall_int2roman(val, " ") -- DEBUG : mathroman.int2roman() can fail without blocking page.
		local word, errs = mathroman.int2roman(val, " ") -- DEBUG : mathroman.int2roman() can fail without blocking page.
		t = (t or "") .. viewer.Tr() .. viewer.Td(val) .. viewer.Td(word or "") .. viewer.Td(errs or "")
		return t
	end
	t = int2roman_test1( t, -10)
	t = int2roman_test1( t, 3.14)
	t = int2roman_test1( t, "not-a-number" )
	t = int2roman_test1( t, "")
	t = int2roman_test1( t, 0)
	t = int2roman_test1( t, 12)
	t = int2roman_test1( t, 17)
	t = int2roman_test1( t, "18")
	t = int2roman_test1( t, "19")
	t = int2roman_test1( t, 111)
	t = int2roman_test1( t, 444)
	t = int2roman_test1( t, 555)
	t = int2roman_test1( t, "777")
	t = int2roman_test1( t, "1111")
	t = int2roman_test1( t, "4999")
	t = int2roman_test1( t, "5000")
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function mathroman.int2roman_test(t, val)

--	vueRomains : XIJ=12 MCXI=1111 MCDXLIV=1444 MDCLXVI=1666 MCMXCIX=1999 MMCCXXII=2222 MMMMCMXCIX=4999 ERREURS=0 erreur caractere S en 7. XIA=11 erreur caractere A en 3. XJI=12 erreur caractere J avant la fin. IXC=89 erreur caracteres croissants. VLD=445 erreur caracteres croissants. MMMMM=5000 erreur > 4999. MMMMMYJXC=5089 erreur > 4999. erreur caractere Y en 6. erreur caractere J avant la fin.
--	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", group = mathroman.TestsCasesGroup, },
--	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 1 }, expect = { "I" } },

mathroman.TestsCasesGroup = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- each test_case defines a name, a function, an input, an output. See also viewer.strTestCase. 8 cases
	--[[
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 1 }, expect = { "I" }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 3 }, expect = { "III" }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 6 }, expect = { "VIII" }, },
	--
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "IV", }, expect = { 4 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "V", }, expect = { 5 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "III", }, expect = { 6 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "VI", }, expect = { 6 }, },
	--]]
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "XXII" }, expect = { 22 }, },
}

mathroman.Tests_cases = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- each test_case defines a name, a function, an input, an output. See also viewer.strTestCase. 5 cases
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 12, }, expect = { "XII" }, },
	--[[
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 17, }, expect = { "II" }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 3, }, expect = { "III" }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 4, }, expect = { "IX" }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 5, }, expect = { "V" }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 6, }, expect = { "VI" }, },
	--
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "I" }, expect = { 1 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "II" }, expect = { 2 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "III", }, expect = { 6 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "VI", }, expect = { 123 }, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "V", }, expect = { 1 }, },
	--]]
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "VIA", }, expect = { 6, "mathroman_char_X_in_N_err-A-3" }, },
}

mathroman.int2romanTests = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- each test_case defines a name, a function, an input, an output. See also viewer.strTestCase. 8 - 1 = 7 + 8 = 15 cases
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { "19" }, expect = { "XII" } },
	-- diffs = mathroman_char_increase_err;mathroman_char_increase_err;mathroman_char_X_in_N_err-0-1; mathroman_char_X_in_N_err---1;mathroman_char_X_in_N_err-A-3;mathroman_char_X_in_N_err-Y-6; mathroman_char_X_in_N_err-Y-6nil;mathroman_greater_4999_err;mathroman_J_before_end_err;mathroman_J_before_end_errnil; mathroman_null_value_err;testsCases_subDiffs_string_error-mathroman_J_before_end_err-XJI-nil, errors =
	--[[
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 12 }, expect = { "XII" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 17 }, expect = { "XVII" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 18 }, expect = { "XVIII" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 111 }, expect = { "CXI" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 444 }, expect = { "CDXLIV" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 555 }, expect = { "DLV" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 777 }, expect = { "DCCLXXVII" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 1111 }, expect = { "MCXI" } },
	--]]
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 4999 }, expect = { "MMMMCMXCIX" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 5000 }, expect = { "0", "mathroman_greater_4999_err-0" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { -10 }, expect = { "X", "mathroman_null_value_err" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 0 }, expect = { "0", "mathroman_null_value_err" } },
	--[[
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { 3.14 }, expect = { "3.3.3" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "int2roman", func = mathroman.int2roman, args = { "not-a-number" }, expect = { "xxx" } },
	--
	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.TestsCasesGroup,
		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.TestsCasesGroup", }, --
	--]]
}
mathroman.roman2intTests = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- each test_case defines a name, a function, an input, an output. See also viewer.strTestCase. 6 cases
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "-X" }, expect = { 10, "mathroman_char_X_in_N_err---1-" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "0" }, expect = { 0, "mathroman_char_X_in_N_err-0-1-" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "XIA" }, expect = { 11, "mathroman_char_X_in_N_err-A" } },
	--[[
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "" }, expect = { 0 } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MCXI" }, expect = { 1111 } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "XIJ" }, expect = { 12 } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "XJI" }, expect = { 12, "X Y Z" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "XJI" }, expect = { 12, "mathroman_char_increase_err" } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "VLD" }, expect = { 445 } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "IXC" }, expect = { 89 } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MMMMCMXCIX" }, expect = { 4999 } },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MMMMM" }, expect = { 5000, "mathroman_J_before_end_err-XJI" } },
	--]]
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MMMMMYJXC" }, expect = { 5089, ";mathroman_J_before_end_err;mathroman_char_increase_err;mathroman_char_X_in_N_err-Y-6;mathroman_greater_4999_err;" } },
}
mathroman.testsRecursive = { -- Autotest cases to validate the mathroman library at mediawiki level.
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "XXIJ" }, expect = { 22 } },
--	{ errorsKey = 2, modulename = "mathroman", funcname = 'recurse', group = mathroman.testsRecursive, },
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MCXI" }, expect = { 1111 } },
--	{ errorsKey = 2, modulename = "mathroman", funcname = 'recurse', group = mathroman.testsRecursive, },
}

mathroman.testsGroups = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- Each test_case defines a name, a function, an input, an output. Else a sub-group of testsCases.
	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.Tests_cases,
		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.Tests_cases", }, --
	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.int2romanTests,
		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.int2romanTests", }, --
	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.roman2intTests,
		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.roman2intTests", }, --
--	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.testsRecursive, -- recursive until recursiveLimit
--		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.testsRecursive", },
}

-- Define ClassCentralMathromanTests.lua

--	mathroman.count = 0 -- generic testcase for mathroman.tests_cases

function mathroman.provide(n, test_name, okString) -- see Extension:Scribunto/Lua reference manual:
	-- Function that returns three values: n, the name of test n, and a string that is the expected output for test n.
	return n, test_name, okString
end

function mathroman.run( n, testcase ) -- Function that runs test n and returns one string.
	return testsCases.run_testcase( n, testcase )
end

function mathroman.getEnv( func, name, recursiveLevel, opt ) -- Get the environment of the module or library
--	local fenv = getfenv() -- Not functional because not activated in fr.wikisource.org.
	local t = luaTable.formSubCounts("_G", { recursiveLimit = 2 }, 1 ) -- rougly equivalent to getfenv()
	return t
end


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:modes support modes and their options, like p.read(frame).
--	count = number of tests; provide( n ) returns three values: n, and expected output; run( n ) returns one string.
--	install by versioning.bind_one_library(modes, library, options)
--	see Extension:Scribunto/Lua reference manual : class nameTest extends Scribunto_LuaEngineTestBase
--	This library support modules to:
--	* Define their modes, used with frame: p.read(frame)
--	* Default modes are read, edit, doc or tests
--	* And their options
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- modes = {} -- already declared by Scribunto, else in _G space.
-- Translations for modes library
modes.i18n = {} -- 12 modes.i18n.
modes.i18n.en = { -- 182 <tab>mode_...
	-- Names and descriptions of configurations arguments
	[1]								= "1",
	[2]								= "2",
	[3]								= "3",
	[4]								= "4",
	label							= 'label',
	label_descr						= "Automatic Wikidata argument.",
	sitelink						= 'sitelink',
	sitelink_descr					= "Automatic Wikidata argument.",
	userlang						= "userlang",
	userlang_descr					= "Language of the user or the helper.",
	wikilang						= "wikilang",
	wikilang_descr					= "Language of the wiki.",
	itemid							= 'itemid',
	itemid_descr					= "Identifier of Wikidata data, such as <code>Q535</code> for Victor Hugo.",
	itemid2							= 'id',
	itemid2_descr					= "Identifier of Wikidata data, such as <code>Q535</code> for Victor Hugo.",
	debug							= 'debug',
	category						= 'Category',
	mode							= "mode",
	mode_descr						= "Type of use of the module or template: read, edit, document, test.",
	options							= "options",
	options_descr					= "Display options of a module or a model.",
	c								= "c",
	c_descr							= "Display options of a module or a model.",
	knownversions					= "knownversions",
	knownversions_descr				= "Known versions, to manage them.",
	soughtversions					= "soughtversions",
	soughtversions_descr			= "Sought versions, to manage them.",
	-- Main texts, errors and categories of tools
	-- Versions management
	-- Titles of tests
	modes_list_all_args_main_title		= "modes.list_all_args_main() List of all accepted arguments",
	modes_used_options_title			= "modes.used_options_list() Options and their uses:",
	modes_brief_options_mode_title		= "modes.options_from_mode_tests() Modes and options in brief",
	modes_options_from_args_test_title	= "modes.options_from_args_test() Test options from modes",
	modes_options_from_args_title		= "Test options from arguments",
	-- Groups of arguments
	modes_needed_to_verify				= "(required, to be checked)",
	modes_list_needed_args				= "List of needed arguments:",
	modes_list_all_config_arguments		= "List of all config arguments:",
	modes_list_all_system_arguments		= "List of all system arguments:",
	modes_list_all_other_args			= "List of all other arguments:",
	-- Main texts, errors and categories of tools
	modes_language_cat					= 'Speaking %1',
	modes_date_months_names				= "January, February, March, April, May, June, July, August, September, October, November, December",
	modes_date_to_part_format			= "dd yyyy mmmm",
	modes_date_to_part_call_err			= "Internal Error: Abnormal calling arguments in date <b>%1</b>.",
	modes_date_to_part_call_cat			= "Module with internal error",
	modes_date_to_part_not_found_err	= "Internal Error: No part found in date <b>%1</b>.",
	-- Arguments management
	modes_error_list_header_err			= "User support about parameters:",
	modes_need_arg_value_err			= "Error: This argument is required but absent : <b>%1</b>. Should define it.",
	modes_none_value_err				= "Error: No argument has been defined.",
	modes_unknown_argument_err			= "Error: parameter <b>%1</b> is unknown in this template. Check the name or flag this gap.",
	modes_too_unnamed_arguments_err		= "Error: This unnamed argument is too many: <b>%1</b> = <b>%2</b>.",
	modes_without_translation_wsid_err	= "Internal Error: Notify the developer that the internal argument <b>%1</b> is unknown in the records.",
	modes_is_defined_err				= "The argument %1:<b>%2</b> is defined.",
	modes_is_undefined_err				= "The argument %1:<b>%2</b> is not defined.",
	modes_args_values_err				= "Abnormal value of the argument <b>%1 = %2</b> including (%3) ",
	modes_nearest_argument_err			= "Error: Do you need the known argument <b>%1</b> ?",
	modes_max_nearest_argument_msg		= "A longer name argument accepts more letter errors.",
	modes_value_re_defined_err			= "Error: The value of the argument <b>%1</b> is already defined. Choose only one value of a single synonymous.",
	modes_lang_table_err				= "Error: The <b>%1</b> language or its table is incorrect.",
--	modes_lang_table_err_cat			= "Module avec langue d'arguments erronée",
	modes_lang_table_err_cat			= "Module avec erreur interne", -- modes_lang_table_err_cat
	modes_lang_not_translated_err		= "Error: The language <b>%1</b> is not translated.",
	modes_generDoc1_paramName_err		= "Internal Error: in generDoc1, bad argument <b>%1</b>.",
	--
	modes_unknown_auto_arg_err			= "Internal Error: Unknown automatic argument: %1 = <b>%2</b>.",
	modes_delete_docbox_wng				= "You must remove this documentation before recording.<br>Remove all modes to return to read mode.",
	modes_auto_val_warning_wng			= "Verify the automatic argument: %1 = <b>%2</b>.",
	--
	modes_assist_user_param_err			= "User support for checking the settings:",
	modes_no_known_arguments_err		= "Module without known arguments table.",
	modes_args_values_err				= "Abnormal value of the argument <b>%1 = %2</b> (%3)",
	--
	modes_no_known_arguments_err		= "Internal Error: Module without known arguments table.",
--	modes_no_known_args_cat				= "Module without known arguments table.",
	modes_no_known_args_cat				= "Module with internal error",	 -- versioning_module_internal_error_cat
	modes_no_source_arguments_err		= "Internal Error: Module without source arguments table.",
--	modes_no_source_arguments_cat		= "Module without source arguments table.",
	modes_no_source_arguments_cat		= "Module with internal error",	 -- versioning_module_internal_error_cat
	modes_list_all_args_main_title		= "modes.list_all_args_main() List of all accepted arguments",
	modes_similar_levenshtein_tests_title		= "modes.levenshtein() Test the distances between words <b>[https://en.wikipedia.org/wiki/Levenshtein_distance Levenshtein]</b>:",
	modes_args_known_structure_title	= "modes.args_known_structure() Arguments definitions",
	modes_similar_args_searchs_title	= "modes.similar_args_search() Test close similar words:",
	modes_normal_box_main_text_1		= "Simple infobox: page title: <b>%1</b>, description: <b>%2</b>",
	modes_normal_box_main_text_2		= "<br/>He was <b>%1</b>, named <b>%2</b> <b>%3</b> was dead in <b>%4</b>.",

	-- These texts are used to title some tests.
	modes_list_wiki_selectors_title		= "List of selectors of this module:",
	modes_all_categories_list_title		= "modes.all_categories_list() List of eventual categories of this module:",
	modes_all_errors_list_title			= "modes.all_errors_list() List of detectable errors in this module:",
	modes_all_G_list_var_tests_title	= "modes.all_G_list_var_test() List of variables in _G global space",
	modes_multiple_values_tests_title	= "modes.multiple_values() Test of multiple values arguments",
	modes_multiple_selection_tests_title= "modes.multiple_selection() Multiple selection test",
	modes_multiple_selection_test_headers= "Options;Selector;To selector;Selected",
	modes_multiple_selection_test_select= "2;nobel;president;deputy;price",
	modes_recursiveLevel_err			= "Recursive level error <b>%1 > %2</b>",
	modes_recursiveNormal_tests_title	= "modes.recursiveNormal() Test: Normalize the recursive level and the recursive limit",
	modes_spaces_page_names_title		= "modes.spacesPageNamesTest() Module, namespaces, and page names:",
} -- modes.i18n.en

modes.i18n.es = {
	-- Nombres y descripciones de argumentos de configuraciones
	[1]								= "1",
	[2]								= "2",
	[3]								= "3",
	[4]								= "4",
	label							= 'label',
	label_descr						= "Wikidata automática argumento.",
	sitelink						= 'sitelink',
	sitelink_descr					= "Wikidata automática argumento.",
	userlang						= "userlang",
	userlang_descr					= "Idioma del usuario o del encargado.",
	wikilang						= "wikilang",
	wikilang_descr					= "Idioma del wiki.",
	itemid							= 'itemid',
	itemid_descr					= "Nombre de los datos Wikidata, como <code>Q535</code> para Victor Hugo.",
	itemid2							= 'id',
	itemid2_descr					= "Nombre de los datos Wikidata, como <code>Q535</code> para Victor Hugo.",
	debug							= 'debug',
	category						= 'Categoría',
	mode							= "modo",
	mode_descr						= "Tipo de uso del módulo o modelo: leer, editar, documentar, probar.",
	options							= 'opciones',
	options_descr					= "Opciones de visualización de un módulo o un modelo.",
	c								= "c",
	c_descr							= "Opciones de visualización de un módulo o un modelo.",
	knownversions					= "knownversions",
	knownversions_descr				= "Conocidas versiones, para manejarlos.",
	soughtversions					= "soughtversions",
	soughtversions_descr			= "Versiones demanda, para manejarlos.",
	--
	events_test_OK_cat					= "Test category generation OK",
	-- Textos principales, errores y categorías de instrumentos
	events_test_OK_cat					= "Prueba generación categoría en OK",
	events_test_OK_cat					= "Test de génération de catégorie OK",
	--
	-- Titres des pruebas
	modes_used_options_title			= "modes.used_options_list() Opciones y sus usos:",
	modes_brief_options_mode_title		= "modes.options_from_mode_tests() Modos y opciones en brief",
	modes_options_from_args_test_title	= "modes.options_from_args_test() Prueba de opciones de modos",
	modes_options_from_args_title		= "Prueba de opciones de argumentos",
	-- Grupos de argumentos
	modes_needed_to_verify				= "(obligatorio, se debe comprobar)",
	modes_list_needed_args				= "Lista de argumentos necesarios:",
	modes_list_all_config_arguments		= "Lista de argumentos de configuración:",
	modes_list_all_system_arguments		= "Lista de argumentos del sistema:",
	modes_list_all_other_args			= "Lista de los otros argumentos:",
	-- Textos principales, errores y categorías de instrumentos
	modes_language_cat					= 'Hablando %1',
	modes_date_months_names				= "Enero, Febrero, Marzo, Abril, Mayo, Junio​​, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre",
	modes_date_to_part_format			= "dd mmmm yyyy",
	modes_date_to_part_call_err			= "Error interno: argumentos de llamadas anómala en fecha <b>%1</b>.",
	modes_date_to_part_call_cat			= "Módulo con error interno",
	modes_date_to_part_not_found_err	= "Error interno: No se encuentra en fecha <b>%1</b>.",
	-- Control de argumentos
	modes_error_list_header_err			= "Asistencia de los parámetros de este modelo:",
	modes_need_arg_value_err			= "Error: Usted tiene que fijar este argumento necesario, pero falta: <b>%1</b>.",
	modes_none_value_err				= "Error: No hay argumento se ha definido.",
	modes_unknown_argument_err			= "Error: El parámetro <b>%1</b> es desconocido en este modelo. Compruebe el nombre o reportar esta falta.",
	modes_too_unnamed_arguments_err		= "Error: Demasiado argumento sin nombre: <b>%1</b> = <b>%2</b>.",
	modes_without_translation_wsid_err	= "Error interno: Informar al el desarrollador que el argumento interno <b>%1</b> es desconocido en los registros.",
	modes_is_defined_err				= "El argumento %1:<b>%2</b> está definido.",
	modes_is_undefined_err				= "El argumento %1:<b>%2</b> no está definido.",
	modes_args_values_err				= "Valor anormal del argumento <b>%1 = %2</b> entre: (%3) ",
	modes_nearest_argument_err			= "Error : ¿Es usted conocido argumento <b>%1</b> ?",
	modes_max_nearest_argument_msg		= "Un argumento nombre más largo acepta más letras errores.",
	modes_value_re_defined_err			= "Error: El valor del argumento <b>%1</b> ya está definido. Elija sólo un valor de un solo sinónimo",
	modes_lang_table_err				= "Error: La <b>1%</b> idioma o su tabla es incorrecta.",
	modes_lang_table_err_cat			= "Módulo con lenguaje de argumentos erróneos",	 -- versioning_module_internal_error_cat
	modes_lang_table_err_cat			= "Módulo con error interno",	 -- modes_lang_table_err_cat
	modes_lang_not_translated_err		= "Error: El lenguaje <b>%1</b> no se traduce.",
	modes_generDoc1_paramName_err		= "Error interno: en generDoc1, mal argumento <b>%1</b>.",
	--
	modes_unknown_auto_arg_err			= "Error interno: Argumento desconocido automático: %1 = <b>%2</b>.",
	modes_delete_docbox_wng				= "Debe quitar esta documentación antes de grabar.<br>Retire todos los modos para volver al modo read.",
	modes_auto_val_warning_wng			= "Verifique el argumento automático: %1 = <b>%2</b>.",
	--
	modes_assist_user_param_err			= "Apoyo al usuario para comprobar la configuración:",
	modes_no_known_arguments_err		= "Módulo sin argumentos tabla conocida.",
	modes_args_values_err				= "Valor anormal del argumento <b>%1 = %2</b> (%3)",
	--
	modes_no_known_arguments_err		= "Error interno: Módulo sin tabla de argumentos conocidos.",
	modes_no_known_args_cat				= "Módulo con error interno", -- versioning_module_internal_error_cat
	modes_no_source_arguments_err		= "Error interno: Módulo sin tabla de argumentos fuentes.",
	modes_no_source_arguments_cat		= "Módulo con error interno", -- versioning_module_internal_error_cat
	modes_list_all_args_main_title		= "modes.list_all_args_main() Lista de todos los argumentos aceptados",
	modes_similar_levenshtein_tests_title= "modes.levenshtein() Prueba de las distancias <b>[https://es.wikipedia.org/wiki/Distancia_de_Levenshtein Levenshtein]</b> entre las palabras:",
	modes_args_known_structure_title	= "modes.args_known_structure() Definiciones de argumentos",
	modes_similar_args_searchs_title	= "modes.similar_args_search() Prueba de palabras similares cercanos:",
	modes_normal_box_main_text_1		= "Simple caja de información: Título de la página: <b>%1</b>, descripción: <b>%2</b>",
	modes_normal_box_main_text_2		= "<br/>Él era un <b>%1</b>, es nombrado <b>%2</b> <b>%3</b> había muerto en <b>%4</b>.",
	-- Estos textos se utilizan para titular algunas pruebas.
	modes_list_wiki_selectors_title		= "Lista de las selectores de este modulo:",
	modes_all_categories_list_title		= "modes.all_categories_list() Lista de las posibles categorías de este modulo:",
	modes_all_errors_list_title			= "modes.all_errors_list() Lista de los errores detectables de este modulo:",
	modes_all_G_list_var_tests_title	= "modes.all_G_list_var_test() Lista de todos las variables en el espacio global _G",
	modes_multiple_values_tests_title	= "modes.multiple_values() Prueba de múltiples valores argumentos",
	modes_multiple_selection_tests_title= "modes.multiple_selection() Prueba de selección múltiple",
	modes_multiple_selection_test_headers= "Opciones;Selector;Para seleccionar;Seleccionadas",
	modes_multiple_selection_test_select= "2;nobel;presidente;diputado;precio",
	modes_recursiveLevel_err			= "Error de nivel recursivo <b>%1 > %2</b>",
	modes_recursiveNormal_tests_title	= "modes.recursiveNormal() Prueba: Normalizar el nivel recursivo y el límite recursiva",
	modes_spaces_page_names_title		= "modes.spacesPageNamesTest() Módulo, namespaces, y nombres de páginas:",
} -- modes.i18n.es

modes.i18n.fr = {
	-- Noms et descriptions des arguments de configurations
	[1]								= "1",
	[2]								= "2",
	[3]								= "3",
	[4]								= "4",
	label							= 'label',
	label_descr						= "Argument automatique de Wikidata.",
	sitelink						= 'sitelink',
	sitelink_descr					= "Argument automatique de Wikidata.",
	userlang						= "userlang",
	userlang_descr					= "Langue de l'utilisateur ou de l'aidant.",
	wikilang						= "wikilang",
	wikilang_descr					= "Langue du wiki.",
	itemid							= 'itemid',
	itemid_descr					= "Identifiant des données de wikidata, comme <code>Q535</code> pour Victor Hugo.",
	itemid2							= 'id',
	itemid2_descr					= "Identifiant 2 des données de wikidata, comme <code>Q535</code> pour Victor Hugo.",
	debug							= 'debug',
	category						= 'Catégorie',
	mode							= "mode",
	mode_descr						= "Type d'utilisation du module ou du modèle : lire, éditer, documenter, tester.",
	options							= "options",
	options_descr					= "Options d'affichage d'un module ou d'un modèle.",
	c								= "c",
	c_descr							= "Options d'affichage d'un module ou d'un modèle.",
	knownversions					= "knownversions",
	knownversions_descr				= "Versions connues, pour les gérer.",
	soughtversions					= "soughtversions",
	soughtversions_descr			= "Versions demandées, pour les gérer.",
	-- Principaux textes, erreurs et catégories des outils
	language							= 'langue',
	-- Gestion des versions
	-- Messages et erreurs divers
--	tools_no_args_known_err				= "Erreur interne : Module sans table d'arguments connus.",
--	tools_no_args_source_err			= "Erreur interne : Module sans table d'arguments sources.",
	modes_used_options_title			= "modes.used_options_list() Options et leurs utilisations :",
	modes_brief_options_mode_title		= "modes.options_from_mode_tests() Modes et options en bref",
	modes_options_from_args_test_title	= "modes.options_from_args_test() Test des options de modes",
	modes_options_from_args_title		= "Test des options des arguments",
	-- Groupes d'arguments
	modes_needed_to_verify				= "(obligatoire, à vérifier)",
	modes_list_needed_args				= "Liste des arguments nécessaires :",
	modes_list_all_config_arguments		= "Liste des arguments de configuration :",
	modes_list_all_system_arguments		= "Liste des arguments système :",
	modes_list_all_other_args			= "Liste des autres arguments :",
	modes_language_cat					= 'Parle %1',
	modes_date_months_names				= "Janvier, Février, Mars, Avril, Mai, Juin, Juillet, Août, Septembre, Octobre, Novembre, Décembre",
	modes_date_to_part_format			= "dd yyyy mmmm",
	modes_date_to_part_call_err			= "Erreur interne : Argument anormal de définition de date <b>%1</b>.",
	modes_date_to_part_call_cat			= "Module avec erreur interne",
	modes_date_to_part_not_found_err	= "Erreur interne : partie non définie de date <b>%1</b>.",
	-- Gestion des arguments
	modes_error_list_header_err			= "Assistance sur les paramètres de ce modèle :",
	modes_need_arg_value_err			= "Erreur : Vous devez définir cet argument nécessaire mais absent : <b>%1</b>.",
	modes_none_value_err				= "Erreur : Aucun argument n'a été défini.",
	modes_unknown_argument_err			= "Erreur : Le paramètre <b>%1</b> est inconnu dans ce modèle. Vérifier ce nom ou signaler ce manque.",
	modes_too_unnamed_arguments_err		= "Erreur : Ce paramètre non nommé est en trop : <b>%1</b> = <b>%2</b>.",
	modes_without_translation_wsid_err	= "Erreur interne : Signaler au développeur que l'argument interne <b>%1</b> est inconnu dans les notices.",
	modes_is_defined_err				= "L'argument %1:<b>%2</b> est défini.",
	modes_is_undefined_err				= "L'argument %1:<b>%2</b> n'est pas défini.",
	modes_args_values_err				= "Valeur anormale de l'argument <b>%1 = %2</b> parmi : (%3) ",
	-- Messages et erreurs divers
	modes_nearest_argument_err			= "Erreur : Voulez vous l'argument connu <b>%1</b> ?",
	modes_max_nearest_argument_msg		= "Un nom d'argument plus long accepte plus d'erreurs de lettres.",
	modes_value_re_defined_err			= "Erreur : La valeur de l'argument <b>%1</b> est déjà définie. Choisir une seule valeur d'un seul synonyme.",
	modes_lang_table_err				= "Erreur interne : La langue <b>%1</b> ou sa table est erronée.",
--	modes_lang_table_err_cat			= "Module avec langue d'arguments erronée",
	modes_lang_table_err_cat			= "Module avec erreur interne", -- versioning_module_internal_error_cat
	modes_lang_not_translated_err		= "Erreur : La langue <b>%1</b> n'est pas traduite.",
	modes_generDoc1_paramName_err		= "Erreur interne : en generDoc1, mauvais argument <b>%1</b>.",
	modes_unknown_auto_arg_err			= "Erreur interne: Argument automatique inconnu : %1 = <b>%2</b>.",
	--
	modes_delete_docbox_wng				= "Vous devez supprimer cette documentation avant d'enregistrer.<br>Supprimez tous les modes pour revenir en mode read.",
	modes_auto_val_warning_wng			= "Vérifiez l'argument automatique : %1 = <b>%2</b>.",
	--
	modes_assist_user_param_err			= "Support aux utilisateurs pour vérifier les paramètres :",
	modes_no_known_arguments_err		= "Module sans table d'arguments connus.",
	modes_args_values_err				= "Valeur anormale de l'argument <b>%1 = %2</b> (%3)",
	--
	modes_no_known_arguments_err		= "Erreur interne : Module sans table d'arguments connus.",
--	modes_no_known_args_cat				= "Module sans table d'arguments connus.",
	modes_no_known_args_cat				= "Module avec erreur interne", -- versioning_module_internal_error_cat
	modes_no_source_arguments_err		= "Erreur interne : Module sans table d'arguments sources.",
--	modes_no_source_arguments_cat		= "Module sans table d'arguments sources.",
	modes_no_source_arguments_cat		= "Module avec erreur interne", -- versioning_module_internal_error_cat
	modes_list_all_args_main_title		= "modes.list_all_args_main() Liste de tous les arguments acceptés",
	modes_similar_levenshtein_tests_title		= "modes.levenshtein() Test des distances de <b>[https://fr.wikipedia.org/wiki/Distance_de_Levenshtein Levenshtein]</b> entre mots:",
	modes_args_known_structure_title	= "modes.args_known_structure() Définitions des arguments",
	modes_similar_args_searchs_title	= "modes.similar_args_search() Test des mots proches similaires :",
	modes_normal_box_main_text_1		= "Boîte d'information simple : titre de cette page <b>%1</b>, description : <b>%2</b>",
	modes_normal_box_main_text_2		= "<br/>Il était <b>%1</b>, se nommait <b>%2</b> <b>%3</b>, il est mort en <b>%4</b>.",
	-- Ces textes sont utilisés pour titrer des tests.
	modes_list_wiki_selectors_title		= "Liste des sélecteurs de ce module :",
	modes_all_categories_list_title		= "modes.all_categories_list() Liste des catégories éventuelles de ce module :",
	modes_all_errors_list_title			= "modes.all_errors_list() Liste des erreurs détectables de ce module :",
	modes_all_G_list_var_tests_title	= "modes.all_G_list_var_test() Liste des variables dans l'espace global _G",
	modes_multiple_values_tests_title	= "modes.multiple_values() Test des arguments à valeurs multiples",
	modes_multiple_selection_tests_title= "modes.multiple_selection() Test de sélection multiple",
	modes_multiple_selection_test_headers= "Options;Sélecteur;À sélectionner;Sélectionnés",
	modes_multiple_selection_test_select= "2;nobel;président;député;prix",
	modes_recursiveLevel_err			= "Erreur de niveau récursif <b>%1 > %2</b>",
	modes_recursiveNormal_tests_title	= "modes.recursiveNormal() Test: Normalise le niveau récursif et la limite récursive",
	modes_spaces_page_names_title		= "modes.spacesPageNamesTest() Module, namespaces, et noms de pages :",
} -- modes.i18n.fr

-- - - - ------------------ - - - - ---------------------------------
-- Support of the management of options. Sostén de la gestión de las opciones. Soutien de la gestion des options.
-- - - - ------------------ - - - - ---------------------------------

modes.options_for_modes = { -- default options_for_modes
	normal	= " noerr ",
	read	= " noerr ",
	edit	= " docdef docline docsrc docdata catview docview : ",
	doc		= " nobox noerr nocat ",
	tests	= " docdef docline docsrc docdata catview docview : tests ",
	-- List of parameters for module documentation
	-- "docview" or ":" add the documentation panel
	-- "docmin" list only some basic parameters
	-- "docdef" list only the parameters defined, having a non-zero value
	-- "docmax" list all known parameters
	-- "docnotice" generate documentations of records
	-- "docline" put all parameters on a single line
	-- "docsrc" put the parameters in colors according to the sources
	-- modes.options = " docdata docmin docdef docmax docline docview docafter docnotice docsrc : " -- for standard documentation
	-- modes.options = " erron noerr nobox nocat " -- do not form normal result
	-- modes.options = " debug tests en es fr " -- enforce a language for debug
	-- Option nocat means "Do not categorize and do not show categories."
}

function modes.trackOptions(where)
	local t = viewer.form9user("\n* trackOptions where=%1, mode_options=%2, invoke_options=%3", where, modes.mode_options, modes.invoke_options)
	modes.report_trackOptions = (modes.report_trackOptions or"") .. t
	return t
end

function modes.init_options(invoke_options)
	-- modes.invoke_options = modes.init_options(args.options)
	-- modes.invoke_options = modes.init_options("fr params docview docmin docmax docdef docnotice docafter docline docsrc")
	modes.mode_name = modes.mode_name
	if (type(invoke_options) == "string") then modes.invoke_options = invoke_options end --	 and (new ~= "")
	-- Early effects on options which modify other ones.
	-- modes.options = " : docdata docmin docdef docmax docline docview docafter docnotice docsrc" -- for documentation
	-- modes.options = " erron noerr nobox nocat " -- without normal result
	-- modes.options = " debug tests en es fr " -- for debug or enforce language
	modes.options_to_catView()
	if modes.option("noerr") then events.erron = false end
	if modes.option("erron") then events.erron = true end
	if modes.option("docolor") then modes.docolor = true end
	-- If an option is a language, enable this language for the user.
	-- Si una opción es un lenguaje, activar esta lengua para usuario.
	-- Si une option est une langue, activer cette langue pour l'utilisateur.
	for lang, tab in pairs(versioning.i18n) do
		if modes.option(lang) then
			translate.init_wiki_user_page_lang(translate.wiki_lang, lang) -- translate.wiki_lang, translate.user_lang
		end
	end
	return modes.invoke_options
end -- function modes.init_options(invoke_options)

-- Manage Library:modes options. Administrar opciones. Gérer les options.
modes.invoke_options = "" -- Normal options from the template. No default values. Options normales venant du modèle. Aucune par defaut.
	--	In the template : options = "params docview docmin docmax docdef docnotice docafter docline docsrc"

modes.mode_options = "" -- Options de debug du module par edit. Aucune par defaut.
	-- modes.mode_options = "unitest debug noerr erron params docview nobox nocat docin docmax docnotice docdef docline docsrc notices "
	-- modes.mode_options change only by editing this module code or the calling module. Empty default value.
	-- modes.mode_options change n'est modifié que par ce module ou celui qui l'appele. Valeur par defaut vide.

modes.used_options = {} -- table to collect tested options then list them at end of tests.
modes.available_options = "" -- table to collect all available options, at any time.

function modes.options_from_mode(mode_name)
	-- mode_name = mode_name or (modes.args_final and modes.args_final.mode) or (modes.args_import and modes.args_import.mode) or p.mode_name or modes.mode_name or "read"
	mode_name = mode_name or "read"
	local mode_options = ""
	if modes.options_for_modes and modes.options_for_modes[mode_name] then mode_options = modes.options_for_modes[mode_name] end
	modes.mode_options = mode_options
	return mode_options, mode_name
end

function tools.option(key, opt) -- Deprecated alias function
	versioning.deprecatedFunction("tools.option", "modes.option")
	return modes.option(key, opt)
end
function modes.option(key, opt) -- Is one option in available options?
	-- Language identifiers allow to enforce some languages.
	-- Errors appear only in Template or Module namespace, waiting the task T53660.
--	local opt = other
	if type(key) ~= "string" then key = "" end
	key = mw.text.trim(key)
	if type(opt) ~= "string" then opt = "" end
	opt = mw.text.trim(opt)
--	available_options = modes.available_options
--	local available_options = " " .. (modes.invoke_options or "") .. " "
	local available_options = (modes.invoke_options or "")
	if (opt == "") -- other param can replace modes.mode_options
		then available_options = " " .. available_options .. " " .. modes.mode_options .. " "
		else available_options = " " .. available_options .. " " .. opt .. " "
	end
--	available_options = string.gsub(available_options, " ", ";")
	available_options = viewer.simpleList(available_options, " ")
	-- The searched keyword is it among the options words? And not included in another word.
	local ifyes = string.is_in_sp(key or "", available_options) -- and true
	-- Collect options tested along the execution of the module
	modes.used_options = modes.used_options or {}
	if ifyes then modes.used_options[key] = "y"
	else modes.used_options[key] = "n" end
	local used = ""
	for key, xx in pairs(modes.used_options) do
		if xx == "y" then used = used .. ", <b>" .. tostring(key) .. "</b> " .. xx
		else used = used .. " " .. tostring(key) .. ", " .. xx end
	end
	modes.available_options = available_options
	modes.used_opts = used -- yes is not nil
	return ifyes, used, available_options -- yes is not nil
end -- function modes.option(key, opt)

function modes.options_from_mode_tests(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = "options_from_mode_test:" or t
	for md, opt in pairs(modes.options_for_modes) do t = t .. "<br>- " .. viewer.ta(md, opt) end
	t = t .. viewer.Th() .. viewer.Tc("Mode") .. viewer.Tc("List of options") .. viewer.Tc("noerr value") .. viewer.Tc("docview value") .. viewer.Tc("tests value")
	local function test_options_from_mode(md, op1, op2, op3)
		modes.available_options = ""
		local opstest = modes.options_from_mode(md) or ""
	--	return viewer.Tr() .. viewer.Td(md) .. viewer.Td(opstest) .. viewer.Td( modes.option(op1, opstest) ) .. viewer.Td( modes.option(op2, opstest) )
		return viewer.Tr() .. viewer.Td(md) .. viewer.Td(opstest) .. viewer.Td( modes.option(op1, opstest) )
		.. viewer.Td(modes.option(op2, opstest)) .. viewer.Td(modes.option(op3, opstest))
	end
	t = t .. "<br>options_from_mode_test:"
	t = t .. test_options_from_mode("read", "noerr", "docview", "tests")
	t = t .. test_options_from_mode("edit", "noerr", "docview", "tests")
	t = t .. test_options_from_mode("tests", "noerr", "docview", "tests")
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function modes.options_from_mode_tests(t)

function modes.trackModes(where, xyz) -- Track formation of modes
	modes.trackModes_txt = (modes.trackModes_txt or "") .. viewer.ta("\n* trackModes", where) .. viewer.ta("mode_options", modes.mode_options) .. viewer.ta("invoke_options", modes.invoke_options) .. viewer.ta("available_options", modes.available_options) .. " used_opts = " .. (modes.used_opts or "")
	return modes.trackModes_txt
end

function modes.options_to_catView() -- Init or restaure modes.catView = modes.catView from options
	if modes.option(":") or modes.option("catview") then modes.catView = ":" else modes.catView = "" end
	return modes.catView
end -- function modes.options_to_catView()

function modes.used_options_list(t, used_options)
	local t = "List of modes:" or t
	used_options = used_options or modes.used_options
	modes.mode_name = modes.mode_name
	for md, opt in pairs(modes.options_for_modes) do t = t .. "<br>- " .. viewer.ta(md, opt) end
	-- List of used options after collect them
	t = t or ""
	t = t .. "\n* " .. viewer.form9user("translate_user_wiki_lang_msg", translate.user_lang, translate.wiki_lang) -- ( or viewer.form9user("translate_user_wiki_lang_msg", translate.user_lang, translate.wiki_lang) or " missing user_wiki_lang_msg " )
	t = t .. "\n* Actual mode used: <b>" .. modes.mode_name .. "</b>"
	t = t .. "\n* Options read the last time in these tests, ones <b>activated</b> are in bold: "
	t = t .. "\n* Options lues pour la dernière fois pendant ces tests, celles <b>activées</b> sont en gras : "
	t = t .. "<br>"
	local used = ""
	for key, x in pairs(modes.used_options) do
		if x == "y" then used = used .. ", <b>" .. tostring(key) .. "</b> "
		else used = used .. ", " .. tostring(key) .. " " end
	end
	t = t .. used
--	t = t .. "\n* import_arguments_track : " .. modes.import_arguments_track
	return t, used
end -- function modes.used_options_list(t, used_options)

function modes.options_from_args_test(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local mode_options_memo = modes.mode_options -- save
	local invoke_options_memo = modes.invoke_options -- save
	local used_options_memo = mw.clone(modes.used_options) -- save
	modes.used_options = {} -- for test
	local t = "options_from_args_test:" or t
	t = t .. "<br>" .. luaTable.formSubCounts("modes.i18n")
	t = t .. viewer.Th() .. viewer.Tc("mode_options") .. viewer.Tc("invoke_options") .. viewer.Tc("opt can replace mode_options") .. viewer.Tc("available options") .. viewer.Tc("option result")
	local function options_from_args(mode_options, invoke_options, opt, key)
		modes.available_options = ""
	--	local mode_options_memo = modes.mode_options -- save
	--	local invoke_options_memo = modes.invoke_options -- save
	--	local used_options_memo = mw.clone(modes.used_options) -- save
	--	modes.used_options = {} -- for test
		modes.mode_options = mode_options or ""
		modes.invoke_options = invoke_options or ""
		local ifyes, available_options = modes.option(key, opt)
	--	modes.mode_options = mode_options_memo -- restore
	--	modes.invoke_options = invoke_options_memo -- restore
	--	modes.used_options = used_options_memo -- restore
		return viewer.Tr() .. viewer.Td( mode_options or "" ) .. viewer.Td( invoke_options or "" ) .. viewer.Td( opt or "" )
		.. viewer.Td( modes.available_options ) .. viewer.Td( viewer.ta(key, ifyes) )
	--	.. viewer.Td( modes.available_options .. "<br/>" .. available_options ) .. viewer.Td(viewer.ta(key, ifyes) )
	end
	t = t .. options_from_args("nobox nocat", "en docview", nil,			"nobox")
	t = t .. options_from_args("nobox nocat", "en docview", nil,			"nocat")
	t = t .. options_from_args("nobox nocat", "en docview", nil,			"en")
	t = t .. options_from_args("nobox nocat", "en docview", nil,			"docview")
	t = t .. options_from_args("nobox nocat", ": docview",	nil,			":")
	t = t .. options_from_args("nobox nocat", "en docview", "docline fr",	"docline")
	t = t .. options_from_args("nobox nocat", "en docview", "docline fr",	"docline")
	t = t .. options_from_args("		   ", nil,			"docline fr",	"docline")
	t = t .. options_from_args("		   ", nil,			"docline fr",	"fr")
	t = t .. options_from_args("		   ", "en docview", "docline fr",	"en")
	t = t .. options_from_args("nobox nocat", nil,			"docline fr",	"docline")
	t = t .. options_from_args("nobox nocat", "en docview", "docline fr",	nil)
	t = t .. options_from_args("nobox nocat", "en docview", nil,			"docline")
	t = t .. viewer.Te()
	t = t .. modes.used_options_list(nil, modes.used_options)
	t = t .. "\n* After these tests, previous options are restored. Après ces tests, les options antérieures sont restaurés."
	modes.mode_options = mode_options_memo -- restore
	modes.invoke_options = invoke_options_memo -- restore
	modes.used_options = used_options_memo -- restore
--	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function modes.options_from_args_test(t)

function p.form_result(args_final)
	versioning.deprecatedFunction("p.form_result", "modes.form_result")
	return modes.form_result(args_final)
end -- translate
-- Normal result of the module
-- Resultado normal del módulo
-- Résultat normal du module
function modes.form_result(args_final)
	local memo = events.init_configs() -- Init a new events group
	if type(args_final) ~= "table" then args_final = modes.args_final end -- optional arguments
	local res = ""
	events.gener_categories(args_final) -- Form all categories without activate them
	-- List of parameters for module documentation
	-- "docview" or ":" add the documentation panel
	-- "docmin" list only some basic parameters
	-- "docdef" list only the parameters defined, having a non-zero value
	-- "docmax" list all known parameters
	-- "docnotice" generate documentations of records
	-- "docline" put all parameters on a single line
	-- "docsrc" put the parameters in colors according to the sources
	-- modes.options = " docdata docmin docdef docmax docline docview docafter docnotice docsrc : " -- for standard documentation
	-- modes.options = " erron noerr nobox nocat " -- do not form normal result
	-- modes.options = " debug tests en es fr " -- enforce a language for debug
	-- Option nocat means "Do not categorize and do not show categories."
	if modes.option("docview") or modes.option(":") or modes.option("tests") or (modes.args_final.mode == "edit") then
		res = res .. viewer.docDropBox("allwaysview", viewer.docDropBoxTitle(), modes.used_options_list() )
		res = res .. p.formDocBox(args_final)
	end
	if not modes.option("nobox") then res = res .. p.normal_box(args_final) end
	-- Display categories. Afficher les catégories.
	if ( not modes.option("nobox") ) and ( modes.option("catview") or modes.option(":") ) then res = res .. events.categories_lister(":") end
	modes.time3 = os.clock()
--	res = res .. modes.used_options_list()
	if modes.option("tests") or (modes.args_final.mode == "tests") then
		local memo2 = events.init_configs(cat_test, tabOptions) -- Init a new events group
		res = res .. viewer.docGroup("allwaysview")
		events.restore_configs(memo2) -- Close a cats and errors table. For one test or main module.
		res = res .. modes.running_times(true, "")
	end
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function modes.form_result(args_final)

--[[	Management of arguments and translations along their transformations :
datas.args_wikidata = {} -- Wikidata arguments, from Wikidata
events.errors_list = {} -- Errors list
events.categories_list = {} -- Categories lists
modes.args_known = {} -- Known arguments, at main module level
modes.args_invoke = {} -- Invoke arguments, at {{#invoke: ... }} level
modes.args_template = {} -- Template arguments, at {{Template| ... }} level
modes.args_source = {} -- Source arguments = args_template + args_invoke
modes.args_unknown = {} -- Unknown arguments
modes.args_import = {} -- Import arguments
modes.args_final = {} -- Final arguments, interactions in p.interact_args_final()
modes.args_selected = {} -- Selected arguments, in some modules
translate.wiki_translations = {} -- Wiki translations
translate.user_translations = {} -- User translations
versioning.loaded_pack = {} -- loaded packages of modules and libraries
versioning.loaded_vers = {} -- loaded versions of modules and libraries
--]]

function modes.argsConfigIinit(args_source)
	-- Memorize direct configuration arguments, without translation, for helpers.
	if type(args_source) ~= "table" then args_source = modes.args_source end
	local new_source = mw.clone(args_source) -- To ensure not disturb original
	local args_config = {}
	args_config.userlang = args_source.userlang -- user language argument
	new_source.userlang = nil
	args_config.wikilang = args_source.wikilang -- wiki language argument
	new_source.wikilang = nil
	args_config.mode = args_source.mode -- mode argument
	new_source.mode = nil
	args_config.c = args_source.c -- invoke options argument
	new_source.c = nil
	args_config.dockey = args_source.dockey or args_source[1] -- doc selector argument
	new_source.dockey = nil
	args_config.itemid = args_source.itemid or args_source.id or args_source[2] -- wikidata item id argument
	new_source.itemid = nil
	args_config.soughtversions = args_source.soughtversions -- sought_versions argument
	new_source.soughtversions = nil
	args_config.knownversions = args_source.knownversions -- knownversions argument
	new_source.knownversions = nil
	modes.args_source = new_source
	modes.args_config = args_config
	return args_config, new_source
end -- function modes.argsConfigIinit()

function modes.change_itemid(id_in)
	local id = ""
	if id_in then -- Local function parameter have priority.
		id = id_in
	else -- Else use the template arguments or inforce itemid here.
		id = modes.args_config.id
	--	id = "Q131671" -- (VIe siècle av. J.-C. – Ve siècle av. J.-C.) Xénophane
	--	id = "Q8739"	--	(vers 287 av. J.-C. – 212 av. J.-C.) Archimède
	--	id = "Q213330"			--		  (1330 – 1418) Nicolas Flamel
	--	id = "Q83428"			--	 (vers 1493 – 1541) Paracelse
		id = "Q41568"			--		  (1533 – 1592) Michel de Montaigne
	--	id = "Q535"				--		  (1802 – 1885) Victor Hugo
	--	id = "Q21157618"		--		  (		– 1932) Charles Maumené
	--	id = "Q9364"			--		  (1905 – 1980) Jean-Paul Sartre
	--	id = "Q213330"			--		  (1330 – 1418) Nicolas Flamel
	end
	local nst = 10 -- mw.site.namespaces.Template
	local nsm = 828 -- mw.site.namespaces.Module
	local mwtitle = mw.title.getCurrentTitle()
	local nsX = mw.title.getCurrentTitle():inNamespaces("10", "11", "828", "829")
	local ns = tostring(mw.site.namespaces.id)
	if string.is_in_sp(ns, "10;11;828;829", ";") then
		-- Change itemid only where title cannot be in wikidata.
		modes.args_config.id = id
		modes.args_config.itemid = id
	end
--	modes.args_config.id = id -- always inforce
--	modes.args_config.itemid = id -- always inforce
	return id
end -- function modes.change_itemid(id_in)

function modes.all_G_list_var_test(t) -- List of all variables in _G global space without packageloaded
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = "\n* <b>modes.all_G_list_var_test </b>: Variables in _G global space without libraries and modules in packageloaded.\n* "
	for key, val in pairs(_G) do
		if not package.loaded[key] then
			t = t .. viewer.ta(key, val)
		end
	end
	--	T2016 -- Found in Lua_reference_manual but not in Module:Central-s-fr :
--	2016-10-22 15:45 : modes.all_G_list_var_test : Variables in _G global space without libraries and modules in packageloaded. , xpcall = function , tostring = function , _VERSION = Lua 5.1 , unpack = function , require = function , pairs = function , next = function , assert = function , ipairs = function , rawequal = function , getmetatable = function , rawset = function , pcall = function , type = function , selector = function , rawget = function , tonumber = function , error = function , setmetatable = function
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end

function modes.args_known_structure(t, args_known) -- modes_args_known_structure_title = Table structure of arguments
--	if true then return "" end -- to bypass in DEBUG
	local t = t or "\n* <b>args_known_structure :</b> "
	local tt
	if type(args_known) ~= "table" then args_known = modes.args_known end
	for key, elem in pairs(args_known) do
		tt = viewer.tam("typ", elem.typ) .. viewer.tam("keyword", elem.keyword) .. viewer.tam("syn", elem.syn)
		tt = tt .. viewer.tam("need", elem.need) .. viewer.tam("prop", elem.prop) .. viewer.tam("format", elem.format)
		tt = string.sub(tt, 3, -1)
		t = t .. '\n: ' .. tostring(key) .. ' = { ' .. tt .. ' } '
	end
	return t
end -- function modes.args_known_structure(t, args_known) old : -- modes_list_all_args_main_title table_args_known_title

-- Compute the Levenshtein distance between 2 ASCII words.
function modes.levenshtein(word1, word2)
	-- prevent exceptions
	local cout = 0
	if (word1 == nil) or (word2 == nil) then
		return 999, "<br>lev: " .. viewer.ta("word1", word1) .. viewer.ta("word2", word2)
	end
	local len1 = string.len(word1)
	local len2 = string.len(word2)
	local lev = 0
	local t = "<br>lev: " .. viewer.ta("word1", word1) .. viewer.ta("word2", word2)
	if (type(word1) ~= "string") or (type(word2) ~= "string") or (word1 == "") or (word2 == "") then
		lev = len1 + len2
		return lev, t .. viewer.ta("lev", lev)
	end
	-- simple case
	if (word1 == word2) then
		lev = 0
		return lev, t .. viewer.ta("lev", lev)
	end
	local d = {}
	for i = 1, len1+2 do -- for i = 1, len1-1+1 do
		d[i] = {}
		--	d[i][1] = 0 -- d[i][1] = i
		for j = 1, len2+2 do
			--	d[i][j] = {}
			d[i][j] = 0 -- d[1][j] = j
		end
	end
	-- simulate double dimensions tables
	for i = 2, len1+1 do -- for i = 1, len1-1+1 do
		--	d[i] = {}
		d[i][1] = i-1 -- d[i][1] = i
	end
	for j = 2, len2+1 do
		d[1][j] = j-1 -- d[1][j] = j
	end
	for i = 2, len1+1 do -- for i = 2, len1+1 do
		for j = 2, len2+1 do -- for j = 2, len2+1 do
			-- on récupère les deux caractères
			local c1 = string.byte(word1, i-1)
			local c2 = string.byte(word2, j-1)
			if (c1 == c2) then
				cout = 0
				d[i][j] = d[i-1][j-1]
			else
				cout = 1
				d[i][j] = math.min(d[i-1][j], d[i][j-1], d[i-1][j-1]) + 1
			end
			--	d[i][j] = math.min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cout)
		end
	end
	local lev = d[len1+1][len2+1] -- return d[len1-1][len2] - 1
	return lev, t .. viewer.ta("lev", lev)
end -- function modes.levenshtein(word1, word2)

function modes.levenshtein_test_1(search, word, max)
	-- search = mot cherché dans la liste
	-- word = un des mot de la liste
	local diffmaxi = modes.similar_args_diffmaxi( string.len(search) )
	local len1, len2, lev, tlev, t, diff
--	t = t .. "<br>- diff: " .. viewer.ta("mot1", mot1) .. viewer.ta("mot2", mot2) .. viewer.ta("diff", diff)
	t = "<br>levenshtein : "
	if (not search ) or (not word) then
		diff = 99
		t = t .. viewer.ta("diff", diff) .. " no word. "
	elseif search == word then
		diff = 0
		t = t .. viewer.ta("diff", diff) .. " , " .. search .. " = " .. word .. " "
	else
	--	len1 = string.len(search)
	--	len2 = string.len(word)
		lev, tlev = modes.levenshtein(search, word)
		if (lev <= diffmaxi) then
			t = t .. viewer.ta("diffmaxi", diffmaxi) .. " >= " .. viewer.ta("lev", lev) .. " <b>" .. search .. " ==> " .. word .. "</b> "
		else
			t = t .. viewer.ta("diffmaxi", diffmaxi) .. " >= " .. viewer.ta("lev", lev) .. " " .. search .. " / " .. word .. " "
		end
		diff = lev
	end
	return diff, t, search, word
end -- function modes.levenshtein_test_1(search, word, max)

function modes.similar_levenshtein_tests(res, c)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if type(res) ~= "string" then res = nil end
	local res = res or ("\n* " .. viewer.form9user("modes_max_nearest_argument_msg") )
	local errors = ""
	local n, t = modes.levenshtein_test_1( "nom", "nom")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "nom", "Nom")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "top", "pot")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "ami", "amis")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "nom", "name")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "m", "mu")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "m", "mur")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "mur", "m")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "c", "C")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "c", "cf")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "c", "long")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "xxx", "")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "", "xyz")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "xxx", "xyz")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "prénom", "Prenom")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "catégorie", "Category")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "description", "Description")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "anneeDeces", "anneeNaissance")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "anoNacimiento", "anneeNaissance")
	res = res .. tostring(t)
	local n, t = modes.levenshtein_test_1( "avant-après", "après-avant")
	res = res .. tostring(t)
	if errors ~= "" then res = res .. "\n* <b>levenshtein_test</b> errors = " .. viewer.errorColor(errors) end
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function modes.similar_levenshtein_tests( res, c)

modes.constants = modes.constants or {}
-- Similar words search : diff on length
-- Cerrar de palabras puscadas: diff en longitud
-- Recherche de mots proches: diff sur longueur
modes.constants.near_word_search_diff_coef = 0.30 -- Access to change these constants from anywhere.
modes.constants.near_word_search_diff_const = 0.82

-- Maximum number of different letters between 2 argument names
-- Número máximo de letras diferentes entre 2 nombres de argumento
-- Nombre maximum de lettres différentes entre deux noms d'arguments
function modes.similar_args_diffmaxi(length, t) -- diffmaxi from length of arg arglingual name
	local coef = modes.constants.near_word_search_diff_coef or 0.30
	local constant = modes.constants.near_word_search_diff_const or 0.82
	local diffmaxi = math.floor( coef * length + constant )
	if t then t = t .. tostring(coef) .. " * length + " .. tostring(constant) end
	return diffmaxi, t
end -- function modes.similar_args_diffmaxi(length, t)

-- For an unknown argument, seeks the name of the closest among the known arguments translated
function modes.similar_args_list(args_known)
	local list, arglingual = {}, "xxx"
	if type(args_known) ~= "table" then args_known = modes.args_known end
	if type(args_known) ~= "table" then return "similar_args_list", 1 end
	for key, argm in pairs(args_known) do -- List only all known arguments
		if not tonumber(key) then -- For named arguments only
			key = tostring(key)
			arglingual = tostring(translate.wiki_translations[key])
			list[key] = arglingual
		end
	end
	return list
end -- function modes.similar_args_list(args_known)

-- For an unknown argument, seeking the name of the nearest argument among the known arguments
function modes.similar_args_search(search, list)
	local dist, lengths, tlev = 9, 0
	local trouve1, trouve2, trouve3 = nil, nil, nil
	local min1, min2, min3 = 99, 99, 99
	local diffmaxi = modes.similar_args_diffmaxi( string.len(search) )
	local t = ", " .. tostring(diffmaxi) .. " / " .. tostring(length) .. " "
	for key, arglingual in pairs(list) do
		-- Search the most similar and same length. Buscar las más similares y la misma longitud. Chercher le plus ressemblant et la même longueur.
		dist, tlev = modes.levenshtein(search, arglingual)
		if (dist <= min1) then
			trouve3 = trouve2
			min3 = min2
			trouve2 = trouve1
			min2 = min1
			min1 = dist
			trouve1 = tostring(arglingual)
		end
	end
	t = t .. "<br>" .. viewer.ta("cherche", cherche) .. viewer.ta("trouve1", trouve1) .. viewer.ta("min1", min1) .. viewer.ta("trouve2", trouve2) .. viewer.ta("min2", min2) .. " " .. t
	if trouve1 and min1 == 0 then t = t .. viewer.wikidataColor(viewer.ta("connu", trouve1)) end
	if trouve1 and min1 <= diffmaxi then t = t .. viewer.wikidataColor(viewer.ta("min1 "..min1.."<="..diffmaxi, trouve1)) end
	if trouve2 and min2 <= diffmaxi then t = t .. viewer.wikidataColor(viewer.ta("min2 "..min2.."<="..diffmaxi, trouve2)) end
	if trouve3 and min3 <= diffmaxi then t = t .. viewer.wikidataColor(viewer.ta("min3 "..min3.."<="..diffmaxi, trouve3)) end
	return trouve1, min1, trouve2, min2, t
end -- function modes.similar_args_search(search, list)

--	t = modes.similar_args_search1( t, "nomm", "digit")
function modes.similar_args_search1( t, search, liste)
	local args_list = modes.similar_args_list(modes.args_known)
	local trouve1, min1, trouve2, min2 = modes.similar_args_search(search, args_list)
--	local trouve1, min1, trouve2, min2 = "aaa", 1, "bbb", 22
	t = t .. "\n* similar_args_search1 : " .. viewer.ta("search", search) .. viewer.ta("trouve1", trouve1) .. viewer.ta("min1", min1) .. viewer.tam("trouve2", trouve2) .. viewer.tam("min2", min2)
	return t or " similar_args_search1 "
end -- function modes.similar_args_search1( t, search, liste)

function modes.similar_args_searchs( t, args_known)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if type(args_known) ~= "table" then args_known = modes.args_known end
	local err = modes.verify_args_tables(args_known, modes.args_source)
	if err then return err end
	--
	local key, argsyn, arglingual, txt
--	local coef = modes.constants.near_word_search_diff_coef
--	local constant = modes.constants.near_word_search_diff_const
	t = "\n* " .. (t or "Formula to compute the near words limit: Formule de calcul de limite des mots proches : ")
	local diffmaxi
	diffmaxi, t = modes.similar_args_diffmaxi(10, t .. "diffmaxi = ")
	t = t ..  "\n* List of diffmaxi / lengths : "
	for length = 1, 16 do -- For all lengths
	--	local diffmaxi = math.floor( coef * length + constant )
		diffmaxi = modes.similar_args_diffmaxi(length)
		t = t .. ", " .. tostring(diffmaxi) .. " / " .. tostring(length) .. " "
	end
	t = t .. "\n* List of known arguments and <b>synonyms</b> : "
	local txt, lingual = "", ""
	local ref_words = {}
	for key_known, argm in pairs(args_known) do -- Pour tous les paramètres connus
		if argm.syn == 1 then
			key = argm.keyword
			argsyn = key_known .. ">" -- synonym argument
		else
			key = key_known
			argsyn = "" -- synonym argument
		end
		lingual = translate.wiki_translations[key] or "-" -- importer un argument source
		txt = argsyn .. key .. "/" .. lingual
		if argm.syn == 1 then
			t = t .. ", <b>" .. txt .. "</b> " -- synonyms
		else
			t = t .. ", " .. txt .. " "
		end
		ref_words[key] = {}
		ref_words[key].argmt = key
		ref_words[key].lingual = lingual
	end
	t = t .. "\n* Test similar arguments 1."
	t = modes.similar_args_search1( t, "anneedece", {["a"]="but", ["b"]="porter", ["c"]="anneedeces"})
	t = t .. "\n* Test similar arguments 2."
	t = modes.similar_args_search1( t, "porte", {["a"]="but", ["b"]="porter", ["c"]="pot"})
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function modes.similar_args_searchs( t, args_known)

-- Check if the value of an argument is among the possible values.
-- Vérifier si la valeur d'un argument est parmi les valeurs possibles.
function modes.multiple_values(argmt, argvalue, args_final, args_known)
	if type(args_final) ~= "table" then args_final = modes.args_final or {} end
	if type(args_known) ~= "table" then args_known = modes.args_known end
	local argvalue = argvalue or args_final[argmt]
	local arg_values, key_values, keyword, keyval, argval, rank
	local argm = args_known[argmt]
	if argm then
		arg_values = translate.wiki_translations[argm.arg_values] or "" -- example "no;nada;cn;50;us;70;mpf" in local language
		key_values = argm.key_values or "" -- example "no;none;cn;50;us;70;mpf" in referal english
	end
	if type(arg_values) == "string" and type(key_values) == "string" then
		local arg_tab = mw.text.split(arg_values, ';') -- table of arg
		local key_tab = mw.text.split(key_values, ';') -- table of key
		-- Default values
		keyword = nil
		rank = 0 -- rank of local value and key value
		keyval = nil -- key value
		argval = nil
		if argm and arg_values and argvalue then
			for i, key in ipairs(arg_tab) do
				if key == argvalue then -- Search argvalue in arg_tab
					rank = i
					keyval = key_tab[i] -- Return correponding keyval in key_tab
					argval = argvalue
					keyword = argm.keyword
				end
			end
		end
	end
	return keyword, keyval, argval, rank, arg_values
end -- function modes.multiple_values(argmt, argvalue, args_final, args_known)

function modes.multiple_values_tests(t) -- Test: convert a string to a table of words
	local t = t or "\n* Test <b>multiple_values</b> :"
	local group_test = { -- events.testGroup
		{ argm = "region",	argvalue = "inde", },
		{ argm = "region",	argvalue = nil, },
		{ argm = "rights",	argvalue = "mpf", },
		{ argm = "rights",	argvalue = "non", },
		{ argm = "rights",	argvalue = nil, },
		{ argm = "sex",	 argvalue = "femme", },
		{ argm = "sex",	 argvalue = "homme", },
		{ argm = "sex",	 argvalue = "enfant", },
--	rights_values					= '70,50,mpf,ONU,none',
--	region							= 'region', -- argument with verified multiple values
	}
	local tabOptions = {}
	tabOptions.headers = viewer.form9user("argm; all values; value; keyword; keyval; argval; rank")
	tabOptions.testGroup = group_test
	tabOptions.rowGroup = {}
	if type(group_test) == "table" then
		for key, test in pairs(group_test) do
			local keyword, keyval, argval, rank, arg_values = modes.multiple_values(test.argm, test.argvalue, test.args_final)
			table.insert( tabOptions.rowGroup, { tostring(test.argm or "-"), tostring(arg_values or "-"), tostring(test.argvalue or "-"), tostring(keyword or "-"), tostring(keyval or "-"), tostring(argval or "-"), tostring(rank or "-") } )
		end
	else t = t .. "error : multiple_values_tests has no table. " end
	t = t .. viewer.tableView(tabOptions) -- Form a table with lines and columns.
	return t
end -- function modes.multiple_values_tests(t)

function modes.multiple_selection(opt, selector, to_select)
	-- Select items to selector containing selecting items
	local t, selected_txt, selector_txt, selector_tab, to_select_txt, to_select_tab, selected_tab = "", ""
	local cut = string.sub( opt, 1, 1 ) or ";"
	if type(selector) == "table" then selector_tab = selector end
	if type(selector) == "string" then selector_tab = mw.text.split(selector, cut, true) end
	if type(to_select) == "table" then to_select_tab = clone(to_select) end
	if type(to_select) == "string" then to_select_tab = mw.text.split(to_select, cut, true) end
	selected_tab = {}
	--
	local k, Nsel, N, maxi, pos = 0, 0, 1, 999, nil
	local reject_select = false
	local equal = true
	for i, selector in ipairs(selector_tab) do -- selector authorities only following selectors
		if Nsel >= maxi then break end
		selector = mw.text.trim(selector)
		N = tonumber(selector)
		if selector == "+" then -- selector all items
			for key, val in pairs(to_select_tab) do
				Nsel = Nsel + 1
				selected_tab[key] = val
			end
		elseif selector == "-" then -- minus sign rejects all
			reject_select = true
		elseif N and N < 1 then -- minus sign rejects all
			reject_select = true
		elseif N and (string.sub(selector, 1, 1) == "+") then -- selector +N more items
			maxi = Nsel + N
		elseif N then -- selector N maximum total items
			maxi = N
		else -- selector ONE item from to_select_tab if it matches selector ( not - or + or +N or N )
			for key, val in pairs(to_select_tab) do
				local select_t, val_t = selector, val
				if not string.is_in("U", opt) then select_t = string.lower(select_t) end -- recognize lowercase and uppercase
				if not string.is_in("t", opt) then select_t = mw.text.trim(select_t) end -- recognize after trim
				if not string.is_in("U", opt) then val_t = string.lower(val_t) end -- recognize lowercase and uppercase
				if not string.is_in("t", opt) then val_t = mw.text.trim(val_t) end -- recognize after trim
				equal = string.is_in("=", opt) -- recognize only equal string
				if equal then equal = (selector == val) else equal = string.is_in(select_t, val_t) end
			--	if string.is_in(selector, val) then
				if equal then -- recognize if selector is equal or is inside an item from to_select_tab
					t = t .. viewer.ta(selector, val)
					selected_tab[selector] = val
					Nsel = Nsel + 1
					to_select_tab[key] = " " -- Delete the selected item to use it only once
					selected_txt = selected_txt .. val .. ', '
				end
			end
		end
	end
--	selected_txt = table.concat(selected_tab)
	return selected_txt, selected_tab, t
end -- function modes.multiple_selection(opt, selector, to_select)

function modes.multiple_selection_tests(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = (t or "") .. "\n* <b>multiple_selection</b> options: " .. viewer.ta("=", "equal only") .. viewer.ta("t", "not trim before and after") .. viewer.ta("U", "not lowercase and uppercase")
	local function multiple_selection_test1(t, opt, selector, to_select)
		local selected_txt, selected_tab, txt = modes.multiple_selection(opt, selector, to_select)
		t = t .. viewer.Tr() .. viewer.Td(opt) .. viewer.Td(selector) .. viewer.Td(to_select) .. viewer.Td( txt .. viewer.ta("selected_txt", selected_txt) )
		return t, opt, selector, to_select -- , selected_txt, selected_tab
	end
	local opt = "; "
	t = t .. "\n: selector = <b>nobel,+1,président,3,député,prix</b> signifie : sélectionner le premier, puis 1 de plus parmi les suivants, puis 3 en tout au maximum."
	local head = mw.text.split( viewer.form9user("modes_multiple_selection_test_headers") , ';')
	t = t .. viewer.Th() .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[3]) .. viewer.Tc(head[4])
	-- Todo ? P39 = fonction = "président de Pologne, député à l'Assemblée, Prix Nehru, Nobel de la paix"
	-- modes_multiple_selection_test_select = "2, nobel, president, deputy, price",
	t = multiple_selection_test1( t, opt, "2; nobel; président; député; prix", "président de Pologne; député à l'Assemblée; Prix Nehru; Nobel de la paix" )
	t = multiple_selection_test1( t, ";U", "2; nobel, président; député; prix", "président de Pologne; député à l'Assemblée; Prix Nehru; Nobel de la paix" )
	t = multiple_selection_test1( t, opt, "3; député; prix; nobel; président", "président de Pologne; député à l'Assemblée; Prix Nehru, Nobel de la paix" )
	t = multiple_selection_test1( t, ";=", "3; député; prix; nobel; président", "président de Pologne; député; Prix Nehru; Nobel" )
	t = multiple_selection_test1( t, ";t", "3; député; prix; nobel; président", "président de Pologne; député; Prix Nehru; Nobel" )
	t = multiple_selection_test1( t, ";", "3; prix; nobel;+1; président; député", "président de Pologne; député à l'Assemblée; Prix Nehru; Nobel de la paix" )
	t = multiple_selection_test1( t, ";", "3; président; nobel; député; prix", "président de Pologne; député à l'Assemblée; Prix Nehru; Nobel de la paix" )
	t = multiple_selection_test1( t, ";t", "3; député; prix; nobel; président", "président de Pologne; député; Prix Nehru; Nobel" )
	t = multiple_selection_test1( t, ";U", "2; nobel; président; député; prix", "président de Pologne; député à l'Assemblée; Prix Nehru; Nobel de la paix" )
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function modes.multiple_selection_tests(t)

function modes.verify_args_tables(_known, _source)
	-- initialize all the values to "" in arg table
	if type(_known) == "table" then
		modes.args_known = _known
	end
	if type(modes.args_known) ~= "table" then
		events.add_err("modes_no_known_arguments_err")
--		events.add_cat("modes_no_known_args_cat")
		return viewer.errorColor(" Internal error : no source or no known arguments ! ")
	end
	if type(_source) == "table" then
		modes.args_source = _source
	end
	if type(modes.args_source) ~= "table" then
		events.add_err("modes_no_source_arguments_err")
--		events.add_cat("modes_no_source_arguments_cat")
		return viewer.errorColor(" Internal error : no source or no known arguments ! ")
	end
	return
end -- function modes.verify_args_tables(_known, _source)

function modes.import_arguments(args_known, args_source, wiki_translations, args_wikidata)
	--	import all arguments from template, or invoke, or wikidata
	local args_import = {} -- table d'arguments internationale simple
	local err = nil
	local cats = ""
	-- default parameters
	if type(args_known) ~= "table" then args_known = modes.args_known end
	if type(args_known) ~= "table" then args_known = modes.args_known end
	if type(args_source) ~= "table" then args_source = modes.args_source end
	wiki_translations = wiki_translations or translate.wiki_translations
	--
	if type(args_wikidata) ~= "table" then args_wikidata = datas.args_wikidata end
	modes.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
	args_import.nowyear = modes.nowyear
	modes.import_arguments_err = ""
	modes.import_arguments_track = ""
	local err = modes.verify_args_tables(args_known, args_source)
	if err then return args_import, err end
	--
	local err, er1, t2 = "", "", ""
	local key, argval, argid = "kkk", "xxx", ""
	local argknw, arglingual, argreceived = nil, "", ""
	local argm = {} -- used arguments
--	events.erron = true -- Errors actived or no. Errores activas o no. Erreurs activées ou non.
--	events.errors_list = {} -- collect all errors
	local key_N, key_NN = 0, 0
	local arg_found, rec_found, already_found = false, false, false
	--
	for key_known, argm in pairs(args_known) do
		argm.found = 0 -- First, initialize all known arguments
	end
	--
	modes.args_unknown = mw.clone(modes.args_source) -- unknown arguments to detect are source arguments without known arguments.
	local key_known_init = nil
--	local argm_orig = nil
	local argm_syn = nil
	-- Try to read all known arguments. Intentar leer todos los argumentos conocidos. Essayer de lire tous les arguments connus.
	for key_known, argm in pairs(args_known) do
		argm.src = nil
		argm.trk = " n"
		key_known_init = key_known
		-- first initialise each known argument
		modes.import_arguments_track = tostring(modes.import_arguments_track) .. " - " .. tostring(key_known)
		argm_syn = args_known[argm.keyword]
		if argm.syn == 2 then
			-- Name an unnamed argument, positional, by its synonym. Nommer un argument non nommé, numéroté, par son synonyme.
			-- Rename a named argument, by its synonym. Renommer un argument nommé, par son synonyme.
--			argm_orig = key_known -- DEBUG
			key_known = argm.keyword
			--	synonyms are defined with the stamp "syn = 2", then here stamp the basic argument with "syn = 1".
			args_known[key_known].syn = 1
			argm = args_known[key_known] -- new variable argm
			argm.src = nil
			argm.trk = "s"
			modes.import_arguments_track = tostring(modes.import_arguments_track) .. ">" .. tostring(key_known)
		end
		-- initialiser un argument
		arg_found = false
		argval = nil
		argm.trk = argm.trk.."="
		-- importer un argument wikidata
		if args_wikidata[key_known] then
			argval = args_wikidata[key_known]
			argm.src = "wd"
			argm.trk = argm.trk.."w"
			if argm_orig then
			--	args_known[argm_orig].src = "wd"
			--	args_known[argm_orig].trk = (args_known[argm_orig].trk or "").."w"
			end
--			if argm_orig then argm_orig = key_known end -- DEBUG
			arg_found = true
			modes.import_arguments_track = modes.import_arguments_track .. "=<b>" .. tostring(argval) .. "</b> "
		end
		-- import a source argument. importer un argument source.
		arglingual = translate.wiki_translations[key_known]
		modes.import_arguments_track = modes.import_arguments_track .. "/" .. tostring(arglingual)
		if arglingual then -- The argument name has a translation in wiki language
		--	if modes.frame[arglingual] then
		--		argval = modes.frame[arglingual]
		--		argm.src = "inv" -- arg comes from invoke
		--	end
			if modes.frame.args[arglingual] then
	--			argval = modes.frame.args[arglingual]
	--			argm.src = "inv" -- arg comes from invoke
	--			arg_found = true
			end
			if modes.frame:getParent().args then
	--			argval = modes.frame:getParent().args[arglingual]
	--			argm.src = "tpl" -- arg comes from template
	--			arg_found = true
			end

			if args_source[arglingual] and not modes.args_config[arglingual] then -- the argument come from template else from invoke else from wikidata
	--		if argval then -- the argument value exist and come from template else from invoke else from wikidata

				argval = args_source[arglingual]
				argm.src = "args"
				argm.trk = argm.trk.." a"
				arg_found = true
				local arg_values = wiki_translations[argm.arg_values]
				if argm.keys_values and arg_values then
					-- The argument is limited to multiple values with arg_values and keys_values, and the values are defined.
					local pos = string.find(arg_values, argval)
					if pos then
						-- The value of the argument is in the multiple values of the arguments.
				--		argm.src = "args"
						argm.trk = argm.trk.."m"
				--		arg_found = true
						if argm_orig then
						--	args_known[argm_orig].src = "args"
						--	args_known[argm_orig].trk = (args_known[argm_orig].trk or "").."d"
						end
					else
						events.add_err("modes_args_values_err", argm.keyword, argval, arg_values)
				--		argval = nil
					end
				else
				--	argm.src = "args"
					argm.trk = argm.trk.."c"
				--	arg_found = true
					if argm_orig then
					--	args_known[argm_orig].src = "args"
					--	args_known[argm_orig].trk = (args_known[argm_orig].trk or "").."c"
					end
				end
				modes.import_arguments_track = modes.import_arguments_track .. "=<b>" .. tostring(argval) .. "</b> "
			end -- not args_source[arglingual] is normal
		else -- internal error and category
		--	events.add_err("versioning_module_miss_i18n_trad_err", key_known)
			-- Generate a category to list all modules with missing translation
			cats = cats .. events.add_cat( "versioning_err_module_miss_i18n_cat" )
		end
		--
		key_N = tonumber(key_known_init)
		if key_N and not args_known[key_N] then
--			events.add_err("modes_too_unnamed_arguments_err", key_N, argval)
		end
		-- Record the argument. Guarde el argumento. Enregistrer l'argument.
		if arg_found == true then
			argm.found = argm.found + 1 -- compter les arguments redéfinis
			argm.val = argval
			args_import[key_known] = argval -- table d'arguments internationale simple
			if modes.args_unknown[arglingual] then
				modes.args_unknown[arglingual] = nil -- unknown arguments are source arguments without known arguments.
			end
		end
	end
	-- modes.import_arguments: after import itself, some surrounding checks.
	for key_known, argm in pairs(args_known) do -- For all known arguments
		-- Redefined arguments. Argumentos redefinieron. Arguments redéfinis.
	--	if argm.found and (argm.found > 1) then
	--		events.add_err("modes_value_re_defined_err", argm["keyword"])
	--		cats = cats .. events.add_cat("versioning_module_usage_error_cat")
	--	end
	--	synonyms are defined with the stamp "syn = 2", then here stamp the basic argument with "syn = 1".
	--	if argm.keyword and args_known[argm.keyword] and args_known[argm.keyword].syn then -- if the argument is a synonym, increase the found level
		if argm.keyword and args_known[argm.keyword] and args_known[argm.keyword].syn then -- if the argument is a synonym, increase the found level
			if argm.found and (argm.found > 2) then
				events.add_err("modes_value_re_defined_err", (argm["keyword"] or "**") ) --.. ">2")
				cats = cats .. events.add_cat("versioning_module_usage_error_cat")
			end
		else
			if argm.found and (argm.found > 1) then
				events.add_err("modes_value_re_defined_err", (argm["keyword"] or "**") ) --.. ">1")
				cats = cats .. events.add_cat("versioning_module_usage_error_cat")
			end
		end
		-- need = 0 not necessary argument
		-- need = 1 necessary from argument
		-- need = 2 necessary from argument or module interaction
		-- Missing Arguments. Argumentos que faltan. Arguments manquants.
		if argm.need and (argm.need == 1) and (not argm.val) then
			arglingual = wiki_translations[key_known]
			if arglingual then
				events.add_err("modes_need_arg_value_err", arglingual)
				cats = cats .. events.add_cat("versioning_module_usage_error_cat")
			end
		end
	end
	-- All arguments sources have they been used?
	--	modes.args_unknown -- unknown arguments are source arguments without known arguments.
	local args_list = modes.similar_args_list(modes.args_known)
	for key_src, val_src in pairs(modes.args_unknown) do -- For all unknown source arguments.
		arglingual = tostring(key_src) -- chercher l'argument traduit
		key_N = tonumber(arglingual)
		-- No error for unmamed arguments
		-- Pas d'erreur pour les arguments non nommés
		if not key_N then
			events.add_err("modes_unknown_argument_err", arglingual, val_src)
			cats = cats .. events.add_cat("versioning_module_usage_error_cat")
			-- "Erreur : Le paramètre <b>%1</b> est inconnu dans ce modèle. Vérifier ce nom ou signaler ce manque.",
			-- Cherche un argument connu et de nom ressemblant
			local diffmaxi = modes.similar_args_diffmaxi( string.len(arglingual) )
			local trouve1, min1, trouve2, min2 = modes.similar_args_search(arglingual, args_list)
			if min1 and (min1 <= diffmaxi) then
				events.add_err("modes_nearest_argument_err", trouve1)
			end
			if min2 and (min2 <= diffmaxi) then
				events.add_err("modes_nearest_argument_err", trouve2)
			end
		end
		key_N = tonumber(arglingual)
		if key_N and not args_known[key_N] then
			events.add_err("modes_too_unnamed_arguments_err", key_N, val_src)
			events.add_cat("versioning_module_usage_error_cat")
		end
	end -- For all unknown source arguments.
	modes.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
	args_import.nowyear = modes.nowyear
	modes.args_import = args_import
	return modes.args_import
end -- function modes.import_arguments(args_known, args_source, wiki_translations, args_wikidata)

-- - - - ------------------ - - - - ---------------------------------
-- Argts : Generate documentation. Generar documentación. Générer la documentation.
-- - - - ------------------ - - - - ---------------------------------

function modes.generDoc1(paramName, n, paramData, opt)
	-- t = t .. generDoc1("nom", "ws-name", "docline docdef")
	-- Normaliser les parametres et signaler les erreurs
	-- Toujours afficher quelque chose dans la documentation du module ou du modèle.
	if type(paramName) ~= "string" or type(paramData) ~= "string" then -- signaler l'erreur
		events.add_err("modes_generDoc1_paramName_err", tostring(paramName) )
		return " "
	end
	opt = " "..tostring(opt).." " -- transparent, sinon peut donner nil, non génant
	--
	-- former le texte à afficher
	local newline = "<br>"
	-- Chaque parametre non nommé est traité par son synonyme.
	if n then return "" end
	if modes.option("docline", opt) then newline = "" end
	local argm = modes.args_known[paramName]
	if argm then
		--[ [
		if modes.docolor then --	Document the data sources by colors
			if argm.src == "wd" then
				paramData = viewer.wikidataColor(paramData)
			elseif argm.src == "args" then
				paramData = viewer.invoke_color(paramData)
		--	elseif argm.src == "inter" then
		--		paramData = viewer.inter_color(paramData)
			else
				paramData = viewer.otherColor(paramData)
			end
		end
		--] ]
		paramData = paramData -- .. (argm.trk or "")
	end
	return " | " .. paramName .. " = <b>" .. paramData .. "</b>" .. newline
end -- function modes.generDoc1(paramName, paramId, opt)

function modes.generDoc(opt, args_final, module_name)
	-- List of paramètres for the module documentation
	-- Lister des paramètres pour la documentation du module
	-- "docview" or ":" ajouter le panneau de documentation
	-- "docmin" quelques paramètres de base
	-- "docdef" seulement les paramètres définis, ayant une valeur non nulle
	-- "docmax" tous les paramètres connus
	-- "docnotice" generer les documentations des notices
	-- "docline" mettre tous les paramètres sur une seule ligne
	-- "docsrc" mettre les paramètres en couleurs selon les sources
	-- modes.options = " docdata docmin docdef docmax docline docview docafter docnotice docsrc: " -- for documentation
	-- modes.options = " erron noerr nobox nocat " -- without normal result
	-- modes.options = " debug tests en es fr " -- for debug or enforce language
	-- Option nocat means "Do not categorize and do not show categories."
	local args_known = modes.args_known
	local err = modes.verify_args_tables(args_known, modes.args_source)
	if err then return args_import, err end
	if type(module_name) ~= "string" then module_name = modes.module_name end
	if type(module_name) ~= "string" then module_name = modes.frame:getTitle() end -- main module, example "Auteur"
	if type(module_name) ~= "string" then module_name = "Central" end
	local n, t, val = 0, "", ""
	if type(args_final) ~= "table" then t = t.."err_args_final="..type(args_final).."<br>" end -- optional arguments
	if type(args_final) ~= "table" then args_final = modes.args_final end -- optional arguments
	local key, argknw, argval, arglingual = "", "", ""
	local lst = true
	local lst_doc, lst_1, lst_t = {}, {}, ""
	for key, parm in pairs(args_final) do -- for all known arguments
		val = ""
		n = tonumber(key)
		if n then key = tostring(parm["keyword"]) end -- key for unnamed arguments, in numeric sort
		argknw = args_known[key]
		arglingual = tostring(translate.user_translations[key]) -- multilingual name of the arg in the template
		if arglingual == "nil" then arglingual = key end -- if argument is translatable
		val = parm -- tostring(args_import[key])
		if not string.isDef(val) then val = "" end
		lst = false
		opt = opt .. " docdef " -- optional display
	--	if modes.option("docmin", opt) and argknw and (argknw["list"] == 1) then lst = true end
		if modes.option("docmin", opt) and argknw and (argknw["need"] > 0) then lst = true end
		if modes.option("docdef", opt) and (val ~= "") then lst = true end
		if modes.option("docmax", opt) then lst = true end
		if not args_known[key] then lst = false end
		if key and args_known[key] and args_known[key].typ == "sys" then lst = false end
		if lst then -- list if found and selected
			lst_t = modes.generDoc1(arglingual, n, val, opt)
			table.insert(lst_doc, {lst_t = lst_t, key = key, user_lang_key = translate.user_translations[key] or "trans"} )
		end
	end
	table.sort(lst_doc, function (a, b) return (a.user_lang_key < b.user_lang_key) end ) -- alphabetic sort of translated arguments
	for i, parm in ipairs(lst_doc) do -- List all found arguments
		t = t .. parm.lst_t or "lst_t"
	end
	t = "\n{{" .. module_name .. " " .. t .. "}}" -- <br>
	return t
end -- function modes.generDoc(opt, args_final, module_name)

function modes.sources_of_datas_colors()
	local res = ""
	if modes.docolor then --	Document the data sources by colors
		local datas_sources_of_datas = viewer.form9user("datas_sources_of_datas")
		--	datas_sources_of_datas = "Informations from: /Wikidata, /template or module, /other, /warning, /error",
		local splitxt = mw.text.split(datas_sources_of_datas, "/", true)
		res = res .. splitxt[1]	 .. " <b> " .. viewer.wikidataColor(splitxt[2]) .. viewer.invoke_color(splitxt[3]) .. viewer.otherColor(splitxt[4]) .. viewer.warningColor(splitxt[5]) .. viewer.errorColor(splitxt[6]) .. ".</b> <br>"
	end
	return res
end -- function modes.sources_of_datas_colors()

function modes.module_init(frame) -- Get modes.args_source with modes.args_config
	frame = frame or modes.frame or mw.getCurrentFrame()
	modes.frame = frame
	modes.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
--	Mix arguments from {{#invoke:}}, then arguments from the prioritary template which replace ones from {{#invoke:}}.
	local v2, nn, ni = 0, 0, 0
	local args_tab, mode = {}
	local templat = frame:getParent().args -- arguments from template
	local invoked = frame.args -- arguments from #invoke module
	 -- Mix invoked modified arguments from prioritary template arguments.
	 -- Argument 1 from template become the mode in #invoke. Other i arguments must be shifted.
	local args_tab = mw.clone(invoked)
	for key, val in pairs(templat) do -- template arguments can modify #invoke arguments.
		local key = mw.text.trim(key)
		local val = mw.text.trim(val)
		local i = tonumber(key)
		if i then
			if i == 1 then
				mode = val -- mode = template[1]
				args_tab.mode = val
			else args_tab[i-1] = val end -- transmit other unnamed arguments template[i], but shifted because the mode is in template[1]
		else args_tab[key] = val end -- transmit any named template arguments template. Even the mode, which replace template[1], behind.
	end
	modes.args_source = args_tab
	modes.args_config = modes.argsConfigIinit(modes.args_source) -- Extract and put apart args_config from args_source
	-- Default values of main module version.
	if versioning.main_versions then
		versioning.main_versions.versionName = versioning.main_versions.versionName or "Central0"
		versioning.main_versions.versionNumber = versioning.main_versions.versionNumber or "0.00"
		versioning.main_versions.versionDate = versioning.main_versions.versionDate or "2013-03-24"
	end
	return -- modes.args_config
end -- function modes.module_init(frame)

function modes.running_times( if_view, res, time1, time2, time3, time4)
	-- Basic result :
	-- Running times: , start in page = 937 mS , import + 0 mS , form result + 191 mS , tests + 537 mS , duration = 728 mS
	-- Execution and test_time: 2016-11-12 07:18:44 UTC , url = https://fr.wikisource.org/wiki/Module:Central-s-fr/Documentation
	local res = ""
	local mwuri = mw.uri.new()
	if modes.frame then
		res = res .. "\n* <b>Revision time</b>: " -- and stats https://fr.wikisource.org/wiki/Sp%C3%A9cial:Version
		res = res .. viewer.ta("module", modes.frame:preprocess( "{{REVISIONTIMESTAMP:" .. mw.getCurrentFrame():getTitle() .. "}}" ) )
		res = res .. viewer.ta("page", modes.frame:preprocess( "{{REVISIONTIMESTAMP}}" ) )
		res = res .. viewer.ta("user", modes.frame:preprocess( "{{REVISIONUSER}}" ) )
		res = res .. viewer.ta("REVISIONID", modes.frame:preprocess( "{{REVISIONID}}" ) )
		res = res .. "\n* <b>[[Special:Statistics | Special:Statistics on users and admins content]] "
		.. viewer.ta("CONTENTLANGUAGE", modes.frame:preprocess( "{{CONTENTLANGUAGE}}" ) )
	--	res = res .. "\n* <b>Versions</b>: " -- https://fr.wikisource.org/wiki/Sp%C3%A9cial:Version
	--	res = res .. "\n* <b>Special:Versions</b>: [[ Special:Version | MediaWiki version ]] " -- https://fr.wikisource.org/wiki/Sp%C3%A9cial:Version
--		local currentVersion = tostring(mw.site.currentVersion)
--		if currentVersion ~= versioning.site_currentVersion_memo then currentVersion = versioning.site_currentVersion_view end
	--	if currentVersion ~= versioning.site_currentVersion_memo then '<span style="color:red;" >' .. currentVersion .. ' has changed</span>' end
		res = res .. "\n* " .. viewer.ta("[[ Special:Version | Local MediaWiki version ]]", versioning.site_currentVersion_view )
		res = res .. ", [[ mw:Extension:Scribunto | Extension:Scribunto ]]"
		res = res .. viewer.ta("[http://tylerneylon.com/a/learn-lua/ Lua] [[ mw:Extension:Scribunto/Lua_reference_manual | version ]]", _VERSION )
		res = res .. "\n* <b>Languages</b>: "
		res = res .. viewer.ta("USERLANG", modes.frame:preprocess( "{{USERLANG}}" ) )
--		res = res .. viewer.ta("USERLANG", mw.getCurrentFrame():expandTemplate{ title = 'USERLANG' } )
		res = res .. viewer.ta("UILANGCODE", modes.frame:preprocess( "{{UILANGCODE}}" ) )
		res = res .. viewer.ta("USERIFCODE", modes.frame:preprocess( "{{USERIFCODE}}" ) )
		res = res .. viewer.ta("USERLANGUAGE", modes.frame:preprocess( "{{USERLANGUAGE}}" ) )
		res = res .. viewer.ta("PAGELANGUAGE", modes.frame:preprocess( "{{PAGELANGUAGE}}" ) )
--		res = res .. viewer.ta("PAGELANGUAGE", mw.getCurrentFrame():expandTemplate{ title = 'PAGELANGUAGE' } )
		res = res .. "\n* <b>Server mw.uri.new</b>: "
		res = res .. viewer.ta("protocol", mwuri.protocol )
		res = res .. viewer.ta("user", mwuri.user )
		res = res .. viewer.ta("password", mwuri.password )
		res = res .. viewer.ta("host", mwuri.host )
		res = res .. viewer.ta("port", mwuri.port )
		res = res .. viewer.ta("path", mwuri.path )
		res = res .. viewer.ta("query", mwuri.query )
		res = res .. viewer.ta("fragment", mwuri.fragment )
		res = res .. viewer.ta("userInfo", mwuri.userInfo )
		res = res .. viewer.ta("hostPort", mwuri.hostPort )
		res = res .. viewer.ta("authority", mwuri.authority )
		res = res .. viewer.ta("queryString", mwuri.queryString )
		res = res .. viewer.ta("relativePath", mwuri.relativePath )
	--	res = res .. mwuri:parse( "uri:parse : user, ( userInfo ), host-Port = hostPort" )
	end
	modes.time4 = os.clock()
	if not if_view then return "" end
	local time4 = time4 or modes.time4
	local time3 = time3 or modes.time3 or modes.time4
	local time2 = time2 or modes.time2 or modes.time3
	local time1 = time1 or modes.time1 or modes.time2
	if time2 < time1 then time2 = time1 end
	if time3 < time2 then time3 = time2 end
	if time4 < time3 then time4 = time3 end
	local nowtime = os.date("%Y-%m-%d %H:%M:%S")
	local mwtitle = mw.title.getCurrentTitle()
	local url = tostring(mwtitle:canonicalUrl( ))
	local time2d = time2 - time1
	local time3d = time3 - time2
	local time4d = time4 - time3
	local duration = time2d + time3d + time4d
	time1  = tostring(math.floor( time1	 * 1000 )) .. " mS"
	time2d = tostring(math.floor( time2d * 1000 )) .. " mS"
	time3d = tostring(math.floor( time3d * 1000 )) .. " mS"
	time4d = tostring(math.floor( time4d * 1000 )) .. " mS"
	duration = tostring(math.floor( duration * 1000 )) .. " mS"
	res = res .. "\n* <b>Execution and test_time</b>: " .. nowtime .. " UTC" .. viewer.ta("url", url) -- prefixedText
	res = res .. "\n* <b>Running times</b>: "
	res = res .. viewer.ta("start in page", time1) .. viewer.ta("import", time2d, "+")
	res = res .. viewer.ta("form result", time3d, "+") .. viewer.ta("tests", time4d, "+") .. viewer.ta("duration", duration)
	--[[ Display example :
	Revision time: , module = 20161223063818 , page = 20161216171553 , user = Rical , REVISIONID = 6431229
	Running times: , start in page = 680 mS , import + 0 mS , form result + 91 mS , tests + 58 mS , duration = 150 mS
	Execution and test_time: 2016-12-23 06:38:19 UTC , url = https://fr.wikisource.org/wiki/Module:Central-s-fr/Documentation
	--]]
	return res
end -- function modes.running_times( if_view, res, time1, time2, time3, time4)

function modes.init_tools(frame, options_for_modes, mode_name)
	local res = ""
	frame = frame or mw.getCurrentFrame()
	modes.frame = frame
	modes.options_for_modes = options_for_modes or modes.options_for_modes
	modes.mode_name = mode_name or modes.mode_name
	modes.mode_options = modes.options_from_mode(modes.mode_name)
--	events.errors_list = {} -- Table to collect errors and warnings
--	events.categories_list = {} -- init the collect of categories
--	events.save_configs() -- Save global configuration before eventual changes.
	return res
end -- function modes.init_tools(frame, options_for_modes, mode_name)

function modes.get_arg_mode(mode_name, source_key, lang, args_source)
	local mode_key = "mode"
	local args_source = args_source or modes.args_source
--	modes.wiki_lang = lang or args_source.wikilang or tostring(mw.language.getContentLanguage().code) or "en"
--	try_lang = try_lang or mw.language.getContentLanguage:getCode() or "en"
--	if type(versioning.main_i18n) == "table" and type(versioning.main_i18n[modes.wiki_lang]) == "table" then translate.wiki_translations = versioning.main_i18n[modes.wiki_lang] end
	if type(args_source) == "table" -- and type(args_source[source_key]) == "string"
	then mode_name = mode_name or args_source[source_key] or "read"
	else modes.mode_name = mode_name or modes.mode_name or "read" end
--	local mode_name = mode_name or args_source[source_key] or "read"
--	modes.mode_name = mode_name or modes.mode_name or "read"
	return mode_name, source_key, try_lang
end -- function modes.get_arg_mode(mode_name, source_key, lang, args_source)

function modes.all_categories_list(t) -- all_categories_list_title
	local t = "\n* " .. (t or " <b>List all eventual categories of this wiki:</b>")
	for key, txt in pairs(translate.user_translations) do
		if string.is_in("cat_", key) or string.is_in("_cat", key) then
			txt = viewer.form9user(txt, "**", "**", "**", "**", "**", "**")
			t = t .. "<br>" .. viewer.ta(key, txt)
		end
	end
	return t
end -- function modes.all_categories_list(t)

function modes.all_errors_list(t) -- all_errors_list_title
	local t = "\n* " .. (t or " <b>List all detectable errors of this wiki:</b>")
	for key, txt in pairs(translate.user_translations) do
		if string.is_in("err_", key) or string.is_in("_err", key) then
			txt = viewer.form9user(txt, "**", "**", "**", "**", "**", "**")
			t = t .. "<br>" .. viewer.ta(key, txt)
		end
	end
	return t
end -- function modes.all_errors_list(t)

function modes.list_all_args_main(t, args_known) -- modes_list_all_args_main_title = List of all arguments for main
	local t = t or "\n* <b>list_all_args_main :</b> "
	if type(args_known) ~= "table" then args_known = modes.args_known end
	t = t .. viewer.ta("args N", #args_known )
	local descr, description = "", ""
	local args = mw.clone(args_known)
	local arglst = {}
	for key, elem in pairs(args) do
		elem.key = tostring(key)
		descr = key .. "_descr" -- key for description of an argument
		elem.description = translate.user_translations[descr] or "**missing translation**"
		elem.user_lang_key = translate.user_translations[elem.key] or "**missing key**"
		elem.user_lang_keyword = translate.user_translations[elem.keyword] or "**missing keyword**"
	--	elem.base_id = elem.base_id
	--	elem.base_base = elem.base_base
	--	elem.not_type = elem.not_type
		table.insert(arglst, elem)
	end -- insert in the arguments their own key
	table.sort(arglst, function (a, b) return (a.user_lang_key < b.user_lang_key) end ) -- alphabetic sort of translated arguments
	local gr_sys, gr_config, gr_need, gr_other, gr_authority = {}, {}, {}, {}, {}
	for i, elem in ipairs(arglst) do -- group arguments in some groups
		if elem.need == 1 or elem.need == 2 then table.insert(gr_need, elem)
		elseif elem.typ == "sys" then table.insert(gr_sys, elem)
		elseif elem.typ == "config" then table.insert(gr_config, elem)
		elseif elem.base_base then table.insert(gr_authority, elem)
		else table.insert(gr_other, elem) end
	end
	local needed = viewer.smallCapsStyle(viewer.form9user("modes_needed_to_verify"))
	local function list_group( group, needed )
		needed = needed or ""
		local t = ""
		for key, elem in pairs(group or {}) do
			if elem.syn
			then t = t .. "<br>* <b>" .. tostring(elem.user_lang_key) .. "</b> => <b>" .. elem.user_lang_keyword .. "</b> : " .. needed .. " " .. tostring(elem.description) -- .. elem.lev_arg_txt
			else t = t .. "<br>* <b>" .. tostring(elem.user_lang_key) .. "</b> : " .. needed .. " " .. tostring(elem.description) end
		end
		return t
	end
	t = t .. "<br><br>* <b>" .. viewer.form9user("modes_list_needed_args") .. "</b> " .. list_group( gr_need, needed )
	t = t .. "<br><br>* <b>" .. viewer.form9user("modes_list_all_other_args") .. "</b> " .. list_group( gr_other )
	t = t .. "<br><br>* <b>" .. viewer.form9user("list_all_authorities") .. "</b> " .. list_group( gr_authority )
	t = t .. "<br><br>* <b>" .. viewer.form9user("modes_list_all_config_arguments") .. "</b> " .. list_group( gr_config )
	t = t .. "<br><br>* <b>" .. viewer.form9user("modes_list_all_system_arguments") .. "</b> " .. list_group( gr_sys )
	return t
end -- function modes.list_all_args_main(t, args_known) -- modes_list_all_args_main_title for main

function modes.recursiveNormal( recursiveLevel_in, recursiveLimit_in, modes_recursiveLevel_err ) -- Normalize recursiveLevel and recursiveLimit
--	recursiveLevel, recursiveLimit, recursiveLevel_err == modes.recursiveNormal(recursiveLevel, recursiveLimit)
	-- default modes.recursiveLimit
	local recursiveLevel, recursiveLimit = recursiveLevel_in, recursiveLimit_in
--	local recursiveLevel_err = ""
--	local modes_recursiveLevel_err = modes_recursiveLevel_err or "modes_recursiveLevel_err"
	-- default modes_recursiveLevel_err
	if type(modes_recursiveLevel_err) ~= "string" then modes_recursiveLevel_err = "modes_recursiveLevel_err" end
	if type(modes.recursiveLimit) ~= "number" then modes.recursiveLimit = 11111 end
	if modes.recursiveLimit < 1 then modes.recursiveLimit = 11111 end
	-- default recursiveLimit
	if type(recursiveLimit) ~= "number" then recursiveLimit = modes.recursiveLimit end
	-- If recursiveLimit is defined, it can be greater or lesser than modes.recursiveLimit
	recursiveLimit = math.floor(recursiveLimit) + 0.999999
	if recursiveLimit < 1 then recursiveLimit = modes.recursiveLimit end
	-- default recursiveLevel
	if type(recursiveLevel) ~= "number" then recursiveLevel = 1 end
	recursiveLevel = math.floor( recursiveLevel ) + 0.999999
	if recursiveLevel < 1 then recursiveLevel = 1 end
	--
	if recursiveLevel > recursiveLimit then
		recursiveLevel_err = viewer.form9user( modes_recursiveLevel_err, recursiveLevel, recursiveLimit)
		recursiveLevel_err = viewer.errorColor( tostring(recursiveLevel_err) )
	end
	--	{ 70, "Other", "T63958", 0, "Open", "Use existing $dateFormats to format dates on Wikidata", "Wikidata $dateFormats", },
	--[[
	recursiveLevel_err = viewer.form9user( "modes_recursiveLevel_err", recursiveLevel, recursiveLimit)
	if recursiveLevel > recursiveLimit then recursiveLevel_err = viewer.errorColor( tostring(recursiveLevel_err) ) end
	if recursiveLevel > recursiveLimit then ------
		recursiveLevel_err = viewer.strTestCase( recursiveLevel_err, recursiveLevel, recursiveLimit)
		recursiveLevel_err = viewer.errorColor( tostring(recursiveLevel_err) )
		recursiveLevel_err = viewer.form9user( recursiveLevel_err, recursiveLevel, recursiveLimit)
		recursiveLevel_err = viewer.errorColor( tostring(recursiveLevel_err) )
	end
	--]]
	return recursiveLevel, recursiveLimit, recursiveLevel_err
end -- function modes.recursiveNormal( recursiveLevel_in, recursiveLimit_in, modes_recursiveLevel_err )

function modes.recursiveNormal_tests( t ) -- Test Normalize recursiveLevel and recursiveLimit
	local memo = events.save_configs() -- Save global configuration before eventual changes.
--	recursiveLevel, recursiveLimit, recursiveLevel_err == modes.recursiveNormal(recursiveLevel, recursiveLimit)
	modes.recursiveLimitMemo = modes.recursiveLimit -- memorize before test value
	local t = t or "\n* modes.recursiveNormal_tests: "
	local function recursiveNormal_test1(recursiveLevel_in, recursiveLimit_in, modes_recursiveLevel_err)
		local recursiveLevel, recursiveLimit, recursiveLevel_err =
			modes.recursiveNormal( recursiveLevel_in, recursiveLimit_in, modes_recursiveLevel_err or "modes_recursiveLevel_err")
	--	modes.recursiveNormal( recursiveLevel_in or modes.recursiveLimit or 1, recursiveLimit_in or modes.recursiveLimit or 3 )
	--	return "\n* " .. "recursiveLevel = " .. (recursiveLevel or "recursiveLevel") .. ", recursiveLimit = " .. (recursiveLimit or "recursiveLimit")
		return "\n* " .. ( recursiveLevel_err or ("recursiveLevel_err:" .. viewer.ta("recursiveLevel", recursiveLevel) .. viewer.ta("recursiveLimit", recursiveLimit) ) )
	end
	t = t .. recursiveNormal_test1()
	t = t .. recursiveNormal_test1(-12, 5)
	t = t .. recursiveNormal_test1(0, 5)
	t = t .. recursiveNormal_test1(1, 5)
	t = t .. recursiveNormal_test1(2.718, 3.1416)
	t = t .. recursiveNormal_test1(3)
	t = t .. recursiveNormal_test1(5, 5)
	t = t .. recursiveNormal_test1(6, 5)
	t = t .. recursiveNormal_test1(11111, 5, "versioning_module_miss_i18n_txt_err")
	t = t .. recursiveNormal_test1(11112, 5, "versioning_module_miss_i18n_count_err")
	t = t .. recursiveNormal_test1(23456, 5, "modes_recursiveLevel_err")
	modes.recursiveLimit = modes.recursiveLimitMemo -- restaure before test value
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function modes.recursiveNormal_tests(recursiveLevel, recursiveLimit)

function modes.spacesPageNamesTest(t)
	if type(t) ~= "string" then t = nil end
	local t = t or "\n* <b>spaces_page_names_test</b> :"
	local mwtitle = mw.title.getCurrentTitle()
	t = t .. viewer.ta("mwtitle", mwtitle)
	local nsText = mwtitle.nsText
	t = t .. viewer.ta("nsText", nsText)
	local baseText = mwtitle.baseText -- namespace for the page
	t = t .. viewer.ta("baseText", baseText)
	local url = tostring(mwtitle:canonicalUrl( ))
	t = t .. viewer.ta("url", url)
	--
	t = t .. "\n* Module namespace : "
	local ns = mw.site.namespaces
	if ns and ns[828] then
		t = t .. viewer.ta("id828", ns[828].id)
		t = t .. viewer.ta("canonicalName828", ns[828].canonicalName)
		t = t .. viewer.ta("name828", ns[828].name)
		t = t .. viewer.ta("displayName828", ns[828].displayName)
	end
	t = t .. "\n* All namespaces (from 0 to 2000) : "
	local ns = ""
	for ins = 0, 2000 do
	--	if mw.site.contentNamespaces[ins] then
		if mw.site.namespaces[ins] then
			ns = mw.site.namespaces[ins].canonicalName
			t = t .. viewer.ta(ns, ins)
		end
	end
	return t
end -- function modes.spacesPageNamesTest()

function modes.warning_short()
--	local warning_versions = modes.warning_short() -- in Module:Author3
--	versioning.deprecatedFunction("modes.interact_args_final", "p.interact_args_final")
--	versioning.report_main_short = main_versions.versionName .. " " .. main_versions.versionNumber
	local warning_versions = versioning.report_main_short
	return warning_versions
end


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:string come from Scribunto. The Module:Central add some functions to it.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- string = {} -- already declared by Scribunto, else in _G space.
-- Translations for string library
string.i18n = {}
string.i18n.en = {
	string_test_no_values				= "Test: without values",
	string_test_the_value_is_table		= "Test: the value is a table",
	string_test_all_types_values		= "Test: values of all types",
} -- string.i18n.en
string.i18n.es = {
	string_test_no_values				= "Prueba: sin valores",
	string_test_the_value_is_table		= "Prueba: el valor es una tabla",
	string_test_all_types_values		= "Prueba: valores de todos los tipos",
} -- string.i18n.es
string.i18n.fr = {
	string_test_no_values				= "Test: sans valeurs",
	string_test_the_value_is_table		= "Test: la valeur est une table",
	string_test_all_types_values		= "Test: valeurs de tous types",
} -- string.i18n.fr

--	 See also string.format( formatstring, ... ) -- 2016-06-25 Rical
--	 Example printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec); -- 2016-06-25 Rical
--	 See also http://pubs.opengroup.org/onlinepubs/9699919799/functions/printf.html -- 2016-06-25 Rical

function string.is_in(word, text) -- The word is it in the text, even inside another word?
	local i = string.find(text or "", word or "", 1, true)
	return (i and true)
end

function string.is_in_sp(word, text, sep) -- The word is it in the text, beetwen spaces or separators, not inside another word?
	sep = sep or " " -- default separator is space. But could be * ; , or even any string.
	if type(word) ~= "string" then word = "" end
	if type(text) ~= "string" then text = "" end
	local i = string.find(sep .. text .. sep, sep .. word .. sep, 1, true)
	if i then i = true else i = false end
	return i
end

function string.isDef(x) -- x is defined or no. x est défini ou non
	return (type(x) == "string") and (x ~= "") and (x ~= " ") and (x ~= "nil")
end


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:translate supports i18n translations.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- translate = {} -- already declared by Scribunto, else in _G space.
-- Translations for translate library
translate.i18n = {}
translate.i18n.en = {
	language								= 'language',
	translate_translationsCounts_title		= "translate.translationsCounts() Counts of translations in tables",
	viewer_form9user_tests_title			= "viewer.form9user() Test: Replace &%;1 to %9 by tostring( argN ) from arguments",
	translate_form9user_all_types_values	= "Test: string=%1 number=%2 nil=%3 function=%4 table=%5.",
	viewer_form99user_tests_title		= "in viewer.form99user() Test: Replace %01 to &%;29 by tostring( argN ) from arguments",
	translate_form99user_all_types_values	= "in viewer.form99user() Values of all types: %1, %2, %3, %4, %5.",
	translate_form99user_no_tranlation		= "in viewer.form99user() Lack of translation.",
	translate_form99user_a_value_is_table	= "in viewer.form99user() A value is a table.",
	translate_translations_key_missing		= "Internal error: Translations key missing or abnormal in translate.trans9vars()",
	translate_lang_not_exist_err			= "Error: The language <b>%1</b> is not available.",
	translate_abnormal_char_in_text_title	= "translate.abnormal_char_in_text() Test: Detect abnormal characters in an unilingual text.",
	translate_abnormal_char_in_alphabet		= "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 013456789 ,;.:!?",
	translate_list_Mediawiki_languages_title= "translate.list_Mediawiki_languages() List all Mediawiki languages.",
	-- Main texts, errors and categories of tools
	translate_first_doc_languages			= "Documentation of first languages",
	translate_user_wiki_lang_msg			= "Languages: user: %1, wiki: %2.",
	translate_without_translation_err		= "Known argument, but not translated: <b>%1</b>.",
	translate_without_translation_N_err		= "There are %1 arguments untranslated.",
	translate_main_i18n_languages_list		= "This module can translate <b>%1</b> sentences into <b>%2</b> languages: ",
	translate_i18n_list_all_texts			= "This list show all the texts, but cannot replace the original declarations.",
	translate_languages_nbr_and_list		= "\n* There are %1 tables of translations in these languages : %2 <br>",
	-- Miscellaneous warnings and errors
	translate_texts_translated_in_langs		= "There are %1 texts translated in %2 languages.",
	translate_no_wiki_translations_err		= "Error: Module without translated arguments table.",
	translate_list_in_translated_lang		= "\n* %1 translations in language %2 : %3 : %4",
	translate_missing_translations_title	= "translate.missing_translations() Missing translations in i18n tables.",
	translate_transdiff_wanted_error_err	= "Wanted error for minimal test of translate.missing_translations().",
	translate_missing_translations_headers	= "Language; i18n Key; Previous translation; New translation; Module",
	translate_changing_translations_title	= "translate.changing_translations() Changing (or missing) translations in i18n tables.",
	translate_changing_translations_headers = "Language; i18n Key; Previous translation; New translation; Module",
	translate_changing_translations_help	= "See also %1 to ask for help to translate.",
	translate_mixed_translations_title		= "translate.i18n_lister() Combined i18n translations tables:",
	translate_dummy_languages_title			= "translate.dummy_languages() Languages Definitions:",
	translate_Module_Central_version		= "Central-w-en",
} -- translate.i18n.en

translate.i18n.es = {
	language								= 'lenguaje',
	translate_translationsCounts_title		= "translate.translationsCounts() Condes des traducciones en tablas",
	viewer_form9user_tests_title			= "viewer.form9user() Prueba: Reemplazar %1 a %9 por tostring( argN ) a partir de argumentos",
	translate_form9user_all_types_values	= "Prueba: string=%1 number=%2 nil=%3 function=%4 table=%5.",
	viewer_form99user_tests_title		= "en viewer.form99user() Prueba: Reemplazar %01 a %29 por tostring( argN ) a partir de argumentos",
	translate_form99user_all_types_values	= "en viewer.form99user() Valores de todos los tipos: %1, %2, %3, %4, %5.",
	translate_form99user_no_tranlation		= "en viewer.form99user() Falta de traducción.",
	translate_form99user_a_value_is_table	= "en viewer.form99user() Un valor es una tabla.",
	translate_translations_key_missing		= "Error interno: Traducciones clave que falta o anormal en translate.trans9vars()",
	translate_lang_not_exist_err			= "Error: El lenguaje <b>%1</b> no está disponible.",
	translate_abnormal_char_in_text_title	= "translate.abnormal_char_in_text() Prueba: Detectar caracteres anormales en un texto monolingüe.",
	translate_abnormal_char_in_alphabet		= "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 013456789 àéèêëiouüœ ÀÉÈÊËIOUÜŒ ,;.:!?",
	translate_list_Mediawiki_languages_title= "translate.list_Mediawiki_languages() Lista de todos los idiomas de Mediawiki.",
	-- Textos principales, errores y categorías de instrumentos
	translate_first_doc_languages			= "Documentación de las primeras idiomas",
	translate_user_wiki_lang_msg			= "Idiomas: usuario: %1, wiki: %2.",
	translate_without_translation_err		= "Argumento conocido, pero no traducido: <b>%1</b>.",
	translate_without_translation_N_err		= "Hay 1% argumentos no traducidos.",
	-- Diversos mensajes y errores
	translate_main_i18n_languages_list		= "Este módulo puede traducir <b>%1</b> frases en <b>%2</b> idiomas: ",
	translate_languages_nbr_and_list		= "\n* Hay %1 mesas de traducciones en estas idiomas: %2 <br>",
	translate_texts_translated_in_langs		= "Hay 1% textos traducidos en 2% idiomas.",
	translate_no_wiki_translations_err		= "Error: Módulo sin argumentos traducidos tabla.",
	translate_i18n_list_all_texts			= "Esta lista muestra todos los textos, pero no puede sustituir a las declaraciones originales.",
	translate_list_in_translated_lang		= "\n* %1 traducciones en idioma %2 : %3 : %4",
	translate_missing_translations_title	= "translate.missing_translations() Traducciones que falta de las tablas i18n.",
	translate_missing_translations_headers	= "Idioma; Clave i18n; Traducción anterior; Nueva traducción; Módulo",
	translate_changing_translations_title	= "translate.changing_translations() Traducciones que cambian (o falta de) en las tablas i18n.",
	translate_changing_translations_headers = "Idioma; Clave i18n; Traducción anterior; Nueva traducción; Módulo",
	translate_changing_translations_help	= "Consulte también %1 para solicitar ayuda para traducir.",
	translate_mixed_translations_title		= "Tablas traducciones i18n combinadas:",
	translate_dummy_languages_title			= "translate.dummy_languages() Definiciones de idiomas:",
	translate_Module_Central_version		= "Central-w-es",
} -- translate.i18n.es

translate.i18n.fr = {
	language								= 'langue',
	translate_translationsCounts_title		= "translate.translationsCounts() Comptages des traductions en tables",
	viewer_form9user_tests_title			= "viewer.form9user() Test: Remplacer %1 à %9 par tostring( argN ) à partir des arguments",
	translate_form9user_all_types_values	= "Test: string=%1 number=%2 nil=%3 function=%4 table=%5.",
	viewer_form99user_tests_title		= "viewer.form99user() Test: Remplacer %01 à %29 par tostring( argN ) à partir des arguments",
	translate_form99user_all_types_values	= "en viewer.form99user() Valeurs de tous types : %1, %2, %3, %4, %5.",
	translate_translations_key_missing		= "Erreur interne: Clé de traduction manquante ou anormale dans translate.trans9vars()",
	translate_form99user_no_tranlation		= "en viewer.form99user() Manque de traduction.",
	translate_form99user_a_value_is_table	= "en viewer.form99user() Une valeur est une table.",
	translate_lang_not_exist_err			= "Erreur : La langue <b>%1</b> n'est pas disponible.",
	translate_abnormal_char_in_text_title	= "translate.abnormal_char_in_text() Test: Détecter les caractères anormaux dans un texte unilingue.",
	translate_abnormal_char_in_alphabet		= "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 013456789 àéèêëiouüœ ÀÉÈÊËIOUÜŒ ,;.:!?",
	translate_list_Mediawiki_languages_title= "translate.list_Mediawiki_languages() Liste de toutes les langues de Mediawiki.",
	-- Principaux textes, erreurs et catégories des outils
	translate_first_doc_languages			= "Documentation des premières langues",
	translate_user_wiki_lang_msg			= "Langues : utilisateur : %1, wiki : %2.",
	translate_without_translation_err		= "Argument connu, mais non traduit : <b>%1</b>.",
	translate_without_translation_N_err		= "Il y a %1 arguments non traduits.",
	translate_main_i18n_languages_list		= "Ce module peut traduire <b>%1</b> phrases en <b>%2</b> langues : ",
	translate_i18n_list_all_texts			= "Cette liste montre tous les textes, mais ne peut remplacer les déclarations originales.",
	translate_languages_nbr_and_list		= "\n* Il y a %1 tables de traductions dans ces langues : %2 <br>",
	translate_texts_translated_in_langs		= "Il y a %1 textes traduits en %2 langues.",
	translate_no_wiki_translations_err		= "Erreur interne : Module sans table d'arguments traduits.",
	translate_list_in_translated_lang		= "\n* %1 traductions en langage %2 : %3 : %4",
	translate_missing_translations_title	= "translate.missing_translations() Traductions manquantes dans les tables i18n.",
	translate_missing_translations_headers	= "Langue; Clé i18n; Traduction précédente; Nouvelle traduction; Module",
	translate_changing_translations_title	= "translate.changing_translations() Traductions changeantes (ou manquantes) dans les tables i18n.",
	translate_changing_translations_headers = "Langue; Clé i18n; Traduction précédente; Nouvelle traduction; Module",
	translate_changing_translations_help	= "Voir aussi %1 pour demander de l'aide pour traduire.",
	translate_mixed_translations_title		= "Tables de traductions i18n combinées :",
	translate_dummy_languages_title			= "translate.dummy_languages() Définitions de langues :",
	translate_Module_Central_version		= "Central-w-fr",
} -- translate.i18n.fr

function translate.main_i18n_languages_list() -- List available translations languages
--	local text, tot_vars, tot_tabs, tot_func, vr_tb = luaTable.formSubCounts(versioning.main_i18n, "versioning.main_i18n") -- count of internal .i18n
	local tot_vars, tot_tabs, tot_func, vr_tb = luaTable.SubCounts(versioning.main_i18n, "versioning.main_i18n") -- count of internal .i18n
	local tab_to_sort = {}
	versioning.main_i18n = versioning.main_i18n or {}
	for lang, modname in pairs(versioning.main_i18n) do
		table.insert(tab_to_sort, lang) -- build a table with only languages codes
	end
	table.sort(tab_to_sort, function (a, b) -- sort le languages codes in stable alphabetic order
		return	( a < b )
	end )
--	local trans_per_lang = vr / #tab_to_sort
--	trans_per_lang = math.vv( trans_per_lang )
	local t = viewer.form9user("translate_main_i18n_languages_list", vr_tb, tot_tabs) -- #tab_to_sort) -- #tab_to_sort
	for i, lang in pairs(tab_to_sort) do -- describe each language
		local nativename = mw.language.fetchLanguageName(lang)
		local englishname = mw.language.fetchLanguageName(lang, "en")
		t = t .. viewer.form9user("<b>%1</b>(%2=%3), ", nativename, lang, englishname)
	end -- see events.selectLang
	return t -- translate_main_i18n_languages_list
end -- function translate.main_i18n_languages_list()

function translate.formTestCase(ref, ...) -- Form ref-v1-v2-v3-...
	local t = tostring(ref)
	for i, var in pairs( { ... } ) do
		if var then t = t .. "|" .. viewer.value(var) end -- tostring() or viewer.value() ?
	end
	return t
end

--	translate.Central_x_y = "<Central_x_y>" -- To adapt the version of Module:Central in any translated text.
translate.Module_Central_version = "Central-w-fr" -- To adapt the version of Module:Central in any translated text.

function translate.trans9vars(translations, ref, ...) -- Replace %1 to %9 by tostring( argN ) from arguments, without any ambiguity in any string.
	local S9, repl, val, charN
	if type(ref) ~= "string" then
		ref = "translate_translations_key_missing"
		ref = translate.formTestCase(ref, ...)-- .. "+fTC"
		return ref -- .. "+t9v"
	end
	if type(translations) ~= "table" then return translate.formTestCase(ref .. "+notrans", ...) end
	if type(translations[ref]) == "string" then ref = translations[ref] end
	local args = {...}
	if #args > 0 then
		local n = 1
		for n = 9, 1, -1 do
			S9 = tostring(n)
			charN = string.find(ref, "%".. S9, 1, true)
			if charN then -- If reference has %0 to %9
				repl = tostring( args[n] or "" )
				ref = string.sub(ref, 1, charN - 1) .. repl .. string.sub(ref, charN + string.len( "%".. S9 )  )
			end
		end
	end
	ref = string.gsub(ref, p.v.version, translate.Module_Central_version or "p.v.version")
	-- Erreur Lua dans Module:Central-s-fr à la ligne 3274 : bad argument #3 to 'gsub' (string/function/table expected).
	return ref -- .. "+t9v"
end -- function translate.trans9vars(translations, key, ...)

function viewer.form9wiki(ref, ...) -- replace %1 to %9 by v1 to v9 in the wiki translation of ref, else in ref
	local lang, tlts = "en", nil
	local form, n = "", 0
	if not viewer.activate_view then return "" end -- Mask or Unmask all views to avoid failures.
	if (type(translate.wiki_translations) == "table") -- Only after versioning.bind_all_i18n()
--	and (type(translate.wiki_translations.en.versioning_luatable_counts) == "string") -- Only after versioning.bind_all_i18n()
	then form = form .. translate.trans9vars(translate.wiki_translations, ref, ...) -- .. "+f9w"
	else form = form .. translate.formTestCase(ref, ...) .. "+wTC" end
	return form
end -- function viewer.form9wiki(ref, ...)

function translate.trans99vars(translations, ref, ...) -- Replace %01 to %99 by tostring( argN ) from arguments, without any ambiguity in any string.
	local S99, repl, val, charN
	if type(translations) ~= "table" then return translate.formTestCase(ref, ...) end
	if type(ref) ~= "string" then ref = "translate_translations_key_missing" end
	if type(translations[ref]) == "string" then ref = translations[ref] end
	local args = {...}
	if #args > 0 then
		local n = 1
		for n = 99, 1, -1 do
			S99 = tostring(n)
			if n < 10 then S99 = "0" .. S99 end
			charN = string.find(ref, "%".. S99, 1, true)
			if charN then -- If reference has %01 to %99
				repl = tostring( args[n] or "" )
				ref = string.sub(ref, 1, charN - 1) .. repl .. string.sub(ref, charN + string.len( "%".. S99 )	)
			end
		end
	end
	return ref
end -- function translate.trans99vars(translations, ref, ...)

function viewer.form99wiki(ref, ...) -- Replace %01 to %99 by tostring( argN ) from arguments, without any ambiguity in any string.
	local form = translate.trans99vars(translate.wiki_translations, ref, ...)
	return form
end

function viewer.form99user(ref, ...) -- Replace %01 to %99 by tostring( argN ) from arguments, without any ambiguity in any string.
	local form = translate.trans99vars(translate.user_translations, ref, ...)
	return form
end

function tools.strVars(ref, ...)
	-- replace %1 to %9 by v1 to v9 in the user translation of ref, else in ref
	versioning.deprecatedFunction("tools.strVars", "viewer.form9user")
	return viewer.form9user(ref, ...)
end -- translate
function viewer.form9user(ref, ...) -- Replace %1 to %9 by tostring( argN ) from arguments, without any ambiguity in any string.
--	local lang, tlts = "en", nil
	local form, n = "", 0
	if not viewer.activate_view then return "" end -- Mask or Unmask all views to avoid failures.
	if (type(translate.user_translations) == "table") -- Only after versioning.bind_all_i18n()
--	and (type(translate.user_translations.en.versioning_luatable_counts) == "string") -- Only after versioning.bind_all_i18n()
	then form = form .. translate.trans9vars(translate.user_translations, ref, ...) -- .. "+f9u"
	else form = form .. translate.formTestCase(ref, ...) .. "+fTC" end
	return form
end -- function viewer.form9user(ref, ...)

function viewer.form9user_test(t) -- Tests of viewer.form9user_test().
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>viewer.form9user_test(t, ref, ...)</b>:"
	t = t .. viewer.Th() .. viewer.Tc("input string and args") .. viewer.Tc("viewer.form9user_test") -- .. viewer.Tc("string.format")
	local function form9user_test1(ref, ...)
	--	local form9user, t9 = translate.trans9vars(translate.user_translations, ref, ...)
		local form9user, t9 = viewer.form9user(ref, ...)
		return viewer.Tr() .. viewer.Td( ref ) .. viewer.Td( form9user ) -- .. t9
	end
	t = t .. form9user_test1("translate_form9user_no_tranlation")
	t = t .. form9user_test1("string_test_no_values") -- translate_form9user_a_value_is_table
	t = t .. form9user_test1("translate_form9user_a_value_is_table", {})
	t = t .. form9user_test1("translate_form9user_all_types_values", "abc", 4, nil, function(x) end, { 55, "xyz" } )
	t = t .. form9user_test1("replace: abc%1efg%2ijk", "<b>D</b>", "<b>H</b>")
	t = t .. form9user_test1("repeat: a=%1 b=%2 c=%1 d=%2", "<b>1</b>", "<b>2</b>")
	t = t .. form9user_test1("altern: 1%13%25", "<b>2</b>", "<b>4</b>")
	t = t .. form9user_test1("list: %0<%1<%2<%3<%4<%5<%6<%7<%8<%9", "00", "11", "22", "33", "44", "55", "66", "77", "88", "99")
	t = t .. form9user_test1("mix: %1, %5, %2, %4, %3, ", "A", "B", "C", "D", "E")
	t = t .. form9user_test1("missing args: yes=%1 no=%5 no=%9 yes=%2", "args[1]", "args[2]")
	t = t .. form9user_test1("MATRIX 4 * 5<br>"
	.. "{ %1%5, %1%6, %1%7, %1%8, %1%9 }<br>"
	.. "{ %2%5, %2%6, %2%7, %2%8, %2%9 }<br>"
	.. "{ %3%5, %3%6, %3%7, %3%8, %3%9 }<br>"
	.. "{ %4%5, %4%6, %4%7, %4%8, %4%9 }<br>", "a", "b", "c", "d", "1", "2", "3", "4", "5")
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function viewer.form9user_test(t)

function viewer.form99user_test(t) -- Tests of viewer.form99user_test().
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>viewer.form99user_test(t, ref, ...)</b>:"
	t = t .. viewer.Th() .. viewer.Tc("input string and args") .. viewer.Tc("viewer.form99user_test")
	local function form99user_test1(ref, ...)
	--	local form99user, t99 = translate.trans99vars(translate.user_translations, ref, ...)
		local form99user, t99 = viewer.form99user(ref, ...)
		return viewer.Tr() .. viewer.Td( ref ) .. viewer.Td( form99user ) -- .. t9
	end
	t = t .. form99user_test1("translate_form99user_no_tranlation")
	t = t .. form99user_test1("string_test_no_values")
	t = t .. form99user_test1("translate_form99user_a_value_is_table", {})
	t = t .. form99user_test1("translate_form99user_all_types_values", "abc", 4, nil, function(x) end, { 55, "xyz" } )
	t = t .. form99user_test1("replace: abc%01fg%02jk", "<b>DE</b>", "<b>HI</b>")
	t = t .. form99user_test1("repeat: a=%01 b=%02 c=%01 d=%02", "<b>01</b>", "<b>02</b>")
	t = t .. form99user_test1("altern: 01%0145%0289", "<b>23</b>", "<b>67</b>")
	t = t .. form99user_test1("list: %00<%01<%02<%03<%04<%05<%06<%07<%08<%09", "00", "11", "22", "33", "44", "55", "66", "77", "88", "99")
	t = t .. form99user_test1("mix: %01, %05, %02, %04, %03, ", "A", "B", "C", "D", "E")
	t = t .. form99user_test1("missing args: yes=%01 no=%05 no=%09 yes=%02", "args[1]", "args[2]")
	t = t .. form99user_test1("matrix: %10<br>"
	.. "{ %01%05, %01%06, %01%07, %01%08, %01%09 }<br>"
	.. "{ %02%05, %02%06, %02%07, %02%08, %02%09 }<br>"
	.. "{ %03%05, %03%06, %03%07, %03%08, %03%09 }<br>"
	.. "{ %04%05, %04%06, %04%07, %04%08, %04%09 }<br>", "a", "b", "c", "d", "1", "2", "3", "4", "5", "MATRIX 4 * 5")
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function viewer.form99user_test(t)

function translate.i18n_trac(id, t, count) -- track for i18n DEBUG
--	translate.i18n_trac("base_func", "end", 1) -- example of use
	local t = t or ""
	t = t .. "<br><b>" .. (id or "") .. "</b> t=" .. t
	if count then
		t = t .. "<br>* " .. luaTable.formSubCounts("versioning.main_i18n")
		t = t .. "<br>* " .. luaTable.formSubCounts("wiki_translations")
		t = t .. "<br>* " .. luaTable.formSubCounts("user_translations")
	end
	return t
end

function modes.init_wiki_user_lang(wiki_lang, user_lang, page_lang)
	versioning.deprecatedFunction("modes.init_wiki_user_lang", "translate.init_wiki_user_page_lang")
	return translate.init_wiki_user_page_lang(wiki_lang, user_lang, page_lang)
end
-- en : Initialize or modify the language of the wiki and of the user and of the page and their tables.
-- es : Inicializar o cambiar el idioma del wiki y del usuario y de la página y sus tablas.
-- fr : Initialiser ou modifier la langue du wiki et de l'utilisateur et de la page et leurs tables.
function translate.init_wiki_user_page_lang(wiki_lang, user_lang, page_lang)
	-- initialize or change the wiki, the user and the page languages and their tables
	-- See https://meta.wikimedia.org/wiki/Translate_extension and [[Special:PageLanguage]] 2016-12-19
	local res = ""
	local getContentLanguage = mw.language.getContentLanguage().code -- default wiki_lang
	if type(versioning.main_i18n) == "table"
		then translate.main_i18n = versioning.main_i18n
		else translate.main_i18n = translate.i18n end
	if modes.args_config then wiki_lang = wiki_lang or modes.args_config.wikilang end
	if modes.args_config then user_lang = user_lang or modes.args_config.userlang end
	--
	-- WIKI -- Activate the wiki language for errors, warnings and categories.
	if wiki_lang and versioning.main_i18n and versioning.main_i18n[wiki_lang] then -- Activate the wiki language for errors, warnings and categories.
		translate.wiki_lang = wiki_lang
	else -- if sought language is unavailable uses default value.
		translate.wiki_lang = getContentLanguage -- default wiki_lang
	end
	if translate.wiki_lang and versioning.main_i18n and versioning.main_i18n[translate.wiki_lang] then
		translate.wiki_translations = versioning.main_i18n[translate.wiki_lang]
	--	translate.wiki_translations = translate.wiki_translations
	else
		translate.wiki_translations = versioning.i18n.en -- wiki's default translations
	--	translate.wiki_translations = translate.wiki_translations
	end
	--
	-- USER -- Activate the user language for errors, warnings and categories.
	translate.user_lang = translate.wiki_lang -- default user like wiki
--	function object.setupInterface( options )
--	user_lang = user_lang or ParserOptions::getUserLang() -- does. See translate.user_lang
--	T142906 Data access in user language do not obey the uselang get parameter / Apparently
	translate.user_translations = translate.wiki_translations -- default user like wiki
--	translate.user_translations = translate.wiki_translations -- default user like wiki
	if user_lang and versioning.main_i18n and versioning.main_i18n[user_lang] then -- Activate the wiki language for errors, warnings and categories.
		translate.user_lang = user_lang
	else -- if sought language is unavailable uses default value.
		translate.user_lang = getContentLanguage -- default user_lang
	end
	if translate.user_lang and versioning.main_i18n and versioning.main_i18n[translate.user_lang] then
		translate.user_translations = versioning.main_i18n[translate.user_lang]
	--	translate.user_translations = translate.user_translations
	else
		translate.user_translations = versioning.i18n.en -- user's default translations
	--	translate.user_translations = translate.user_translations
	end
	--
	-- PAGE -- Activate the page language for errors, warnings and categories.
	--	{ 30, "Important", "T20161220", 0, "Close", "To test: Central modules need the page language to display errors categories and datas for helpers", "user language getContentLanguage()", },
	--	T20161220 : ToDo: test: local getContentLanguage = mw.language.getContentLanguage().code -- default wiki_lang
	translate.page_lang = translate.wiki_lang -- default user like wiki
	translate.page_translations = translate.wiki_translations -- default user like wiki
--	translate.page_translations = translate.wiki_translations -- default user like wiki
--	if user_lang and versioning.main_i18n and versioning.main_i18n[user_lang] then -- Activate the wiki language for errors, warnings and categories.
--		translate.page_lang = user_lang
--	else -- if sought language is unavailable uses default value.
--		translate.page_lang = getContentLanguage -- default user_lang
--	end
	translate.page_lang = getContentLanguage -- default user_lang
	if translate.page_lang and versioning.main_i18n and versioning.main_i18n[translate.page_lang] then
		translate.page_translations = translate.main_i18n[translate.page_lang]
	--	translate.page_translations = translate.page_translations
	else -- if sought language is unavailable uses default value.
		translate.page_lang = translate.wiki_lang -- default user_lang
		translate.page_translations = translate.main_i18n[translate.page_lang]
	end
	return res
end -- function translate.init_wiki_user_page_lang(wiki_lang, user_lang)

function translate.list_Mediawiki_languages(t, lang) -- List all Mediawiki languages.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if type(t) ~= "string" then t = "\n* <b>translate.list_Mediawiki_languages()</b> List all known Mediawiki languages:" end
	local lang = mw.language.new( translate.user_lang )
	local LangCode = lang:getFallbackLanguages()
	local lang_code_name = mw.language.fetchLanguageNames() -- inLanguage, 'mwfile' )
	local toSort, t2 = {}, ""
	t = t .. "\n* Infos lang_code_name: " .. viewer.ta("#lang_code_name", #lang_code_name)
	for code, name in pairs(lang_code_name) do -- make a true table in rough order
		table.insert(toSort, { code, 	 name } )
		-- insert detail { [1]=code, [2]=name } )
	end
--	t = t .. "\n* List of <b>known</b> languages of MediaWiki lang_code_name: <br/>" .. t2
	table.sort(toSort, function (row_x, row_y) return ( row_x[1] < row_x[1] ) end ) -- Sort based on [1]=code
	table.sort(toSort, function (row_x, row_y) return ( row_x[2] < row_x[2] ) end ) -- Sort based on [2]=name
	t = t .. "\n* List of <b>known</b> languages of MediaWiki lang_code_name: <br/>" .. t2
	t = t .. "\n* Infos: " .. viewer.ta("#lang_code_name", #lang_code_name) .. viewer.ta("#toSort", #toSort) .. viewer.ta("user_lang", translate.user_lang)
--	t = t .. viewer.ta("#toSort[1]", #toSort[1] ) .. viewer.ta("toSort[1][1]", toSort[1][1] ) .. viewer.ta("toSort[1][2]", toSort[1][2] )
	t2 = ""
	for i, lang in pairs(toSort) do
		local ok = translate.abnormal_char_in_text(lang[2], translate.user_lang) -- Detect abnormal characters in an unilingual text.
		local lang_2 = lang[2]
		if not ok then lang_2 = viewer.errorColor(lang_2) end
		t2 = t2 .. viewer.ta( lang[1], lang_2 )
	end
	t = t .. "\n* List of <b>known</b> languages of MediaWiki: <br/>" .. t2
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function translate.list_Mediawiki_languages(t, lang)

function translate.abnormal_char_in_text(t) -- Test: Detect abnormal characters in an unilingual text.
	if type(t) ~= "string" then return false end -- translate_abnormal_char_in_text_title
	local ok, normal = true, viewer.form9user("translate_abnormal_char_in_alphabet")
--	local ok, normal = true, "ABCDEFGHIJKLMNOPQRSTUVWXYZ 013456789 abcdefghijklmnopqrstuvwxyz àéèêëiouüœ ÀÉÈÊËIOUÜŒ ,;.:!?"
	local letter, nn = "x", string.len(t .. " abcdef") - 3
--	local letter, nn = "x", mw.ustring.len(t)
	for i = 1, nn do -- for all chars in t
		letter = string.byte(t, i)
		if not string.is_in(letter, normal) then ok = false ; break end
	end
	return ok
end -- function translate.abnormal_char_in_text(t)

function translate.missing_translations(t) -- List all missing translations
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>translate.missing_translations()</b>: List all missing translations:"
	local errors = "*"
	local tabOptions, i18n, rowGroup
	if type(i18n) ~= "table" then i18n = translate.main_i18n or versioning.main_i18n end
	tabOptions = { -- options for viewer.tableView() -- Form a table with lines and columns.
		headers = "translate_missing_translations_headers", -- headers = "Language; Key; Previous translation; New translation; Module",
		kind = "projects", -- mediawiki or projects
		typ = "err",
		testGroup = i18n, -- tests or tabOptions.testGroup or
		rowGroup = {}, -- tests or tabOptions.testGroup or
	}
	tabOptions.rowGroup = {} -- tests or tabOptions.testGroup or
	rowGroup = tabOptions.rowGroup -- tests or tabOptions.testGroup or
	local missingGroup = {}
	--
	versioning.translations_differences = versioning.translations_differences or {}
	--
	local head = mw.text.split( viewer.form9user("translate_missing_translations_headers") , ',')
--	t = t .. viewer.Th("wikitable sortable") .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[3]) .. viewer.Tc(head[4]) .. viewer.Tc(head[5])
	for key, trans in pairs(versioning.translations_differences ) do
		local trans1 = trans.trans1
		local trans2 = trans.trans2
--		t = t .. viewer.Tr() .. viewer.Tc(trans1.dlang) .. viewer.Tc(trans1.dkey) .. viewer.Tc(trans1.dtrans) .. viewer.Tc(trans2.dtrans) .. viewer.Tc(trans2.dfile)
	end
--	t = t .. viewer.tableView(tabOptions) -- Form a table with lines and columns.
	--
	if type(i18n) ~= "table" then return " verif_i18n MISSING. " end
	local nerr, trans, err = 0, "", ""
	local nbr, nbr_in_lang, list = 0, 0, ""
	local nbr_in_lang_limit = 9
	-- List any gaps of translations in i18n tables.
	local prev_table, next_table, max, nkeys = nil, nil, 0, 0
	local lang, prev_lang, next_lang, cats = "", "", "", ""
	local all_txts = {}
	for lang, texts in pairs(i18n) do -- For all languages and texts to translate
		next_lang = lang -- next language
		next_table = texts -- next language table
		list = list .. lang .. ", "
		nbr = nbr + 1
		nbr_in_lang = 0
		if type(texts) == "table" then -- For each pair of languages to compare
			for keyi18n, stri18n in pairs(next_table) do -- List all texts translated in at least one language
				all_txts[keyi18n] = stri18n
			end
			if (prev_table ~= nil) and (next_table ~= nil) then -- For each pair of languages to compare
				nkeys = 0
				for keyi18n, stri18n in pairs(all_txts) do -- For all texts to translate
					trans = next_table[keyi18n]
					nkeys = nkeys + 1
					if (trans == nil) and (not tonumber(keyi18n) ) then -- and keyi18n ~= nil then -- args to a named argument
						err = events.add_err("versioning_module_miss_i18n_trad_err", next_lang, keyi18n) -- tools_module_miss_i18n_txt_err
						nerr = nerr + 1
						local cut = string.find( keyi18n, "_")
						cut = cut or 11
						local modul = string.sub( keyi18n, 1, cut - 1)
						if (nbr_in_lang == nbr_in_lang_limit) and (not tonumber(keyi18n) ) then -- and keyi18n ~= nil then -- args to a named argument
							table.insert( missingGroup, {next_lang, keyi18n, stri18n, viewer.errorColor("missing limit = " .. nbr_in_lang_limit), modul} )
							nbr_in_lang = nbr_in_lang + 1
						elseif (nbr_in_lang < nbr_in_lang_limit) and (not tonumber(keyi18n) ) then -- and keyi18n ~= nil then -- args to a named argument
							table.insert( missingGroup, {next_lang, keyi18n, stri18n, "missing: " .. nerr .. " / " .. nkeys, modul} )
							nerr = nerr + 1
							nbr_in_lang = nbr_in_lang + 1
						end
					end
				end
				if nkeys > max then max = nkeys end
			end
		end
		prev_lang = next_lang
		prev_table = next_table
	end
	if nerr > 0 then
		err = events.add_err("versioning_module_miss_i18n_count_err", nerr, max)
		errors = errors .. err
		cats = cats .. events.add_cat("versioning_err_module_miss_i18n_cat")
	else
		errors = errors .. " no errors "
	end
	tabOptions.headers = viewer.form9user("translate_missing_translations_headers")
	tabOptions.rowGroup, tabOptions.rowGroup_j = events.tableView_form_all_cases(tabOptions) -- Run tableView_form_one_case for all cases in testGroup{}.
	if errors ~= "" then errors = viewer.errorColor(errors) end
	local tabView = {
		testGroup = missingGroup,
		headers = "translate_missing_translations_headers",
	--	translate_missing_translations_headers	= "Language; i18n Key; Previous translation; New translation; Module",
	}
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
--	t = t .. tabView.t -- internal track to debug
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t --, errors -- translate_missing_translations_title
end -- function translate.missing_translations(t)

function tools.verif_i18n(i18n) -- Deprecated alias function
	versioning.deprecatedFunction("tools.verif_i18n", "translate.changing_translations")
	return translate.changing_translations(i18n)
end
function translate.changing_translations(t) -- Translations missing and changing in i18n tables
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or ("\n* <b>translate.changing_translations(t)</b> Changing (or missing) translations in i18n tables.:")
--	t = t .. viewer.form9user("translate_changing_translations_help", "[https://translatewiki.net/wiki/Translating:MediaWiki Translating:MediaWiki]")
	--
--	t = t .. ("\n* <b>translate.changing_translations(t)</b> Changing (or missing) translations in i18n tables.:")
--	translate_changing_translations_title	= "translate.changing_translations(i18n) Changing (or missing) translations in i18n tables.",
	-- translate_changing_translations_title -- translate_changing_translations_headers
--	if type(i18n) ~= "table" then i18n = versioning.i18n end
--	if (type(i18n) ~= "table") and (type(translate.main_i18n) == "table") then i18n = translate.main_i18n end
	if (type(i18n) ~= "table") and (type(versioning.i18n) == "table") then i18n = versioning.i18n end
	local nerr, trad, err = 0, "", "", ""
--	t = t .. "\n* translate.changing_translations( Comparer les langues : <b>" .. prev_lang .. "/" .. next_lang .. "</b>, "
	local nbr, list = 0, ""
	local tabOptions = { -- options for viewer.tableView() -- Form a table with lines and columns.
		headers = "translate_changing_translations_headers", -- headers = "Language; Key; Previous translation; New translation; Module",
		kind = "projects", -- mediawiki or projects
		typ = "err",
		testGroup = i18n, -- tests or tabOptions.testGroup or
		rowGroup = {}, -- tests or tabOptions.testGroup or
	}
	rowGroup = tabOptions.rowGroup -- tests or tabOptions.testGroup or
	local changingGroup = {}
	-- List any gaps of translations in i18n tables.
	local next_table, next_table, max, nkeys = nil, nil, 0, 0
	local lang, prev_lang, next_lang, cats = "", "", "", ""
	local all_txts = {}
	-- ToDo: changing i18n, Rical 20170210 23:13
	-- List of origins to compare for each translation:
	--		1: internal minimal library or Module, example: datas or Module.Central-s-fr
	--		2: /I18N submodule, example: datas or Module.Central-s-fr or Module:Library/versioning/I18N
	--		3: external translate.i18n for all languages.
	--		4: the same (1+2+3) for upper modules until the main module. Example: datas or/and Module.Central-s-fr or/and Module:Author3
	--		4: lo mismo (1+2+3) para los módulos superiores hasta al módulo principal. Ejemplo: datas o/y Módulo:Central-s-fr o/y Módulo:Author3
	--		4: les mêmes (1+2+3) pour les modules supérieurs jusqu'au module principal. Exemple: datas ou/et Module:Central-s-fr ou/et Module:Author3
	-- Bind modules and submodules from the main module to all sub-modules recurcively, and memorize all the actual binding.
	-- Translate all translations in the exact reverse order, using the memorised binding order. Then upper modules can adapt translations.
	for lang, texts in pairs(i18n) do -- Pour toutes les langues et tous les textes à traduire
		next_lang = lang -- table suivante de traduction d'une langue
		next_table = texts -- table suivante de traduction d'une langue
		list = list .. lang .. ", "
		nbr = nbr + 1
		if type(texts) == "table" then -- pour chaque couple de langues à comparer
			for keyi18n, v in pairs(texts) do -- Lister tous les textes traduits au moins dans une langue
				all_txts[keyi18n] = "x"
			end
			if next_table ~= nil and next_table ~= nil then -- pour chaque couple de langues à comparer
				t = t .. "\n* Comparer les langues : <b>" .. prev_lang .. "/" .. next_lang .. "</b>, "
				nkeys = 0
				for keyi18n, x in pairs(all_txts) do -- Pour tous les textes à traduire
					trad = next_table[keyi18n]
					nkeys = nkeys + 1
					if trad == nil then -- and keyi18n ~= nil then -- args vers un argument nommé
						err = events.add_err("versioning_module_miss_i18n_trad_err", keyi18n, next_lang) -- versioning_module_miss_i18n_txt_err
						t = t .. err .. " " -- .. "<br>"
						nerr = nerr + 1
					end
				end
				if nkeys > max then max = nkeys end
			end
		end
		prev_lang = next_lang
		next_table = next_table
	end
	if nerr > 0 then
		err = events.add_err("versioning_module_miss_i18n_count_err", nerr, max)
		t = t .. "<br>" .. err .. " "
		cats = cats .. events.add_cat("versioning_err_module_miss_i18n_cat")
--		cats = cats .. events.add_cat("versioning_module_internal_error_cat")
	else
		err = events.add_wng("versioning_module_miss_i18n_none_err")
		t = t .. "<br>" .. err .. " "
	end
	-- tester vr le nombre total de variables de traductions
	local st, vr, fn, tb = luaTable.structure(i18n, "i18n")
	if vr < 33 then
		err = events.add_err("versioning_module_miss_i18n_mini_err", vr)
		t = t .. "<br>" .. err .. " "
		cats = cats .. events.add_cat("versioning_err_module_miss_i18n_cat")
--		cats = cats .. events.add_cat("versioning_module_internal_error_cat")
	end
	--
	-- List all translated languages
	t = t .. "\n* Translated languages: Langues traduites : "
	local an, ln, Nadd, Nchange, Ntotal = 0, 0, 0, 0, 0
	for lng, argmts in pairs(i18n) do -- For all languages to be imported
		if i18n[lng] then -- The language already exists, add or replace imported fields
			t = t .. viewer.form9user(", <b>Langue : %1</b> ", lng)
			ln = ln + 1
			if type(texts) == "table" then -- For each pair of languages to be compared
				for argn, val in pairs(argmts) do -- For all imported fields
					if translate.main_i18n[lng][argn] then
						Nchange = Nchange + 1
						table.insert( changingGroup, {next_lang, keyi18n, stri18n, "missing: " .. nerr .. " / " .. nkeys, modul} )
					else Nadd = Nadd + 1 end
					-- Add or replace a field and its translation. Ajouter ou remplacer un champs et sa traduction
					translate.main_i18n[lng][argn] = val -- t = t .. viewer.form9user(", %1 ", argn)
					an = an + 1
				end
			end
		else -- Add the table and all its fields if it is not yet in translate.main_i18n
			if mw.language.isKnownLanguageTag(lng) then
				translate.main_i18n[lng] = i18n[lng] -- translate.main_i18n
			end
		end
	end
	local lst_lng = ""
	for lang, argmts in pairs(i18n) do -- Pour toutes les langues à importer
		Ntotal = 0
		if type(texts) == "table" then -- pour chaque couple de langues à comparer
			for argn, val in pairs(translate.main_i18n[lang]) do -- For all existing fields
				Ntotal = Ntotal + 1
			end
			local langname = mw.language.fetchLanguageName(lang, "en")
			local native = mw.language.fetchLanguageName(lang)
			lst_lng = lst_lng .. viewer.form9user("translate_list_in_translated_lang", Ntotal, lang, langname, native)
			-- translate_list_in_translated_lang	= "\n* %1 translations in language %2 : %3 : %4",
		end
	end
	translate.user_wiki_lang_msg = viewer.form9user("translate_user_wiki_lang_msg", translate.user_lang, translate.wiki_lang)
	translate.user_wiki_lang_err = translate.user_wiki_lang_msg -- deprecated for Module:Author3
	translate.Lang_list = lst_lng
	t = t .. viewer.form9user("translate_languages_nbr_and_list", nbr, list) .. lst_lng
	t = t .. "\n* Same example without translation for a test case : " .. translate.formTestCase("translate_languages_nbr_and_list", nbr, list)
--	t = t .. "\n\n"
--	t = t .. translate.missing_translations() -- Compare all differences of translations
	-- translate_changing_translations_headers
	t = t .. viewer.tableView("translate_languages_nbr_and_list", nbr, list) .. lst_lng
	tabOptions.headers = viewer.form9user("translate_missing_translations_headers")
	tabOptions.rowGroup, tabOptions.rowGroup_j = events.tableView_form_all_cases(tabOptions) -- Run tableView_form_one_case for all cases in testGroup{}.
	if errors ~= "" then errors = viewer.errorColor(errors) end
	local tabView = {
		headers = "translate_missing_translations_headers",
	--	translate_missing_translations_headers	= "Language; i18n Key; Previous translation; New translation; Module",
		testGroup = changingGroup
	}
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t --, errors -- translate_changing_translations_title
end -- function translate.changing_translations(i18n)

function translate.i18n_lister(t, i18n_tables)
	if type(i18n_tables) ~= "table" then i18n_tables = translate.main_i18n end
	local t = t or "\n* <b>i18n_lister</b> :"
	t = t .. viewer.errorColor("\n* <b>This list show all the texts, but cannot replace the original declarations.</b> ")
	t = t .. viewer.errorColor("\n* <b>" .. viewer.form9user("translate_i18n_list_all_texts") .. " </b> " )
	for lang, lang_table in pairs(i18n_tables) do
		t = t .. "\np.i18n." .. lang .. ' = { '
		if type(lang_table) == "table" then
			for key, text in pairs(lang_table) do
				if key and text then
					t = t .. "\n:" .. key .. ' = "' .. text .. '",'
				end
			end
		end
		t = t .. "\n}\n"
	end
	return t
end -- function translate.i18n_lister(t, i18n_tables)

-- - - - ------------------ - - - - ---------------------------------
-- Manage translations of arguments and warnings
-- Gestione traducciones de argumentos y advertencias
-- Gérer les traductions des arguments et warnings
-- - - - ------------------ - - - - ---------------------------------

function translate.dummy_languages() -- Test dummy languages
	-- https://fr.wikipedia.org/wiki/%C3%89tiquette_d%27identification_de_langues_IETF
	-- https://fr.wikipedia.org/wiki/Module:Langue/Data
	--	pour convertir en code de langue IETF les noms français de langues
	-- https://www.mediawiki.org/wiki/Manual:$wgDummyLanguageCodes
	--	List of language codes that have been renamed to new (correct) codes, or don't correspond to an actual interface language.
	--	array( 'als' => 'gsw', 'bat-smg' => 'sgs', 'be-x-old' => 'be-tarask', 'bh' => 'bho'...
	local dummy_i18n = {
		en = "Module:Central/I18N", -- english
		es = "Modul:Central/I18N", -- spanish
		fr = "Module:Central/I18N", -- french
		Fr = "Module:Central/I18N", -- error test
		als = "Modulen:Central/I18N", -- alemanish
		gsw = "Mod-gsw:Central/I18N", -- unknown
		["bat-smg"] = "Mod-bat-smg:Central/I18N", -- alemanish
		sgs = "Mod-sgs:Central/I18N", -- unknown
	}
	local t = '\n* dummy_languages : ' ..  viewer.ta("isTag", "isKnownLanguageTag(xx)") .. viewer.ta("isLang", "isSupportedLanguage(xx)") .. viewer.ta("isBuilt", "isValidBuiltInCode(xx)")
	t = t .. viewer.Th() .. viewer.Tc("lang") .. viewer.Tc("isTag") .. viewer.Tc("isLang") .. viewer.Tc("isBuilt")
	t = t .. viewer.Tc("langname") .. viewer.Tc("native")
	for lang, modname in pairs(dummy_i18n) do -- Pour tous les parametres connus
		local isTag = mw.language.isKnownLanguageTag(lang)
		local isLang = mw.language.isSupportedLanguage(lang)
		local isBuilt = mw.language.isValidBuiltInCode(lang)
		local langname = mw.language.fetchLanguageName(lang, "en") -- see events.selectLang
		local native = mw.language.fetchLanguageName(lang)
		local space_name = tostring(mw.site.namespaces.Module.name)
		t = t .. viewer.Tr() .. viewer.Td(lang) .. viewer.Td(isTag) .. viewer.Td(isLang) .. viewer.Td(isBuilt)
		t = t .. viewer.Td(langname) .. viewer.Td(native)
	end
	t = t .. viewer.Te()
	return t
end -- function translate.dummy_languages()

function translate.track_i18n_t(t)
	translate.track_i18n_tt = (translate.track_i18n_tt or "track_i18n_tt ") .. (t or "track_i18n_tt") .. ":" .. viewer.form9user("err_J_before_end") .. " - "
	return translate.track_i18n_tt -- see also track_i18n_vers
end


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:modes support modes and their options, like p.read(frame).
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:luaTable enhances the support of previous table library from Lua.
--	"luaTable" avoids ambiguities with other kind of tables.
--	To enhance, we could write here : luaTable = table.
--	install by versioning.bind_one_library(luaTable, library, tabOptions)
--	see Extension:Scribunto/Lua reference manual : class nameTest extends Scribunto_LuaEngineTestBase
-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------


-- luaTable = {} -- already declared by Scribunto, else in _G space.
-- Translations for luaTable library
luaTable.i18n = {}
luaTable.i18n.en = {
	luaTable_recursive_luaTable_title	= "Mediawiki recursive luaTable",
	luaTable_roughView_tests_title		= "luaTable.roughView() Test: Form a rough LUA code of a table",
	luaTable_structure_level_maxi		= "Level limit recursiveLimit = <b>%1</b> ",
	luaTable_table_listlimit_max_n		= "Length limit max_n = <b>%1</b> ",
	luaTable_structure_nolimits_title	= "luaTable.structure() List a table, test without limits:",
	luaTable_structure_limits_title		= "luaTable.structure() List a table, test with limits:",
	luaTable_toList_tests_title			= "luaTable.toList() Return a list from a LUA table.",
	luaTable_toTable_tests_title		= "luaTable.toTable() Test: convert a string to a table of words",
	luatable_form_subcounts_subtables	= "The table <b>%1</b> counts <b>%2</b> values, <b>%3</b> sub-tables and <b>%4</b> functions.",
	luaTable_fromstring_base_err		= "Internal error: in luaTable.fromstring(), base unavailable for the %1 sub-table.",
	luaTable_fromstring_no_subtable_err = "Internal error: in luaTable.fromstring(), %1 is not a sub-table.",
	luaTable_formSubCounts_type_err		= "A <b>%1</b> replaces the table: <b>%2</b>.",

	-- Miscellaneous warnings and errors
	luaTable_table_dont_exists_err		= "The table <b>%1</b> does not exist.",

	-- Titles of tests
	luaTable_table_args_source_title	= "luaTable.structure() Table of received arguments, args_source:",
	luaTable_table_args_unknown_title	= "luaTable.structure() Table of unknown arguments, args_unknown:",

} -- luaTable.i18n.en
luaTable.i18n.es = {
	luaTable_recursive_luaTable_title	= "Mediawiki luaTable recursiva",
	luaTable_roughView_tests_title		= "luaTable.roughView() Prueba: Formar un código de LUA grueso de una tabla",
	luaTable_structure_level_maxi		= "Límite de nivel recursiveLimit = <b>%1</b> ",
	luaTable_table_listlimit_max_n		= "Límite de longitud max_n = <b>%1</b> ",
	luaTable_structure_nolimits_title	= "luaTable.structure() Enumerar una tabla, prueba sin límites:",
	luaTable_structure_limits_title		= "luaTable.structure() Enumerar una tabla, prueba con límites:",
	luaTable_toList_tests_title			= "luaTable.toList() Devolver una lista de una tabla LUA.",
	luaTable_toTable_tests_title		= "Prueba: convertir una cadena en una tabla de palabras",
	luatable_form_subcounts_subtables	= "La tabla <b>%1</b> cuenta <b>%2</b> valores, <b>%3</b> sub-tablas y <b>%4</b> funciones.",
	luaTable_fromstring_base_err		= "Error interno: en luaTable.fromstring(), base no disponible para la sub-tabla %1.",
	luaTable_fromstring_no_subtable_err = "Error interno: en luaTable.fromstring(), %1 no es una sub-tabla.",
	luaTable_formSubCounts_type_err		= "Un <b>%1</b> reemplaza la tabla: <b>%2</b>.",

	-- Textos principales, errores y categorías de instrumentos

	-- Diversos mensajes y errores
	luaTable_table_dont_exists_err		= "La tabla <b>%1</b> no existe.",

	-- Titres des pruebas
	luaTable_table_args_source_title	= "luaTable.structure() Tabla de argumentos recibido, args_source:",
	luaTable_table_args_unknown_title	= "luaTable.structure() Tabla de argumentos desconocidos, args_unknown:",

} -- luaTable.i18n.es
luaTable.i18n.fr = {
	luaTable_recursive_luaTable_title	= "Mediawiki luaTable récursive",
	luaTable_roughView_tests_title		= "luaTable.roughView() Test: Former un code LUA grossier d'une table",
	luaTable_structure_level_maxi		= "Limite de niveau recursiveLimit = <b>%1</b> ",
	luaTable_table_listlimit_max_n		= "Limite de longueur max_n = <b>%1</b> ",
	luaTable_structure_nolimits_title	= "luaTable.structure() Lister une table, test sans limites :",
	luaTable_structure_limits_title		= "luaTable.structure() Lister une table, test avec limites :",
	luaTable_toList_tests_title			= "luaTable.toList() Retourne une liste à partir d'une table LUA.",
	luaTable_toTable_tests_title		= "luaTable.toTable() Test: convertir une phrase en table de mots",
	luatable_form_subcounts_subtables	= "La table <b>%1</b> compte <b>%2</b> variables, <b>%3</b> sous-tables, <b>%4</b> fonctions.",
	luaTable_fromstring_base_err		= "Erreur interne: dans luaTable.fromstring(), base indisponible pour la sous-table %1.",
	luaTable_fromstring_no_subtable_err = "Erreur interne: dans luaTable.fromstring(), %1 n'est pas une sous-table.",
	luaTable_formSubCounts_type_err		= "Un <b>%1</b> remplace la table : <b>%2</b>.",
	-- Principaux textes, erreurs et catégories des outils
	-- Messages et erreurs divers
	luaTable_table_dont_exists_err		= "La table <b>%1</b> n'existe pas.",
	-- Titres des tests
	luaTable_table_args_source_title	= "luaTable.structure() Table des arguments reçus, args_source :",
	luaTable_table_args_unknown_title	= "luaTable.structure() Table des arguments inconnus, args_unknown :",
} -- luaTable.i18n.fr

function luaTable.toTable(stringList, pattern, plain) -- convert a string to a table of words
	if type(stringList) ~= "string" then stringList = "" end
	if type(pattern) ~= "string" then pattern = "" end
--	plain = plain or true
	if plain then plain = true else plain = false end
	plain = true
	local tab = mw.text.split(stringList, pattern, plain)
	local tab2 = {}
	for i, word in ipairs(tab) do -- for all names
	--	word = mw.text.trim(word) -- clean spaces in each name
		tab2[word] = word
	--	table.insert(tab2, name)
	end
	return tab2
end

function luaTable.toTable_test(t) -- Test: convert a string to a table of words
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if type(stringList) ~= "string" then stringList = "" end
	if type(pattern) ~= "string" then pattern = "" end
--	if type(plain) ~= "boolean" then plain = false end
	local t = t or "\n* stringToTable_test: "
	local group_test = { -- events.testGroup
		{ stringList = "docview nocat : tests docline",	 pattern = nil, },
		{ stringList = "docview nocat : tests docline",	 pattern = " ", plain = nil, },
		{ stringList = "docview,nocat, : ,tests docline",  pattern = ",", plain = true, },
		{ stringList = "docview docline : tests",  pattern = " ", plain = true, },
		{ stringList = "docview; nocat; : tests; docline",	pattern = ";", plain = false, },
		{ stringList = "abc ; ; def.;ghi;,jkl",	 pattern = ";", },
	}
	local tabOptions = {}
	tabOptions.headers = viewer.form9user("stringList; pattern; plain; result")
	tabOptions.testGroup = group_test
	tabOptions.rowGroup = {}
	local function stringToTable_1(stringList, pattern, plain) -- convert a string to a table of words
		local test = {}
		test.result = luaTable.toTable(stringList, pattern, plain)
		test.result = table.concat(test.result)
		table.insert( tabOptions.rowGroup, { (test.stringList or "nil"), (test.pattern or "nil"), (test.plain or "nil"), (test.result or "nil") } )
		table.insert( tabOptions.rowGroup, { tostring(test.stringList) .. "xx", tostring(test.pattern), tostring(test.plain) or "plain", tostring(test.result) } )
	end
	if type(group_test) == "table" then
		for key, test in pairs(group_test) do
			test.table = luaTable.toTable(test.stringList, test.pattern, test.plain)
			test.result = " "
			for key, val in pairs(test.table) do test.result = test.result .. val .. " " end
			table.insert( tabOptions.rowGroup, { tostring(test.stringList), tostring(test.pattern), tostring(test.plain), luaTable.roughView(test.table) } )
		end
	else t = t .. "error : stringToTable_test has no table. " end
	t = t .. viewer.tableView(tabOptions) -- Form a table with lines and columns.
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function luaTable.toTable_test(t)

function luaTable.toList(tab, ...) -- Return a list from a LUA sequence table where i = 1 to n.
	-- This function is the reverse of func( ... )
	if (type(tab) ~= "table") then tab = {} end
	local tab = mw.clone(tab) -- to not disturb original table
	local maxn = table.maxn(tab)
	local function tabN_to_list1(tab, n, ...)
		local max_tab = table.maxn(tab)
		local listab = {...}
		local max_list = table.maxn(listab)
		local tab_last = mw.clone(tab[n]) -- last element, even a table to not delete below
			tab_last = tostring(tab_last)
		if tab_last and (n <= max_tab) then
			tab[n] = nil
			return tab_last , tabN_to_list1(tab, n + 1, ...)
		else return end
	end
	return tabN_to_list1(tab, 1, ...)
end

function luaTable.toList_test(t) -- Return a list from a LUA table.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "Test the <b>luaTable.toList( tab )</b> which return a list from a LUA table."
	local function toList_test1( case, tab )
		local tabview = luaTable.roughView( tab )
		local res_tab = { luaTable.toList( tab ) }
		local t1 = ""
		for i, elem in ipairs(res_tab) do
			t1 = t1 .. viewer.ta( "[" .. i .. "]", tostring(elem) )
		end
		local t = viewer.Tr() .. viewer.Td(case) .. viewer.Td(tabview) .. viewer.Td(t1)
		return t
	end
	t = t .. viewer.Th() .. viewer.Tc("Tested case") .. viewer.Tc("Input table, roughView") .. viewer.Tc("Output list")
	t = t .. toList_test1( "Simple table", { 123, "abc", key = "txt"} )
	t = t .. toList_test1( "All types transmited", { "name", 111, true, false, { 789, "xyz"}, function()end,} )
	t = t .. toList_test1( "10 values in the table", { "a", "b", "c", "d", "e", "f", "g", "h", "i9", "j10" } )
	t = t .. toList_test1( "Any number of values", {
		"a", "b", "c", "d", "e", "f", "g", "h", "i09", "j10",
		"a", "b", "c", "d", "e", "f", "g", "h", "i19", "j20",
		"a", "b", "c", "d", "e", "f", "g", "h", "i29", "j30",
		"a", "b", "c", "d", "e", "f", "g", "h", "i39", "j40",
		"a", "b", "c", "d", "e", "f", "g", "h", "i49", "j50",
		"a", "b", "c", "d", "e", "f", "g", "h", "i59", "j60", } )
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function luaTable.toList_test(t)

--	if (opt.recursiveLevel > 0) and (opt.recursiveLevel < opt.recursiveLimit) then
--	local recursiveLevel, recursiveLimit, recursiveLevel_err = recursiveLevel or 1, recursiveLimit or 1111, ""
--	if viewer.activate_view then
--	end -- on modes.recursiveNormal(
--	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)

--	t = t .. luaTable.SubCounts(tablebase, tabname, { recursiveLimit = 2 }, recursiveLevel ) -- how to call
function luaTable.SubCounts(tablebase, tabname, opt, recursiveLevel) -- Recursive counts in table of vars, sub-tables and functions
--	function versioning.antiCrash(options, content_or_func, ...) -- Form the display of a running error.
--	function versioning.antiLoop("luaTable.SubCounts", 11, luaTable.SubCounts, tablebase, tabname, opt, recursiveLevel) -- Limit the n-th reentrant call of any function.
	local opt = opt or {}
	opt.recursiveLevel = recursiveLevel or opt.recursiveLevel
--	opt.recursiveLevel, opt.recursiveLimit, opt.recursiveLevel_err = modes.recursiveNormal(opt.recursiveLevel, opt.recursiveLimit)
	opt.recursiveLevel, opt.recursiveLimit, opt.recursiveLevel_err = modes.recursiveNormal(opt.recursiveLevel, modes.recursiveLimit)
	local isempty = true
	local tot_vars, tot_tabs, tot_func = opt.tot_vars or 0, opt.tot_tabs or 0, opt.tot_func or 0
	-- luaTable.SubCounts(get.i18n, get.alias) -- : attempt to index local 'opt' (a nil value).
	local nbr_vars, nbr_tabs, nbr_func = 0, 0, 0
	local txt, vr, tb, fn, vrtb, vr_tb = "", 0, 0, 0, 0, 0
	local case = ""
	if (type(tablebase) == "table") and (type(tabname) == "string") then -- for any tables
		-- normal case OK, do nothing
		case = " tab+name"
	elseif (type(tablebase) == "table") and (type(tabname) ~= "string") then -- for abnormal or missing tabname
		tabname = "tabname=" .. tostring(tablebase) .. "?"
		tabname = viewer.errorColor(tabname)
		case = " tab+noname"
	elseif (type(tablebase) ~= "table") and (type(tabname) == "string") then -- for abnormal table
		tabname = "tabname=" .. tostring(tabname) .. "?"
		tabname = viewer.errorColor(tabname)
		case = " notab+name"
	elseif type(tablebase) == "string" then -- for viewer.tables
		tabname = "tools." .. tablebase
	--	tab = tools[tab]
		case = " tab=name"
	else
		tabname = "tabname=" .. tostring(tablebase) .. "?"
		tabname = viewer.errorColor(tabname)
		case = " notab+noname"
	end
	if (type(tablebase) == "table") and (type(tabname) == "string") then -- for normal cases
		for key, var in pairs(tablebase) do -- List and count vars, functions and sub tables
			local sub_tabname = tostring(key)
			if type(var) == "table" then
			local sub_tablebase = var
				nbr_tabs = nbr_tabs + 1
				if (opt.recursiveLevel < opt.recursiveLimit) then
			--	if (opt.recursiveLevel > 0) and (opt.recursiveLevel < opt.recursiveLimit) then
				--	vr, tb, fn, vrtb, opt.recursiveLevel = luaTable.SubCounts(var, tabname, opt)
					opt.recursiveLevel = opt.recursiveLevel + 111
					vr, tb, fn, vrtb = luaTable.SubCounts(sub_tablebase, sub_tabname, opt, opt.recursiveLevel)
					tot_vars = tot_vars + vr
					tot_tabs = tot_tabs + tb + 1
					tot_func = tot_func + fn
				end
			elseif type(var) == "function" then
				nbr_func = nbr_func + 1
				tot_func = tot_func + 1
				isempty = false
			else -- type(var) == other
				nbr_vars = nbr_vars + 1
				tot_vars = tot_vars + 1
				isempty = false
			end
		end
	end
	local vr_tb = 0
	if tot_tabs == 0 then
		vr_tb = tot_vars -- texts number in all languages
	else -- How many translations in each language? Approximative if not equal.
		vr_tb = tot_vars / tot_tabs -- texts number in one language, like + 33.49
		vr_tb = math.floor( vr_tb ) -- digital texts number in each language
	end
	opt.tot_vars = tot_vars -- total number of variables in the table
	opt.tot_tabs = tot_tabs -- total number of sub-tables in the table
	opt.tot_func = tot_func -- total number of functions in the table
	opt.vr_tb = vr_tb -- digital texts number in each language
	return tot_vars, tot_tabs, tot_func, vr_tb -- , opt.recursiveLevel
end -- function luaTable.SubCounts(tablebase, tabname, opt, recursiveLevel)

--	t = t .. luaTable.SubCounts(tablebase, tabname, { recursiveLimit = 2 }, recursiveLevel ) -- how to call
function luaTable.SubCounts_new(tablebase, tabname, opt, recursiveLevel) -- Recursive counts in table of vars, sub-tables and functions
--	local recursiveLevel, recursiveLimit, recursiveLevel_err = 1, 1111, ""
	local loc = {}
	loc.recursiveLevel = recursiveLevel
	loc.recursiveLevel = loc.recursiveLevel or 1
	if viewer.activate_view then
		loc.recursiveLevel, loc.recursiveLimit, loc.recursiveLevel_err = modes.recursiveNormal( loc.recursiveLevel, modes.recursiveLimit)
	end
--	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
	if (type(opt) ~= "table") then opt = {} end-- for any tables
	opt.recursiveLevel = opt.recursiveLevel or loc.recursiveLevel
	opt.recursiveLimit = opt.recursiveLimit or loc.recursiveLimit
	local isempty = true
	local tot_vars, tot_tabs, tot_func = opt.tot_vars or 0, opt.tot_tabs or 0, opt.tot_func or 0
	local nbr_vars, nbr_tabs, nbr_func = 0, 0, 0
	local txt, vr, tb, fn, vrtb, vr_tb = "", 0, 0, 0, 0, 0
	local case = ""
	if (type(tablebase) == "table") and (type(tabname) == "string") then -- for any tables
		-- normal case OK, do nothing
		case = " tab+name"
	elseif (type(tablebase) == "table") and (type(tabname) ~= "string") then -- for abnormal or missing tabname
		tabname = "tabname=" .. tostring(tablebase) .. "?"
		tabname = viewer.errorColor(tabname)
		case = " tab+noname"
	elseif (type(tablebase) ~= "table") and (type(tabname) == "string") then -- for abnormal table
		tabname = "tabname=" .. tostring(tabname) .. "?"
		tabname = viewer.errorColor(tabname)
		case = " notab+name"
	elseif type(tablebase) == "string" then -- for viewer.tables
		tabname = "tools." .. tablebase
	--	tab = tools[tab]
		case = " tab=name"
	else
		tabname = "tabname=" .. tostring(tablebase) .. "?"
		tabname = viewer.errorColor(tabname)
		case = " notab+noname"
	end
	if (type(tablebase) == "table") and (type(tabname) == "string") then -- for normal cases
		for key, var in pairs(tablebase) do -- List and count vars, functions and sub tables
			key = tostring(key)
			if type(var) == "table" then
				nbr_tabs = nbr_tabs + 1
				if loc.recursiveLevel < loc.recursiveLimit then
					vr, tb, fn, vrtb, loc.recursiveLevel = luaTable.SubCounts(var, tabname, opt, loc.recursiveLevel + 1)
					tot_vars = tot_vars + vr
					tot_tabs = tot_tabs + tb + 1
					tot_func = tot_func + fn
				end
			elseif type(var) == "function" then
				nbr_func = nbr_func + 1
				tot_func = tot_func + 1
				isempty = false
			else -- type(var) == other
				nbr_vars = nbr_vars + 1
				tot_vars = tot_vars + 1
				isempty = false
			end
		end
	end
	local vr_tb = 0
	if tot_tabs == 0 then
		vr_tb = tot_vars -- texts number in all languages
	else -- How many translations in each language? Approximative if not equal.
		vr_tb = tot_vars / tot_tabs -- texts number in one language, like + 33.49
		vr_tb = math.floor( vr_tb ) -- digital texts number in each language
	end
	opt.tot_vars = tot_vars -- total number of variables in the table
	opt.tot_tabs = tot_tabs -- total number of sub-tables in the table
	opt.tot_func = tot_func -- total number of functions in the table
	opt.vr_tb = vr_tb -- digital texts number in each language
	return tot_vars, tot_tabs, tot_func, vr_tb, loc.recursiveLevel
end -- function luaTable.SubCounts_new(tablebase, tabname, opt, recursiveLevel)

function luaTable.fromstring(tabname, tablebase, t)
	-- Find, from the string, the last table in a chain of subtables.
	-- also DEBUG the bug T20160615 : Lua functions calls lost multilevel table arguments
	group = testsCases.groupMemoDebug -- Find the table from its name
	local err, subtab, names, i18n = "", {}, nil
	local t = t or "\n* luaTable.fromstring: "
	t = t .. viewer.ta("tabname", tabname) .. viewer.ta("tablebase", tablebase)
	if (type(tablebase) ~= "table") then tablebase = package.loaded end -- Default tablebase
	if (type(tabname) == "string") and (type(tablebase) == "table") then
		names = mw.text.split(tabname, '.', true) -- from the subgroup in string form
		subtab = tablebase
		if type(names[1]) == "string" and type(tablebase[ names[1] ]) == "table" then
			-- If the first sought sub-table is in base table, this base seems right.
			t = t .. viewer.ta("first", names[1])
			if (type(tabname) == "string") and (type(subtab) == "table") then
				t = t .. viewer.ta("subtab", subtab) .. ", for:"
				for i, part in ipairs(names) do -- Build the subgroup table from the tabname string
					if type(subtab[part]) == "table" then
						subtab = subtab[part]
						t = t .. viewer.ta("part", part)
					else -- add a subgroup to the previous subgroup
						err = err .. events.add_err("luaTable_fromstring_no_subtable_err", part) -- "Internal error: in luaTable.fromstring(), %1 is not a sub-table."
						break
					end
				end
			end
		else
			-- If the first sought sub-table is not in base table, this base is not the right.
			err = err .. events.add_err("luaTable_fromstring_base_err")
		end
	else
		err = err .. events.add_err("luaTable_fromstring_base_err")
	end
	if subtab == tablebase then subtab = nil end
	return subtab, err, t
end -- function luaTable.fromstring(tabname, tablebase, t)

function luaTable.formSubCounts_old(tabname, tablebase, form) -- Form text of translations counts of a i18n table.
--		 luaTable.formSubCounts(tabname, tablebase) -- Alternative combination of arguments
--		 luaTable.formSubCounts(tabname, form) -- Alternative combination of arguments
--		 luaTable.formSubCounts(tabname) -- Alternative combination of arguments
--		 luaTable.formSubCounts() -- Alternative combination of arguments
--	if type(tabname) ~= "string" then tabname = "i18n" end
	local i18n, err = versioning.main_i18n, "" -- for any tables
	local opt, i18n_name = {}, "versioning.main_i18n" -- for any tables
	local i, k, t = 0, 0, "* formSubCounts : "
--	luatable_form_subcounts_subtables	= "The table <b>%1</b> counts <b>%2</b> values, <b>%3</b> sub-tables and <b>%4</b> functions.",
	local formtext = "luatable_form_subcounts_subtables" -- "translate_main_i18n_languages_list"
	local subtab, err, t2 = {}, "", ""
	local text = "table name"
	local tot_vars, tot_tabs, tot_func, vr_tb = 0, 0, 0, 0 -- = luaTable.SubCounts(tablebase, tabname, opt, recursiveLevel)
	-- Adapt the function to available arguments.
	if (type(tabname) == "string") and (type(tablebase) == "table") and (type(form) ~= "string") then -- no form arg
		form = formtext -- default text to form
	elseif (type(tabname) == "string") and (type(tablebase) == "string") and (type(form) == "string") then -- no form arg
	--	subtab, err, t2 = luaTable.fromstring(tabname, tablebase, t) -- Find, from the string, the last table if a chain of subtables.
	elseif (type(tabname) == "string") and (type(tablebase) == "string") and (type(form) == "nil") then -- form in arg 2
		form = formtext -- arg 2
		local tbnm = tablebase
		tablebase = package.loaded -- get.TitleI18N elem.alias
		tablebase = {}
		-- on luaTable.formSubCounts(...) risk "Error Lua : stack overflow" run error if all call are not perfect.
	elseif (type(tabname) == "string") and (type(tablebase) == "nil") and (type(form) == "nil") then -- standard case, do nothing
		tablebase = package.loaded
		form = formtext -- default text to form
	else
		tabname = nil -- "versioning.main_i18n" -- default main i18n
		tablebase = {}
		form = formtext -- default text to form
	end
	-- Try to convert the tabname string to a table
	local subtab, err, t2 = luaTable.fromstring(tabname, tablebase, t) -- Find, from the string, the last table if a chain of subtables.
	-- Detec abnormal args
	local case, err1 = "", ""
	if (type(subtab) == "table") and (type(tabname) == "string") then -- for any tables
		case = " tab+name" -- normal case OK, nothing to do here.
		if subtab == {} then -- for any tables
			case = " {}+name" -- normal case OK, nothing to do here.
			err = err .. "tabname=" .. tostring(tabname) .. "? "
		end
	elseif (type(subtab) == "table") and (type(tabname) ~= "string") then -- for abnormal or missing tabname
		case = " tab+noname"
		err = err .. "tabname=" .. tostring(tabname) .. "? "
	elseif (type(subtab) ~= "table") and (type(tabname) == "string") then -- for abnormal table
		case = " notab+name"
		err = err .. "tabname=" .. tostring(tabname) .. "? " .. "subtab=" .. tostring(subtab) .. "? "
		subtab = nil
	else
		case = " notab+noname"
		tabname = "tabname=" .. tostring(tab) .. "?"
		tabname = viewer.errorColor(tabname)
		subtab = nil
	end
	-- form final result
	if (type(subtab) == "table") then -- standard case, do nothing
		tot_vars, tot_tabs, tot_func, vr_tb = luaTable.SubCounts(subtab, tabname, opt, 1)
		text = viewer.form9user(form, tostring(tabname), tot_vars, tot_tabs, tot_func, vr_tb)
	else
		text = tostring(subtab)
		text = viewer.form9user("luaTable_formSubCounts_type_err", text, tabname) -- luaTable_formSubCounts_type_err or luaTable_formSubCounts_number_err
	end
	return text, tot_vars, tot_tabs, tot_func, vr_tb
end -- function luaTable.formSubCounts_old(tabname, tablebase, form)

--	function versioning.bind_modules_report(t, tracks_level) -- Report all modules installation
--	..	viewer.Td( luaTable.formSubCounts(elem.i18nRoot .. ".i18n", elem.alias .. ".i18n", "this + %2/%3 T/L, ")
--				.. luaTable.formSubCounts("versioning.main_i18n", "versioning.main_i18n", ", (all = %2/%3 T/L)") )
--	if type(tabname) ~= "string" then tabname = "i18n" end
function luaTable.formSubCounts(tabname, tablebase, form) -- Form text of translations counts of a i18n table.
--	versioning.antiLoop("luaTable.formSubCounts", 11, luaTable.formSubCounts, tabname, tablebase, form) -- Limit the n-th reentrant call of any function.
--	local i18n, err = versioning.main_i18n, "" -- for any tables
	local opt, i18n_name = {}, "versioning.main_i18n" -- for any tables
	local i, k, t = 0, 0, "* formSubCounts : "
	local default_form = "luatable_form_subcounts_subtables" -- "translate_main_i18n_languages_list"
	local subtab, err, t2, sub_name = {}, "", "", ""
	local t = "formSubCounts: "
	-- Adapt to available arguments.
	if (type(form) ~= "string") then form = default_form end
	local t = viewer.form9user("\n* call:" .. form, tostring(tabname), tostring(tablebase) )
	if (type(tablebase) ~= "table") then tablebase = package.loaded end -- package.loaded or versioning.main_i18n
	if (type(tabname) ~= "string") then tabname = "versioning.main_i18n" end -- or p.v.version
	t = t .. viewer.form9user(" * norm:" .. form, tostring(tabname), tostring(tablebase) ) -- formTestCase
--	return subtab, err, t = luaTable.fromstring(tabname, tablebase, t)
	local subtab, err, t2 = luaTable.fromstring(tabname, tablebase, t)
--	local subtable, sub_name = luaTable.subname_to_subtable(sub_base, sub_name) -- Get the last sub-table from a sub-table name.
--	local tablebase, sub_name = luaTable.subname_to_subtable(tablebase, tabname) -- Get the last sub-table from a sub-table name.
	t = t .. viewer.form9user(" * sub:" .. form, tostring(tabname), tostring(tablebase) )
-- DEBUG			.. viewer.Td( luaTable.formSubCounts(elem.i18nRoot .. ".i18n", elem.alias .. ".i18n", "this + %2/%3 T/L, ") )
-- DEBUG				.. luaTable.formSubCounts("versioning.main_i18n", "versioning.main_i18n", ", (all = %2/%3 T/L)")
	local tot_vars, tot_tabs, tot_func, vr_tb = luaTable.SubCounts(tablebase, tabname ) -- , opt, 1) -- function luaTable.SubCounts(
--	local txt2 = viewer.form9user(form, tostring(tabname), tot_vars, tot_tabs, tot_func, tostring(tablebase) )
	t = t .. viewer.form9user(" * counts:" .. form, tot_vars, tot_tabs, tot_func, tostring(tablebase) ) -- end -- tostring(tabname), 
--	res = res .. " ; " .. txt2 .. " ; " .. t .. " ; " .. (err or " no err")
	return t, tot_vars, tot_tabs, tot_func, vr_tb
end -- function luaTable.formSubCounts(tabname, tablebase, form)

function luaTable.subname_to_subtable(sub_base, sub_name) -- Get the last sub-table from a sub-table name.
	if sub_base == {} then sub_base = package.loaded end
	local split, subtable, sub_nm, sub_tb, sub_sub_nm -- part of the chain of tables and their names.
	if type(sub_name) ~= "string" then
		sub_name = ""
	else -- normal case
		if type(sub_base) == "table" then -- sub_base = package.loaded end
			split = mw.text.split(sub_name, '.', true) -- from the subgroup in string form
			sub_nm = split[1]
			if (type(tab) == "table") and (type(sub_nm) == "string") and (type(sub_base) == "table") then
				sub_sub_nm = string.sub( sub_name, (string.len(sub_nm) + 1), -1 ) -- chain of names of tables, without the first one.
				subtable = sub_base[ sub_nm ]
				if (type(subtable) == "table") then
					sub_tb, sub_nm = subtable, sub_nm -- luaTable.subname_to_subtable(subtable, sub_nm)
					sub_base = sub_tb or sub_base
				end
			end
		end
	end
	return sub_base, sub_name
--	local sub_base, sub_name = luaTable.subname_to_subtable(sub_base, sub_name) -- Get the last sub-table from a sub-table name.
--	local sub_base, sub_name = luaTable.subname_to_subtable( {}, "luaTable.i18n") -- Get the last sub-table from a sub name string.
end -- function luaTable.subname_to_subtable(sub_base, sub_name)

function luaTable.subname_to_subtable_test(t) -- Test: Get the last sub-table from a sub-table name.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* <b>luaTable.subname_to_subtable_test()</b> Test: Get the last sub-table from a sub-table name."
	local tabTest = { tb12 = { "one", "two" }, tb23 = { s = "six", t = "ten", m = "more" } }
	local tabView = {
		testGroup = { -- versioning.deprecatedFunctionGroup
			{ "rough2names", { "one", "two" },	"luaTable : name=%1, t=%2, l=%3, base=%4." },
			{ "tb12",   tabTest, "luaTable : name=%1, t=%2, l=%3, base=%4." },
			{ "tb23.s", tabTest, "luaTable : name=%1, t=%2, l=%3, base=%4." },
			{ "luaTable.i18n", luaTable.i18n,	"luaTable.i18n : name=%1, t=%2, l=%3, base=%5." },
			{ "begin.i18n", 	begin.i18n, 	"luatable_form_subcounts_subtables" },
			{ "begin", "none", 	"luatable_form_subcounts_subtables" },
			{ "begin.i18n", "none", 	"luatable_form_subcounts_subtables" },
			{ "versioning.i18n", versioning.i18n },
			{ "versioning.i18n", "none", "versioning : name=%1, t=%2, l=%3, base=%5." },
		headers = "sub name; base table; table size; luaTable.roughView ...",
		}
	}
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		local sub_name, sub_base, sub_table = case[1], case[2], {}
		local form_counts, sub_table_size = case[3], 0
		local sub_tb, sub_nm = luaTable.subname_to_subtable(sub_base, sub_name)
		if type(sub_base) ~= "table" then sub_base = type(sub_base) end
		sub_table_size = #sub_tb
		if not form_counts then form_counts = "luatable_form_subcounts_subtables" end
	--	luatable_form_subcounts_subtables	= "The table <b>%1</b> counts <b>%2</b> values, <b>%3</b> sub-tables and <b>%4</b> functions.",
		if form_counts == "luatable_form_subcounts_subtables" then form_counts = "The table <b>%1</b> counts <b>%2</b> values, <b>%3</b> sub-tables and <b>%4</b> functions." end
		form_counts = viewer.form9user(form_counts, sub_name, sub_table_size) .. viewer.ta("size", sub_table_size)
		local roughView = luaTable.roughView(sub_tb, opt) -- Form a rough LUA code to understand the structure of the table.
		roughView = string.sub(roughView, 1, 222) .. " <b>. . . limited to 222 chars.</b>"
		-- headers = "sub name; base table; table counts; luaTable.roughView", viewer_tableView_tests_headers
	--	tabView.headers = tabView.headers or "viewer_tableView_tests_headers"
		return {	sub_name, 	sub_base, 	form_counts, 	roughView }
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.headers = "sub name; base table; table size; luaTable.roughView"
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
--	t = t .. tabView.t
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function luaTable.subname_to_subtable_test(t)

function translate.translationsCounts(t, ...)
--	versioning.antiLoop("translate.translationsCounts", 3, translate.translationsCounts, t, ...) -- Limit the n-th reentrant call of any function.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "\n* Counts of contents of some tables: "
	t = t .. "<br>" .. luaTable.formSubCounts("begin.i18n", begin.i18n, "begin : name=%1, t=%2, l=%3, base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("datas.i18n", "datas.i18n", "datas : name=%1, t=%2, l=%3, base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("events.i18n", "events.i18n", "events : in table %1 : %2 trans in %3 langs., base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("luaTable.i18n", luaTable.i18n, "luaTable : in table %1 : %2 trans in %3 langs., base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("mathroman.i18n", mathroman.i18n, "mathroman : in table %1 : %2 trans in %3 langs., base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("modes.i18n", modes.i18n, "modes : in table %1 : %2 trans in %3 langs., base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("string.i18n", nil, "string : name=%1, t=%2, l=%3, base=%5.")
	t = t .. "<br>" .. luaTable.formSubCounts("testsCases.i18n", nil, "testsCases : name=%1, t=%2, l=%3, base=%5.")
--	function luaTable.fromstring(tabname, tablebase, t) -- Find the table from its name
--	luaTable.formSubCounts(tabname, tablebase) -- Form text of translations counts of a i18n table.
--	local subtable, sub_name = luaTable.subname_to_subtable( {}, "luaTable.i18n") -- Get the last sub-table from a sub name string.
	t = t .. "<br>" .. luaTable.formSubCounts("tools.i18n")
	t = t .. "<br>" .. luaTable.formSubCounts("translate.i18n")
	t = t .. "<br>" .. luaTable.formSubCounts("versioning.i18n")
	t = t .. "<br>" .. luaTable.formSubCounts("viewer.i18n")
	--
	t = t .. "<br>" .. luaTable.formSubCounts("versioning.main_i18n")
	t = t .. "<br>" .. luaTable.formSubCounts("translate.main_i18n")
	--
--	t = t .. "<br>" .. luaTable.formSubCounts("versioning.wiki_translations")
--	t = t .. "<br>" .. luaTable.formSubCounts("versioning.user_translations")
--	t = t .. "<br>" .. luaTable.formSubCounts("versioning.page_translations")
	t = t .. "<br>" .. luaTable.formSubCounts("translate.wiki_translations")
	t = t .. "<br>" .. luaTable.formSubCounts("translate.user_translations")
	t = t .. "<br>" .. luaTable.formSubCounts("translate.page_translations")
	--
	t = t .. "<br>" .. luaTable.formSubCounts("Module:Central") -- not available in any module
	t = t .. "<br>" .. luaTable.formSubCounts("Module:p.v.version") -- not available in any module
	t = t .. "<br>" .. luaTable.formSubCounts("Module:p.v.version.i18n") -- not available in any module
	--
	t = t .. "\n* Errors cases: "
	t = t .. "<br>" .. luaTable.formSubCounts("Empty table", {} ) -- do not fail in errors cases
	t = t .. "<br>" .. luaTable.formSubCounts("a string", "A string replaces the table.") -- do not fail in errors cases
	t = t .. "<br>" .. luaTable.formSubCounts(123.456, "A number replaces the table.") -- do not fail in errors cases
	t = t .. "<br>" .. luaTable.formSubCounts(function() end, "A function replaces the table.") -- do not fail in errors cases
	t = t .. "<br>" .. luaTable.formSubCounts(true , "A boolean value replaces the table.") -- do not fail in errors cases
	t = t .. "<br>" .. luaTable.formSubCounts(nil , "A nil value replaces the table.") -- do not fail in errors cases
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function translate.translationsCounts(t)

function luaTable.roughView(table, opt) -- Form a rough LUA code to understand the structure of the table.
	if type(table) ~= "table" then return viewer.value(table) end
	opt = opt or {}
	opt.level = (opt.level or 1)
	opt.recursiveLimit = tonumber(opt.recursiveLimit) or 11111
	opt.max_n = tonumber(opt.max_n) or 11111
	local shift = "\n" .. string.rep(":", opt.level)
	local t = ""
	local n = 0
	local continue = true
	if opt.level > opt.recursiveLimit then -- Limit the number of levels of sub-tables to opt.recursiveLimit
		local col = viewer.warningColor( viewer.form9user( '%1 = %2', "recursiveLimit", opt.recursiveLimit) )
		t = t .. viewer.form9user( '{ %1 }', col )
	else
		shift = "\n" .. string.rep(":", opt.level)
		for k, v in pairs(table) do
			n = n + 1
			if n > opt.max_n then -- Limit the number of elements in each-subtable to opt.max_n.
				local col = viewer.warningColor( viewer.form9user( '%1 = %2', "max_n", opt.max_n ) )
				continue = false
			end
			if continue then
				if (type(v) == "string") then
					if (type(k) == "string") then
					--	t = t .. viewer.form9user( '%1 = "%2", ', k, v, shift )
						if (string.gsub(k, "%x", "") == k) then
							t = t .. viewer.form9user( '%1 = "%2", ', tostring(k), v )
						else
							t = t .. viewer.form9user( '["%1"] = "%2", ', tostring(k), v )
						end
					else
						t = t .. viewer.form9user( '[%1] = "%2", ', tostring(k), v )
					end
				--	if n == (#table - 0) then t = t .. ' }s# ' end
				elseif (type(v) == "table") or (type(v) == "function") then
				else
					if (type(k) == "string")
					then t = t .. viewer.form9user( '%1 = %2, ', k, tostring(v), shift )
					else t = t .. viewer.form9user( '[%1] = %2, ', tostring(k), tostring(v), shift ) end
				end
			end
		end
		for k, v in pairs(table) do
			n = n + 1
			if n > opt.max_n then
				local col = viewer.warningColor( viewer.form9user( '%1 = %2', "max_n", opt.max_n ) )
				continue = false
			end
			if continue then -- Do not list if exclude1, exclude2 or exclude3 are in the table name.
				if (type(v) == "table") and (k ~= opt.exclude1) and (k ~= opt.exclude2) and (k ~= opt.exclude3) then
					opt.level = (opt.level or 1) + 1
					shift = "\n" .. string.rep(":", opt.level)
					local tt = luaTable.roughView(v, opt)
					opt.level = (opt.level or 1) - 1
					shift = "\n" .. string.rep(":", opt.level)
					t = t .. viewer.form9user( '%3 %1 = %2 ', k, tt, shift )
				elseif (type(v) == "function") then
					t = t .. viewer.form9user( '%3 %1 = %2() ', k, "function", shift )
				elseif (type(v) == "nil") then
					t = t .. viewer.form9user( '%3 %1 = %2() ', k, "nil", shift )
				end
			end
		end
	end
	t = "{ " .. t .. " }"
	return t
end -- function luaTable.roughView(table, opt)
function testsCases.luaTabView(table, opt) return luaTable.roughView(table, opt) end -- Form a rough LUA code to understand the structure of the table.

function testsCases.luaTabView_test(t) -- Deprecated alias function
	versioning.deprecatedFunction("testsCases.luaTabView_test", "luaTable.roughView_test")
	return luaTable.roughView_test(t)
end
function luaTable.roughView_test(t) -- Form a rough LUA code to understand the structure of the table.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or "luaTable.roughView_test(t) : From a LUA table, re-form a string roughly equivalent to the table."
	local table_key = { "A table used as key.", key = "string", bar = "bartxt", }
	local table_value = { "A table used as value is expended in the main table.", abc = "ABC", [5] = "five", ghi = "JKL", }
	local tae_val = "A table used as value is expended in the main table."
	local function funcView() return "A function result in this string." end
	local test_table = {
		key = "string",
		"string one without key",
		[12] = "number key twelve",
		["12"] = "string key twelve in brace",
		sub_table = { [4] = "four", abc = "ABC", [5] = "five", ghi = "JKL", },
		func = funcView(),
		"string two without key",
		[tonumber] = "A function used as key.",
--		[table_key] = "A table used as key.",
--		table_as_value = table_value,
		[true] = "A LUA reserved value used as key.",
	}
	local t = t .. luaTable.roughView(test_table)
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function luaTable.roughView_test(t)

-- - - - ------------------ - - - - ---------------------------------
-- Document the tables and their structures. List a table content, with formating.
-- Documentar las tablas y sus estructuras. Lista de un contenido de la tabla, con el formateo.
-- Documenter les tables et leurs structures. Lister un contenu de la table, avec le formatage.
-- - - - ------------------ - - - - ---------------------------------

-- Dump and format the content of a table and its sub-tables ; with limits in length, deep and exceptions.
-- Listar y formatar le contento de una tabla y su sub-tablas.
-- Lister et formater le contenu d'une table et ses sous-tables ; avec des limites en longueur, profondeur et exceptions.
-- For each (sub)table, list : in first vars, then functions, then sub-tables list, then sub-tables contents

function luaTable.structure_recursive(tbl, uppername, name, recursiveLevel, opt) -- countonly, level_maxi, max_n, exclude1, exclude2, exclude3)
	if type(name) ~= "string" then name = "table" end
	local res, newname, part, shift = "", "", "", ""
	local sep, N = ", ", 0
	local isempty = true
	local levelname = uppername .. "." .. name
	local tot_vars, tot_func, tot_tabs = 0, 0, 0
	local nbr_vars, lst_vars = 0, ""
	local nbr_func, lst_func = 0, ""
	local nbr_tabs, lst_tabs = 0, ""
	local max, lst_subtabs = 0, ""
	local st, vr, fn, tb = "", 0, 0, 0
	local list = not opt.countonly
	if recursiveLevel > opt.level_maxi then -- limit the number of the level of sub-tables
		local t_levelmaxi = viewer.form9user("luaTable_structure_level_maxi", recursiveLevel)
		return viewer.warningColor( tostring(t_levelmaxi ) ), tot_vars, tot_func, tot_tabs
	end
	opt.max_n = tonumber(opt.max_n) or 11111
	shift = string.rep("*", recursiveLevel)
	-- Do not list if exclude1, exclude2 or exclude3 is in the table name.
	if type(opt.exclude1) == "string" and opt.exclude1 ~= "" and string.find(name, opt.exclude1) ~= nil
	then return "", tot_vars, tot_func, tot_tabs end
	if type(opt.exclude2) == "string" and opt.exclude2 ~= "" and string.find(name, opt.exclude2) ~= nil
	then return "", tot_vars, tot_func, tot_tabs end
	if type(opt.exclude3) == "string" and opt.exclude3 ~= "" and string.find(name, opt.exclude3) ~= nil
	then return "", tot_vars, tot_func, tot_tabs end
	if type(tbl) ~= "table" then -- display table error
		return '<br>The variable "' .. tostring(name) .. '" is not a table.<br>', tot_vars, tot_func, tot_tabs
	end
	for k, v in pairs(tbl) do -- List and count vars, functions and sub tables
		k = tostring(k)
		if type(v) == "table" then
			lst_tabs = lst_tabs .. k .. sep
			nbr_tabs = nbr_tabs + 1
			isempty = false
			newname = tostring(k)
			max = max + 1
			if recursiveLevel <= opt.level_maxi then -- List recursively each sub-table
				st, vr, fn, tb = luaTable.structure_recursive(v, levelname, newname, recursiveLevel + 1, opt) -- countonly, level_maxi, max_n, exclude1, exclude2, exclude3)
				tot_vars = tot_vars + vr
				tot_func = tot_func + fn
				tot_tabs = tot_tabs + tb + 1
				if list then lst_subtabs = lst_subtabs .. st end
			else
				local t_levelmaxi = viewer.form9user("luaTable_structure_level_maxi", recursiveLevel)
				if list then res = res .. " " .. viewer.warningColor( t_levelmaxi ) end
			end
			local sep = ""
			if max == opt.max_n then
				local luaTable_table_listlimit_max_n = viewer.form9user("luaTable_table_listlimit_max_n", opt.max_n)
				if list then res = res .. "\n" .. shift .. " Table <b>" .. levelname .. "</b> " .. viewer.warningColor(luaTable_table_listlimit_max_n) end
				break
			end
		elseif type(v) == "function" then
			nbr_func = nbr_func + 1
			tot_func = tot_func + 1
			isempty = false
			if list then lst_func = lst_func .. k .. sep end
		else -- type(v) == other
			nbr_vars = nbr_vars + 1
			tot_vars = tot_vars + 1
			isempty = false
			if list then lst_vars = lst_vars .. type(v) .. " - <b>" .. tostring(k) .. "</b> = " .. tostring(v) .. " " .. sep end
		end
	end
	if list then res = res .. "\n" .. shift .. " Table <b>" .. levelname .. "</b>, " .. tostring(nbr_vars) .. " vars: " .. lst_vars end
	if list then res = res .. "\n" .. shift .. " Table <b>" .. levelname .. "</b>, " .. tostring(nbr_func) .. " functions: " .. lst_func end
	if list then res = res .. "\n" .. shift .. " Table <b>" .. levelname .. "</b>, " .. tostring(nbr_tabs) .. " tables: " .. lst_tabs .. lst_subtabs end
	return res, tot_vars, tot_func, tot_tabs
end -- function luaTable.structure_recursive(tbl, uppername, name, recursiveLevel, opt) -- countonly, level_maxi, max_n, exclude1, exclude2, exclude3)

function tools.luaTabStruc(	  table, tablename, opt) -- Deprecated alias function
	versioning.deprecatedFunction("tools.luaTabStruc", "luaTable.structure")
	return luaTable.structure(table, tablename, opt)
end
--		{ boxstyle = "boxstyle", level_maxi = 3, max_n = 2, exclude1 = "hou" } ) -- , {width = "88%", text_color = "blue"}
function luaTable.structure(table, tablename, opt)
--	luaTable_structure_limits_title		= "List a table, test with limits:",
	local recursiveLevel, recursiveLimit, recursiveLevel_err = 1, 1111, ""
	local res = "\n* Content of the <b>" .. tostring(tablename) .. "</b> table, begin:"
	if not opt then opt = { recursiveLimit = 11111 } end
	local recursiveLevel = opt.recursiveLevel or 1
	if viewer.activate_view then
		recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
	end
--	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
	if type(opt.level_maxi) ~= "number" then opt.level_maxi = 222 end
--	opt.level_maxi = opt.level_maxi or 222
	if type(opt.max_n) ~= "number" then opt.max_n = 3333 end
	opt.max_n = opt.max_n or 3333
	opt.recursiveLevel = opt.recursiveLevel or recursiveLevel or 1
	opt.exclude1 = opt.exclude1 or ""
	opt.exclude2 = opt.exclude2 or ""
	opt.exclude3 = opt.exclude3 or ""
	opt.countonly = opt.countonly and true
	local tot_vars, tot_func, tot_tabs = 0, 0, 0
	if recursiveLevel > opt.level_maxi then -- even for abnormal limit -- Erreur Lua dans Module:Central-s-fr à la ligne 7271 : attempt to compare number with nil.
		return "", tot_vars, tot_func, tot_tabs
	end
	local st, vr, fn, tb = luaTable.structure_recursive(table, "", tablename, 1, opt) -- countonly, level_maxi, max_n, exclude1, exclude2, exclude3)
	res = res .. st
	tot_vars = tot_vars + vr
	tot_func = tot_func + fn
	tot_tabs = tot_tabs + tb
	res = res .. "\n* Content of the <b>" .. tostring(tablename) .. "</b> table, end: "
	res = res .. viewer.form9user(" %1 variables, %2 functions, %3 sub-tables.\n", vr, fn, tb)
	return res, tot_vars, tot_func, tot_tabs
end -- function luaTable.structure(table, tablename, opt)

-- Auto test of limits of the table list.
-- Auto test des limites de liste de table.
luaTable.tablim = { "one", "two", max1 = "MAX1", max2 = "MAX2", max3 = "MAX3"}
luaTable.tablim.life = { animal = "dog", vegetal = { big = "tree", small = "flower"} }
luaTable.tablim.life.animals = { "turtle"}
luaTable.tablim.comfort = { "tv", mobile = "car"}
luaTable.tablim.house = { "kitcheen", "bedroom"}
luaTable.tablim.house.garden = { flower = "rose", nature = "river"}


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:testsCases implements recursive mediawiki tests cases and reports them for Lua coders.
--	New testsCases library part, to support tests cases
--	count = number of tests; provide( n ) returns three values: n, and expected output; run( n ) returns one string.
--	Starter is testsCases.report for recursive testsCases.runGroups
--	see Extension:Scribunto/Lua reference manual#Test_cases : class nameTest extends Scribunto_LuaEngineTestBase
-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------


-- testsCases = {} -- already declared by Scribunto, else in _G space.
-- Translations for testsCases library
testsCases.i18n = {}
testsCases.i18n.en = {
	testsCases_subDiffs_abnormal_error		= "Internal error: abnormal subDiffs process",
	testsCases_subDiffs_boolean_error		= "subDiffs boolean error",
	testsCases_subDiffs_function_error		= "subDiffs function error",
	testsCases_subDiffs_nil_error			= "subDiffs nil error",
	testsCases_subDiffs_number_error		= "subDiffs number error",
	testsCases_subDiffs_string_error		= "subDiffs string error",
	testsCases_subDiffs_table_error			= "subDiffs table error",
	testsCases_subDiffs_tables_error		= "subDiffs tables error",
	testsCases_subDiffs_type_error			= "subDiffs type error",
	testsCases_subDiffs_types_error			= "subDiffs types error",
	testsCases_recursiveLevel_error			= "subDiffs recursion level exceeded error",
	testsCases_search_Diffs_tests_title		= "testsCases.search_Diffs_tests() Search detailed differences in testsCases",
	testsCases_search_Diffs_tests_headers	= "Expected value; Actual value; Test indentifier; Results + Errors : expected-actual-details",
	testsCases_tests_Cases_report_title	= "testsCases.tests_Cases_report() Mediawiki recursive testsCases",
	testsCases_group_Diffs_tests_title		= "testsCases.group_Diffs_tests() Tests: Report detailed differences, where, recursively",
	testsCases_group_Diffs_test_headers		= "Expected value; Actual value; Test indentifier; Results + Errors : expected-actual-details",
} -- testsCases.i18n.en
testsCases.i18n.es = {
	testsCases_subDiffs_abnormal_error		= "Error interno: subDiffs proceso anormal",
	testsCases_subDiffs_boolean_error		= "subDiffs booleano error",
	testsCases_subDiffs_function_error		= "subDiffs función error",
	testsCases_subDiffs_nil_error			= "subDiffs nulo error",
	testsCases_subDiffs_number_error		= "subDiffs numero error",
	testsCases_subDiffs_string_error		= "subDiffs texto error",
	testsCases_subDiffs_table_error			= "subDiffs tabla error",
	testsCases_subDiffs_tables_error		= "subDiffs tablas error",
	testsCases_subDiffs_type_error			= "subDiffs typo error",
	testsCases_subDiffs_types_error			= "subDiffs typos error",
	testsCases_recursiveLevel_error			= "subDiffs nivel de recursividad superó error",
	testsCases_search_Diffs_tests_title		= "testsCases.search_Diffs_tests() Buscar diferencias detalladas en testsCases",
	testsCases_search_Diffs_tests_headers	= "Valor prevista; Valor activa; Identificador del prueba; Resultados + Errores : prevista-activa-detalles",
	testsCases_tests_Cases_report_title	= "testsCases.tests_Cases_report() Mediawiki testsCases recursivos",
	testsCases_group_Diffs_tests_title		= "testsCases.group_Diffs_tests() Pruebas: Reporte detallado de las diferencias, ¿dónde? recursivamente",
	testsCases_group_Diffs_test_headers		= "Valor prevista; Valor activa; Identificador del prueba; Resultados + Errores : prevista-activa-detalles",
} -- testsCases.i18n.es
testsCases.i18n.fr = {
	testsCases_subDiffs_abnormal_error		= "Erreur interne : processus subDiffs anormal",
	testsCases_subDiffs_boolean_error		= "subDiffs erreur de booléen",
	testsCases_subDiffs_function_error		= "subDiffs erreur de fonction",
	testsCases_subDiffs_nil_error			= "subDiffs erreur de nul",
	testsCases_subDiffs_number_error		= "subDiffs erreur de nombre",
	testsCases_subDiffs_string_error		= "subDiffs erreur de texte",
	testsCases_subDiffs_table_error			= "subDiffs erreur de table",
	testsCases_subDiffs_tables_error		= "subDiffs erreur de tables",
	testsCases_subDiffs_type_error			= "subDiffs erreur de type",
	testsCases_subDiffs_types_error			= "subDiffs erreur de types",
	testsCases_recursiveLevel_error			= "subDiffs erreur de niveau de récursivité dépassé",
	testsCases_search_Diffs_tests_title		= "testsCases.search_Diffs_tests() Chercher les différences détaillées dans les testsCases",
	testsCases_search_Diffs_tests_headers	= "Valeur attendue; Valeur active; Identifiant du test; Résultats + Erreurs : attendue-active-détails",
	testsCases_tests_Cases_report_title	= "testsCases.tests_Cases_report() Mediawiki testsCases recursifs",
	testsCases_group_Diffs_tests_title		= "testsCases.group_Diffs_tests() Tests: Rapport detaillé des différences, où, recursivement",
	testsCases_group_Diffs_test_headers		= "Valeur attendue; Valeur active; Identifiant du test; Résultats + Erreurs : attendue-active-détails",
} -- testsCases.i18n.fr

-- testsCases = {} -- already declared by Scribunto, else in _G space.
testsCases.recursiveLimit = 11
testsCases.OK = "OK"
testsCases.ERR = "ERR"
testsCases.okStrings = "OK"
--	local args = { ... } -- optional arguments

-- Starter for testsCases.runGroups for testsCases.report
-- protected static $moduleName = 'ClassNameTest'; -- See Extension:Scribunto/Lua_reference_manual#Test_cases
function testsCases.debugGetGroup(group, groupname) -- DEBUG the bug T20160615 : Lua functions calls lost multilevel table arguments
	group = testsCases.groupMemoDebug -- Try to transmit the group across testsCases.groupMemoDebug
	if groupname and not group then -- OK
		t = t .. "DEBUG_group: "
		t = t .. viewer.ta("begin group", group)
		t = t .. viewer.ta("testsCases.groupMemoDebug", testsCases.groupMemoDebug)
		t = t .. viewer.ta("groupname", groupname)
	--	local subgroup = { errorsKey = 2, modulename = "mathroman", ..., groupname = "mathroman.testscases.TestsGroups", } -- Example of link to subgroup
		--[ [ Normal method: found the missing group from groupname
		local tab = mw.text.split(groupname, '.', true) -- from the subgroup in string form
		for i, part in ipairs(tab) do -- Build the subgroup table from the groupname string
			t = t .. viewer.ta("part", part)
			if i == 1 then -- The start point is always a recorded module or library
				module = package.loaded[ part ]
				if type(module) == "table" then
					group2use = module
				end
			else -- add a subgroup to the previous subgroup
				if type(group2use) == "table" then
					subgroup = group2use[ part ]
					group2use = subgroup
				end
			end
		end
		group = group2use
		t = t .. viewer.ta("end group", group)
	end
	return group, t
end -- function testsCases.debugGetGroup(group, groupname)

function testsCases.searchDiffs( subcase, name_key, expect, actual, recursiveLevelX )
--	Report all differences between expect and actual variables of any types. Recursivity limited to recursiveLimit
	local expect = subcase.expect
	local actual = subcase.actual
	local opt = mw.clone(subcase.Options)
--	local recursiveLevel = recursiveLevel or 1
	subcase.name = subcase.name or "group_Diffs"
--	subcase.name_key = name_key or subcase.name_key or "searchDiffs"
	subcase.diffdetails = subcase.diffdetails or ""

--	local name_key = subcase.name_key
	local diffs = {}
	subcase.all_diffs = subcase.all_diffs or ""
	if (type(actual) == "table") and (type(expect) == "table") then -- Compare 2 tables of elements
	--	if recursiveLevel <= (events.recursiveLimit or 3) then
		if opt.recursiveLevel <= opt.recursiveLimit then
			opt.recursiveLevel = opt.recursiveLevel + 1
			for key, val in pairs(actual) do
				if not expect[key] then -- Detect actual values in args{} missing in expect{}, which is one difference.
					local txt_diffs = translate.formTestCase("testsCases_search_Diffs_tests_title", key, val) -- Report differences, for simple elements
				end
			end
			subcase.diffdetails = subcase.diffdetails .. "_r" .. opt.recursiveLevel
			for key, val in pairs(expect) do -- Compare all table elements
				local subcase = {}
				subcase.Options = opt
				if opt.recursiveLevel == 1
					then subcase.name_key = subcase.name .. "_" .. tostring(key)
					else subcase.name_key = "searchDiffs_" .. tostring(key) end
			--	subcase.name_key = name_key or "searchDiffs"
				subcase.diffdetails = ""
				subcase.expect = expect[key]
				subcase.actual = actual[key]
				subcase.expect_t = viewer.value(subcase.expect)
				subcase.actual_t = viewer.value(subcase.actual)
				subcase.name = subcase.name or "group_Diffs"
				subcase.name_key = subcase.name .. "_" .. tostring(key)
				subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_tables_error"
			--	Report all differences between expect and actual variables of any types. Recursivity limited to recursiveLimit
				local sub_diffs = testsCases.searchDiffs( subcase, subcase.name_key, subcase.expect, subcase.actual, recursiveLevelX )
				if (subcase.diffdetails ~= nil) and (subcase.diffdetails ~= "") then
					table.insert( sub_diffs,
						translate.formTestCase(subcase.result or "testsCases_subDiffs_tables_error", viewer.value(subcase.expect), viewer.value(subcase.actual) )
					)
				end
				for key, diff in pairs(sub_diffs) do -- Add all sub-diffs to diffs at this recursive level of details.
					table.insert( diffs, diff )
				end
			end
		end
	end
	-- Report simple differences, for simple elements
	subcase.name_key = subcase.name -- .. "_" .. tostring(key)
	subcase.expect_t = viewer.value(subcase.expect)
	subcase.actual_t = viewer.value(subcase.actual)
	subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_boolean_error"
	diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
	--[ [
	if subcase.expect_t ~= subcase.actual_t then
		if (actual ~= expect) and (type(expect) == "table") and (type(actual) == "table") then
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_tables_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		elseif (actual ~= expect) and (actual == nil) then
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_nil_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		elseif (actual ~= expect) and (actual == true) then
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_boolean_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		elseif (actual ~= expect) and (type(actual) == "number") then
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_number_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		elseif (actual ~= expect) and (type(actual) == "table") then
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_table_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		elseif (actual ~= expect) and (type(actual) == "function") then
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_function_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		else
			subcase.result = ( subcase.name_key .. subcase.diffdetails ) or "testsCases_subDiffs_other_error"
		--	table.insert( diffs, (#diffs + 1), viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t ) )
			diffs[(#diffs + 1)] = viewer.form9user(subcase.result, subcase.expect_t, subcase.actual_t )
		end
	end --] ]
	return diffs
end -- function testsCases.searchDiffs( subcase, name_key, expect, actual, recursiveLevel )

function testsCases.group_Diffs_tests( t, casesgroup, tabOptions ) -- Tests a group of tests cases.
	local t = t or "\n* <b>testsCases.group_Diffs_tests()</b>" -- intern report to display for humans
	if type(casesgroup) ~= "table" then casesgroup = nil end
	if type(tabOptions) ~= "table" then tabOptions = nil end
	local default_group_test = { -- events.testGroup
		{ expect = "abc", actual = "abc", name = "search_Diffs_tests", }, -- expect, actual, name,
		{ expect = "ABC", actual = "abc", name = "search_Diffs_tests", },
		{ expect = 123, actual = 456, name = "search_Diffs_tests", },
		{ expect = 33, actual = nil, name = "search_Diffs_tests", },
		{ expect = 33, actual = true, name = "search_Diffs_tests", },
		{ expect = {"abc"}, actual = "ABC", name = "search_Diffs_tests", },
	}
	local default_tabOptions = {
		headers = "testsCases_group_Diffs_test_headers",
		group = casesgroup or default_group_test,
		concat = "; <br/>",
		rowGroup = {},
		recursiveLevel = 1,
		recursiveLimit = 3,
	}
	tabOptions = tabOptions or default_tabOptions
	tabOptions.testGroup = tabOptions.testGroup or default_group_test
	tabOptions.headers = tabOptions.headers or "testsCases_group_Diffs_test_headers"
	tabOptions.concat = tabOptions.concat or "; <br/>"
	tabOptions.rowGroup = {}
	tabOptions.run = tabOptions.run or testsCases.group_Diffs_tests
	if type(tabOptions.testGroup) == "table" then
		for key, subcase in pairs(tabOptions.testGroup) do
		--	local diffs = {}
			subcase.name = subcase.name or "group_Diffs"
			subcase.name_key = subcase.name .. "_" .. tostring(key)
		--	Report all differences between expect and actual variables of any types. Recursivity limited to recursiveLimit
			local sub_diffs = testsCases.searchDiffs( subcase, subcase.name_key, subcase.expect, subcase.actual, recursiveLevel )
			for key, diff in pairs(sub_diffs) do -- Add all sub-diffs to diffs at this recursive level of details.
				if (diff ~= nil) and (diff ~= "") then
				--	diff = ( subcase.name_key .. diff ) -- or "testsCases_subDiffs_tables_error"
					subcase.result = subcase.result .. "\n* " .. viewer.errorColor(diff)
				end
			end
			table.insert( tabOptions.rowGroup, {
				viewer.value(subcase.expect),
				viewer.value(subcase.actual),
				tostring( subcase.name_key or "name_key"),
				tostring( subcase.result or "-"),
			} )
		end
	else t = t .. "error : multiple_values_tests has no table. " end
	t = t .. viewer.tableView(tabOptions) -- Form a table with lines and columns.
	return t, diffs
end -- function testsCases.group_Diffs_tests( t, casesgroup, tabOptions )

function testsCases.search_Diffs_tests( t ) -- Tests of report differences, which kind, where, recursively.
	local t = t or "\n* Test <b>testsCases.search_Diffs_tests():synthesis...</b>" -- intern report to display for humans
	local res_g, res_t, strResults, value_1 = "", "", "", ""
	local recursiveLevel = 1
	local tabOptions = { -- Init a new events group for erros, warnings and categories
		headers = "typ;key;testcase idargs;result", -- headers of columns to display in a tableview for users and coders.
		kind = "projects", -- mediawiki or projects. How to run and to display testscases results.
	--	typ = "cat", -- Type of events to selector for list or for tests.
		catview = ":", -- "" to really categorize. ":" to display links to categories.
		user_lang = translate.user_lang, -- "en", -- User language to use for tests and/or to display tests.
		wiki_lang = translate.wiki_lang, -- "fr", -- Wiki language to use for tests and/or to display tests.
		recursiveLevel = 1,
		recursiveLimit = 3,
	--	tableViewStyle = "wikitable alternative center", -- example of style for a viewer.tableView() with lines and columns.
	--	testsFunction = function_name, -- Dedicated function for all tests in the eventsGroup of tests.
	}
	local function search_Diffs_tests1(t, i, expect, actual, name, expectKey, errorsKey)
	--	t, i = search_Diffs_tests1(t, i, {"abc"}, "ABC", "search_Diffs_tests")
	--	t, i = search_Diffs_tests1(t, i, {123, "err"}, {456, "err"}, "search_Diffs_tests-string", 2)
		subcase = {}
	--	testcase.Options = opt
		subcase.Options = tabOptions
		subcase.recursiveLevel = 1
		subcase.recursiveLimit = 3
		subcase.actual = actual
		subcase.expect = expect
		subcase.expect_t = viewer.value(expect)
		subcase.actual_t = viewer.value(actual)
		subcase.name = name or subcase.name or "search_Diffs_tests"
		if subcase.name == "search_Diffs_tests-recursiveLimit-5" then
			subcase.recursiveLimit = 5 -- default subcase.recursiveLimit = 3
		end
		subcase.expectKey = expectKey or subcase.expectKey or 1
		subcase.errorsKey = errorsKey or subcase.errorsKey or 2
		subcase.name_key = subcase.name .. "_" .. i
		subcase.n_diffs = 0
		subcase.all_diffs = ""
		local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal( subcase.recursiveLevel, subcase.recursiveLimit)
	--	subcase.actual_t = luaTable.roughView(actual)
	--	subcase.expect_t = luaTable.roughView(expect)
		local sub_diffs = testsCases.searchDiffs(subcase) -- Report simple differences, for simple elements
		subcase.n_diffs = #sub_diffs
		subcase.all_diffs = ""
		for i, diff in ipairs(sub_diffs) do -- List all diffs
			if (type(diff) == "string") then subcase.all_diffs = subcase.all_diffs .. "\n* " .. viewer.errorColor(diff) end
		end
		return subcase, t, (i + 1)
	end -- function search_Diffs_tests1(t, i, args, expected, name)
	local group_test = { -- events.testGroup
		{ expect = "abc", actual = "abc", name = "search_Diffs_tests", }, -- expect, actual, name,
		{ expect = "ABC", actual = "abc", name = "search_Diffs_tests", },
		{ expect = 123, actual = 123, name = "search_Diffs_tests", },
		{ expect = 123, actual = "123", name = "search_Diffs_tests", },
		{ expect = 321, actual = 123, name = "search_Diffs_tests", },
		{ expect = true, actual = true, name = "search_Diffs_tests", },
		{ expect = false, actual = true, name = "search_Diffs_tests", },
		{ expect = true, actual = false, name = "search_Diffs_tests", },
		{ expect = false, actual = false, name = "search_Diffs_tests", },
		{ expect = nil, actual = "nil", name = "search_Diffs_tests", },
		{ expect = nil, actual = nil, name = "search_Diffs_tests", },
		{ expect = 33, actual = nil, name = "search_Diffs_tests", },
		{ expect = 33, actual = true, name = "search_Diffs_tests", },
		{ expect = function() end, actual = "func", name = "search_Diffs_tests", },
		{ expect = "func", actual = function() end, name = "search_Diffs_tests", },
		{ expect = function() end, actual = function() end, name = "search_Diffs_tests", },
		{ expect = tostring, actual = tostring, name = "search_Diffs_tests-tostring()", },
		{ expect = "ABC", actual = {"abc"}, name = "search_Diffs_tests", },
		{ expect = {"abc"}, actual = "ABC", name = "search_Diffs_tests", },
		{ expect = {"abc", "def"}, actual = {"abc", "def"}, name = "search_Diffs_tests", },
		{ expect = {"ABC", "err"}, actual = {"abc", "err"}, name = "search_Diffs_tests", },
		{ expect = {123, "err"}, actual = {456, "err"}, name = "search_Diffs_tests-string", 2, },
		{ expect = {"ABC", "DEF"}, actual = {"abc", "def"}, name = "search_Diffs_tests", },
		{ expect = {123, 456}, actual = {"abc", "def"}, name = "search_Diffs_tests", },
		{ expect = { u = {"u1", "u2"}, x = {"x1", "x2"} }, actual = { u = {"u1", "u2"}, x = {"x1", "x2"} }, name = "search_Diffs_tests-2-levels-ident-tables", },
		{ expect = { u = {"u1", "u2"}, x = {"H1", "x2"} }, actual = { u = {"u1", "u2"}, x = {"x1", "x2"} }, name = "search_Diffs_tests-2-levels-diffs-tables", 2, },
		{	expect = { a1 = { a2 = { a3 = { a4 = { a5 = { a6 = { a7 = "A7" } } } } } } },
			actual = { a1 = { a2 = { a3 = { XX = { a5 = { a6 = { a7 = "A7" } } } } } } },
			name = "search_Diffs_tests-recursiveLimit-3",
		},
		{	expect = { a1 = { a2 = { a3 = { a4 = { a5 = { a6 = { a7 = "A7" } } } } } } },
			actual = { a1 = { a2 = { a3 = { XX = { a5 = { a6 = { a7 = "A7" } } } } } } },
			name = "search_Diffs_tests-recursiveLimit-5",
		},
	}
	local tabOptions = {}
	tabOptions.headers = "testsCases_search_Diffs_tests_headers"
	tabOptions.testGroup = group_test
	tabOptions.concat = "; <br/>"
	tabOptions.rowGroup = {}
	if type(group_test) == "table" then
		for i, test in pairs(group_test) do
			local subcase = search_Diffs_tests1("", i, test.expect, test.actual, test.name, expectKey, errorsKey)
			table.insert( tabOptions.rowGroup, {
				tostring( subcase.expect_t),
				tostring( subcase.actual_t),
				tostring( subcase.name_key .. '<br>' .. viewer.ta("n_diffs", subcase.n_diffs) ),
				tostring( subcase.all_diffs),
			} )
		end
	else t = t .. "error : multiple_values_tests has no table. " end
	t = t .. viewer.tableView(tabOptions) -- Form a table with lines and columns.
	return t
end -- function testsCases.search_Diffs_tests()

function testsCases.tests_Cases_report() -- Run a group of tests and returns one string.
	local res_g, res_t = testsCases.report( mathroman.testsGroups, "mathroman.testsGroups", 1 ) -- Run N groups of tests and returns one string.
	return res_t
end -- function testsCases.tests_Cases_report()

function testsCases.report( group, groupname, recursiveLevel ) -- , recursiveLevel ) -- Run a group of tests and returns one string.
	local group = group
	testsCases.groupMemoDebug = group
	if type(modes.recursiveLimit) ~= "number" then modes.recursiveLimit = 11 end -- debug 11111
	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, testsCases.recursiveLimit or 11 )
	testsCases.recursiveLimit = recursiveLimit
	local t = "\n* testsCases.report()..." -- intern report to display for coders
	local res = ""
	events.strKeyOnly = true
	local ttcs = testsCases.ModuleOrName("testsCases") -- Search the name of a module or reverse.
	local cent = testsCases.ModuleOrName(versioning) -- Search the name of a module or reverse.
	local tls = testsCases.ModuleOrName(tools) -- Search the name of a module or reverse.
--	local grp = testsCases.ModuleOrName(groupname) -- Search the name of a module or reverse.
	t = t .. viewer.ta( "\n* Group:ModuleOrName " .. groupname, type(groupname) ) .. viewer.ta( "testsCases", ttcs )
	t = t .. viewer.ta( cent, versioning ) .. viewer.ta( tls, tools ) .. "<br>"
--	t = t .. "\n* luaTable.roughView <b>mathroman.testsGroups</b> \n" .. luaTable.roughView(mathroman.testsGroups) --, { recursiveLimit = 11111, max_n = 11111, } )
	t = t .. viewer.ta("report events.strKeyOnly", events.strKeyOnly)
	local st, vr, fn, tb = luaTable.formSubCounts(group, "group") -- count of internal .i18n
	t = t .. "\n* " .. st
	testsCases.trackTests_t = "" -- init debug track
	testsCases.countFullCompute( group, groupname, recursiveLevel ) -- Compute testsCases.countFull
	testsCases.trackTests_t = "" -- init debug track
	local res_g, res_t = testsCases.runTestsCases( group, groupname, 1 ) -- Run only all tests cases in one group.
--	local errors_or_diffs = testcase.errors or string.gsub(testcase.all_diffs, ";", "; ")
	t = t .. viewer.form9user("\n* testsCases.report count/groups = <b>%1/%2</b>", testsCases.countFull, group.countGroups)
	t = t .. viewer.form9user("\n* report subkey + n = <b>%1 + %2</b>, recur=%3, bug group type=%4, ", group.subkey, group.subkey_n, recursiveLevel, type(group))
--[[
	mathroman.testsGroups = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- Each test_case defines a name, a function, an input, an output. Else a sub-group of testsCases.
	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.Tests_cases,
		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.Tests_cases", }, --
--]]
	t = t .. res_t .. viewer.ta("res_g", res_g)
	events.strKeyOnly = false
	t = t .. viewer.ta("end events.strKeyOnly", events.strKeyOnly)
--	t = t .. viewer.ta("\n* after rg type(group)", type(group) )
	return res, t
--	Extension:Scribunto/Lua reference manual : run( n ): Function that runs test n and returns one string.
end -- function testsCases.report( group, groupname, recursiveLevel )

function testsCases.run_testcase( n, testcase ) -- Function that runs test n and returns one string.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	-- Record all input arguments and output results for any function with any number of arguments and results.
	-- For library.run( n, testcase ) -- see Extension:Scribunto/Lua reference manual:
	-- For testsCases.runOneTest( testcase, n, module ) -- see Extension:Scribunto/Lua reference manual:
	--
	local tabOptions = { -- Init a new events group for erros, warnings and categories
		headers = "typ;key;testcase idargs;result", -- headers of columns to display in a tableview for users and coders.
		kind = "projects", -- mediawiki or projects. How to run and to display testscases results.
	--	typ = "cat", -- Type of events to selector for list or for tests.
		catview = ":", -- "" to really categorize. ":" to display links to categories.
		user_lang = translate.user_lang, -- "en", -- User language to use for tests and/or to display tests.
		wiki_lang = translate.wiki_lang, -- "fr", -- Wiki language to use for tests and/or to display tests.
	--	tableViewStyle = "wikitable alternative center", -- example of style for a viewer.tableView() with lines and columns.
	--	testsFunction = function_name, -- Dedicated function for all tests in the eventsGroup of tests.
	}
	testcase.Options = tabOptions
	events.init_configs(testcase, tabOptions, {}) -- Init a new events group for erros, warnings and categories
	-- This events group is init, used and closed from Mediawiki tests or from the main module in pages.
	testcase.events.startPoint = testsCases.run_testcase -- Start testcase.events for Gerrit tests
	testcase.diffs = {}
	testcase.all_diffs = ""
	testcase.expectKey = testcase.expectKey or 1
	testcase.errorsKey = testcase.errorsKey or 2
	--
	testcase.runResults = { testcase.func( luaTable.toList( testcase.args ) ) } -- args table to list to a function which return a list received in a table.
	-- idargs  .actual
	local x, evt = events.add_err(testcase.name or "idargs", luaTable.toList(testcase.runResults) ) -- error event
	--
	testcase.runErrors = testcase.runResults[testcase.errorsKey]
	testcase.runResults[testcase.errorsKey] = testcase.runErrors
--	function testsCases.searchDiffs( subcase, name_key, expect, actual, recursiveLevel )
	local sub_diffs = testsCases.searchDiffs( testcase ) -- Report differences, recursiveLevel -- DEBUG 'name_key' (a nil value).
	testcase.runArgsRoughView = luaTable.roughView(testcase.args)
	testcase.runResultsRoughView = luaTable.roughView(testcase.runResults)
	testcase.runDetails = viewer.form9user("runDetails = <b>%1</b>(%2) = <b>%3</b>, \n** sub_diffs = <b>%4</b>, \n** testcase.runErrors = <b>%5</b>",
	testcase.name, testcase.runArgsRoughView, testcase.runResultsRoughView, sub_diffs.all_diffs, testcase.runErrors )
	local nil_or_0 = ( (sub_diffs.all_diffs) and string.find(sub_diffs.all_diffs, "nil") ) or 0
	testsCases.trackTests( testcase, "run_testcase nil?/len", nil_or_0, string.len(sub_diffs.all_diffs or "-" ) ) -- testcase.trackTests_t
--	if testcase.events.startPoint == testsCases.run_testcase then testcase.events.restore_configs(testcase.memo) end -- Close testcase.events for Gerrit tests
--	if testcase.memo and (testcase.events.startPoint == testsCases.run_testcase) then testcase.events.restore_configs(testcase.memo) end -- Close testcase.events for Gerrit tests
--	testcase.events.restore_configs(testcase.memo)
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return testcase.runErrors, value -- Function that runs test n and returns one string.
end -- function testsCases.run_testcase( n, testcase )

function testsCases.runOneTest( testcase, n, module ) -- see Extension:Scribunto/Lua reference manual:
--	run( n ): Function that runs test n and returns one string. (to compare with okStrings)
	testcase.runOneTest_t = ""
	local tabOptions = { -- Init a new events group for erros, warnings and categories
		headers = "typ;key;testcase idargs;result", -- headers of columns to display in a tableview for users and coders.
		kind = "projects", -- mediawiki or projects. How to run and to display testscases results.
	--	typ = "cat", -- Type of events to selector for list or for tests.
		catview = ":", -- "" to really categorize. ":" to display links to categories.
		user_lang = translate.user_lang, -- "en", -- User language to use for tests and/or to display tests.
		wiki_lang = translate.wiki_lang, -- "fr", -- Wiki language to use for tests and/or to display tests.
	--	tableViewStyle = "wikitable alternative center", -- example of style for a viewer.tableView() with lines and columns.
	--	testsFunction = function_name, -- Dedicated function for all tests in the eventsGroup of tests.
	}
	-- This events group is init, used and closed from Mediawiki tests or from the main module in pages.
	if not testcase.events then testcase.memo, testcase.events = events.init_configs(testcase, tabOptions, {}) end -- Init a new events group for erros, warnings and categories
	testcase.events.startPoint = testsCases.runOneTest -- For Modules tests
	testcase.diffs = {}
	testcase.all_diffs = ""
	testcase.expectKey = testcase.expectKey or 1
	testcase.errorsKey = testcase.errorsKey or 2
	local number_of_one_test, name_of_the_test, ok_expected_output = module.provide(testsCases.countRun, testcase.name, testsCases.OK)
	testsCases.trackTests( testcase, "provide out", name_of_the_test, ok_expected_output) -- testcase.runOneTest_t
	local errors, value = testsCases.run_testcase( n, testcase ) -- Function that runs test n and returns one string.
--	local sub_diffs, recursiveLevel_err = testsCases.searchDiffs( testcase, 1 ) -- Report differences, recursiveLevel
	local sub_diffs, recursiveLevel_err = testsCases.searchDiffs( testcase, 1 ) -- Report differences, recursiveLevel
	testcase.n_diffs = sub_diffs.n_diffs -- testcase.n_diffs +
--	See example in testcase: mathroman.roman2int, args = { "MMMMMYJXC" }, expect = { 5089, "mathroman_J_before_end_err;mathroman_char_increase_err" } },
--	Select errors from diffs, without expected errors coming from testcase.expect[testcase.errorsKey]
	testcase.errors = testcase.all_diffs or ""
	testsCases.trackTests( testcase, "runOneTest nil?", string.find(testcase.all_diffs, "nil") or 0, string.len(testcase.all_diffs or "-" ) ) -- testcase.trackTests_t
	if testcase.events.startPoint == testsCases.runOneTest then testcase.events = testcase.events.restore_configs(testcase.memo) end -- Close testcase.events for Modules tests
	return testcase.all_diffs, testcase.errors
end -- function testsCases.runOneTest( testcase, n, module )

function testsCases.trackTests( testcase, where, x1, x2, x3, x4, x5, x6, x7, x8, x9 )
	if not testcase then return end
	local t = ""
	testcase = testcase or testsCases.subgroup_memo
	t = t .. viewer.form9user(". %1 ", where)
	if x1 then t = t .. viewer.tam("1", x1 ) end
	if x2 then t = t .. viewer.tam("2", x2 ) end
	if x3 then t = t .. viewer.tam("3", x3 ) end
	if x4 then t = t .. viewer.tam("4", x4 ) end
	if x5 then t = t .. viewer.tam("5", x5 ) end
	if x6 then t = t .. viewer.tam("6", x6 ) end
	if x7 then t = t .. viewer.tam("7", x7 ) end
	if x8 then t = t .. viewer.tam("8", x8 ) end
	if x9 then t = t .. viewer.tam("9", x9 ) end
	testcase.trackTests_t = (testcase.trackTests_t or ".") .. t
	return t
end

function testsCases.countFullCompute( group, groupname, recursiveLevel ) -- Compute testsCases.countFull
	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, testsCases.recursiveLimit or 111 )
	testsCases.recursiveLimit = recursiveLimit
	if recursiveLevel == 1 then testsCases.countRun = 0 end
	if type(group) == "table" then -- Recursive run groups of tests cases.
		for k, testcase in ipairs(group) do -- Run only all tests cases in one group.
			if (type(testcase) == "table") and (type(testcase.args) == "table") and (type(testcase.expect) == "table") then -- Run tests cases with args and expect tables.
				local module = nil -- provide
				module = package.loaded[ tostring(testcase.modulename) ] -- Each testcase can change of module
				if (type(module) == "table") then -- If the module to test is in package.loaded
					testsCases.countRun = (testsCases.countRun or 0) + 1
				end -- if module
			else -- if testcase and testcase.args and testcase.expect
				local subgroup = testcase.group
				if (type(subgroup) == "table") then -- Run all tests cases in one subgroup.
					if recursiveLevel < recursiveLimit then
						local res_g, res_t = testsCases.countFullCompute( subgroup, subgroup.groupname, recursiveLevel + 1 ) -- Run only all tests cases in one group.
					end
				end
			end
		end -- for k, testcase in ipairs(group) do
	end -- if type(group) == "table" then -- Recursive run groups of tests cases.
	testsCases.countFull = testsCases.countRun
	if recursiveLevel == 1 then testsCases.countRun = 0 end
end -- function testsCases.countFullCompute( group, groupname, recursiveLevel )

function testsCases.runTestsCases( group, groupname, recursiveLevel ) -- Run only all tests cases in one group.
--	Extension:Scribunto/Lua reference manual : run( n ): Function that runs test n and returns one string.
--	events.strKeyOnly = true -- error no tests
	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, testsCases.recursiveLimit or 11 )
	testsCases.recursiveLimit = recursiveLimit
	local t = "" -- intern report to display for humans
	local res, res_g, tt, tt_g, module, diffs, sub_where = "", "", "", "", "", "", "", ""
--	t = t .. viewer.ta( "\n* Group:runTestsCases start", type(group) ) .. viewer.ta( "recursiveLevel", recursiveLevel ) .. viewer.ta( "groupname", groupname )
	if type(group) ~= "table" then group, tt = testsCases.debugGetGroup(group, groupname) end
	if type(group) ~= "table" then return res, t end
	if type(groupname) ~= "string" then return res, t end
	group.countGroups = #group
	group.countCases = 0
	group.all_diffs = ""
	--[[ mathroman.testsGroups = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- Each test_case defines a name, a function, an input, an output. Else it defines a sub-group of testsCases.
	{ name = "mathroman", func = testsCases.runGroups, group = mathroman.Tests_cases,
		modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.Tests_cases", }, --
	{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MCXI" }, expect = { 1111 } }, --]]
--	t = t .. viewer.ta( "\n* Group:runTestsCases type", tostring(group) ) .. viewer.ta( "group.countGroups", tostring(group.countGroups) ) -- #group
--	t = t .. viewer.ta( "group.modulename", group.modulename ) .. viewer.ta( "group.funcname", group.funcname ) .. viewer.ta( "preview countRun", testsCases.countRun )
--	local groupOfTestsCases = (type(group) == "table")	-- and (not group.args) and (not group.expect)
	if recursiveLevel == 1 then testsCases.runCount = 0 end
	if type(group) == "table" then -- Recursive run groups of tests cases.
		t = t .. viewer.ta( "group.args", group.args ) .. viewer.ta( "group.expect", group.expect )
		for k, testcase in ipairs(group) do -- Run only all tests cases in one group.
			local testcase = mw.clone(testcase) -- debug ?
		--	The group (or the root of tests) contain some testsCases which could point to subgroup.
			group.countCases = k
			testcase.runOneTest_t = "" -- init main result track
			testcase.name = (testcase.name or group.name or "name") .. "_" .. group.countCases
			testcase.all_diffs = ""
			testsCases.trackTests_t = ""
			if (type(testcase) == "table") and (type(testcase.args) == "table") and (type(testcase.expect) == "table") then -- Run tests cases with args and expect tables.
				local memo = events.save_configs() -- Save global configuration before eventual changes.
				local module = nil -- provide
				testcase.name = group.name .. "." .. testcase.funcname .. "_" .. k
				group.countGroups = (group.countGroups or 0) + 0
				testcase.all_diffs = ""
				local module = nil -- provide
				module = package.loaded[ tostring(testcase.modulename) ] -- Each testcase can change of module
				local fntn = testcase.func -- provide
				if (type(module) == "table") and (type(fntn) == "function") then -- If the module to test is in package.loaded
					testsCases.countRun = (testsCases.countRun or 0) + 1
					testcase.all_diffs, testcase.errors = testsCases.runOneTest( testcase, testsCases.countRun, module ) -- rrt
					testcase.all_diffs = viewer.simpleList(testcase.all_diffs, ";")
				--	local errors_or_diffs = testcase.errors or testcase.all_diffs
				--	t = t .. "\n* " .. viewer.form9user("tc/gr/count = <b>%1/%2, %3/%4</b>, <b>%5</b>(%6) = <b>%7</b>, diffs/errors = <b>%8</b>",
					t = t .. "\n* " .. viewer.form9user("nTest/nGroup = <b>%1/%2</b>, count/groups = <b>%3/%4</b> ",
									group.countCases, group.countGroups, testsCases.countRun, testsCases.countFull)
					t = t .. "\n* " .. viewer.form9user("testcase.errors = <b>%1</b>, ", testcase.errors)
					t = t .. testcase.runDetails
					t = t .. testsCases.trackTests( testcase, "runTestsCases testcase nil?"..k, string.find(testcase.all_diffs, "nil") or 0, string.len(testcase.all_diffs or "-" ) ) -- testsCases.trackTests_t
				end -- if module
				events.restore_configs(memo) -- Restore global configurations after eventual changes.
			else -- if testcase and testcase.args and testcase.expect
				local subgroup = testcase.group
				if (type(subgroup) == "table") then -- Run all tests cases in one subgroup.
					--[[ Each test_case defines a name, a function, an input, an output. Else a sub-group of testsCases.
					mathroman.testsGroups = { -- Autotest cases to validate the mathroman library at mediawiki level.
					{ errorsKey = 2, modulename = "mathroman", funcname = "roman2int", func = mathroman.roman2int, args = { "MCXI" }, expect = { 1111 } },
					{ name = "mathroman", func = testsCases.runGroups, group = mathroman.Tests_cases,
						modulename = "mathroman", funcname = "runGroups", groupname = "mathroman.Tests_cases", }, --]]
				--	subgroup.recursiveLimit = subgroup.recursiveLimit or modes.recursiveLimit
					subgroup.groupname = subgroup.groupname or group.groupname or groupname or "groupname" --
					subgroup.modulename = subgroup.modulename or testcase.modulename or group.modulename or "modulename"
					subgroup.funcname = subgroup.funcname or "funcname"
					subgroup.name = group.name or subgroup.modulename or "name"
					subgroup.upername = subgroup.name
					subgroup.all_diffs = ""
					if recursiveLevel < recursiveLimit then
						testsCases.subgroup_memo = subgroup
						testsCases.trackTests( subgroup, "runTestsCases in args/groupname", tostring(subgroup.args), tostring(subgroup.groupname) )
						local res_g, res_t = testsCases.runTestsCases( subgroup, subgroup.groupname, recursiveLevel + 1 ) -- Run only all tests cases in one group.
						subgroup.all_diffs = viewer.simpleList(subgroup.all_diffs, ";")
						t = t .. testsCases.trackTests( testcase, "runTestsCases subgroup nil?"..k, string.find(subgroup.all_diffs, "nil") or 0, string.len(subgroup.all_diffs or "-" ) ) -- testsCases.trackTests_t
						t = t .. res_g .. res_t
						testsCases.trackTests( subgroup, "runTestsCases out res_g/res_t", res_g, res_t)
					end
				end
				testcase.all_diffs = viewer.simpleList(testcase.all_diffs, ";")
				t = t .. viewer.form9user("\n* Group:synthesis : name=<b>%1</b>, count/groups=<b>%2/%3</b>, recursiveLevel=<b>%4</b>, ", -- .. testsCases.trackTests_t
															group.name, group.countCases, group.countGroups, recursiveLevel)	--testcase	.groupname
			end
		end -- for k, testcase in ipairs(group) do
	end -- if type(group) == "table" then -- Recursive run groups of tests cases.
	if recursiveLevel > 1 then -- In each sub-groups.
		testsCases.runCount = (testsCases.runCount or 0) + group.countCases
		t = t .. viewer.form9user("\n* Group:synthesis : name=<b>%1</b>, count/groups=<b>%2/%3</b>, recursiveLevel=<b>%4</b>, ",
													group.name, group.countCases, group.countGroups, recursiveLevel) --testcase .name
		t = t .. testsCases.trackTests_t
	else -- In the main group, after all sub-groups.
		testsCases.countCases = group.countCases
		testsCases.countGroups = group.countGroups
		t = t .. viewer.form9user("\n* All group:synthesis : name=<b>%1</b>, countCases/Groups=<b>%2/%3</b>, recursiveLevel=<b>%4</b>, ",
														group.name, testsCases.countFull, group.countGroups, recursiveLevel)
		t = t .. testsCases.trackTests_t
	end
	tools.closeCatsErrs() -- Close a cats and errors table.
	return res_g, t
end -- function testsCases.runTestsCases( group, groupname, recursiveLevel ) -- Run only all tests cases in one group.

function testsCases.ModuleOrName(search) -- Search the name of a module or reverse.
	local result
	if type(search) == "string" then
		for name, module in pairs(package.loaded) do
			if search == name then result = module end -- Name to module.
		end
	elseif type(search) == "table" then
		for name, module in pairs(package.loaded) do  -- Module to name.
			if search == module then result = name end
		end
	end
	return result
end -- function testsCases.ModuleOrName(search)

function testsCases.debugGetGroup(group, tabname)
-- DEBUG the bug T20160615 : Lua functions calls lost multilevel table arguments
	group = testsCases.groupMemoDebug
	if tabname and not group then -- OK
		t = t .. "DEBUG_group: "
		t = t .. viewer.ta("begin group", group)
		t = t .. viewer.ta("testsCases.groupMemoDebug", testsCases.groupMemoDebug)
		t = t .. viewer.ta("tabname", tabname)
	--	local subgroup = { errorsKey = 2, modulename = "mathroman", ..., tabname = "mathroman.testscases.TestsGroups", } -- Example of link to subgroup
		--[ [ Normal method: found the missing group from tabname
		local tab = mw.text.split(tabname, '.', true) -- from the subgroup in string form
		for i, part in ipairs(tab) do -- Build the subgroup table from the tabname string
			t = t .. viewer.ta("part", part)
			if i == 1 then -- The start point is always a recorded module or library
				module = package.loaded[ part ]
				if type(module) == "table" then
					group2use = module
				end
			else -- add a subgroup to the previous subgroup
				if type(group2use) == "table" then
					subgroup = group2use[ part ]
					group2use = subgroup
				end
			end
		end
		group = group2use
		--] ]
		t = t .. viewer.ta("end group", group)
	end
	return group, t
end -- function testsCases.debugGetGroup(group, tabname)


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:versioning installs other libraries, supports versions, then binds modules, libraries and i18n translations.
--	This library support modules to:
--	* Collect and bind modules and sub-modules depending from their p.versions{}
--	* Collect and bind translations from modules or libraries itself, and their /I18N sub-modules
--	* Form parametrable strings
--	* Add a language needs only to add its own i18n subtable, like i18n.en = {}
--	* Manage alternate versions of sub-modules sought in main, like Module:Sub, Module:Sub.2.1, Module:SubPlus.
--	A central module and its translations can inpedendently change. Its p.versions{} identifications change also.
--	For this goal a module contains at least one i18n tranlations table. Its /I18N sub-modules contains translations in several languages.
--	Central Libraries, like modules, contain their own p.versions{} identifications and i18n translations in Module:Library/library_name/I18N.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


--	local librariesList = librariesList or
versioning.librariesList =
	{ -- New libraries to record in package.loaded[]
		["begin"]			= begin,
		["datas"]			= datas,
		["events"]			= events,
		["luaTable"]		= luaTable,
		["modes"]			= modes,
	--	["string"]			= string, -- string = {}	The Library:string come from Scribunto. The Module:Central add some functions to it.
		["testsCases"]		= testsCases,
		["tools"]			= tools,
		["translate"]		= translate,
		["versioning"]		= versioning,
		["viewer"]			= viewer,
		--
		["mathroman"]		= mathroman, -- The Library:mathroman is here as an example of very small central library. It becomes central using the central system.
	}

-- versioning = {} -- already declared by Scribunto, else in _G space.
-- Translations for versioning library
versioning.i18n = {}
versioning.i18n.en = {
	versioning_luatable_counts				= "%1 variables, %3 sub-tables, %2 functions",
	versioning_table_dont_exists			= "The table <b>%1</b> does not exist.",
	versioning_table_listlimit_levelmaxi	= "Level limit recursiveLimit = <b>%1</b> ",
	versioning_table_listlimit_max_n		= "Length limit max_n = <b>%1</b> ",
	versioning_antiCrash_tests_title		= "versioning.antiCrash() Test: in case of running error, do not crash the page.",
	versioning_antiCrash_reference_label	= "References",
	versioning_antiLoop_function_err		= "versioning.antiLoop() error in the <b>%3</b> <b>%1()</b>, loop n = <b>%2</>, args = ",

	-- Main texts, errors and categories of tools
	versioning_module_usage_error_cat		= "Module with usage error",
	versioning_module_internal_error_cat	= "Module with internal error",
	versioning_module_with_error_cat		= "Module with error",
	versioning_module_with_error_err		= "Module with error",

	-- Versions management
	versioning_versions_no_select_altern_err= "Versions error: no selection in the alternative <b>%1</b> of the module <b>%2</b>.",
	versioning_versions_too_select_altern_err= "Versions error: <b>%1</b> selections in the alternative <b>%2</b> of the module <b>%3</b>.",
	versioning_versions_select_not_used_err = "Versions error: <b>%1</b> selector is not in alternative <b>%2</b> of the module <b>%3</b>.",
	versioning_versions_missing_module_err	= "Versions error: missing module for the version <b>%1</b> of the module <b>%2</b>.",
	versioning_main_module_missing_err		= "The main module is not found.",
	versioning_select_unknown_module_err	= "Internal error: The missing Module:<b>%1</b> is replaced by the normal Module:<b>%2</b>.",
	versioning_I18N_module_no_base_err		= "The <b>%1</b> translations module has no basic version.",
	versioning_no_versions_module_err		= "The module <b>%1</b> is not in the system of versions.",
	versioning_all_versions_tests			= "Versions warning: <b>%1</b>, normal: <b>%2</b>, listall: <b>%3</b>.",
	versioning_all_versions_check			= "Versions: missings: <b>%1</b>, unknowns: <b>%2</b>, normals: <b>%3</b>, excess: <b>%4</b>, all selected: <b>%5</b>.",
	versioning_missing_versions_err			= "The module <b>%1</b> misses in the main module <b>%2</b>.",
	versioning_replaced_versions_err		= "The module <b>%1</b> replaces the missing sub-module in the main module <b>%2</b>.",
	versioning_unknownversions_err			= "The module <b>%1</b> is unknown in the main module <b>%2</b>.",
	versioning_module_miss_i18n_txt_err		= "Internal Error: The text <b>%1</b> lack of translation in <b>%2</b> language, and/or others.",
	versioning_module_miss_i18n_count_err	= "Internal Error: There are <b>%1</b> missings in <b>%2</b> translations.",
	versioning_module_miss_i18n_none_err	= "OK, none missing translations.",
	versioning_module_miss_i18n_trad_err	= "Internal Error: Module missing i18n translation for the argument <b>i18n.%1.%2</b>.",
--	versioning_err_module_miss_i18n_cat		= "Module missing i18n translation",
	versioning_err_module_miss_i18n_cat		= "Module with internal error",
	versioning_module_miss_i18n_mini_err	= "Error: I18n table has only <b>1%</b> translations.",
	versioning_module_error_err				= "Module with error.",

	-- Titles of tests
	versioning_support_desk_title			= "Support desk",
	versioning_bind_modules_report_title	= "versioning.report_bind_modules_report_details() Modules and libraries versions",
	versioning_versions_management_report	= "versioning.versions_management_report() Versions management report",
	versioning_bind_modules_tests_title		= "versioning.bind_modules_test() Test for management of modules versions",
	versioning_verif_bind_modules_report	= "Binding of modules and versions control",
	versioning_bind_modules_test_headers	= "Sought versions; Used versions; Known versions; ACTUAL: Errors in the active module; SIMULATION: Errors in the active module",
	versioning_tasks_changes_report			= "versioning.tasks_changes_report() Documentations, tasks and changes",
	versioning_list_loaded_modules_headers	= "Title; Version; Date; i18n; Translations / Languages",
	versioning_try_waits_debug_T122752		= "#invoke has not loaded <b>%1</b>, but the debug of T122752 has loaded it.",
	versioning_tasks_report_title			= "versioning.versioning_tasks_report() States of known tasks",
	versioning_tasks_report_headers			= "Importance; Task; State; Title; In short",
	versioning_add_deprecated_err			= "Deprecated function <b>%1(...)</b>. Replace it by <b>%2(...)</b> in the module <b>%3</b>.",
	versioning_deprecatedFunction_tests_title= "versioning.deprecatedFunction() Test: Deprecated function. Replace it in the module.",
	versioning_defaultCrashResult_ref_err	= "The antiCrash() function protects this page against a complete crash due to an internal error: %1.",
} -- versioning.i18n.en

versioning.i18n.es = {
	versioning_luatable_counts				= "%1 variables, %3 subtablas, %2 funciones",
	versioning_table_dont_exists			= "La tabla <b>%1</b> no existe.",
	versioning_table_listlimit_levelmaxi	= "Límite de nivel recursiveLimit = <b>%1</b>",
	versioning_table_listlimit_max_n		= "Límite de longitud max_n = <b>%1</b>",
	versioning_antiCrash_tests_title		= "versioning.antiCrash() Prueba: en caso de error de ejecución, no sobrescribir la página.",
	versioning_antiCrash_reference_label	= "Referencias",
	versioning_antiLoop_function_err		= "versioning.antiLoop() Error en el <b>%3</b> <b>%1()</b>, bucle n = <b>%2</>, args = ",

	-- Textos principales, errores y categorías de instrumentos
	versioning_module_usage_error_cat		= "Módulo con error del uso",
	versioning_module_internal_error_cat	= "Módulo con error interno",
	versioning_module_with_error_cat		= "Módulo con error",
	versioning_module_with_error_err		= "Módulo con error",

	-- Gestión de versiones
	versioning_versions_no_select_altern_err= "Error de versiones: no hay selección en alternativa <b>%1</b> del módulo <b>%2</b>.",
	versioning_versions_too_select_altern_err= "Error de versiones: <b>%1</b>  selecciones en la alternativa <b>%2</b> del módulo <b>%3</b>.",
	versioning_versions_select_not_used_err = "Error de versiones: selector <b>%1</b> no es en alternativas <b>%2</b> del módulo <b>%3</b>.",
	versioning_versions_missing_module_err	= "Error de versiones: módulo ausente para la versión <b>%1</b> del módulo <b>%2</b>.",
	versioning_main_module_missing_err		= "El módulo principal no es encontrado.",
	versioning_select_unknown_module_err	= "Internal error: El Modulo:<b>%1</b> faltante se reemplaza por lo normal Module:<b>%2</b>.",
	versioning_I18N_module_no_base_err		= "El módulo de traducciones <b>%1</b> no tiene ninguna versión básica.",
	versioning_no_versions_module_err		= "El <b>%1</b> módulo no está en el sistema de versiones.",
	versioning_all_versions_tests			= "Versiones aviso: <b>%1</b>, normal: <b>%2</b>, lista: <b>%3</b>.",
	versioning_all_versions_check			= "Versiones: falta: <b>%1</b>, incógnitas: <b>%2</b>, normales: <b>%3</b>, en exceso: <b>%4</b>, selecciona todo: <b>%5</b>.",
	versioning_missing_versions_err			= "El módulo <b>%1</b> no disponible en el módulo principal <b>%2</b>.",
	versioning_replaced_versions_err		= "El módulo <b>%1</b> reemplaza el sub-módulo ausente en el módulo principal <b>%2</b>.",
	versioning_unknownversions_err			= "El módulo <b>%1</b> es desconocido en el módulo principal <b>%2</b>.",

	-- Diversos mensajes y errores
	versioning_module_miss_i18n_txt_err		= "Error Interno: El texto <b>%1</b> falta de traducción en <b>%2</b> lengua, y / o otras.",
	versioning_module_miss_i18n_count_err	= "Error interno: Hay <b>%1</b> que falta en <b>%2</b> traducciones.",
	versioning_module_miss_i18n_none_err	= "OK, ninguno traducciones que faltan.",
	versioning_module_miss_i18n_trad_err	= "Error interno: translation faltan en Módulo i18n para el argumento <b>i18n.%1.%2</b>.",
--	versioning_err_module_miss_i18n_cat		= "Módulo traducción i18n que falta",
	versioning_err_module_miss_i18n_cat		= "Módulo con error interno",
	versioning_module_miss_i18n_mini_err	= "Error: la tabla i18n tiene solamente <b>1%</b> traducciones.",
	versioning_support_desk_title			= "Escritorio de apoyo",
	versioning_bind_modules_report_title	= "versioning.report_bind_modules_report_details() Versiones de los módulos y bibliotecas",
	versioning_versions_management_report	= "versioning.versions_management_report() Informe de gestión de versiones",
	versioning_bind_modules_tests_title		= "versioning.bind_modules_test() Test for management of modules versions",
	versioning_verif_bind_modules_report	= "Binding of modules and versions control",
	versioning_bind_modules_test_headers	= "Sought versions; Used versions; Known versions; ACTUAL: Errors in the active module; SIMULATION: Errors in the active module",
	versioning_tasks_changes_report			= "versioning.tasks_changes_report() Documentación, parches y modificaciones",
	versioning_list_loaded_modules_headers	= "Título; Versión; Fecha; i18n; Traducciones / Idiomas",
	versioning_module_error_err				= "Módulo con error.",
	-- Titres des pruebas
	versioning_try_waits_debug_T122752		= "#invoke no ha cargado <b>%1</b>, pero la depuración de T122752 ha lo cargó.",
	versioning_tasks_report_title			= "versioning.versioning_tasks_report() Estados del tareas conocidas",
	versioning_tasks_report_headers			= "Importancia; Tarea; Estado; Título; En breve",
	versioning_add_deprecated_err			= "Función obsoleta <b>%1(...)</b>. Sustitúyala por <b>%2(...)</b> en el módulo <b>%3</b>.",
	versioning_deprecatedFunction_tests_title= "versioning.deprecatedFunction() Prueba: Función obsoleta. Sustitúyala en el módulo.",
} -- versioning.i18n.es

versioning.i18n.fr = {
	-- Principaux textes, erreurs et catégories des outils
	versioning_luatable_counts				= "%1 variables, %3 sous-tables, %2 fonctions",
	versioning_table_dont_exists			= "La table <b>%1</b> n'existe pas.",
	versioning_table_listlimit_levelmaxi	= "Limite de niveau recursiveLimit = <b>%1</b> ",
	versioning_table_listlimit_max_n		= "Limite de longueur max_n = <b>%1</b> ",
	versioning_antiCrash_tests_title		= "versioning.antiCrash() Test: en cas d'erreur en cours d'exécution, ne pas écraser la page.",
	versioning_antiCrash_reference_label	= "Références",
	versioning_antiLoop_function_err		= "versioning.antiLoop() Erreur dans la <b>%3</b> <b>%1()</b>, boucle n = <b>%2</>, args = ",
	
	versioning_module_usage_error_cat		= "Module avec erreur d'utilisation",
	versioning_module_internal_error_cat	= "Module avec erreur interne",
	versioning_module_with_error_cat		= "Module avec erreur",
	versioning_module_with_error_err		= "Module avec erreur",

	-- Gestion des versions
	versioning_versions_no_select_altern_err= "Erreur de versions : pas de sélection dans l'alternative <b>%1</b> du module <b>%2</b>.",
	versioning_versions_too_select_altern_err= "Erreur de versions : <b>%1</b> sélections dans l'alternative <b>%2</b> du module <b>%3</b>.",
	versioning_versions_select_not_used_err = "Erreur de versions : le sélecteur <b>%1</b> n'est pas dans les alternatives <b>%2</b> du module <b>%3</b>.",
	versioning_versions_missing_module_err	= "Erreur de versions : module absent pour la version <b>%1</b> du module <b>%2</b>.",
	versioning_main_module_missing_err		= "Le module principal est introuvable.",
	versioning_select_unknown_module_err	= "Internal error: Le Module:<b>%1</b> manquant est remplacé par le module normal Module:<b>%2</b>.",
	versioning_I18N_module_no_base_err		= "Le module de traductions <b>%1</b> n'a pas de version de base.",
	versioning_no_versions_module_err		= "Le module <b>%1</b> n'est pas dans le système des versions.",
	versioning_all_versions_tests			= "Versions avertissements : <b>%1</b>, normaux : <b>%2</b>, liste : <b>%3</b>.",
	versioning_all_versions_check			= "Versions: manquantes: <b>%1</b>, inconnues: <b>%2</b>, normales: <b>%3</b>, en excès: <b>%4</b>, toutes selectionées : <b>%5</b>.",
	versioning_missing_versions_err			= "Le module <b>%1</b> manque dans le module principal <b>%2</b>.",
	versioning_replaced_versions_err		= "Le module <b>%1</b> remplace le sous-module absent dans le module principal <b>%2</b>.",
	versioning_unknownversions_err			= "Le module <b>%1</b> est inconnu dans le module principal <b>%2</b>.",
	versioning_module_miss_i18n_txt_err		= "Erreur interne : Le texte <b>%1</b> manque de traduction en langue <b>%2</b>, et/ou d'autres.",
	versioning_module_miss_i18n_count_err	= "Erreur interne : Il y a <b>%1</b> manques parmi <b>%2</b> traductions.",
	versioning_module_miss_i18n_none_err	= "OK, aucune traduction manquante.",
	versioning_module_miss_i18n_trad_err	= "Erreur interne : manque de traduction pour l'argument <b>i18n.%1.%2</b>.",
--	versioning_err_module_miss_i18n_cat		= "Module manquant de traduction i18n",
	versioning_err_module_miss_i18n_cat		= "Module avec erreur interne",
	versioning_module_miss_i18n_mini_err	= "Erreur interne : La table i18n n'a que <b>%1</b> traductions.",

	-- Messages et erreurs divers
	versioning_module_error_err				= "Module avec erreur",
	versioning_support_desk_title			= "Bureau d'aide",
	versioning_bind_modules_report_title	= "versioning.report_bind_modules_report_details() Versions des modules et librairies",
	versioning_versions_management_report	= "versioning.versions_management_report() Rapport de gestion des versions",
	versioning_bind_modules_tests_title		= "versioning.bind_modules_test() Test de la gestion des versions des modules",
	versioning_verif_bind_modules_report	= "Liaison des modules et contrôle des versions",
	versioning_bind_modules_test_headers	= "Versions demandées; Versions utilisées; Versions connues; ACTUEL : Erreurs dans le module actif; SIMULATION : Erreurs dans le module actif",
	versioning_tasks_changes_report			= "versioning.tasks_changes_report() Documentations, tâches et modifications",
	versioning_list_loaded_modules_headers	= "Titre; Version; Date; i18n; Traductions / Langues",

	-- Titres des tests
	versioning_try_waits_debug_T122752		= "#invoke n'a pas chargé <b>%1</b>, mais le débogage de T122752 l'a chargé.",
	versioning_tasks_report_title			= "versioning.versioning_tasks_report() États des tâches connues",
	versioning_tasks_report_headers			= "Importance; Tâche; Etat; Titre; En bref",
	versioning_add_deprecated_err			= "Fonction obsolète <b>%1(...)</b>. Remplacez-la par <b>%2(...)</b> dans le module <b>%3</b>.",
	versioning_deprecatedFunction_tests_title= "versioning.deprecatedFunction() Test: Fonction obsolète. Remplacez-la dans le module.",
	versioning_defaultCrashResult_ref_err	= "La fonction antiCrash() protège cette page contre un effondrement complet dû à une erreur interne : %1.",
} -- versioning.i18n.fr

versioning.ModuleNS = mw.site.namespaces.Module.name .. ":"

function versioning.trackAllVersions(where, txt)
		versioning.report_trackAllVersions = (versioning.report_trackAllVersions or "function report_trackAllVersions ") .. viewer.ta(where, txt)
	return versioning.report_trackAllVersions -- search .all_versions
end

function versioning.form_version(vers) -- Form the version and the main version of a module or a library.
	local version = string.gsub(vers, "p.v.version", p.v.version )
	local main_version = string.gsub(vers, "p.v.version", p.v.main_version )
	return version, main_version
end

function versioning.pcallRequire( version ) -- require() must not fail and block the page.
	if type(version) == "string" then
		version = string.gsub( version, versioning.ModuleNS, "" ) -- normalize for always one "Module:"
		version = versioning.ModuleNS .. version -- example Module:Central/I18N
	end
	local success, module = pcall( require, version ) -- pcall or xpcall can run any function without blocking page.
	if success then return module else return nil end
end

function versioning.getModuleAndTime(title, given_module) -- Try to load a module and forms a descriptor of one loaded object.
--	local get = versioning.getModuleAndTime("Module:Central") -- basic example of use
	local get = {}
	local module, required, loaded
	if type(title) ~= "string" then
		get.title = "abnormal title : " .. tostring(title)
		get.isNil = true
		return get
	end -- If not loaded, return only an empty get
	if (type(given_module) == "table") or (type(given_module) == "function") then
		module = given_module
	else -- to require a new module
		loaded = versioning.pcallRequire(title) -- require() must not fail and block the page if the module do not exists.
		if (type(loaded) == "table") or (type(loaded) == "function") then module = loaded -- The module is well loaded in package.loaded
		else return get end -- If not loaded, return only get.isTitled and get.title
	end
	get.title = title -- like Module:Author, or the versioning library
	get.module = module -- like {...}
	get.isNil = not module -- like true or false
	get.isLoaded = true and package.loaded[title] -- true if the module is in package.loaded
	get.isFunction = (type(module) == "function") -- true if the module is a function
	get.isTable = (type(module) == "table") -- true if the module is a table
	get.i18n = module.i18n -- i18n table of the module
	get.versions = module.versions -- table including module.versions.known and module.versions.sought
	get.isLibrary = (type(module)=="table") and not (type(module)=="function") and not (string.is_in(versioning.ModuleNS, title) ) -- true if the object is a library
	-- T20160616 Rical task: Add module and library types in scribunto ?
	get.hasi18n = (type(module) == "table") and (type(get.i18n) == "table") -- true if the object has a i18n translations table
	get.versionName = string.gsub(get.title or "", versioning.ModuleNS, "") -- without Module:		like versioning or Author
	get.versionTitle = versioning.ModuleNS .. get.versionName -- normalize title					like Module:Author
	get.isModule = string.find(get.title, versioning.ModuleNS) -- true if the object is				like Module:Author
	get.isI18N = true and string.find(get.title, "/I18N") -- true if the title contains /I18N		like Module:Author/I18N
	get.withoutI18N = string.gsub(get.versionName, "/I18N", "") -- version without /I18N			like versioning or Module:Author
	get.versionI18N = get.withoutI18N .. "/I18N" -- version with /I18N								like Module:Author/I18N
	get.nowstamp = os.date("%Y%m%d%H%M%S") -- like YYYYMMDDhhmmss from module
	-- Special for isModule, isLibrary, isFunction
	if get.isLibrary then
	--	get.revistamp = "20010101000000" -- default libraries timestamp at 2000-01-01T00:00:00Z, see ISO 8601 time format
		get.revistamp = get.nowstamp
		get.alias = "Library:" .. get.versionName -- Locate translations							like Module:Library/versioning/I18N
		get.TitleI18N = versioning.ModuleNS .. "Library/" .. get.withoutI18N .. "/I18N" -- eventual	like Module:Library/versioning/I18N
	--
	--	Module:Library/versioning/I18N	International translations old
	--	Module:Central-s-fr/I18N		International translations new
	--
	--	like Module:Library/versioning/I18N		> 	Module:Central-s-fr/I18N
	--	and
	--	like Module:Library/translate/I18N		> 	Module:Central-s-fr/I18N
	--
		get.i18nRoot = get.versionName -- Where locate translations are								like versioning
	elseif get.isModule then
		get.revistamp = mw.getCurrentFrame():preprocess( "{{REVISIONTIMESTAMP:" .. get.title .. "}}" )
		get.alias = "Module:" .. get.versionName -- Locate translations example in Module:Central/I18N
		get.i18nRoot = get.alias -- Where locate translations are									like Module:Author
		get.TitleI18N = versioning.ModuleNS .. get.withoutI18N .. "/I18N" -- eventual /I18N module to search
	else
		get.revistamp = mw.getCurrentFrame():preprocess( "{{REVISIONTIMESTAMP:" .. get.title .. "}}" )
	end
	get.versionName = string.gsub(get.title or "", versioning.ModuleNS, "") -- full version name with perhaps /I18N
	get.simplename = string.gsub(get.title or "", versioning.ModuleNS, "") -- delete "Module:" if any
	get.simplename = string.gsub(get.simplename, "/I18N", "") -- delete "/I18N" if any
	if type(module) == "function" then
		get.isFunction = true
		get.isModule = false
		get.isLibrary = false
		get.functionName = get.simplename
		get.alias = "function:" .. get.simplename
	elseif string.find(get.title, versioning.ModuleNS) then -- If the module title begin with "Module:"
		get.isFunction = false
		get.isModule = true
		get.isLibrary = false
		get.moduleName = get.title -- complete the title with "Module:"
		get.subI18N = get.title .. "/I18N" -- eventual sub-module "/I18N"
		get.alias = versioning.ModuleNS .. get.simplename
		if get.isI18N then get.alias = get.alias .. "/I18N" end
	else -- If the module is a library
		get.isFunction = false
		get.isModule = false
		get.isLibrary = true
		get.libraryName = get.title -- standard library name
		get.versionName = get.libraryName
		get.subI18N = versioning.ModuleNS .. "Library/" .. get.title .. "/I18N" -- eventual sub-module "/I18N"
		get.alias = "Library:" .. get.title -- get.simplename
		if get.isI18N then get.alias = get.alias .. "/I18N" end
	end
	-- stamp, date and time
	local function days(stamp) -- approximate the number of days from 0000-01-01
		local day = 0
		if (type(stamp) == "string") and (string.len(stamp) > 7) then
			day = tonumber(string.sub(stamp,1,4)) * 365 +
			tonumber(string.sub(stamp,5,6)) * 30 +
			tonumber(string.sub(stamp,7,8))
		end
		return day
	end
	if get.revistamp then
		get.days_revis = days(get.revistamp)
		get.days_now = days(get.nowstamp)
		get.delay = get.days_now - get.days_revis
		get.revistime = string.sub(get.revistamp,1,4) .. "-" .. string.sub(get.revistamp,5,6) .. "-" .. string.sub(get.revistamp,7,8)
			.. " " .. string.sub(get.revistamp,-6,-5) .. ":" .. string.sub(get.revistamp,-4,-3)
		get.shortdays = string.sub(get.revistamp or "", 3, 8)
		get.shorttime = string.sub(get.revistime or "", -9, -1)
		if get.delay < 25
			then get.shortVersion = "v" .. get.shorttime -- short version in "vDD hh:mm" until around 25 days
			else get.shortVersion = "v" .. get.shortdays -- later in date vYYMMDD
		end
		get.versionDate = get.revistime
		get.versionNumber = get.shortVersion
		get.versionName = string.gsub(get.title or "", versioning.ModuleNS, "") -- full version name with perhaps /I18N
		get.versionDate = get.revistime
		if type(get.module.versions) == "table" then -- For central modules or libraries only:
			get.module.versionName = get.versionName -- Record in each module to later IDENTIFY any module. See task T119978, "own module name"
			get.module.versionDate = get.versionDate -- Record in each module to later DATE any module. See task T119978, "own module name"
		end
	end
	if get.versions then
		get.sought = get.versions.sought or get.versions.selector or ""
		get.known = get.versions.known or get.versions.all_versions or ""
		--
		local str = string.gsub(get.sought, " ", ";")
		get.sought_tab = mw.text.split(str, ";", true)
		get.sought_count = #get.sought_tab
		--
		local str = string.gsub(get.known, "*", ";")
		get.known_tab = mw.text.split(str, ";", true)
		get.known_count = #get.known_tab
	end
	--[ [
	if get.hasi18n then
	--	version with internal .i18n ( module or its /I18N submodule )
	--	local vn1, tb1, fn1, vt1 = luaTable.SubCounts(get.i18n, "i18n") -- count of internal .i18n
	--	local vn1, tb1, fn1, vt1 = luaTable.SubCounts(get.i18n, get.TitleI18N) -- count of internal .i18n
		local vn1, tb1, fn1, vt1 = luaTable.SubCounts(get.i18n, get.alias) -- count of internal .i18n
		get.i18n_vars = vn1
		get.i18n_tabs = tb1
		get.i18n_func = vt1
		local tot_vars, tot_tabs, tot_func, vr_tb = luaTable.SubCounts(versioning.main_i18n, "versioning.main_i18n") -- count of main_i18n
		get.main_i18n_vars = tot_vars
		get.main_i18n_tabs = tot_tabs
		get.main_i18n_func = tot_func
		get.i18n_varstabs = viewer.form9user("%1 texts in %2 languages, (all %3/%4)", vn1, tb1, tot_vars, tot_tabs) .. (get.alias .. "get.alias")
	end --] ]
	versioning.trackAllVersions("getModuleAndTime " .. get.versionName, get.all_versions)
	return get, module
	--	get = { isTitled, title, module, isLoaded, isLibrary, hasi18n, versionName, versionTitle, versionDate, versionNumber, shortVersion,
	--			i18n, versions, sought, known, sought_tab, sought_count, known_tab, known_count, ,
	--			isModule, isI18N, lessI18N, versionI18N, nowstamp, revistamp, revistime, shortdays, shorttime, nowstamp, delay }
	-- title = Module:Central, revistamp = 20151207214027, revistime = 2015-12-07 21:40, gettitle = Module:Author3, shortVersion = v07 21:40 or v150227
end -- get, module = function versioning.getModuleAndTime(title) -- Get a descriptor of one loaded object

function versioning.get_loaded_modules(tab) -- List all package.loaded modules
	local loaded_txt = ""
	local loaded_pack = {}
	local elem = {}
	local tab_to_report = package.loaded
	versioning.loaded_descriptors = {}
	local title, revistamp, revistime
	if type(tab) == "table" then tab_to_report = tab end
	local loaded_txt_memo = loaded_txt
	for title, module in pairs(tab_to_report) do -- List package.loaded modules
		local md = string.find(title, versioning.ModuleNS)
		if (type(module) == "table") then --  md and
			local get = versioning.getModuleAndTime(title, module)
			loaded_txt = loaded_txt .. " <b>" .. (get.title or "") .. "</b>, "
		--	elem.title = title
		--	versioning.trackAllVersions("loaded_modules", get.known)
			loaded_pack = versioning.add_module_in_loaded_pack(loaded_pack, title, get)
		end
	end
	-- versioning.loaded_descriptors = loaded_pack
	if loaded_txt == loaded_txt_memo then loaded_txt = loaded_txt .. "The input table contains no module." end
	return loaded_pack, loaded_txt
end -- function versioning.get_loaded_modules(tab)

function versioning.add_i18n(t, module_name, module_tab) -- Add with others an i18n translations table and list all differences of translations.
	local t = t or ""
	versioning.main_i18n = versioning.main_i18n or {}
	local i18n, lang_N, text_N = {} ,0, 0
	local translations_differences = {}
	function versioning_detect_transDiff(trans1, trans2) -- Collect all differences of translations
		if trans1.dtrans and trans2.dtrans and (trans1.dlang == trans2.dlang) and (trans1.dkey == trans2.dkey) and (trans1.dtrans ~= trans2.dtrans) then
			table.insert(translations_differences, { ["trans1"] = trans1, ["trans2"] = trans2 }) -- build a table with only languages codes
		end -- Only fast collect to treat later if needed.
	end
	if type(module_tab) == "table" and type(module_tab.i18n) == "table"	 then -- and type(module_tab.i18n) == "table" then
		i18n = module_tab.i18n or {} -- i18n
		lang_N, text_N = 0, 0
		for lang, trans in pairs(i18n) do -- For all languages of the mixed module or library
			if versioning.main_i18n[lang] then -- the language already exists, add or replace imported fields
				lang_N = lang_N + 1
				if type(trans) == "table" then -- if the language to add is really in a table
					for key, val in pairs(trans) do -- For all imported fields
						versioning_detect_transDiff( -- Collect all differences of translations
							{ dlang = lang, dkey = key, dfile = "previous", dtrans = versioning.main_i18n[lang][key] },
							{ dlang = lang, dkey = key, dfile = module_name, dtrans = val} )
						versioning.main_i18n[lang][key] = val -- add or replace a field and its translation
						text_N = text_N + 1
					end
				end
				t = t .. viewer.form9user(", lang:<b>%1:%2:%3</b> ", lang, lang_N, text_N)
			--	t = t .. translate.formTestCase(", lang:<b>%1:%2:%3</b> ", lang, lang_N, text_N)
			else -- add the table and all is fields if it is not already in versioning.main_i18n
				if mw.language.isKnownLanguageTag(lang) then -- only for languages known in wikis
					lang_N = lang_N + 1
					versioning.main_i18n[lang] = mw.clone(trans)
					text_N = text_N + #trans
					t = t .. viewer.form9user(", lang:<b>%1:%2:%3</b> ", lang, lang_N, text_N)
				--	t = t .. translate.formTestCase(", lang:<b>%1:%2:%3</b> ", lang, lang_N, text_N)
				end
			end
		end
	end
	local st, vr, fn, tb = luaTable.formSubCounts("versioning.main_i18n")
	t = t .. viewer.form9user(", addi18n:%1:<b>%2/%3</b> texts/langs, ", module_name, vr, tb)
--	t = t .. translate.formTestCase(", addi18n:%1:<b>%2/%3</b> texts/langs, ", module_name, vr, tb)
	-- versionversion, lang:tot = ta:0 mix:Central1:441+3181=3181,
	return t, lang_N, text_N -- t, ln, an
end -- function versioning.add_i18n(t, module_name, module_tab)

function versioning.bind_one_library(name, library, options) -- Record a library in package.loaded
	local err = "OK"
	-- RunOnce : Do not repeat this function to not disturb subsequent processes.
	if type(name) == "string" then
		if type(package.loaded[name]) ~= "nil" then
			return package.loaded[name], "" -- This library already exists, return it
		else end -- record the bind_one_library below.
	else -- RunOnce
		return nil, "INTERNAL ERROR : bind_one_library name is not a string." -- minimal answer in case of abnormal name.
	end -- RunOnce
--	if type(name) ~= "string" then library = nil ; err = "name is not a string" end -- A library name must be a string.
--	if library == nil then return "INTERNAL ERROR : bind_one_library is not an object." end -- A new temporary library must be a table.
	if type(library) ~= "table" then return "INTERNAL ERROR : bind_one_library is not an object." end -- A new temporary library must be a table.
	if package.loaded[name] then library = nil ; err = "cannot replace a previous library" end -- A library cannot replace a previous library.
	-- below: A library name must be a string in %w ASCII alphanumeric characters only. With ":" for modules.
	local correctname = string.gsub(name, "[^:./%w]", "" )
	if name ~= correctname then library = nil ; err = "name with forbiden characters -> " .. correctname end
	php = nil
	-- Remove setup function
	if library then library.setupInterface = nil end
	-- Copy the PHP callbacks to a local variable, and remove the global
	php = mw_interface
	mw_interface = nil
	-- Do any other setup here
	-- Install into the mw global
	mw = mw or {}
	mw.ext = mw.ext or {}
	mw.ext[name] = library
	-- Indicate that we're loaded
	package.loaded[name] = library
--	package.loaded['mw.ext.' .. name] = library
	return library, err
end -- function versioning.bind_one_library(name, library, options)

function versioning.newLibrary_test(t) -- Show some use cases on versioning.bind_one_library(name, library, options)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local function newLibrary_test_1(t, name, library, options)
		libout, err = versioning.bind_one_library(name, library, options)
		if err ~= "OK" then err = viewer.errorColor(err) end
		t = t .. viewer.Tr() .. viewer.Td(name) .. viewer.Td(library) .. viewer.Td( type(libout) ) .. viewer.Td(err)
		return t
	end
	local t = t or "\n* <b>newLibrary_test</b> :"
	t = t .. "\n* Show some tests of use cases of versioning.bind_one_library(name, library, options) :"
	t = t .. viewer.Th() .. viewer.Tc("library name") .. viewer.Tc("library input") .. viewer.Tc("library output") .. viewer.Tc("error")
	t = newLibrary_test_1(t, "emptyTable", {}, "")
	t = newLibrary_test_1(t, "emptyFunction", function()end, "")
	t = newLibrary_test_1(t, "simpleNumber", 123, nil)
	t = newLibrary_test_1(t, "simpleString", "ABC", nil)
	t = newLibrary_test_1(t, "libraryExample", { a = "a", fctn = function(i)end }, "")
	t = newLibrary_test_1(t, "Module:Main/I18N", {}, "")
	t = newLibrary_test_1(t, "text/slash", {} )
	t = newLibrary_test_1(t, "version.2.3", {} )
	t = newLibrary_test_1(t, "available:non/alphanumeric.chars", {} )
	t = newLibrary_test_1(t, "Module:Main/sub-version23", {}, "")
	t = newLibrary_test_1(t, "name with spaces", {} )
	t = newLibrary_test_1(t, "semi;column", {} )
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function versioning.newLibrary_test(t)

-- Translations for versioning library

function versioning.bind_libraries(librariesList, opt) -- Bind translatable libraries before bind modules and versions.
	-- Record centralisable libraries if they are not included in standard mw.Extension:Scribunto
	-- RunOnce : Do not repeat this function to not disturb subsequent processes.
	local librariesList = librariesList or versioning.librariesList
	for name, library in pairs(librariesList) do
		versioning.bind_one_library(name, library) -- RunOnce
	end
	local name, name_I18N, tt, lang_N, text_N
	local nm = "<br>Libraries after bind_one_library in bind_libraries() : "
	for name, library in pairs(package.loaded) do nm = nm .. name .. ", " end
	versioning.tracki18n_new_libraries123 = nm or "no library in package.loaded"
	local t = ""
	t = t .. "\n* effective versioning.bind_libraries : "
	versioning.loaded_modules = versioning.get_loaded_modules() -- List all libraries with internal .i18n translations
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N sub-modules.
	for title, get in pairs(versioning.loaded_modules) do
	--	if package.loaded[name] and package.loaded[name].i18n then
		if get.hasi18n and get.isLibrary then
			t = t .. "\n*" .. viewer.ta("get.libraryName", get.libraryName) -- .. viewer.ta("library", get.module)
			-- viewer.ta("get.title", get.title) .. viewer.ta("get.moduleName", get.moduleName) ..
----			tt, lang_N, text_N = versioning.add_i18n(t, get.libraryName, get.module)
			t = t .. "\n*" .. viewer.ta("get.subI18N", get.subI18N) -- .. viewer.ta("name", get.libraryName) -- .. viewer.ta("name_I18N", name_I18N)
			local getI18N = versioning.getModuleAndTime(get.subI18N)
----			tt, lang_N, text_N = versioning.add_i18n(t, getI18N.subI18N, getI18N.module)
			if getI18N.libraryName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
				t = t .. "\n*" .. viewer.ta("I18N", getI18N.libraryName) -- .. viewer.ta("name_I18N", name_I18N)
			end
		end
	end
	versioning.bind_i18n_report = (versioning.bind_i18n_report or ".") .. (t or ".")
	return t
end -- function versioning.bind_libraries(librariesList, opt)

function versioning.bind_libraries_from_i18n(package_loaded, opt) -- Bind translatable libraries before bind modules and versions.
--	versioning.bind_libraries(librariesList, opt) -- Bind translatable libraries before bind modules and versions.
	-- Record centralisable libraries if they are not included in standard mw.Extension:Scribunto
	-- RunOnce : Do not repeat this function to not disturb subsequent processes.
	local librariesList = librariesList or versioning.librariesList
	local librariesList = {}
	for name, library in pairs(package.loaded) do -- library already installed from mediawiki or an eventual preview installation -- example viewer.i18n.en = {}
		if (type(library) == "table") and (type(library.i18n) == "table") then -- A module can lauch itself or another as main module.
			librariesList[name] = library -- versioning.insert(librariesList, library)
		end
	end
	for name, library in pairs(_G) do -- library in the global space from an eventual preview creation -- example viewer.i18n.en = {}
		if (type(library) == "table") and (type(library.i18n) == "table") then -- A module can lauch itself or another as main module.
			librariesList[name] = library -- versioning.insert(librariesList, library)
		end
	end
	for name, library in pairs(librariesList) do -- viewer.i18n.en = {
		versioning.bind_one_library(name, library) -- RunOnce
	end
	local name, name_I18N, tt, lang_N, text_N
	local nm = "<br>Libraries after bind_one_library in bind_libraries() : "
	for name, library in pairs(package.loaded) do nm = nm .. name .. ", " end
	versioning.tracki18n_new_libraries123 = nm or "no library in package.loaded"
	local t = ""
	t = t .. "\n* effective versioning.bind_libraries : "
	versioning.loaded_modules = versioning.get_loaded_modules() -- List all libraries with internal .i18n translations
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N sub-modules.
	for title, get in pairs(versioning.loaded_modules) do
	--	if package.loaded[name] and package.loaded[name].i18n then
		if get.hasi18n and get.isLibrary then
			t = t .. "\n*" .. viewer.ta("get.libraryName", get.libraryName) -- .. viewer.ta("library", get.module)
			-- viewer.ta("get.title", get.title) .. viewer.ta("get.moduleName", get.moduleName) ..
			tt, lang_N, text_N = versioning.add_i18n(t, get.libraryName, get.module)
			t = t .. "\n*" .. viewer.ta("get.subI18N", get.subI18N) -- .. viewer.ta("name", get.libraryName) -- .. viewer.ta("name_I18N", name_I18N)
			local getI18N = versioning.getModuleAndTime(get.subI18N)
			tt, lang_N, text_N = versioning.add_i18n(t, getI18N.subI18N, getI18N.module)
			if getI18N.libraryName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
				t = t .. "\n*" .. viewer.ta("I18N", getI18N.libraryName) -- .. viewer.ta("name_I18N", name_I18N)
			end
		end
	end
	versioning.bind_i18n_report = (versioning.bind_i18n_report or ".") .. (t or ".")
	return t, librariesList
end -- function versioning.bind_libraries_from_i18n(package_loaded, opt)

function versioning.bind_modules(t) -- Deprecated alias function
	versioning.deprecatedFunction("versioning.bind_modules", "versioning.bind_main_module")
	return versioning.versions_management_test(t)
end
-- local tt, vers_test = versioning.bind_modules(p, vers_test) -- The main module can bind all its sub-modules versions, and their /I18N sub-modules.
-- function versioning.bind_modules_method(main_p, vers_test)
function versioning.bind_modules_method(main_p, vers_test) -- Bind all modules and versions, and their translations
	local t, librariesList = "", {}
	-- Actual binding process
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N sub-modules.
	-- Binding 2 - Bind "new" libraries and i18n translations for each: from central module, dated from its last change.
	-- Binding 3 - Bind sub-modules, for each: if selected following sought and known versions in main module, from the last to the first known.
	-- Binding 4 - Bind sub-modules, for each: internal i18n then from its /I18N sub-module.
	-- Binding 5 - Bind sub-modules and i18n translations for each: dated from their last change dates.
	-- Binding 6 - Bind the main module and its i18n translations.
	-- Binding 7 - sortkey to bind i18n in right order = i18nLibMW, i18nLibPrev, i18nLibSucc, i18nModAltern, 18nModAlternI18N, i18nModNorm, i18nModNormI18N
	--
	-- Normalize recursiveLevel for any further process.
	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
--	versioning.bind_one_library("versioning", versioning, opt) -- Record a library in package.loaded{}. Move it from _G global space to package.loaded{}.
	-- Binding 1 - Libraries are independent of each others and must be registered before any module.
	-- Central libraries are not fully independent, they call functions of other ones.
	versioning.bind_libraries(package.loaded, opt) -- Bind previous translatable libraries before bind modules and versions.
	versioning.bind_libraries(_G, opt) -- Bind new translatable libraries from _G space before bind modules and versions.
	-- Record new libraries in package.loaded = Move them from _G global space to package.loaded{}.
	--
	-- Binding 3 - Bind sub-modules from the main one.
	versioning.bind_modules(main_p, vers_test) -- Bind sub-modules from the main one.
	-- Binding 3 - Bind following sought and known versions as asked in main_module.versions{}.
	--
	-- Binding 6 - Bind all modules i18n translations.
	versioning.bind_all_i18n(main_p, package.loaded, opt) -- Bind all modules i18n translations.
--	versioning.add_i18n(main_p, package.loaded, opt) -- Bind all modules i18n translations.
	--	versioning.bind_one_i18n(main_p, package.loaded, opt) -- Bind all modules i18n translations.
	-- Each module, in its own i18n{} tables can change or add translations from any sub-module or library.
	-- The order for replace or add for each translation is, at any level, from the most inner until the main module.
	translate.init_wiki_user_page_lang(wiki_lang, user_lang, page_lang)
	-- initialize or change the wiki, the user and the page languages and their tables
	-- Bind all i18n translations from libraries, modules and their /I18N translations sub-modules
	--
	-- Binding 3 - Bind sub-modules from the main, for each: if selected following sought and known versions in main module, from the last to the first known.

	--[[
	-- Actual binding process
	-- Binding 0 - Bind first some i18n libraries to test binding itself.
	local opt = { dated = "MW", xxx = "" }
	versioning.bind_one_library("tools", tools, opt)
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N
	sub-modules.
	versioning.bind_libraries(package.loaded, opt) -- Bind translatable libraries before bind modules and versions.
	local opt = { dated = "Prev", xxx = "" }
	versioning.bind_libraries(package.loaded, opt) -- Bind translatable libraries before bind modules and versions.
	-- Binding 2 - Bind "new" libraries and i18n translations for each: from central module, dated from its last change.
	local opt = { dated = "Succ", xxx = "" }
	versioning.bind_libraries(versioning.librariesList, opt) -- Bind translatable libraries before bind modules and versions.
	-- Binding 3 - Bind sub-modules, for each: if selected following sought and known versions in main module, from the last to the first known.
	-- Binding 4 - Bind sub-modules, for each: internal i18n then from its /I18N sub-module.
	-- Binding 5 - Bind sub-modules and i18n translations for each: dated from their last change dates.
	-- Binding 6 - Bind the main module and its i18n translations.
	versioning.bind_all_i18n(main_p, package.loaded, opt) -- Bind all i18n translations from modules and their /I18N translations sub-modules
	--]]
	--[[
	-- New binding process
	-- Binding 0 - Bind first some i18n libraries to test binding itself.
	local opt = { dated = "MW", xxx = "" }
--	versioning.bind_one_library("tools", tools, opt)
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N sub-modules.
	versioning.bind_libraries(package.loaded, opt) -- Bind translatable libraries before bind modules and versions.
	-- Binding 2 - Bind "new" libraries and i18n translations for each: from central module, dated from its last change.
	local opt = { dated = "Succ", xxx = "" }
	versioning.bind_libraries(versioning.librariesList, opt) -- Bind translatable libraries before bind modules and versions.
	--]]
--[==[
	-- Binding 1 i18n - Bind first i18n translations from all libraries and their /I18N sub-modules.
	local opt = { dated = "Prev", xxx = "" }
	local t, librariesList = versioning.bind_libraries_from_i18n(package.loaded, opt) -- Bind translatable libraries before bind modules and versions.
	-- Binding 3 - Bind sub-modules, for each: if selected following sought and known versions in main module, from the last to the first known.
	-- Binding 4 - Bind sub-modules, for each: internal i18n then from its /I18N sub-module.
	-- Binding 5 - Bind sub-modules and i18n translations for each: dated from their last change dates.
	-- Binding 6 - Bind the main module and its i18n translations.
	local opt = { dated = "Prev", xxx = "" }
	versioning.bind_all_i18n(main_p, librariesList, opt) -- Bind all i18n translations from modules and their /I18N translations sub-modules
--	versioning.bind_all_i18n(main_p, package.loaded, opt) -- Bind all i18n translations from modules and their /I18N translations sub-modules
--]==]
	return t, librariesList
end -- function versioning.bind_modules_method(main_p, vers_test)

-- local tt, vers_test = versioning.bind_modules(p, vers_test) -- The main module can bind all its sub-modules versions, and their /I18N sub-modules.
function versioning.bind_all_i18n(main_versions, loaded_pack) -- Bind all i18n translations from modules and their /I18N translations sub-modules
	-- Already made:
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N sub-modules.
	-- To do:
	-- Binding 4 - Bind i18n sub-modules, for each its internal i18n then its /I18N sub-module.
	local main_versions = main_versions or versioning.main_versions
	local loaded_pack = loaded_pack or versioning.loaded_pack or versioning.get_loaded_modules()
	local known = known or main_versions.known or " "
	known = string.gsub(known, "*", ";")
	local known_tab = mw.text.split(known, ";", true)
	local known_maxn = table.maxn(known_tab) -- local known_maxn = #known_tab
	-- Before libraries and modules were already selected and loaded.
	-- Here we only bind i18n from loaded modules and their /I18N sub-modules.
	local altern = ""
	local name, name_I18N, tt, lang_N, text_N
	for i = known_maxn, 1, -1 do -- from the most sub to the least sub
		altern = known_tab[i]
		altern = mw.text.trim(altern)
		local get = loaded_pack[altern]
		if get and get.isModule then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"

			tt, lang_N, text_N = versioning.add_i18n(t, get.moduleName, get.module)
			if get.moduleName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
				t = t .. "\n*" .. viewer.ta("I18N", get.moduleName) -- .. viewer.ta("name_I18N", name_I18N)
				t = t .. luaTable.formSubCounts("i18n", get.module, get.moduleName .. " <b>LB18+ %2/%3</b>, ", lang_N, text_N) -- Count 1 module translations
				t = t .. luaTable.formSubCounts("versioning.main_i18n", " <b>all+ %2/%3</b>, ", lang_N, text_N) -- Count all translations
			end
--	debug debug
			local getI18N = loaded_pack[get.subI18N]
			if getI18N.isI18N and getI18N.module then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
				tt, lang_N, text_N = versioning.add_i18n(t, getI18N.moduleName, getI18N.module)
				if getI18N.moduleName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
					t = t .. "\n*" .. viewer.ta("I18N", getI18N.moduleName) -- .. viewer.ta("name_I18N", name_I18N)
					t = t .. luaTable.formSubCounts("i18n", getI18N.module, getI18N.moduleName .. " <b>LB18+ %2/%3</b>, ", lang_N, text_N) -- Count 1 module translations
					t = t .. luaTable.formSubCounts("versioning.main_i18n", " <b>all+ %2/%3</b>, ", lang_N, text_N) -- Count all translations
				end
			end
		end
	end
--	versioning.bind_modules(main_p, vers_test) -- Bind all modules and versions, and their translations
--	versioning.bind_all_i18n(main_p, package.loaded, opt) -- Bind all modules and versions, and their translations
	local t = (t or "") .. "\n* effective versioning.bind_modules END: " .. viewer.ta("main_versions.versionName", main_versions.versionName)
	.. viewer.ta("main_versions.sought", main_versions.sought) .. viewer.ta("sought_maxn", sought_maxn)
	.. "<br>" .. viewer.ta("main_versions.known", main_versions.known) .. viewer.ta("known_max", known_max)
	return t
end -- function versioning.bind_all_i18n(name, loaded_pack)

-- local tt, vers_test = versioning.bind_modules(p, vers_test) -- The main module can bind all its sub-modules versions, and their /I18N sub-modules.
function versioning.bindOneModuleAndI18N(name, loaded_pack) -- Load one module and its /I18N translations sub-module
	-- Binding 4 - Bind sub-modules internal i18n then from its /I18N sub-module.
	-- Binding 5 - Bind sub-modules and i18n translations for each: dated from their last change dates.
	local t = ""
	local tt, lang_N, text_N
	loaded_pack = loaded_pack or versioning.loaded_pack or versioning.required_descriptors
	versioning.loaded_pack = loaded_pack
	versioning.required_descriptors = loaded_pack
	local getI18N = nil
	local get = versioning.getModuleAndTime(name)
	if (type(get) == "table") and (type(get.module) == "table") and (type(get.title) == "string") then
		versioning.add_module_in_loaded_pack(loaded_pack, get.title, get)
		tt, lang_N, text_N = versioning.add_i18n(t, get.moduleName, get.module)
		getI18N = versioning.getModuleAndTime(get.subI18N)
		if (type(getI18N) == "table") and (type(getI18N.module) == "table") and (type(getI18N.title) == "string") then
			versioning.add_module_in_loaded_pack(loaded_pack, getI18N.title, getI18N)
		end
	end
	-- addi18n from the sub-module /I18N
	if (type(getI18N) == "table") and (type(getI18N.module) == "table") and (type(getI18N.moduleName) == "string") then
--	if getI18N and getI18N.moduleName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
		tt, lang_N, text_N = versioning.add_i18n(t, getI18N.moduleName, getI18N.module)
		t = t .. "\n*" .. viewer.ta("I18N", getI18N.moduleName) -- .. viewer.ta("name_I18N", name_I18N)
		t = t .. luaTable.formSubCounts("i18n", getI18N.module, getI18N.moduleName .. " <b>MDI18N+ %2/%3</b>, ") -- Count available translations and languages
		t = t .. luaTable.formSubCounts("versioning.main_i18n", " <b>all+ %2/%3</b>, ") -- N translations/languages
	end
	return t, get, getI18N
end -- function versioning.bindOneModuleAndI18N(name, loaded_pack)

-- local tt, vers_test = versioning.bind_modules(p, vers_test) -- The main module can bind all its sub-modules versions, and their /I18N sub-modules.
function versioning.bind_modules(main_p, vers_test) -- Bind all modules and versions, and their translations
	-- Binding 1 - Bind first i18n translations from all libraries and their /I18N sub-modules.
	-- Binding 2 - Bind "new" libraries and i18n translations for each: from central module, dated from its last change.
	-- Binding 3 - Bind sub-modules, for each: if selected following sought and known versions in main module, from the last to the first known.
	-- Binding 4 - Bind sub-modules, for each: internal i18n then bindSortI18N.
	-- Binding 5 - Bind sub-modules and i18n translations for each: dated from their last change dates.
	-- Binding 6 - Bind the main module and its i18n translations.
	local recursiveLevel, recursiveLimit, recursiveLevel_err = 1, 1111, ""
	if viewer.activate_view then
		recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
	end
--	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
	local versioning_main_i18n_memo = versioning.main_i18n
	local modes_args_source_memo = modes.args_source
	local modes_args_config_memo = modes.args_config
	modes.args_source = new_source -- = nil
	modes.args_config = args_config -- = nil
	-- versioning.bind_libraries() -- Bind translatable libraries before bind modules and versions.
	-- To do:
	-- Binding 5 - Bind sub-modules and i18n translations for each: dated from their last change dates.
	-- Binding 6 - Bind the main module and its i18n translations.
	versioning.invoke_record_module_correct_T122752( ) -- Correct the bug T122752 : #invoke do not record the main module in package.loaded
	local t = "\n*<b>versioning.bind_modules() start</b>, effective Search the main module: "
	-- Waiting the debug of T119978 Get the own module name and last record date-time from each module
--	versioning.main_versions = nil
--	versioning.main_module = nil
	local main_module = nil -- To get first
	local main_versions = nil
	local versions, version = nil
	local title = nil
	local gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
	local package_loaded_gettitle = package.loaded[gettitle]
	versioning.main_gettitle = gettitle
	t = t .. viewer.ta("gettitle", gettitle)
	main_p = main_p or versioning.debugT122752_main_module -- waiting debug of T122752 module found module.versions.sought
	-- Normal process
	if type(main_p) == "table" then -- A module can lauch itself or another as main module.
		main_module = main_p
		t = t .. viewer.ta("main_p", main_p)
		if type(main_p) == "table" and type(main_p.versions) == "table" then -- For main modules using versions management
			title = versioning.ModuleNS .. main_p.versions.versionName
			versioning.main_p_versions_txt = "main_p.versions : "
			.. viewer.ta("main_p.title", title) .. viewer.ta("main_p.versionNumber", main_p.versions.versionNumber)
			.. viewer.ta("main_p.sought", main_p.versions.sought) .. viewer.ta("main_p.known", main_p.versions.known)
		end
	elseif type(package_loaded_gettitle) == "table" then -- If #invoke has already loaded the main module in package.loaded
--		local main_i18n_memo = mw.clone(versioning.main_i18n)
		main_p = package_loaded_gettitle
		main_module = main_p
		t = t .. "\n* main module from gettitle : correct T122752 <b>" .. tostring(gettitle) .. "</b>. "
		if type(main_p) == "table" and type(main_p.versions) == "table" then -- For main modules using versions management
			versioning.gettitle_versions_txt = "getTitle.versions : "
			.. viewer.ta("getTitle.versionName", main_p.versions.versionName) .. viewer.ta("getTitle.versionNumber", main_p.versions.versionNumber)
			.. viewer.ta("getTitle.sought", main_p.versions.sought) .. viewer.ta("getTitle.known", main_p.versions.known)
		end
	else -- If the main module was not recorded in package.loaded, try to require() it.
--		local module = versioning.pcallRequire(gettitle) -- require() must not fail and block the page.
		if module then-- Alert by a warning in edit mode: the bug is already fixed.
			local main_i18n_memo = versioning.main_i18n
			main_p = module
			main_module = main_p
			t = t .. "\n* main module from gettitle : repairs T122752 <b>" .. tostring(gettitle) .. "</b>. "
		else -- The main module called this module, but something else is in error.
--			local main_i18n_memo = versioning.main_i18n
			main_p = p
			main_module = main_p
			t = t .. "\n* main module from gettitle : not found <b>" .. tostring(gettitle) .. "</b>. "
		end -- If not loaded, return only get.isTitled and get.title
	end
	-- After get the main module, get the versions for management.
	-- The main module can use or not the versions management.
	if type(main_p) == "table" and type(main_p.versions) == "table" then -- For main modules using versions management
		main_module = main_p
		versioning.main_module = main_module
		t = t .. viewer.ta("versioning.main_module", versioning.main_module)
		main_versions = main_p.versions
		t = t .. viewer.ta("versioning.main_versions", versioning.main_versions)
	end
	if not versioning.main_module then -- The main module is not found.
		return viewer.errorColor( viewer.form9user("versioning_main_module_missing_err") )
	end
	versioning.main_i18n = versioning_main_i18n_memo -- Restore from versioning.bind_libraries()
	modes.args_source = modes_args_source_memo -- Restore from modes.argsConfigIinit()
	modes.args_config = modes_args_config_memo -- Restore from modes.argsConfigIinit()
	-- In normal cases
	if type(versioning) == "table" and type(versioning.main_gettitle) == "string" then
		main_versions.versionName = string.gsub(versioning.main_gettitle, versioning.ModuleNS, "") -- without Module:
	end
	--
	if (type(vers_test) == "table") then -- In tests cases
		main_module.versions = vers_test -- In versions management test, vers_test replace the main versions
		main_versions = main_module.versions
		main_versions.used = ";"
	end
	t = t .. "<br>" .. viewer.ta("main_module", main_module) .. viewer.ta("main_versions", main_versions)
	t = t .. "<br>BEFORE sought, known, versionName, versionNumber, version, main_module : <br>"
	local versionName, versionNumber, sought, known
	if type(main_module) == "table" and type(main_versions) == "table" then -- Verify main module and versions management ability.
		sought = main_versions.sought
		known = main_versions.known
		versionName = main_versions.versionName
		versionNumber = main_versions.versionNumber
		t = t .. viewer.ta("main_module", main_module) .. viewer.ta("main_versions", main_versions)
		t = t .. viewer.ta("sought", sought) .. viewer.ta("known", known)
		t = t .. viewer.ta("versionName", versionName) .. viewer.ta("versionNumber", versionNumber)
	end
	t = t .. "<br>AFTER sought, known, versionName, versionNumber, version, main_module : "
	versioning.bind_modules_start = t
	if type(main_versions.sought) == "string" and type(main_versions.known) == "string" then
		known = main_versions.known
		sought = main_versions.sought
	end
	t = t .. "\n*bind_modules() starts: "
	local sought_t = string.gsub(main_versions.sought or sought or "", " ", ";") -- ";" .. .. ";"
	local sought_tab = mw.text.split(sought_t, " ", true)
	local sought_maxn = table.maxn(sought_tab)
	--
	local known_tab = mw.text.split(main_versions.known or known or "", "*", true)
	local known_maxn = table.maxn(known_tab) -- local known_maxn = #known_tab
	local used = ";"
	local t = ""
	t = t .. "\n* effective versioning.bind_modules : " .. viewer.ta("main_versions.versionName", main_versions.versionName)
	.. viewer.ta("main_versions.sought", main_versions.sought) .. viewer.ta("sought_maxn", sought_maxn)
	.. "<br>" .. viewer.ta("main_versions.known", main_versions.known) .. viewer.ta("known_max", known_max)
	local alternatives, kind
	for i = known_maxn, 1, -1 do -- alternatives = known_tab[i]
		alternatives = known_tab[i]
		alternatives = ";" .. mw.text.trim(alternatives) .. ";" -- normalize versions between ";"
		alternatives = viewer.simpleList(alternatives)
		local altern_tab = mw.text.split(alternatives, ";", true)
		local elem, module, normal, alias, name_I18N
		for v, altern in ipairs(altern_tab) do
			normal = "" ; version = ""
			altern = tostring(altern)
			altern = mw.text.trim(altern)
			if v == 1 and string.is_in_sp(altern, sought, ";") then
				normal = altern
				kind = "normal"
			end
			if v > 1 and string.is_in_sp(altern, sought, ";") then
				version = altern
				kind = "version"
			end
			local name_mod, name_I18N, tt, lang_N, text_N, get_i18n, getI18N
			if vers_test then -- In versions management test
				if string.is_in_sp(altern, vers_test.loaded_list, ";") then
					used = (used or "- ") .. altern .. "; "
				end
				name_I18N = altern .. "/I18N"
			else -- In normal run
				-- addi18n from the module itself
				local tb, get, getI18N = versioning.bindOneModuleAndI18N(altern, versioning.loaded_pack) -- Load one module and its /I18N translations sub-module
				if (type(getI18N) == "table") and (type(getI18N.moduleName) == "string") then -- Verify main module and versions management ability.
					t = t .. "\n*" .. viewer.ta("I18N", getI18N.moduleName) -- .. viewer.ta("name_I18N", name_I18N)
				end
			end
			if vers_test then -- In versions management test
				if string.is_in_sp(name_I18N, vers_test.loaded_list, ";") then
					used = (used or "- ") .. name_I18N .. "; "
				end
			else -- In normal run
				-- addi18n from the sub-module /I18N
				local tb, get, getI18N = versioning.bindOneModuleAndI18N(name, versioning.loaded_pack) -- Load one module and its /I18N translations sub-module
			--	if getI18N.moduleName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
				if (type(getI18N) == "table") and (type(getI18N.moduleName) == "string") then -- Verify main module and versions management ability.
					tt, lang_N, text_N = versioning.add_i18n("", getI18N.subI18N, getI18N.module)
					t = t .. "\n*" .. viewer.ta("I18N", getI18N.moduleName) -- .. viewer.ta("name_I18N", name_I18N)
				end
			end
			versioning.track_i18n_vers = (versioning.track_i18n_vers or "") .. viewer.ta("Module:", (alias or "") .. ", / " .. (normal or "") .. " / " .. (version or "") )
		end -- for v, altern in ipairs(altern_tab) do
	end -- for i = known_maxn, 1, -1 do -- alternatives = known_tab[i]
	local word, errs = "", ""
	if type(vers_test) == "table" then -- In tests run
		-- Versions management errors from overall look on modules
		local alloncesort = viewer.simpleList(vers_test.sought .. ";" .. vers_test.known .. ";" .. vers_test.used)
		local alloncetab = mw.text.split(alloncesort, ',') -- table of words
		for i, name in ipairs(alloncetab) do -- list all used names, only one each, in alphabetic order
			if string.is_in_sp(name, vers_test.sought, ";") and not string.is_in_sp(name, vers_test.known, ";") then
				errs = errs .. ";" .. events.add_err("versioning_missing_versions_err", name, vers_test.versionName ) -- new
			--	versioning_missing_versions_err		= "The module <b>%1</b> misses in the main module <b>%2</b>.",
			end
			if string.is_in_sp(name, vers_test.used, ";") and not string.is_in_sp(name, vers_test.sought, ";") then
				vers_test.replaced = (vers_test.replaced or "") .. name .. ";" -- sought but not found
				errs = errs .. ";" .. events.add_err("versioning_replaced_versions_err", name, vers_test.versionName ) -- new
			--	versioning_replaced_versions_err		= "The module <b>%1</b> replaces the missing module in the main module <b>%2</b>.",
			--	versioning_select_unknown_module_err = "Internal error: The missing Module:<b>%1</b> is replaced by the normal Module:<b>%2</b>.", -- versionsmanagement
			end
			if string.is_in_sp(name, vers_test.alloncesort, ";") and not string.is_in_sp(name, vers_test.known, ";") then
				vers_test.unknown = (vers_test.replaced or "") .. name .. ";" -- sought but not found
				errs = errs .. ";" .. events.add_err("versioning_unknownversions_err", name, vers_test.versionName ) -- new
			--	versioning_unknownversions_err		= "The module <b>%1</b> is unknown in the main module <b>%2</b>.",
			end
		end -- for v, altern in ipairs(altern_tab) do
	end
	main_versions.used = used
	if not (type(vers_test) == "table") then -- In normal run
		versioning.main_module = main_module -- In normal cases and not in tests cases
		versioning.main_versions = mw.clone(main_versions) -- In normal cases and not in tests cases
	end
	--
	local loaded_pack = loaded_pack or versioning.get_loaded_modules() -- List all package.loaded modules			normal
	t = t .. "\n* versioning.bind_modules END : " .. viewer.ta("main_versions.used", main_versions.used) .. viewer.ta("#loaded_pack", #loaded_pack)
	versioning.bind_modules_start = t
	versioning.required_descriptors = loaded_pack
	versioning.bind_i18n_report = (versioning.bind_i18n_report or ".") .. (t or ".")
	return t, main_versions -- loaded_pack, versioning.track_i18n_vers
end -- function versioning.bind_modules(main_p, vers_test) -- Bind all modules and versions and translations

function versioning.add_module_in_loaded_pack(tab, name, elem)
	-- Add an element to a table, with an index to permit later to sort the table in the insert order
	if not (type(tab) == "table") then
		tab = {}
	end
	if (type(tab) == "table") and (type(name) == "string") and (type(elem) == "table") then
		tab[name] = elem
		elem.elem_index_order = #tab -- order of insert
	end
	return tab
end -- function versioning.add_module_in_loaded_pack(tab, name, elem)

-- for loaded_descriptors and other
function versioning.sort_table_in_insert_order(tab)
	-- Sort a table in the insert order of its elements, previously adapted
	if not (type(tab) == "table") then
		tab = {}
	end
	local tab_to_sort = mw.clone(tab) or {}
	table.sort(tab_to_sort, function (a, b) -- in recording order
		return	( a.elem_index_order < b.elem_index_order )
	end )
	return tab_to_sort
end -- function versioning.sort_table_in_insert_order(tab)

function versioning.tab_elem_report(tab, key) -- simple text report of versions elements
	local mode = mode or 1
	local t = ""
	if (type(tab) == "table") and (type(key) == "number") then
		for i, elem in ipairs(tab) do
			t = t .. "\n* " .. viewer.ta("i", i) .. viewer.ta("title", elem.title)
			t = t .. viewer.ta("versionName", elem.versionName) .. viewer.ta("versionNumber", elem.versionNumber)
			t = t .. viewer.ta("versionDate", elem.versionDate) .. viewer.ta("i18n_varstabs", elem.i18n_varstabs)
		end
	end
	if (type(tab) == "table") and (type(key) == "string") then
		for k, elem in pairs(tab) do
			if not tonumber(key) then
				t = t .. "\n* " .. viewer.ta(key, k)
				t = t .. viewer.ta("versionName", elem.versionName) .. viewer.ta("versionNumber", elem.versionNumber)
				t = t .. viewer.ta("versionDate", elem.versionDate) .. viewer.ta("i18n_varstabs", elem.i18n_varstabs)
			end
		end
	end
	return t
end -- function versioning.tab_elem_report(tab, key)

function versioning.version_warning_normal_list(d) -- From alternatives forms version, title, alias, warning, normal
	local d = d.versions or versioning.versions or {} -- descriptor of submodule
	--	example of d.alternatives = "Central,CA,Central1,Central3"
	if type(d.alternates) == "string" then d.alternates = mw.text.trim(d.alternates) end
	if not d.alternates then d.alternates = "" ; return d	end
	local altern_tab = mw.text.split(d.alternates, ";", true)
	local version, alias, normal
	for v, altern in ipairs(altern_tab) do
		altern = mw.text.trim(altern)
		if v == 1 and string.is_in(altern, d.selector) then normal = altern ; version = altern end
		if v == 2 and string.is_in(altern, d.selector) then normal = altern ; version = altern end
		if v > 2 and string.is_in(altern, d.selector) then version = altern end
	end
	if version then
		d.version = string.gsub(version, versioning.ModuleNS, "") -- version without Module:
		d.title = versioning.ModuleNS .. d.version
		d.normal = normal
		d.normal_I18N = versioning.ModuleNS .. normal .. "/I18N"
		d.normal_I18N = string.gsub(d.normal_I18N, "/I18N/I18N", "/I18N")
		d.version_I18N = versioning.ModuleNS .. version .. "/I18N"
		d.version_I18N = string.gsub(d.version_I18N, "/I18N/I18N", "/I18N")
		d.alias = alias
		if d.normal and not string.is_in(d.normal, d.norm or " ") then d.norm = (d.norm or " ") .. " " .. d.normal .. " " end
		if d.version and not string.is_in(d.version, d.list or " ") then d.list = (d.list or " ") .. " " .. d.version .. " " end
		if d.normal_I18N and package.loaded[d.normal_I18N] and not string.is_in(d.normal_I18N, d.list or " ") then d.list = (d.list or " ") .. " " .. d.normal_I18N .. " " end
		if d.version_I18N and package.loaded[d.version_I18N] and not string.is_in(d.version_I18N, d.list or " ") then d.list = (d.list or " ") .. " " .. d.version_I18N .. " " end
	--	d.missing = d.missing .. " " .. d.version .. " " -- managed elsewhere
	--	d.excess = d.excess .. " " .. d.version .. " " -- managed elsewhere
	end
	return d
end -- function versioning.version_warning_normal_list(d)

-- Detect the task T122752 : #invoke seems do not record the module
local debugT122752_task_state = "triage" -- default value, at Require() time
local debugT122752_gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
if type(package.loaded[debugT122752_gettitle]) == "table" -- get task state at Require() time
then debugT122752_task_state = "OK" else debugT122752_task_state = "triage" end -- Detect T122752 : #invoke seems do not record the module

function versioning.invoke_record_module_debug_T122752() -- Detect T122752 : #invoke seems do not record the module
	local gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
	if debugT122752_task_state then return end
	if type(package.loaded[gettitle]) == "table" then debugT122752_task_state = "OK" else debugT122752_task_state = "triage" end
end

function versioning.invoke_record_module_correct_T122752(t) -- Bypass the bug T122752 : #invoke do not record the main module in package.loaded
	-- When the parser build a wiki page, it do {{#invoke:MainModule | ... "}}
	-- Because the bug T122752 : #invoke do not record the main module in package.loaded
	-- but the MainModule is active and require("Module:Central")
	-- Then	 versioning.invoke_record_module_correct_T122752() verify versioning.debugT122752_status
	-- before versioning.bind_modules() bind all modules and versions, and their translations
	local t = t or ""
	local gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
	versioning.main_gettitle = gettitle
	local module
	if type(package.loaded[gettitle]) == "table" then
		versioning.debugT122752_status = "Resolved"
	else
		versioning.debugT122752_status = "getmodule" -- "Open"
		module = versioning.pcallRequire(gettitle) -- require() must not fail and block the page if the module do not exists.
		if module then -- Alert? by a warning in edit mode: the bug is already fixed.
			versioning.debugT122752_main_module = module
			versioning.debugT122752_status = "module found" -- "Open"
		--	t = t .. viewer.form9user("versioning_try_waits_debug_T122752", tostring(gettitle) ) .. "#invoke T122752 replaced by pcallRequire."
			t = t .. "\n* invoke_record_module_correct_T122752: repairs the main <b>" .. tostring(gettitle) .. "</b>. See: [https://phabricator.wikimedia.org/T122752 T122752 #invoke seems do not record the module] "
			if type(package.loaded[gettitle]) == "table" then versioning.debugT122752_status = "get corrected Resolved" end
		else
			versioning.debugT122752_status = "module missing" -- "Open" -- Main error, there is no main module.
		end
	end
	versioning.debugT122752_status = versioning.debugT122752_status or "nil status" -- "Open"
	return t
end -- function versioning.invoke_record_module_correct_T122752(t) -- Bypass the bug T122752 : #invoke do not record the main module in package.loaded

function versioning.deprecatedFunction(old, new, kind) -- Deprecated function. Replace it in the module.
	local res = ""
--	if type(translate.user_lang) == "string" then
		res = events.add_err("versioning_add_deprecated_err", old, new, versioning.main_versions.versionName)
		events.add_cat("versioning_module_internal_error_cat")
--	end
	versioning.deprecatedFunctionGroup = versioning.deprecatedFunctionGroup or {}
	table.insert(versioning.deprecatedFunctionGroup, {
		old, new, versioning.main_versions.versionName, (kind or "actual")
	} )
	return res
end

function versioning.deprecatedFunction_test(t) -- Tests of deprecated function.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or ""
	t = t .. "\n* Test <b>versioning.deprecatedFunction_tests</b> :"
	-- deprec
	-- versioning.deprecatedFunction("tools.option", "modes.option")
	local func, argmt, old, new = tools.option, "docview", "tools.option", "modes.option"
	local ifyes, used, available_options = func(argmt)
	t = t .. "\n\n* Test 1 : " .. viewer.ta("old", old) .. viewer.ta("new", new) .. viewer.ta("ifyes", ifyes) .. viewer.ta("available_options", available_options)
	--
	-- versioning.deprecatedFunction("tools.luaTabStruc", "luaTable.structure")
	local func, argmt, old, new = tools.luaTabStruc, {}, "tools.luaTabStruc", "luaTable.structure"
	local test = func(argmt)
	t = t .. "\n\n* Test 2 : " .. viewer.ta("old", old) .. viewer.ta("new", new) .. viewer.ta("test", test)
	versioning.deprecatedFunctionGroup = versioning.deprecatedFunctionGroup or {}
--	table.insert(versioning.deprecatedFunctionGroup, {
--		old, new, versioning.main_versions.versionName, "test")
--	} )
	local version = versioning.main_versions.versionName -- Save global configuration before eventual changes.
	local t = t or "\n* <b>Tests of main central modules:</b> :"
	local tabView = {
		testGroup = { -- versioning.deprecatedFunctionGroup
			{ "versioning.bind_modules", "versioning.bind_main_module", version, "test" },
			{ "tools.luaTabStruc", "luaTable.structure", version, "test" },
			{ "p.form_result", "modes.form_result", version, "test" },
			{ "tools.option", "modes.option", version, "test" },
			{ "tools.closeCatsErrs", "events.close", version, "test" },
			{ "tools.initCatsErrs", "events.memorize", version, "test" },
			{ "versioning.bind_modules", "versioning.bind_main_module", version, "test" },
			{ "tools.luaTabStruc", "luaTable.structure", version, "test" },
		headers = "old; new; versionName; kind",
		}
	}
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		return case
	end
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
	local t = "\n* Test <b>versioning.deprecatedFunction()</b> :"
--	t = t .. tabView.t
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function versioning.deprecatedFunction_test(t)

function versioning.all_versions_text(all_versions) -- DEBUG
	all_versions = all_versions or versioning.all_versions
	if type(all_versions) == "string" then return all_versions end
--	if type(all_versions) == "string" then all_versions = { all_versions } end
	if type(all_versions) == "table" then return table.concat(all_versions, " >> ")
	else return "" end
end

function versioning.bind_modules_report(t, tracks_level) -- Report all modules installation
	local memo = events.save_configs() -- Save global configuration before eventual changes.
--	versioning.main_module = versioning.main_module
--	versioning.main_versions = versioning.main_versions
--	versioning.main_gettitle = versioning.main_gettitle
--	versioning.bind_modules_start = versioning.bind_modules_start
	if tracks_level and type(tracks_level) == "number" then tracks_level = math.floor(tracks_level)
	else tracks_level = 1 end
	if tracks_level < 0 then tracks_level = 0 end
	local t, t2, pp = (t or "") .. "\n* versioning.versioning_verif_bind_modules_report</b> : actual list and verify many modules functions."
	-- Get previous loaded modules and record tables for further uses and count
--	local loaded_pack, loaded_txt = versioning.get_loaded_modules() -- get the modules state
	versioning.loaded_txt = (loaded_txt or "")
--	local loaded_to_sort = versioning.loaded_descriptors -- or versioning.loaded_pack or versioning.loaded_pack or {x=x}
	versioning.report_bind_modules_init_i18n = (versioning.report_bind_modules_init_i18n or "")
	versioning.required_sorted = versioning.sort_table_in_insert_order(versioning.required_descriptors)
	--[ [ simulate main_versions to try here
	local vers = { -- initial default version descriptor
		versionName = "Box3", versionNumber = "3.33", versionDate = "2015-09-09 09:09",
		sought = " Box3 Group2 Central1 ", -- Sought module and submodules versions
		known = " Box,Box3 * Group,Group2 * Central,Central1 ",
	}
	--] ]
	if versioning.main_versions then vers = versioning.main_versions end
	local main_versions = vers -- versioning.main_versions
	local main_versionNumber = vers -- versioning.main_versions
	-- Use main_versions rather than simulate it
	--
	-- Search the main module which has the most known and sought versioning.
	local loaded_pack = versioning.get_loaded_modules() -- List all package.loaded modules
	-- in _G to DEBUG T122752 : #invoke do not record the main module in package.loaded
	local loaded_max, loaded_main = 0, {}
	local versionNumber = main_versions.versionNumber -- versioning.main_versions.versionNumber
	for title, get in pairs(loaded_pack) do -- DEBUG reverse order
		t = t .. viewer.ta("title", title)
		if get.sought_count and get.known_count and ( get.sought_count + get.known_count ) > loaded_max then
			loaded_max = get.sought_count + get.known_count
			loaded_main = package.loaded[get.title]
			pp = loaded_main
			t = t .. viewer.ta("loaded_max", loaded_max)
		end
		if get.title == versioning.main_gettitle then versionNumber = get.versionNumber end --	get.versionNumber = get.shortVersion
	end
	local main_module = loaded_main
	main_versions.versionNumber = versionNumber
	if main then
		t = t .. viewer.form9user(
		"<br>Versions report for <b>%1</b> %2 <b>%3</b> : "
		.. "<br>Main module selector: <b>%4</b>"
		.. "<br>Main module all versions: <b>%5</b>", main_versions.versionName, main_versions.versionNumber, main_versions.versionDate,
			main_versions.sought, main_versions.known )
	end
	t = t .. viewer.ta("modules number", #loaded_pack)
	versioning.bind_verif_modules_report_start = t
	--
	-- Form the report of versioning.loaded_pack = package.loaded modules
--[= =[
--	begin DEBUG Erreur Lua : stack overflow:	versioning.report_loaded_pack_alias_versions = t2 .. " effective " .. (versioning.bind_modules_start or "")
	local loaded_sorted = {}
	for key, elem in pairs(loaded_pack) do
	--	if elem.isModule then elem.alias = "Module:" .. elem.versionName end -- Locate translations example in Module:Central/I18N
	--	if elem.isLibrary then elem.alias = "Library:" .. elem.versionName end -- Locate translations example in Module:Library/versioning/I18N
		table.insert(loaded_sorted, elem)
	end
	table.sort(loaded_sorted, function (a, b) return ( a.alias < b.alias ) end )
	t = "Report of effective mixed modules: " -- get.TitleI18N elem.alias
	t2 = ""
	local head = mw.text.split( viewer.form9user("versioning_list_loaded_modules_headers") , ';')
	t = t .. viewer.Th("wikitable sortable") .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[3]) .. viewer.Tc(head[4]) .. viewer.Tc(head[5])
	-- A triable table start with : {| class="wikitable sortable"
	-- A column become fix and not triable with : ||class="unsortable"|
	local subI18N = ""
	for key, elem in ipairs(loaded_sorted) do
		if elem.i18n then -- elem.i18n
			local vers18 = elem.versionName --; if not elem.isI18N then vers18 = "" end
			if elem.isModule then elem.alias = "Module:" .. elem.versionName end -- Locate translations example in Module:Central/I18N
			if elem.isLibrary then elem.alias = "Library:" .. elem.versionName end -- Locate translations example in Module:Library/versioning/I18N
			if package.loaded[elem.subI18N] then subI18N = elem.subI18N else subI18N = "" end -- Locate translations example in Module:Library/versioning/I18N
			t = t .. viewer.Tr()
			.. viewer.Td( (elem.alias or "") )
			.. viewer.Td(elem.versionName or "")
			.. viewer.Td( (elem.versionDate or "date") .. " , " .. (elem.shortVersion or "vers") )
			.. viewer.Td(subI18N) -- version_I18N subI18N
			.. viewer.Td( luaTable.formSubCounts(elem.i18nRoot .. ".i18n", elem.alias .. ".i18n", "this + %2/%3 T/L, ") )
				.. luaTable.formSubCounts("versioning.main_i18n", "versioning.main_i18n", ", (all = %2/%3 T/L)")
-- DEBUG			.. viewer.Td( luaTable.formSubCounts(elem.i18nRoot .. ".i18n", elem.alias .. ".i18n", "this + %2/%3 T/L, ") )
-- DEBUG				.. luaTable.formSubCounts("versioning.main_i18n", "versioning.main_i18n", ", (all = %2/%3 T/L)")
			if elem.versionName and elem.alias then
				t2 = t2 .. viewer.form9user("<br>* alias <b>%1</b> = <b>%2</b>, version = <b>%3</b>, date = <b>%4</b>",
				elem.alias, elem.versionName, elem.shortVersion, elem.versionDate)
			end
		end
	end
	t = t .. viewer.Te()
	versioning.report_loaded_pack = t
	versioning.report_loaded_pack_alias_versions = t2 .. " effective " .. (versioning.bind_modules_start or "")
--	end DEBUG Erreur Lua : stack overflow:	-- t = t .. "\n\n\n* effective versioning.bind_modules_start: " .. (versioning.bind_modules_start or " versioning.bind_modules_start missing ")
--]= =]
	versioning.report_loaded_txt = (loaded_txt or " loaded_txt ")
	--
	t = ""
	local head = mw.text.split( viewer.form9user("versioning_bind_modules_test_headers") , ';') -- versioning_bind_modules_tests_title
	t = t .. viewer.Th() .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[4])
	if type(main_versions) == "table" then
		t = t .. viewer.Tr() .. viewer.Td(main_versions.sought) .. viewer.Td(main_versions.known) .. viewer.Td(select_txt) .. viewer.Td(alias_txt) .. viewer.Td(err)
	end
	t = t .. viewer.Te()
	versioning.report_bind_verif_modules = t
	--
	versioning.report_loaded_descriptors = versioning.tab_elem_report(versioning.loaded_descriptors, "title")
	versioning.report_required_descriptors = versioning.tab_elem_report(versioning.required_descriptors, "title")
	versioning.report_loaded_descriptors_loaded_sorted = versioning.tab_elem_report(versioning.loaded_sorted, 1)
	--
	-- Form the report of loaded_sorted
--	local CentralModule, loaded_vers, err, select_txt, alias_txt = versioning.bind_modules_verif(main, "list", versioning.loaded_pack)
	versioning.report_bind_verif_modules_list = select_txt
	local main_versions
	if type(versioning.main_module) == "table" and type(versioning.main_versions) == "table" then
		main_versions = versioning.main_versions or { versionName = "versionName", versionNumber = "0.0", }
		versioning.report_main_short = main_versions.versionName .. " " .. main_versions.versionNumber
		versioning.report_main_discreet = viewer.discreetColor( versioning.report_main_short )
		versioning.report_REVISIONTIMESTAMP = modes.frame:preprocess( "{{REVISIONTIMESTAMP}}" )
		versioning.report_actual_versions = (versioning.report_main_discreet or "versioning.report_main_discreet") -- .. versioning.report_REVISIONTIMESTAMP .. " "
	else
		versioning.report_main_short = "versioning.report_main_short is missing."
		versioning.report_main_discreet = "versioning.report_main_discreet is missing."
		versioning.report_REVISIONTIMESTAMP = "versioning.report_REVISIONTIMESTAMP is missing."
		versioning.report_actual_versions = "versioning.report_actual_versions is missing."
	end
	--
	-- Form the detailed Versions report
	t = "\n* Discreet main version: " .. (versioning.report_main_discreet or " report_main_discreet missing ")
--	t = t .. "\n* All versions from main module: " .. (versioning.report_main_select_all or " report_main_select_all missing ")
--	t = t .. "\n* Modules alias and versions: " .. (versioning.report_loaded_pack_alias_versions or " report_loaded_pack_alias_versions missing ")
	t = t .. "\n\n* All selected modules versions: " .. (versioning.report_bind_verif_modules or " report_bind_verif_modules missing ")
	t = t .. "\n\n* All <b>loaded modules</b> versions: " .. (versioning.report_loaded_pack or " report_loaded_pack missing ") -- loaded_modules
	t = t .. "\n* Start versions from main module: " .. (versioning.bind_verif_modules_report_start or " bind_verif_modules_report_start missing ")
	t = t .. "\n* effective versioning.bind_modules_start: " .. (versioning.bind_modules_start or " versioning.bind_modules_start missing ")
	versioning.support_desk_report = t -- versioning.bind_modules_report() -- Report all modules installation
	--
	t = "" -- versioning.report_bind_modules_report_details
	t = t .. "\n* versioning.bind_modules_start: " .. (versioning.bind_modules_start or " versioning.bind_modules_start missing ")
	t = t .. "\n\n\n* effective report_find_main_module: " .. (versioning.report_find_main_module or " report_find_main_module missing ")
	t = t .. "<br/>\n* bind_verif_modules_recursive: " .. (viewer.ta("bind_verif_modules_recursive", versioning.bind_verif_modules_recursive) or " bind_verif_modules_recursive missing ")
	t = t .. "<br/>\n* effective list loaded descriptors: " .. (versioning.report_loaded_descriptors or " report_loaded_descriptors missing ")
	t = t .. "<br/>\n* List required descriptors: " .. (versioning.report_required_descriptors or " report_required_descriptors missing ")
	t = t .. "<br/>\n* List loaded descriptors sizes: " .. (versioning.report_loaded_descriptors_loaded_sorted or " report_loaded_descriptors_loaded_sorted missing ")
	t = t .. "<br/>\n* Search the MainModule: " .. (versioning.report_find_main_module or " report_find_main_module missing ")
	t = t .. "<br/>\n* List elem.alias : " .. (versioning.alias_txt or " alias_txt missing ")
	t = t .. "<br/>\n\n* " .. (versioning.report_bind_verif_modules_list or " report_bind_verif_modules_list missing ")
--	t = t .. "<br/>\n\n* All used modules versions: " .. (versioning.report_main_used or " report_main_used missing ")
	t = t .. "\n* Text report of <b>package.loaded</b> modules: " .. (versioning.report_loaded_txt or " report_loaded_txt missing ") -- loaded_modules
	t = t .. "\n\n\n* Text report of <b>_G global</b> modules: " .. (versioning.report_loaded_G or " report_loaded_G missing ") -- loaded_modules
	t = t .. "<br/>\n\n* All effective I18N modules used in this order. t/l = texts/languages : " .. (versioning.report_bind_modules_init_i18n or " report_bind_modules_init_i18n missing ")
--	t = t .. versioning.get_edit_state() -- Detect if the wiki page using the module is in edit state
--	t = t .. "<br/>\n* " .. versioning.i18n_trc
	versioning.report_bind_modules_report_details = t
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function versioning.bind_modules_report(t, tracks_level) -- Report all modules installation

function versioning.tasks_changes_report(t) -- The tests function uses mode and options to include edit or tests.
	local res = t or ""
--	res = res .. "\n: Documentation of Module:Central: [[s:fr:Rical/Central modules|wikisource:Central modules]]."
--	res = res .. "\n: Development of Module:Central: [[s:fr:Module:Central|wikisource:Module:Central]]."
--	res = res .. "\n: Example of use: [[s:fr:Utilisateur:Rical/Victor_Hugo|wikisource:Rical/Victor_Hugo]]."
--	res = res .. versioning.tasks_changes_report_short
	res = res .. "\n* List of blocking tasks: " .. versioning.tasks_changes_report_normal()
	return res -- versioning.tasks_changes_report_normal -- versioning_tasks_changes_report
end -- function versioning.tasks_changes_report(t)

function translate.key(key, lang) -- Translate the key in user, wiki and content languages.
	if (type(key) ~= "string") then key = "translate_missing_key_err" end
	if (type(lang) ~= "string") then lang = nil end
	local translate_i18n = translate.main_i18n
	function translate_one_key(translate_i18n, key, lang)
		if lang and (type(translate_i18n) == "table") then translate_i18n = translate_i18n[lang] end
		if key and (type(translate_i18n) == "table") then translate_i18n = translate_i18n[key] end
		return translate_i18n
	end
	translate_i18n = translate_one_key(translate_i18n, key, lang)
	if (type(lang) == "string") then -- Only the selected language
		return translate_i18n
	else
		return	translate_one_key(translate_i18n, key, translate.user_lang),
				translate_one_key(translate_i18n, key, translate.wiki_lang),
				translate_one_key(translate_i18n, key, translate.page_lang)
	end
--	Examples of uses
--	local user, wiki, page = translate.key(key, lang)
--	if translate.key(key, lang) ~= key then block end
end -- function translate.key(key, lang)

function versioning.versioning_tasks_report(t, short) -- List states of known bugs or tasks.
--	versioning.versioning_tasks_report() États des tâches connues
	local t = t or "\n* <b>versioning.versioning_tasks_report()</b> List states of known bugs or tasks, to suport the tasks management from inside versions."
	versioning.debugT53660isEdited = mw.title.getCurrentTitle().isEdited or "Open"
	-- Detect the status of the task : T68051_status.
	versioning.debugT68051_status = mw.uri.new().userLanguage
	if versioning.debugT68051_status then versioning.debugT68051_status = "Resolved" else versioning.debugT68051_status = "args" end
	--	{ 18, "Important", "T68051", 1, "Open", "Central modules need the user language to display errors and category names", "user language for errors", },
	-- Detect the status of the task : T133498_status.
	local mwtitle = mw.title.getCurrentTitle()
	local url = tostring(mwtitle:canonicalUrl( ))
--	versioning.debugT133498_status = mw.uri.canonicalUrl() -- mw.uri.canonicalUrl
	local title_object = mw.title.new(url) -- .itemid -- mw.uri.canonicalUrl
	if title_object then versioning.debugT133498_status = title_object.itemid end -- mw.uri.canonicalUrl
	if versioning.debugT133498_status then versioning.debugT133498_status = "Resolved" else versioning.debugT133498_status = "Open" end
	-- Any title with datas in wikibase give its item.id to any module. Probably across mw.title.new().itemid
	--
	local tabView = {
		testGroup = {
		--	Importance: Important
			{ 10, "Important", "T20170130", 0, "ToCreate", "Begin to use and support central modules in 10 wikis", "Begin to use central", },
			{ 12, "Important", "T135845", 0, "Open", "Convert any module as central or centralisable", "Convert as central", },
			{ 14, "Important", "T53660", 0, "Open", "Detect the edit state for modules which help users and helpers", "edit state", },
			{ 16, "Important", "T4085", 0, "Assigned", "Add a {{USERLANGUAGE}}/{{USERLANG}} magic word", "USERLANGUAGE", },
			{ 18, "Important", "T68051", 1, "Open", "Central modules need the user language to display errors and category names", "user language for errors", },
			{ 20, "Important", "T142906", 1, "Stalled", "Data access in user language doesn't obey the uselang get parameter", "User language not OK", },
		--[[ T142906 Data access in user language do not obey the uselang get parameter / Apparently
		We use the former as an optimization in
		Scribunto_LuaWikibaseLibrary::getLanguage and
		Scribunto_LuaWikibaseEntityLibrary::getLanguage to only split the parser cache when actually needed. --]]
			{ 22, "Important", "T159322", 0, "Open", "Central categories and alerts for central modules", "Central categories/alerts", },
		--	T159322 : Central modules are able to detect internal errors which could block the display of a page, like in development phase but also later.
		--	T159322 : And that can happen in any wiki-project without enough helpers, then central categories and alerts for central modules seem usefull to help them.
			{ 24, "Important", "T85419", 0, "Open", "User groups should be exposed to Scribunto", "user groups", },
		--	T85419: to adapt errors and warning for administrators or modules coders
			{ 26, "Important", "T142093", 0, "Open", "Decide how to do usage tracking for strings used to lookup entities (page titles, external ids, …)", "how to link entities/titles", },
			{ 28, "Important", "T20170211", 1, "ToCreate", "In mw.language.fetchLanguageNames( 'fr' ) some languages names are not in French", "French languages names", },
			{ 30, "Important", "T20161220", 0, "Close", "To test: Central modules need the page language to display errors categories and datas for helpers", "user language getContentLanguage()", },
		--	T20161220 : ToDo: test: local getContentLanguage = mw.language.getContentLanguage().code -- default wiki_lang
			{ 32, "Important", "T147618", 0, "Open", "Localize one or more major WMF software products related to new editor retention to hu.wikipedia", "Localize new editor retention", },
		--
		--	Importance: Central
			{ 40, "Central", "T121470", 0, "Open", "Central Global Repository for Templates, Lua modules, and Gadgets", "central repository", },
			{ 42, "Central", "T52329", 0, "Stalled", "We need a common repository for Scribunto modules and templates", "common repository", },
			{ 44, "Central", "T41610", 0, "Open", "Scribunto should support global module invocations", "global invocations", },
			{ 46, "Central", "T20170301", 0, "ToCreate", "Phabricator tasks states for easier versions management of modules", "Phab tasks states", },
		--	datas to get for each query: Task number, title, state, priority, Authored By and date, Last event date, number of subscribers, Assigned.
			{ 48, "Central", "T20170225", 0, "ToCreate", "Report Mediawiki changes for the versions management, and use {{subst:}}", "Mediawiki changes", },
		--	T20170225: The gerrit team needs the date of a bug and the dates of mw install in each wiki to debug mw.
		--	T20170225: We could detect, record and report all date-time changes of mediawiki, for some weeks.
		--	T20170225: We could use {{subst:}} in 2 ways. See https://www.mediawiki.org/wiki/Manual:Substitution
		--	T159322 Central categories
		--	Importance: Other
			{ 70, "Other", "T63958", 0, "Open", "Use existing $dateFormats to format dates on Wikidata", "Use $dateFormats on Wikidata", },
			{ 72, "Other", "T85412", 0, "Open", "The formatPropertyValues needs an option to format data from date-time, angle or geographic coordinates", "format date-time", },
			{ 74, "Other", "T156048", 0, "Open", "Add syntax highlighting to wiki diff of source code pages (like Gerrit)", "syntax highlighting", },
			{ 76, "Other", "T20170213", 0, "ToCreate", "Some languages have not French names in French language", "languages in stranger names", },
			{ 78, "Other", "T20151215", 0, "ToCreate", "In some cases the box of the dropbox is not displayed", "dropbox not displayed", },
		--	T20151215: .. "\n------ " -- This code interact with DropBox and debug the task T20151215 2016-11-13 17:57.
		--	T20151215: if there is "\n------ " before editDocBox and  then the editDocBox <div> is only a bar of 1 em hight, and its content is after the <div>. Rical 2016-10-24 08:15.
		--	T20151215: Original code from fr.wikisource : "{{Boîte déroulante/début|titre=" .. title .. "|alignT=" .. alignT .. "}}" .. content .. "{{Boîte déroulante/fin}}"
			{ 80, "Other", "T20160616", 0, "ToCreate ?", "Add module and library types in scribunto ?", "module and library types?", },
		--	T20160616: get.isLibrary = ( true ) -- true if the object is a library
		--
		--	Importance: Obsolete
			{ 99, "Obsolete", "T20161022", 0, "ToCreate", "? Scribunto functions are out of package.loaded", "Lua functions out of package.loaded", },
		--	T20161022: --	Found in Lua_reference_manual but not in Module:Central-s-fr :
		--	T20161022: --	2016-10-22 15:45 : modes.all_G_list_var_test() : Variables in _G global space without libraries and modules in packageloaded. , xpcall = function , tostring = function , _VERSION = Lua 5.1 , unpack = function , require = function , pairs = function , next = function , assert = function , ipairs = function , rawequal = function , getmetatable = function , rawset = function , pcall = function , type = function , selector = function , rawget = function , tonumber = function , error = function , setmetatable = function
			{ 99, "Obsolete", "T133498", 1, "Declined", "Detect and extend known title <-> entity links in a semi-automatic way", "semi-automatic wikidata itemid", },
			{ 99, "Obsolete", "T151715", 0, "Invalid", 'string.gsub( s, "%%1", repl) fails as "invalid capture index"', 'string.gsub(s,"%%1",r) fails', },
			{ 99, "Obsolete", "T154769", 0, "Invalid", 'When "Erreur Lua : attempt to call a string value", where?', "Error Lua call a string where?", },
		--	T154769: Rical 2017-01-08 My main mistake was to write xpcall() where pcall() is right.
		--	T154769: Tests are there https://fr.wikisource.org/wiki/Module:Phab.T154769.Test
		--	T154769: versioning.antiCrash() preserve pages against residual errors which can crash pages.
			{ 99, "Obsolete", "T155898", 0, "Invalid", "Preview change from external local editors", "Change from local", },
		-- T155898: OK, I will continue to search in JEdit plugins. Perhaps I will ask them an adapted solution.
			{ 99, "Obsolete", "T67507", 0, "Resolved", "It should be possible to get entity id from page title", "entity id from title", },
			{ 99, "Obsolete", "T119978", 0, "Invalid", "Get name and record-time of all modules to better manage their versions", "own module name", },
			{ 98, "Obsolete", "T122752", 1, "Open", "#invoke do not record the main module in package.loaded", "invoke# main not loaded", },
		--	T122752: To locate and to debug the bug T122752 in time, Rical lists here the versions of fr.wikisource.org/wiki/Module:ControlArgs1
		--	T122752: Oldest research of "Replace the missing of record of the module by" : ModuleControlArgs 6 tools...lua 2016-01-16
		--	(actu | diff) 2015-11-25T11:43:11‎ Rical (discussion | contributions | bloquer)‎ . . (261 314 octets) (-3 519)‎ . . (The versions management seems OK) (annuler)
		--	(actu | diff) 2015-11-28T09:55:37‎ Rical (discussion | contributions | bloquer)‎ . . (267 472 octets) (+5 809)‎ . . (find_main_module)
		--	(actu | diff) 2015-12-01T14:18:22‎ Rical (discussion | contributions | bloquer)‎ . . (268 956 octets) (+1 614)‎ . . (find_main_module OK ?)
		--	(actu | diff) 2016-01-18T02:09:45‎ Rical (discussion | contributions | bloquer)‎ . . (319 599 octets) (+10 183)‎ . . (translate library.i18n first)
		--	T122752: 2016-01-02 phabricator: T122752 Rical created this task. Sat, Jan 2, 2016-01-02 23:08
			{ 99, "Obsolete", "T75460", 0, "Resolved", "[Story] Make Lua functions default to the user's language on multilingual wikis", "multilingual wikis", },
			{ 99, "Obsolete", "T119978", 0, "Invalid", "Get name and record-time of all modules to better manage their versions", "own module name", },
		},
		"versioning_tasks_report_headers",
	}
--	versioning_tasks_report_title			= "versioning.versioning_tasks_report() States of known tasks",
--	versioning_tasks_report_headers			= "Importance; Task; State; Title; In short",
--	versioning_tasks_report_headers			= "Importancia; Tarea; Estado; Título; En breve",
--	versioning_tasks_report_headers			= "Importance; Tâche; Etat; Titre; En bref",
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		case.sort = case[1] ; case.state = case[2] ; case.task = case[3] ; case.detect = case[4]
		case.state = case[5] ; case.title = case[6] ; case.shortext = case[7]
		case.task = "[[phab:" .. case.task .. "|" .. case.task .. "]]"
		if string.is_in(case.state, "ToCreate, Resolved, Declined, Invalid, ") then case.title = viewer.discreetColor(case.title)
		elseif string.is_in(case.state, "Open, Stalled, Assigned, ") then case.title = viewer.warningColor(case.title)
		else case.title = viewer.errorColor(case.title) end -- All states : ToCreate, Open, Stalled, Assigned, Resolved, Declined, Invalid, , , , , , , , , , , , , 
		if case.detect == 1 then case.title = case.title .. " - ( detected state )" end
		--	headers =	"Importance; 	Task; 	State; 		Title; 	In short"
		return {			case[2], case.task, case[5], case.title, case[7] }
	end
	t = t .. "<br>Used colors: "
	.. viewer.ta("ToCreate, Resolved, Declined, Invalid", viewer.discreetColor("task already Resolved, or to create") )
	.. viewer.ta("Open, Stalled, Assigned", viewer.warningColor("task already Resolved") )
	.. viewer.ta("Other", viewer.errorColor("other tasks") )
--	.. viewer.ta("args", viewer.otherColor("task replaced by arguments or by the module") )
--	.. viewer.ta("ToCreate", viewer.discreetColor("tasks ToCreate") )
--	.. viewer.ta("other", viewer.errorColor("other tasks") )
	--
	local tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
--	t = t .. tabView.t
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function versioning.versioning_tasks_report(t, short)

function versioning.init(frame, mode_name, args_known, options_for_modes, itemid)
	-- Initializes translations, bind_libraries, bind_modules, get arguments, modes, options.
	-- Imports arguments from wikidata, module and template with increasing priorities.
	viewer.activate_view = false -- Mask any views to avoid failures until enough init.
	local res, t = "", ""
	modes.time1 = os.clock()
	modes.time2 = os.clock()
	modes.time3 = os.clock()
	modes.time4 = os.clock()
--	translate.Central_x_y = p.Central_x_y -- To adapt the version of Module:Central in any translated text.
	translate.Module_Central_version = p.Module_Central_version -- To adapt the version of Module:Central in any translated text.
	options_for_modes = options_for_modes or modes.options_for_modes
	versioning.bind_modules_method() -- Bind all modules and versions, and their translations
	modes.module_init(frame) -- Get modes.args_source with modes.args_config
	modes.init_tools(frame, options_for_modes, mode_name) -- Needed to init categories and errors
	local mode_name, source_key, try_lang =
	modes.get_arg_mode(mode_name, source_key, try_lang, args_source)
	modes.mode_name = mode_name or modes.mode_name or "read"
	events.init_configs() -- testcase, tabOptions, {}) -- Init a new events group for erros, warnings and categories
	local recursiveLevel, recursiveLimit, recursiveLevel_err = modes.recursiveNormal(recursiveLevel, modes.recursiveLimit)
	translate.init_wiki_user_page_lang(modes.args_config.wikilang, modes.args_config.userlang) -- init user and the wiki languages from args
	res = res .. viewer.ta("translate.init_wiki_user_page_lang : mode_name", mode_name) .. viewer.ta("wiki_lang", translate.wiki_lang) .. viewer.ta("user_lang", translate.user_lang)
--	translate.Central_x_y = p.Central_x_y -- To adapt the version of Module:Central in any translated text.
	translate.Module_Central_version = p.Module_Central_version -- To adapt the version of Module:Central in any translated text.
--	local args_known = args_known or modes.args_known
	if type(args_known) == "table" then modes.args_known = args_known end
	local dockey = modes.args_config.dockey -- or modes.args_config[1]
	local itemid = itemid or modes.args_config.itemid -- or modes.args_config.id or modes.args_config[2]
	modes.init_tools(frame, options_for_modes, mode_name) -- Raz categories and errors before full translations
	local mode_name = modes.get_arg_mode(mode_name, source_key)
	modes.change_itemid() -- "Q41568"
	modes.time2 = os.clock()
	datas.args_wikidata, t = datas.import_wikidata(modes.args_known, itemid)
	modes.options_for_modes = options_for_modes
	if type(modes.args_config.c) == "string" then
		modes.invoke_options = modes.args_config.c
	end
	if type(modes.args_config.mode) == "string" then
		mode_name = modes.args_source.mode
	end
	modes.invoke_options = modes.args_source.c or ""
	modes.mode_options = modes.options_from_mode(source_mode_name or mode_name)
	modes.args_import, t = modes.import_arguments()
	local mode_name = modes.get_arg_mode() -- (mode_name, source_key)
	viewer.activate_view = true -- Unmask all views after enough init.
--[ [
	versioning.bind_modules_report() -- Report all modules installations, before any other eventual process interfers.
--	versioning.tasks_changes_report_normal = versioning.versioning_tasks_report()
--	versioning.tasks_changes_report_short = "\n*" .. versioning.versioning_tasks_report("Tasks list and their states:", "short")
--] ]
	return res
end -- function versioning.init(frame, mode_name, args_known, options_for_modes, itemid)

function tools.versionsmanagement_test(t) -- Deprecated alias function
	versioning.deprecatedFunction("tools.versionsmanagement_test", "versioning.versions_management_test")
	return versioning.versions_management_test(t)
end
function versioning.versions_management_test(t) -- Test : List all loaded modules versioning_bind_modules_tests_title
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = (t or "") .. "\n* <b>versioning.versions_management_test</b> : The selector = <b> Box3, Group, Item1 </b> means: check these modules versions among those available."
	t = t .. "<br>" .. luaTable.formSubCounts("versioning.i18n")
	local loaded_tests = { -- loaded_tests
		-- Cases to look for the main module
		{ key = "Box", versionName = "Box", versionDate = "2015-12-02 02:02",
			request = "normal modules", comment = 'normal module for Box, with alias like : <code>local Box = require("Box")</code>',
			i18n = { en = { a = "a" } }, sought = "Box", known = "Box", loaded_list = ";Box;", },
		--
		{ key = "Box/I18N", versionName = "Box", versionDate = "2016-05-07 18:36",
			request = "normal with /I18N", comment = "I18N translations for Box",
			i18n = { en = { a = "a" } }, sought = "Box", known = "Box", loaded_list = ";Box;Box/I18N;", },
		--
		{ key = "Box3", versionName = "Box2", versionDate = "2016-05-07 11:11",
			request = "normal replace known alternate", comment = "alternate version module for Box",
			i18n = { en = { a = "a" } }, sought = "Box2", known = "Box;Box2", loaded_list = ";Box;Box/I18N;Box2;Box2/I18N;", },
		--
		{ key = "Box2/I18N", versionName = "Box2", versionDate = "2016-05-07 19:19",
			request = "missing actual module", comment = "I18N translations without basic module. Form an alert.",
			i18n = { en = { a = "a" } }, sought = "Box", known = "Box2;Box3", loaded_list = ";Box3;Box3/I18N;Box2;Box2/I18N;", },

		-- Cases to look for sub-modules
		--
		{ key = "Group", versionName = "Box", I18N = "Group/I18N",
			request = "normal sub-module", comment = "normal module for Group",
			i18n = { en = { a = "a" } }, sought = "Group", known = "Group", loaded_list = ";Group;Group/I18N;", },
		--
		{ key = "Group4", versionName = "Box", versionDate = "2015-12-11 11:11",
			request = "unknown sought version", comment = "Mask this to test missing sought version",
			i18n = { en = { a = "a" } }, sought = "Box;Group", known = "Box", loaded_list = ";Box;", },
		--
		{ key = "Group/I18N", versionName = "Box", versionDate = "2016-05-07 22:2",
			request = "sub-module not exists", comment = "I18N translations for the normal module Group",
			i18n = { en = { a = "a" } }, sought = "Box;Group", known = "Box * Group", loaded_list = ";Box;", },
		--
		-- Cases to look for /I18N sub-modules
		{ key = "Group2", versionName = "Box3", versionDate = "2015-12-22 22:2", versionDate = "2016-05-07 05:05",
			request = "normal sub-module with normal /18N", comment = "normal alternate sub-module Group for Group2",
			i18n = { en = { a = "a" } }, sought = "Box;Group2", known = "Box;Box2 * Group;Group2", loaded_list = ";Group;Group/I18N;Group2;Group2/I18N;Box;Box/I18N;Box2;Box2/I18N;", },
		--
		{ key = "Group2", versionName = "Box3", versionDate = "2015-12-22 22:2", versionDate = "2016-05-07 05:05",
			request = "unknown module for /18N sub-module", comment = "normal alternate sub-module Group for Group2",
			i18n = { en = { a = "a" } }, sought = "Box;Group2", known = "Box;Box2 * Group;Group2", loaded_list = ";Group;Group/I18N;Group2;Group2/I18N;Box;Box/I18N;Box2;Box2/I18N;", },
		--
		{ key = "Group2", versionName = "Box3", versionDate = "2015-12-22 22:2", versionDate = "2016-05-07 05:05",
			request = "missing module Box for /18N sub-module", comment = "normal alternate sub-module Group for Group2",
			i18n = { en = { a = "a" } }, sought = "Box;Group2", known = "Box;Box2 * Group;Group2", loaded_list = ";Group;Group/I18N;Group2;Group2/I18N;Box/I18N;Box2;Box2/I18N;", },
		--
		{ key = "Jocker", versionName = "Jocker", versionDate = "2015-12-21 11:33",
			request = "not central module, without i18n", comment = "Any module can have no i18n table.",
			sought = "Box", known = "Box", loaded_list = ";Box;", }, -- i18n = { en = { a = "a" } },
		--
		{ key = "Jocker", versionName = "Jocker", versionDate = "2015-12-21 11:33",
			request = "unknown and not sought actual module", comment = "Any module can be outside of the central system.",
			i18n = { en = { a = "a" } }, sought = "Jocker.2", known = "Jocker.2", loaded_list = ";Box;", },
	}
	local function bind_modules_test1(t, vers_test)
		local tt, vers = versioning.bind_modules(main_p, vers_test) -- Bind all modules and versions
		local vers_err = versioning.versions_management_report(vers) -- Detect versions management errors and warnings after bind_modules
		vers.sought = viewer.simpleList(vers.sought)
	--	local vers_sought = viewer.simpleList(vers.used)
		local vers_sought = ";" ..	string.gsub( (vers.sought or "-s-"), ";", ",<br>" ) .. ";" -- names in column
		local vers_used = viewer.simpleList(vers.used)
		vers_used = ";" ..string.gsub( (vers_used or "-u-"), ";", ",<br>" ) .. ";" -- names in column
		local vers_known = ";" ..	string.gsub( (vers.known or ""), ";", "; " ) .. ";" -- add spaces
		local vers_loaded_list = ";" .. (vers.versionName or "versionName") .. ";"
		.. (vers.sought or "sought") .. ";" .. (vers.known or "known") .. ";"
		vers_loaded_list = vers.loaded_list or vers_loaded_list or "empty loaded list"
		vers_loaded_list = viewer.simpleList(vers_loaded_list)
		vers_loaded_list = ";" .. string.gsub(vers_loaded_list, ";", "; " ) .. ";" -- add spaces
		local vers_results = "<b>" .. (vers.request or "vers.request") .. "</b>"
		.. "<br>sought: " .. (vers.sought or "") .. " in the main module: <b>" .. vers.versionName .. "</b>"
		.. "<br>" .. (vers_err or "")
		.. "<br>known: " .. (vers_known or "")
		.. "<br>loaded list: " .. vers_loaded_list
		.. "<br>comment: " .. (vers.comment or "")
		t = (t or "") .. viewer.Tr() .. viewer.Td(vers_sought) .. viewer.Td(vers_used) .. viewer.Td(vers_results)
		return t -- viewer.Td(vers.used) ..
	end
	t = t .. "\n* <b>loaded_vers</b> : Table of all loaded modules, or simulation."
	t = t .. "\n* Simulate existing versions: "
--	local loaded_versions = "Group;Group/I18N;Group2;Box;Box/I18N;Box2/I18N;Box3;Item;Item1/I18N;Jocker" -- example
	local loaded_versions = ";"
	local loaded_vers = {}
	local head = mw.text.split( viewer.form9user("versioning_bind_modules_test_headers") , ';') -- versioning_bind_modules_tests_title
	t = t .. viewer.Th() .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[4])
	t = bind_modules_test1( t, nil )
	t = t .. viewer.Tr() .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[5])
	local i = 0
	for i, tst in ipairs(loaded_tests) do -- build a simili loaded_vers, from loaded_tests, for only versions management
		local vers_test = mw.clone(tst) -- vers_test is an empty module, only for version management.
		vers_test.comment = tst.comment -- .. "<br>known : " .. tst.known
	--	vers_test.loaded_list = "Box,Box/I18N;Box2/I18N;Box3;Group;Group/I18N;Group2;Item;Item1/I18N;Jocker"
		local loaded_list = ";" .. vers_test.key .. ";" .. (vers_test.versionName or "versionName") .. ";"
		.. (vers_test.sought or "sought") .. ";" .. (vers_test.known or "known") .. ";"
		vers_test.loaded_list = tst.loaded_list or viewer.simpleList(vers_test.loaded_list)
	--	vers_test.i18n = { en = { txt = "txt" } } --
		if string.is_in("/I18N", vers_test.key) then vers_test.isI18N = true end
		loaded_vers[i] = vers_test
		t = bind_modules_test1( t, vers_test)
	--	t = t .. "<b>" .. vers_test.key .. "</b>, "
	end
	t = t .. viewer.Tr() .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[5])
	t = t .. viewer.Te()
	local t2 = ""
	local head = mw.text.split( viewer.form9user("versioning_list_loaded_modules_headers") , ';')
	t2 = t2 .. "\n* The table below simulates loaded modules for behind tests cases."
	t2 = t2 .. viewer.Th() .. viewer.Tc(head[1]) .. viewer.Tc(head[2]) .. viewer.Tc(head[3]) .. viewer.Tc(head[4]) .. viewer.Tc(head[5])
	for key, elem in pairs(loaded_tests) do
		t2 = t2 .. viewer.Tr() .. viewer.Td(elem.key) .. viewer.Td(elem.versionName) .. viewer.Td(elem.versionDate) .. viewer.Td( type(elem.i18n) ) .. viewer.Td(elem.comment)
	end
	t2 = t2 .. viewer.Te()
	t2 = t2 .. "\n* " .. viewer.ta("Number of versions simulated:", #loaded_tests)
	t = t .. t2
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function versioning.versions_management_test(t)

function versioning.antiCrash_wanted_error() -- Bug to test versioning.antiCrash()
	versioning.antiCrash_wanted_error_text = "antiCrash_bug_text_before_BUG"
	local x = wanted.error.runtime		-- Produces a wanted.runtime.error because these sub-tables do not exist.
--	local x = "wanted.error.runtime"	-- Produces a normal string.
	versioning.antiCrash_wanted_error_text = "antiCrash_bug_text_after_OK"
	return versioning.antiCrash_wanted_error_text
end --	function versioning.antiCrash_wanted_error() -- Bug to test versioning.antiCrash()

function versioning.antiLoop(funcname, n, func, ...) -- Limit the n-th reentrant call of any function.
	if true then return end
	-- Enhancement: search the shortest loop?
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if type(funcname) ~= "string" then funcname = "funcname" end
	if type(n) ~= "number" then n = 3 end
	if type(func) ~= "function" then n = 0 end
	versioning.antiLoop_memo = versioning.antiLoop_memo or {}
	versioning.antiLoop_memo[funcname .. "_" .. n] = {funcname, n, func, ...}
	versioning.antiLoop_list_t = versioning.antiLoop_list_t or ""
	versioning.antiLoop_list_t = versioning.antiLoop_list_t
		--	versioning_antiLoop_function_err = "versioning.antiLoop() error in the <b>%3</b> <b>%1()</b>, loop n = <b>%2</>, args = ",
		.. "\n* antiLoop: " .. viewer.form9user("versioning_antiLoop_function_err", tostring(funcname .. "_" .. n), n, viewer.value(func), ... )
		.. translate.formTestCase(tostring(funcname .. "_" .. n), n, viewer.value(func), ... )
--	versioning.antiLoop_list_t = versioning.antiLoop_list_t .. "\n* antiLoop: " .. translate.formTestCase(tostring(funcname .. "_" .. n), n, viewer.value(func), ... )
	events.add_cat("versioning_module_internal_error_cat")
	n = n - 1
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	if n < 1 then return end
	return versioning.antiLoop(funcname, n, func, ...) -- Loop only in limited n times.
end -- function versioning.antiLoop(funcname, n, func, ...)

function versioning.antiLoop_list(t) -- List versioning.antiLoop().
	local t = t or ""
	t = t .. "\n* List <b>versioning.antiLoop()</b> :"
	if (type(versioning.antiLoop_list_t) == "string") then
		t = t .. versioning.antiLoop_list_t
	end
	t = t .. "<br/>"
	return t
end -- function versioning.antiLoop_list(t)

-- {{#invoke:Phab.T154769.Test|antiCrash_frame}}
--	Simplified code to test
function versioning.antiCrash(options, content_or_func, ...) -- Form the display of a running error.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local res, t = "", ""
	local ac_opt = {}
	if type(options) == "table" then -- Easy long term compatibility against variable arguments number.
		ac_opt = options -- Always use the same table to transmit details across pcall or xpcall.
	end
	if (ac_opt.selector == "nocontent") then -- "nocontent" always masks the internal content without try it.
		ac_opt.success = true -- because sought
		ac_opt.content = ""
		return (ac_opt.content or "ac_opt.content"), ac_opt
	end
	-- - - - - - ----------------- To debug : start with only pcall() then step by step, add other code -------------------
	--
	function pcall_protect(content_or_func, ac_opt)
		if (type(content_or_func) == "function") then -- replaces result = func( ... ) without blocking cases to avoid crash.
			ac_opt.try_success, ac_opt.try_content = pcall( content_or_func(luaTable.toList(ac_opt.args) ), "pcall_protect(content_or_func, ac_opt)" ) -- Try test case.
		end
		ac_opt.try_content = ac_opt.try_content or "ac_opt.try_content"
		return ac_opt.try_content -- , ac_opt
	end
	if ac_opt.errortype == "wanted_error" then -- replaces result = func( ... ) without blocking cases to avoid crash.
		ac_opt.try_success, ac_opt.try_content = pcall( versioning.antiCrash_wanted_error(...), "pcall_protect(content_or_func, ac_opt)" ) -- Try test case.
	--	res = res .. versioning.antiCrash(ac_opt, versioning.antiCrash_wanted_error() )
	elseif (type(content_or_func) == "string") then -- replaces result = func( ... ) without blocking cases to avoid crash.
		ac_opt.try_content = content_or_func -- Minimal default
		ac_opt.try_success = true
	elseif (type(content_or_func) == "function") then -- replaces result = func( ... ) without blocking cases to avoid crash.
		ac_opt.try_success, ac_opt.try_content = pcall( content_or_func(...), "pcall_protect(content_or_func, ac_opt)" ) -- Try test case.
	--	debug.traceback( "beginning message", level ) -- Returns a string with a traceback of the call stack from level, else level=1.
	--	ac_opt.try_content = ", try_content = " .. ac_opt.try_content ..  ", traceback = " .. debug.traceback()
		local TB = debug.traceback()
		local tb = string.gsub(TB, "\n", "")
	--	tb = string.gsub(tb, "<br>", "br")
		tb = string.gsub(tb, "stack traceback:", "")
		tb = string.gsub(tb, "%sModule:", "<br>Module:")
		tb = string.gsub(tb, "%smw.lua:", "<br>mw.lua:") -- enhanced traceback
		t = t .. TB
		if ac_opt.try_success then -- replaces result = func( ... ) without blocking cases to avoid crash.
		--	t = t .. viewer.ta("traceback", debug.traceback( "antiCrash success:" ))
			t = viewer.ta("try_success", ac_opt.try_success)
			t = t .. viewer.ta("try_content", ac_opt.try_content)
			t = t .. "<b>antiCrash success:" .. tb .. "</b>"
		else -- replaces result = func( ... ) without blocking cases to avoid crash.
		--	t = t .. viewer.ta("traceback", debug.traceback( "antiCrash ERROR:" ))
			t = viewer.ta("try_success", ac_opt.try_success)
			t = t .. viewer.ta("try_content", ac_opt.try_content)
			t = t .. "<b>antiCrash ERROR:" .. tb .. "</b>"
			ac_opt.try_content = ac_opt.try_content .. t
		end
	elseif (type(content_or_func) == "table") then -- replaces result = func( ... ) without blocking cases to avoid crash.
		ac_opt.try_content = ac_opt.try_content .. "-tbl-" .. viewer.value(content_or_func)
	--	ac_opt.try_content = "content table OK"
		ac_opt.try_success = true
	else
		ac_opt.try_content = "content_or_func_else OK"
		ac_opt.try_success = true
	end
	ac_opt.content = ac_opt.try_content
	ac_opt.content = ac_opt.title .. (ac_opt.content or "antiCrash.ac_opt.content")
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return (ac_opt.content or "ac_opt.content"), ac_opt
end -- function versioning.antiCrash(ac_opt, content_or_func, ...) -- Form the display of a running error.

function versioning.antiCrash2(ac_opt, content_or_func, ...) -- Recursive.antiCrash test 2
	return versioning.antiCrash(ac_opt, content_or_func, ...)
end

function versioning.antiCrash3(ac_opt, content_or_func, ...) -- Recursive.antiCrash test 3
	return versioning.antiCrash2(ac_opt, content_or_func, ...)
end

-- {{#invoke:Phab.T154769.Test|antiCrash_frame}}
function versioning.antiCrash_tests() -- Main test
	local res = "\n* Tests of versioning.antiCrash(ac_opt, content_or_func, ...) : "
	res = res .. "\n* This test is suspended to less delay central modules."
	if 1 then return res end
-- {{#invoke:Phab.T154769.Test|antiCrash_frame}}
--function versioning.antiCrash_tests() -- Main test
--	local res = "\n* Central-s-fr.T154769.Tests of versioning.antiCrash(ac_opt, content_or_func, ...) : "
	local ac_opt = {}
	-- Parser errors without ModuleName:line:
	-- Erreur Lua : attempt to call a string value
	-- Erreur Lua : ...?
	--
	local tst = "\n* <b>Crash test</b> input: "
	ac_opt.title = tst .. "a simple string : "
	local content_or_func = "simple test of string"
	res = res .. versioning.antiCrash(ac_opt, content_or_func)
	--
	ac_opt.title = tst .. 'a function return a "string": '
	function content_or_func() return "string from function" end -- T154769 Erreur Lua : attempt to call a string value
	res = res .. versioning.antiCrash(ac_opt, content_or_func)
	--
	ac_opt.title = tst .. "a table = { x=1, y=2 } : "
	local content_or_func = { x=1, y=2 }
	res = res .. versioning.antiCrash(ac_opt, content_or_func)
	--
	local content_or_func = "x = wanted.runtime.error fails"
	ac_opt.title = tst .. "wanted.runtime.error : "
	ac_opt.errortype = "wanted_error"
	res = res .. versioning.antiCrash(ac_opt, "wanted_error call" )
	--
	ac_opt.title = tst .. "Recursive antiCrash3() : "
	local content_or_func = "antiCrash3 test" -- Recursive.antiCrash test 3
	res = res .. versioning.antiCrash3(ac_opt, versioning.antiCrash_wanted_error() )
	return res
end -- function versioning.antiCrash_tests(frame) -- Main test

function versioning.antiCrashFormError(funcname, title, errortext, ...) -- Form the running error: text, event and category.
	if type(funcname) ~= "string" then funcname = "funcname" end
	if type(title) ~= "string" then title = "title" end
	if type(errortext) ~= "string" then errortext = "errortext" end
	local t = ""
	local args = translate.formTestCase("", ...)
	t = t .. viewer.ta("function", viewer.errorColor(funcname) .. "( " .. args .. " ), ")
	t = t .. viewer.ta("title", viewer.errorColor(title))
	t = t .. viewer.ta("error", viewer.errorColor(errortext) )
	local cat = events.add_cat("versioning_module_internal_error_cat")
	if string.sub(cat, 1, 3) ~= "[[:" then cat = "[[:" .. string.sub(cat, 4) end -- To always display de link to the category, with or without catview.
	t = t .. "See: " .. cat -- link to internal_error_cat
	events.categories_lister("") -- activate categories at page level
	return t
end -- function versioning.antiCrashFormError(funcname, title, errortext, ...)

function versioning.antiCrash_details(ac_opt) -- Details about antiCrash
	local res =	 "\n* " .. viewer.ta("ac_opt.title", ac_opt.title)
	res = res .. "\n* " .. viewer.ta("ac_opt.title_error", ac_opt.title_error)
	res = res .. "\n* " .. viewer.ta("ac_opt.selector", ac_opt.selector)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_success", ac_opt.try_success)
	res = res .. "\n* " .. viewer.ta("ac_opt.success", ac_opt.success)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_error", ac_opt.try_error)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_error_string", ac_opt.try_error_string)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_error_string_color", ac_opt.try_error_string_color)
	res = res .. "\n* " .. viewer.ta("ac_opt.funcname", ac_opt.funcname)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_form_error", ac_opt.try_form_error)
	res = res .. "\n* " .. viewer.ta("ac_opt.resultKind", ac_opt.resultKind)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_error_color_ref", ac_opt.try_error_color_ref)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_error_ref", ac_opt.try_error_ref)
	res = res .. "\n* " .. viewer.ta("ac_opt.content_error", ac_opt.content_error)
	res = res .. "\n* " .. viewer.ta("ac_opt.try_content", ac_opt.try_content)
	res = res .. "\n* " .. viewer.ta("ac_opt.content", ac_opt.content)
--	res = res .. "\n* " .. viewer.ta("ac_opt.result", ac_opt.result)
	return res
end

--	function versioning.antiCrash(options, content_or_func, ...) -- Form the display of a running error.
--	function versioning.antiLoop("luaTable.SubCounts", 11, luaTable.SubCounts, tablebase, tabname, opt, recursiveLevel) -- Limit the n-th reentrant call of any function.


--[[
	Another versioning is successive states of a page or a module.
	Here versions management means:
	To manage versions in a main_module, a user defines a list of soughtversions included in a list of knownversions.
	Example of arg: soughtversions = "Author3 MathRoman2 Central1"
	Example of arg: knownversions = " Author,Author3 * MathRoman,MathRoman2 * Central,Central1 "
	The user changes sought and known at module level or at Args level.
	And the "versions" library supports the module and the user.
	The main_module can display warnings like:
	versioning_all_versions_tests		= "Versions warning: <b>%1</b>, normal: <b>%2</b>, listall: <b>%3</b>.", -- versionsmanagement
	versioning_select_unknown_module_err = "Internal error: The missing Module:<b>%1</b> is replaced by the normal Module:<b>%2</b>.", -- versionsmanagement
	versioning_I18N_module_no_base_err	= "The <b>%1</b> translations module has no basic version.", -- versionsmanagement
	versioning_no_versions_module_err	= "The module <b>%1</b> is not in the system versioning.", -- versionsmanagement
--]]

function tools.versionsmanagement_report(vers) -- Deprecated alias function
	versioning.deprecatedFunction("tools.versionsmanagement_report", "versioning.versions_management_report")
	return versioning.versions_management_report(vers)
end
function versioning.versions_management_report(vers) -- Detect versions management errors and warnings after bind_modules
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t, errs, cats = "", "", ""
	if type(vers) ~= "table" then vers = versioning.main_versions end
	if type(vers) ~= "table" then vers = versioning.main_versions end
--[[ Example in a module:
p.versions = { -- Modules dependencies. Dependencias del módulo. Dépendances du module.
	versionName = "Author3", versionNumber = "3.03", versionDate = "2016-04-25 10:31",
	sought = " Author3, Central01 ", -- sought submodules versions
	known = " Author3 * Central;Central01 ", -- known submodules versions
} --]]
	local sought_report, known_report, used_report, missing_report, unknown_report, replaced_report = "", "", "", "", "", ""
	local sought_report = viewer.simpleList(vers.sought)
	local known_report = viewer.simpleList(vers.known)
	local loaded_report = viewer.simpleList(vers.loaded_list)
	local used_report = viewer.simpleList(vers.used)
	local alloncesort = viewer.simpleList(sought_report .. ";" .. known_report .. ";" .. used_report) -- .. ";" .. loaded_report)
--	t = t .. "\n* <b>Versions management report</b>, second: " .. alloncesort
	local argm = modes.args_known
	local alloncetab = mw.text.split(alloncesort, ';') -- table of words
	for i, name in ipairs(alloncetab) do -- list all used names, only one each, in alphabetic order
		local name_I18N = string.find(name, "/I18N")
		if string.is_in_sp(name, sought_report, ";") and not string.is_in_sp(name, known_report, ";") and not name_I18N then
			missing_report = missing_report .. name .. ";" -- sought but not found
			errs = errs .. ";" .. events.add_err("versioning_missing_versions_err", name, vers.versionName ) .. ";"
			cats = cats .. events.add_cat("versioning_module_usage_error_cat")
		end
		if string.is_in_sp(name, used_report, ";") and not string.is_in_sp(name, sought_report, ";") and not name_I18N then
			replaced_report = replaced_report .. name .. ";" -- missing version replaced by normal
			errs = errs .. ";" .. events.add_err("versioning_replaced_versions_err", name, vers.versionName ) .. ";"
			cats = cats .. events.add_cat("versioning_module_usage_error_cat")
		end
		if string.is_in_sp(name, alloncesort, ";") and not string.is_in_sp(name, known_report, ";") and not name_I18N then
			unknown_report = unknown_report .. name .. ";" -- found but not known
			errs = errs .. ";" .. events.add_err("versioning_unknownversions_err", name, vers.versionName ) .. ";"
			cats = cats .. events.add_cat("versioning_module_usage_error_cat")
		end
	end
	missing_report = viewer.simpleList(missing_report)
	replaced_report = viewer.simpleList(replaced_report)
	unknown_report = viewer.simpleList(unknown_report)
	t = t .. errs -- "<br/>" ..
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function versioning.versions_management_report(vers)


-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	The Library:viewer forms some viewers for tables(in lines and columns), dropboxes, recursive luatables...
--	The name"luaTable" avoids ambiguities. To enhance rather than add it, we could write here : luaTable = table.
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------


-- viewer = {} -- already declared by Scribunto, else in _G space.
-- Translations for viewer library
viewer.i18n = {}
viewer.i18n.en = {
	-- Main texts, errors and categories of tools
	viewer_DropBox_label_Unwrap			= "Unwrap/Wrap",
	viewer_page_tests_title				= "Testing and information related to this page:",
	viewer_page_tests_h2_title			= "Tests of this page",
	viewer_module_tests_h2_title		= "Tests of this module",
	viewer_internal_tests_h2_title		= "Internal tests",
	viewer_dropdown_missing_title		= "Missing title for this dropbox",
	-- Titles of tests
	viewer_module_tests_title			= "Testing and information related to this module:",
	viewer_internal_tests_title			= "Internal regression tests:",
	viewer_tableView_tests_headers		= "Case;Value;Result",
	viewer_tableView_tests_title		= "viewer.tableView() Test: Form a table with lines and columns.",
	viewer_tableView_tests_err			= "Internal error : viewer.tableView() abnormal fail.",
	viewer_format_time_tests_title		= "viewer.formatDate() Tests of coding and conversion of dates",
	viewer_parts_of_date_tests_title	= "viewer.date_to_part() Tests of date to day, month, year or era",
	viewer_simpleList_tests_title		= "viewer.simpleList() Test: Sort a list of words without repetition",
} -- viewer.i18n.en
viewer.i18n.es = {
	-- Textos principales, errores y categorías de instrumentos
	viewer_DropBox_label_Unwrap			= "Desenvolver/Envolver",
	viewer_page_tests_title				= "Pruebas y información relacionada con esta página:",
	viewer_page_tests_h2_title			= "Pruebas de esta página",
	viewer_module_tests_h2_title		= "Pruebas de esto página",
	viewer_internal_tests_h2_title		= "Pruebas internas",
	viewer_dropdown_missing_title		= "Falta el título para este dropbox",
	-- Titres des pruebas
	viewer_page_tests_title				= "Pruebas y información relacionada con esta página:",
	viewer_module_tests_title			= "Pruebas y información relacionada con esto módulo:",
	viewer_internal_tests_title			= "Pruebas de no regresión internas:",
	viewer_tableView_tests_headers		= "Caso; Valor; Resultado",
	viewer_tableView_tests_title		= "viewer.tableView() Prueba: Formar una tabla con líneas y columnas.",
	viewer_tableView_tests_err			= "Error interno: viewer.tableView() error anormal.",
	viewer_format_time_tests_title		= "viewer.formatDate() Pruebas de codificación y conversión fechas",
	viewer_parts_of_date_tests_title	= "viewer.date_to_part() Pruebas de fecha a día, mes, año o época",
	viewer_simpleList_tests_title		= "viewer.simpleList() Prueba: Ordenar una lista de palabras sin repetición",
} -- viewer.i18n.es
viewer.i18n.fr = {
	-- Principaux textes, erreurs et catégories des outils
	viewer_DropBox_label_Unwrap			= "Dérouler/Enrouler",
	viewer_page_tests_title				= "Tests et informations liées à cette page :",
	viewer_page_tests_h2_title			= "Tests de cette page",
	viewer_module_tests_h2_title		= "Tests de ce module",
	viewer_internal_tests_h2_title		= "Tests internes",
	viewer_dropdown_missing_title		= "Titre manquant pour cette boite déroulante",
	-- Titres des tests
	viewer_page_tests_title				= "Tests et informations liées à cette page :",
	viewer_module_tests_title			= "Tests et informations liées à ce module :",
	viewer_internal_tests_title			= "Tests internes de non régression :",
	viewer_tableView_tests_headers		= "Case;Value;Result",
	viewer_tableView_tests_title		= "viewer.tableView() Test: former un tableau avec lignes et colonnes.",
	viewer_tableView_tests_err			= "Erreur interne: viewer.tableView() échec anormal.",
	viewer_format_time_tests_title		= "viewer.formatDate() Tests de codage et conversions de dates",
	viewer_parts_of_date_tests_title	= "viewer.date_to_part() Tests de date vers jour, mois, année ou ère",
	viewer_simpleList_tests_title		= "viewer.simpleList() Test: Trier une liste de mots sans répétition",
} -- viewer.i18n.fr

--	function versioning.antiLoop(funcname, n, func, ...) -- Limit the n-th reentrant call of any function.
--	Try versioning.antiLoop() in functions recently changes, in case of errors:
--		"Erreur Lua : stack overflow" or "Le temps alloué pour l’exécution des scripts a expiré." or "The time allowed for script execution has expired".
--	$wgParserCacheExpireTime - Expiration time (in seconds) of cached parser information
--	function versioning.antiLoop_list(t) -- List versioning.antiLoop().

function viewer.value(value) -- Form a string to discribe a value like: true, 123.456, "abcd", func(), table{}
	local typ, view = type(value), "-"
	if typ == "boolean" then
		if value == true then view = "true" else view = "false" end
	elseif typ == "function" then view = "func()"
	elseif typ == "number" then view = tostring(value)
	elseif typ == "string" then view = '"' .. value .. '"'
	elseif typ == "table" then view = "table{}"
	elseif typ == "nil" then view = "nil"
	end
	return view
end -- function viewer.value(value)

function viewer.ta(txt, val, sep) -- Form an argument and its value in a documentation. The text is "nil" if the value is nil.
	if val == nil then val = "nil" end
	if sep == nil then sep = "=" end
	return " , " .. tostring(txt) .. " " .. tostring(sep) .. " <b>" .. tostring(val) .. "</b> "
end

function viewer.tam(txt, val, sep) -- Form an argument and its value, or mask it if nil.
	if not val then return "" end
	return viewer.ta(txt, val, sep)
end

-- Build a table, with headers for columns. Example:
-- ! style="text-align:left;"| Item
-- {| class="wikitable" style="text-align: center; color: green;"
-- {| border="1" style="border-crash:crash"
-- {| class="wikitable sortable" border="1" mw-collapsible mw-crashd"
-- |} end of table
function viewer.Th(t) return '\n{| class="' .. tostring( t or 'wikitable alternative center' ) .. '" | ' end -- Table columns headers -- or " "
-- A triable table start with : {| class="wikitable sortable"
-- A column become fix and not triable with : ||class="unsortable"|
-- A row become fix and not triable with : |- class="sortbottom"
function viewer.Tc(t) return '\n! scope="col" | ' .. tostring( t or " " ) end -- Table repeated or different columns headers
function viewer.Tr(t) return "\n|- " .. tostring( t or " " ) end -- Table -- row
function viewer.Td(t) return "\n| " .. tostring( t or " ") end -- Table data -- or " "
function viewer.Te(t) return "\n|}" end -- Table end

function viewer.wiki_modules_users(t) -- Short counts of modules and users in this wiki.
	t = t or "<b>Modules and users in this wiki</b>: "
	t = t .. viewer.ta("Modules", tostring(mw.site.stats.pagesInNamespace( 828 ) ) )
	t = t .. viewer.ta("activeUsers", tostring(mw.site.stats.activeUsers ) )
	t = t .. viewer.ta("Administrators(sysop)", tostring(mw.site.stats.usersInGroup( "sysop" ) ) )
	t = t .. viewer.ta("bots(bot)", tostring(mw.site.stats.usersInGroup( "bot" ) ) )
	t = t .. viewer.ta("patrollers(patroller)", tostring(mw.site.stats.usersInGroup( "patroller" ) ) )
	t = t .. viewer.ta("bureaucrats(bureaucrat)", tostring(mw.site.stats.usersInGroup( "bureaucrat" ) ) )
--	t = t .. viewer.ta("checkuser(checkuser)", tostring(mw.site.stats.usersInGroup( "checkuser" ) ) )
--	t = t .. viewer.ta("autoconfirmed(autoconfirmed)", tostring(mw.site.stats.usersInGroup( "autoconfirmed" ) ) )
--	t = t .. viewer.ta("Autopatrol users(autoreview)", tostring(mw.site.stats.usersInGroup( "autoreview" ) ) )
--	t = t .. viewer.ta("Account creators(accountcreator)", tostring(mw.site.stats.usersInGroup( "accountcreator" ) ) )
	t = t .. " mw [[:mw:Special:ListGroupRights]] " -- https://www.mediawiki.org/wiki/Special:ListGroupRights
	t = t .. "<br/>\n* <b>Revision time and user language</b>: "
	t = t .. viewer.ta("REVISIONTIMESTAMP", modes.frame:preprocess( "{{REVISIONTIMESTAMP}}" ) )
	t = t .. viewer.ta("CONTENTLANGUAGE", modes.frame:preprocess( "{{CONTENTLANGUAGE}}" ) )
	t = t .. viewer.ta("REVISIONUSER", modes.frame:preprocess( "{{REVISIONUSER}}" ) )
	t = t .. viewer.ta("USERLANG", modes.frame:preprocess( "{{USERLANG}}" ) )
	t = t .. viewer.ta("UILANGCODE", modes.frame:preprocess( "{{UILANGCODE}}" ) )
	t = t .. viewer.ta("USERIFCODE", modes.frame:preprocess( "{{USERIFCODE}}" ) )
	t = t .. viewer.ta("USERLANGUAGE", modes.frame:preprocess( "{{USERLANGUAGE}}" ) )
	return t
end -- function viewer.wiki_modules_users(t)

-- Some usual fonctional styles for this module and those using it.
-- Quelques styles fonctionnels banals dans ce module et ses modules appelants.
function viewer.errorColor(t) -- Usual fonctional style for errors in red.
	return '<span style="color:red;" >' .. tostring(t) .. '</span>'
end

function viewer.warningColor(t) -- Usual fonctional style for warnings in blue.
	return '<span style="color:blue;" >' .. tostring(t) .. '</span>'
end

function viewer.wikidataColor(t) -- Usual fonctional style for wikidata in green.
	return '<span style="color:green;" >' .. tostring(t) .. '</span>'
end

function viewer.invoke_color(t) -- Usual fonctional style for invoke args in brown.
	return '<span style="color:#804020;" >' .. tostring(t) .. '</span>'
end

function viewer.discreetColor(t) -- Usual fonctional style for discreet in light grey.
	return '<span style="color:#B0B0B0;" >' .. tostring(t) .. '</span>'
end

function viewer.otherColor(t) -- Usual fonctional style for other standard in black.
	return '<span style="color:black;" >' .. tostring(t) .. '</span>'
end

function viewer.smallCapsStyle(t) -- Display a text in small-caps style.
	return '<span style="font-variant: small-caps">' .. t .. '</span>'
end

function viewer.classDayTimeStyle(title, DATE) -- Display a date in class="(b|d)day" style.
	-- Todo: Tpt 20121005 19:48 : <span class="(b|d)day" title="DATE AU FORMAT YYYY">AFFICHAGE DE LA DATE</span> Tpt (d) 5 septembre 2012 à 19:48 (UTC)
	if type(title) ~= "string" then title = "title" end
	if type(DATE) ~= "string" then DATE = "DATE" end
	return '<span class="(b|d)day" title="' .. title .. '">' .. DATE .. '</span>'
end

-- Extract a part of date following a pre-defined format which starts with separator
function viewer.date_split(date, format)
	-- local dd, mmmm, yyyy, era = viewer.date_split(a.birthdate, " dd mmmm yyyy")
	local dd, mmmm, yyyy, era
	local format_split = mw.text.split(format, "%s")
	local split = mw.text.split(date, "%s")
	for i, date_part in ipairs(split) do
		if format_split[i] == "dd" then dd = tonumber(date_part) end
		if format_split[i] == "mmmm" then mmmm = date_part end
		if format_split[i] == "yyyy" then yyyy = tonumber(date_part) end
		if format_split[i] == "era" then era = date_part end
	end
	return dd, mmmm, yyyy, era
end -- function viewer.date_split(date, format)

-- Extract a part of date following a pre-defined format which starts with separator
function viewer.date_to_part(date, part)
	-- local t, err = viewer.date_to_part(a.birthyear, " dd mmmm yyyy", "yyyy")
	part = part or "yyyy"
	local dd, mmmm, yyyy, era
	local found = false
	--	local err = "modes_date_to_part_not_found_err"
	if not found then
		dd, mmmm, yyyy, era = viewer.date_split(date, "dd mmmm yyyy")
		if dd and mmmm and yyyy and not era then
			found = true
		end
	end
	if not found then
		dd, mmmm, yyyy, era = viewer.date_split(date, "mmmm dd yyyy")
		if dd and mmmm and yyyy and not era then
			found = true
		end
	end
	if not found then
		dd, mmmm, yyyy, era = viewer.date_split(date, "yyyy era")
		if not dd and not mmmm and yyyy then
			if era == "BCE" then yyyy = - yyyy end
			found = true
		end
	end
	if not found then
		dd, mmmm, yyyy, era = viewer.date_split(date, "mmmm yyyy")
		if not dd and mmmm and yyyy and not era then
			--	modes_date_months_names				= "January, February, March, April, May, June, July, August, September, October, November, December",
			--	modes_date_months_names				= "Enero, Febrero, Marzo, Abril, Mayo, Junio​​, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre",
			--	modes_date_months_names				= "Janvier, Février, Mars, Avril, Mai, Juin, Juillet, Août, Septembre, Octobre, Novembre, Décembre",
			found = true
		end
	end
	if not found then
		dd, mmmm, yyyy, era = viewer.date_split(date, "yyyy")
		if not dd and not mmmm and yyyy and not era then
			found = true
		end
	end
	-- + cccc = roman number ?
	if found then
		if part == "yyyy" then return yyyy or "" end
		if part == "mmmm" then return mmmm or "" end
		if part == "dd" then return dd or "" end
		if part == "era" then return era or "" end
	end
	return "" -- tostring(dd)..tostring(mmmm)..tostring(yyyy)..tostring(era)
--	A faire : Titus Livius (Q2039), format de dates : P569 = date of birth = 59 BCE, P570 = date of death = 17
--	Socrate (470-399 av. J.-C.).
end -- function viewer.date_to_part(date, part)

function mw.text.sortCommaList(list, sep1, sep2, sep3) -- Sort without repeat a list with separators like "Central01;versioning 2.4;tools;two words"
	versioning.deprecatedFunction("mw.text.sortCommaList", "viewer.simpleList")
	return viewer.simpleList(list, sep1, sep2, sep3, sep4)
end
function viewer.simpleList(list, sep1, sep2, sep3, sep4) -- Remove repeats in a string list with separators. Can also sort.
	-- sep1 is used in final list. sep1 to sep4 can be "sort" or "trim".
	-- wanted.
	local sort, trim = false, false
	if sep1 == "sort" then sep1 = "" ; sort = true end
	if sep2 == "sort" then sep2 = "" ; sort = true end
	if sep3 == "sort" then sep3 = "" ; sort = true end
	if sep4 == "sort" then sep4 = "" ; sort = true end
	if sep1 == "trim" then sep1 = "" ; trim = true end
	if sep2 == "trim" then sep2 = "" ; trim = true end
	if sep3 == "trim" then sep3 = "" ; trim = true end
	if sep4 == "trim" then sep4 = "" ; trim = true end
	if type(list) ~= "string" then list = "" end
	if type(sep1) ~= "string" then sep1 = "" end -- Default separators value
	if type(sep2) ~= "string" then sep2 = "" end
	if type(sep3) ~= "string" then sep3 = "" end
	if type(sep4) ~= "string" then sep4 = "" end
--	Patterns: Dot (.) always matches all characters, including newlines.
--	Patterns: Quantifiers (*, +, ?, and -) may only be applied to individual characters.
	if string.is_in(sep1, ".*+?-/") then sep1 = "%" .. sep1 end
	if string.is_in(sep2, ".*+?-/") then sep2 = "%" .. sep2 end
	if string.is_in(sep3, ".*+?-/") then sep3 = "%" .. sep3 end
	if string.is_in(sep4, ".*+?-/") then sep4 = "%" .. sep4 end
	local listtab = mw.text.split(list or "", sep1, true) -- names between commas
	local listtabOut = {}
	local listtabSingle = {}
	local list2 = ""
	local t = " * t = "
	for i, name in ipairs(listtab) do -- list all names, only one each, in alphabetic order
	--	t = t .. "-" .. i
		if trim then name = mw.text.trim(name) end -- do not keep spaces inside each name
		if not listtabSingle[name] then
			listtabSingle[name] = name
			list2 = list2 .. name .. sep1
		end
	--	if (name ~= "") and not string.is_in_sp(name, list2, sep1) then list2 = list2 .. name .. sep1 end
	end
	if sort then
		table.sort(listtabSingle, function (a, b) return (a < b) end ) -- sort in alphabetic order
	end
	for i, name in ipairs(listtabSingle) do -- for all names
	--	if trim then name = mw.text.trim(name) end -- do not keep spaces inside each name
	--	table.insert(listtabOut, name)
	--	listtabSingle[name] = name
--		list2 = list2 .. name .. sep1
	end
	t = t .. " * list2 = " .. list2 .. " * "
	local output = string.sub( list2, 1, (-string.len(sep1)-1) ) -- without last sep1 or ;
	return output .. t
end -- function viewer.simpleList(list, sep1, sep2, sep3)

function viewer.simpleList_test(t) -- Test of : Sort without repeat a list with separators like "Central01;versioning 2.4;tools;two words"
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local t = t or ""
	t = t .. "\n* Test <b>viewer.simpleList(list, sep1, sep2, sep3)</b>: "
	t = t .. '\n* Remove repeats in a string list with separators. Can also sort.'
	local function simpleList_test_1(t, input, sep1, sep2, sep3, sep4)
		local output = tostring( viewer.simpleList(input, sep1, sep2, sep3, sep4) )
		input = viewer.value(input) ; viewer.value(input) ;
		t = t .. viewer.Tr() .. viewer.Td(viewer.value(input)) .. viewer.Td(sep1) .. viewer.Td(sep2)
		t = t .. viewer.Td(sep3) .. viewer.Td(sep4) .. viewer.Td(output)
	--	.. viewer.Td(viewer.value(input)) .. viewer.Td(viewer.value(input)) .. viewer.Td(sep1) .. viewer.Td(sep2) .. viewer.Td(sep3) .. viewer.Td(sep4) .. viewer.Td(output)
		return t
	end
	t = t .. viewer.Th() .. viewer.Tc("input") .. viewer.Tc("sep1") .. viewer.Tc("sep2") .. viewer.Tc("sep3") .. viewer.Tc("sep4") .. viewer.Tc("output")
	t = simpleList_test_1(t, "abc;def;abc", ";", "-", "|")
	t = simpleList_test_1(t, "xyz;def;abc", ";", "sort")
	t = simpleList_test_1(t, "abc-def=abc", "-", "=")
	t = simpleList_test_1(t, "abc - def = abc", "-", "=", "sort")
	t = simpleList_test_1(t, "abc - def = abc", "-", "=", "sort", "trim")
	t = simpleList_test_1(t, ";;", ";", ";", ",")
	t = simpleList_test_1(t, "abc/def;abc", ";", "/")
	t = simpleList_test_1(t, "abc;def/abc/def;abc;def;abc", ";", "/", "-")
	t = simpleList_test_1(t, "abc;def;abc", ";", ";", "")
	t = t .. viewer.Te()
	--
	local t = t or "\n* Test <b>viewer.simpleList(list, sep1, sep2, sep3, sep4)</b>:<br> "
	t = t .. '\n* Remove repeats in a string list with separators. Can also sort.'
	local s = "x"
	s = "abc;def;abc" ; t = t .. "\n* " .. s .. " --> ; --> " .. viewer.simpleList(s, ";")
	s = "abc;def,abc;" ; t = t .. "\n* " .. s .. " --> , --> " .. viewer.simpleList(s, ",")
	s = ",ABC - XYZ , abc" ; t = t .. "\n* " .. s .. " --> ; --> " .. viewer.simpleList(s, ";")
	s = ";abc;def;def;abc;" ; t = t .. "\n* " .. s .. " --> ; --> " .. viewer.simpleList(s, ";")
	s = ";abc;def;def;:123;abc;" ; t = t .. "\n* " .. s .. " --> ; --> " .. viewer.simpleList(s, ";")
	s = ";abc;def;def;:123;abc;" ; t = t .. "\n* " .. s .. " --> ; : --> " .. viewer.simpleList(s, ";", ":")
	s = ";abc/def;def;/123///xyz" ; t = t .. "\n* " .. s .. " --> ; / --> " .. viewer.simpleList(s, ";", "/")
	s = ";abc/def;def;/123///xyz" ; t = t .. "\n* " .. s .. " --> ; : / --> " .. viewer.simpleList(s, ";", ":", "/")
	s = ";abc/def;def;/123///xyz" ; t = t .. "\n* " .. s .. " --> ; / : --> " .. viewer.simpleList(s, ";", "/", ":")
	t = t .. "\n* Special separators chars or cases:"
	s = ";Other;Middle;Begin" ; t = t .. "\n* " .. s .. " --> sort --> " .. viewer.simpleList(s, "sort")
	s = ";Other;Middle;Begin" ; t = t .. "\n* " .. s .. " --> ; sort --> " .. viewer.simpleList(s, ";", "sort")
	s = ";def;abc;def;abc" ; t = t .. "\n* " .. s .. " --> ; sort --> " .. viewer.simpleList(s, ";", "sort")
	s = ";abc,def;def,abc" ; t = t .. "\n* " .. s .. " --> sort ; , --> " .. viewer.simpleList(s, "sort", ";", ",")
	s = ";abc.def;def.abc" ; t = t .. "\n* " .. s .. " --> ; . --> " .. viewer.simpleList(s, ";", ".")
	s = ";abc.def;def.abc" ; t = t .. "\n* " .. s .. " --> ; . --> " .. viewer.simpleList(s, ";", ".")
	s = ";abc,def;def,abc" ; t = t .. "\n* " .. s .. " --> ; , --> " .. viewer.simpleList(s, ";", ",")
	s = ";abc-def;def-abc" ; t = t .. "\n* " .. s .. " --> ; - --> " .. viewer.simpleList(s, ";", "-")
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- viewer.simpleList_test(t)

-- mw.language:formatDate lang:formatDate( format, timestamp, local )
-- Formats a date according to the given format string. timestamp else current time. The value for local must be a boolean or nil; if true, the time is formatted in the wiki's local time rather else in UTC.
-- The format string and supported values for timestamp are identical to those for the #time parser function from Extension:ParserFunctions. Note that backslashes may need to be doubled in the Lua string where they wouldn't in wikitext:
-- {{#time:d F Y|1988-02-28|nl}} → 28 februari 1988
-- {{#time: U | now }} → 1424087375
-- {{#time: r|@1424087374}} → Mon, 16 Feb 2015 11:49:34 +0000

function viewer.day_to_UTC(jj, mm, aaaa)
	local t = "@" .. tostring( jj*86400 + mm*30*86400 + (aaaa-1970)*31556926 )
	return t
end

function viewer.day_to_stamp(jj, mm, aaaa)
	jj = tonumber(jj)
	if not jj then jj = "2" else jj = string.sub("0000" .. tostring(jj), -2, -1 ) end
	mm = tonumber(mm)
	if not mm then mm = "2" else mm = string.sub("0000" .. tostring(mm), -2, -1 ) end
	aaaa = tonumber(aaaa)
	if not aaaa then aaaa = "2000" else aaaa = string.sub("0000" .. tostring(aaaa), -4, -1 ) end
	return aaaa .. "-" .. mm .. "-" .. jj
end

function viewer.formatDate(format, timestamp, lang, loc)
	if not viewer.language_obj then viewer.language_obj = mw.language.new( "fr" ) end
	if lang then viewer.language_obj = mw.language.new( lang ) end
	if viewer.language_obj then viewer.language_code = viewer.language_obj:getCode() end
	if type(loc) ~= "boolean" then loc = true end
	return tostring(viewer.language_obj:formatDate(format, timestamp, loc))
end -- function viewer.formatDate(format, timestamp, loc)

function viewer.time_format_test_1(t, test, format, timestamp, lang, loc)
	if lang then viewer.language_obj = mw.language.new( lang ) end
	t = t .. viewer.Tr() .. viewer.Td(test or "-") .. viewer.Td(format or "-") .. viewer.Td(timestamp or "-") .. viewer.Td(tostring(lang)) .. viewer.Td( viewer.formatDate(format, timestamp, loc) )
	return t
end

function viewer.format_time_tests(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if not viewer.language_obj then viewer.language_obj = mw.language.new( "fr" ) end
	if viewer.language_obj then viewer.language_code = viewer.language_obj:getCode() end
	local t = t or "\n<b>time_format_test</b> :"
	t = t .. "\nCoding and conversion of dates by the parser function language_obj:formatDate(format, timestamp, loc)."
	t = t .. viewer.ta("viewer.language_code", viewer.language_code)
	t = t .. "<br>Verify some date formats: Vérifier quelques formats de dates :"
	t = t .. viewer.Th() .. viewer.Tc("Test dates from seconds") .. viewer.Tc("format") .. viewer.Tc("timestamp") .. viewer.Tc("lang") .. viewer.Tc("Formated date")
	t = viewer.time_format_test_1(t, " now", "U", "now")
	t = viewer.time_format_test_1(t, " now", "F j Y", "now")
	t = viewer.time_format_test_1(t, " seconds to full UTC ", "r", "@1421117374")
	t = viewer.time_format_test_1(t, " seconds to full UTC ", "r", "@1424087374")
	t = viewer.time_format_test_1(t, " 2015-02-16T11:49:34+00:00 ", "c", "@1424107003")
	t = viewer.time_format_test_1(t, " 14/7/1789 to english ", "F j Y", viewer.day_to_UTC(14, 7, 1789), "en")
	t = viewer.time_format_test_1(t, " seconds to french format ", "j F Y", "@1499997003")
	t = viewer.time_format_test_1(t, " french 14/7/1789 ", "j F Y", viewer.day_to_UTC(14, 7, 1789), "fr")
	t = viewer.time_format_test_1(t, " french 14/7/234 ", "j F Y", viewer.day_to_UTC(14, 7, 234), "fr")
	t = t .. viewer.Tr() .. viewer.Tc("Test from UTC") .. viewer.Tc("format") .. viewer.Tc("timestamp") .. viewer.Tc("lang") .. viewer.Tc("Formated date")
	t = viewer.time_format_test_1(t, " french date ", "j F Y", "1915-02-16T17:16:43+00:00", "fr")
	t = viewer.time_format_test_1(t, " french date ", "j F Y", "1515-02-22T17:16:43+00:00", "fr")
	t = t .. viewer.Tr() .. viewer.Tc("Test dates only") .. viewer.Tc("format") .. viewer.Tc("timestamp") .. viewer.Tc("lang") .. viewer.Tc("Formated date")
	t = viewer.time_format_test_1(t, " french 2015-02-16 ", "j F Y", "2015-02-16", "fr")
	t = viewer.time_format_test_1(t, " french 32-12-25 ", "j F Y", "32-12-25", "fr")
	t = viewer.time_format_test_1(t, " french 0032-12-25 ", "j F Y", "0032-12-25", "fr")
	t = viewer.time_format_test_1(t, " french 3/4/5 ", "j F Y", viewer.day_to_stamp(3, 4, 5), "fr")
	t = viewer.time_format_test_1(t, " french 24/11/31 ", "j F Y", viewer.day_to_stamp(24, 11, 31), "fr")
	t = viewer.time_format_test_1(t, " french 24/11/32 ", "j F Y", viewer.day_to_stamp(24, 11, 32) )
	t = viewer.time_format_test_1(t, " french 24/12/32 ", "j F Y", viewer.day_to_stamp(24, 12, 32) )
	t = viewer.time_format_test_1(t, " french 25/12/32 ", "j F Y", viewer.day_to_stamp(25, 12, 32) )
	t = viewer.time_format_test_1(t, " french 25/12/32 roman year ", "j F xrY", viewer.day_to_stamp(25, 12, 32) )
	t = t .. viewer.Tr() .. viewer.Tc("Test partial formats & missing datas") .. viewer.Tc("format") .. viewer.Tc("timestamp") .. viewer.Tc("lang") .. viewer.Tc("Formated date")
	t = viewer.time_format_test_1(t, " partial format 3/4/5 ", "j F Y", viewer.day_to_stamp(3, 4, 5) )
	t = viewer.time_format_test_1(t, " partial format 3/4/5 ", "Y", viewer.day_to_stamp(3, 4, 5) )
	t = viewer.time_format_test_1(t, " missing day -/4/5 ", "F Y", viewer.day_to_stamp(nil, 4, 5) )
	t = viewer.time_format_test_1(t, " missing month 3/-/5 ", "j Y", viewer.day_to_stamp(3, nil, 5) )
	t = viewer.time_format_test_1(t, " missing year 3/4/- ", "j F", viewer.day_to_stamp(3, 4, nil) )
	t = viewer.time_format_test_1(t, " missing day -/4/5 ", "F Y", viewer.day_to_stamp(nil, 4, 5) )
	t = viewer.time_format_test_1(t, " missing month 3/-/5 ", "j Y", viewer.day_to_stamp(3, nil, 5) )
	t = viewer.time_format_test_1(t, " missing year 3/4/- ", "j F", viewer.day_to_stamp(3, 4, nil) )
	t = t .. viewer.Te()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function viewer.format_time_tests(t)

function viewer.parts_of_date_tests(t)
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	local function date_to_part_test_1(t, nom, date, part)
		t = t .. viewer.Tr() .. viewer.Td(nom) .. viewer.Td(date) .. viewer.Td(part) .. viewer.Td(tostring(viewer.date_to_part(date, part)))
		return t
	end
	local t = t or "\n* <b>date_to_part</b> :"
	t = t .. "\n* Verify each value of part of date:"
	t = t .. viewer.Th() .. viewer.Tc("Example") .. viewer.Tc("date") .. viewer.Tc("part") .. viewer.Tc("value")
	t = date_to_part_test_1(t, "Socrate birth", "470 BCE", "era")
	t = date_to_part_test_1(t, "Tite-Live birth", "59 BCE", "yyyy")
	t = date_to_part_test_1(t, "Tite-Live death", "17", "yyyy")
	t = date_to_part_test_1(t, "empty", "", "yyyy")
	t = date_to_part_test_1(t, "Revolution", "14 July 1789", "mmmm")
	t = date_to_part_test_1(t, "Walk on Moon", "20 July 1969", " ")
	t = date_to_part_test_1(t, "English date", "July 20 1969", "yyyy")
	t = date_to_part_test_1(t, "English date", "July 20 1969", "mmmm")
	t = date_to_part_test_1(t, "Nelson Mandela birth", "July 1918", "yyyy")
	t = date_to_part_test_1(t, "Nelson Mandela death", "5 Decembre 2013", "dd")
	t = t .. viewer.Te()
	-- see https://fr.wikipedia.org/wiki/Discussion:ISO_8601#Av._J.C._:_contradiction_avec_l.27article_anglais
	-- see https://fr.wikipedia.org/w/index.php?title=ISO_8601&diff=next&oldid=12449725
	-- see ISO 8601 time format like : 1977-04-22T01:00:00-05:00 (5 heures de décalage) = 1977-04-22T06:00:00Z local time
	-- os.date( format, time ) get datetime
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t
end -- function viewer.parts_of_date_tests(t)

function viewer.tableView(tabView) -- Form a table with lines and columns. Using a tableView object.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if type(tabView) ~= "table" then tabView = {} end -- tabView must be a table
	local tabView = mw.clone(tabView)
	tabView.t = (tabView.t or "") .. viewer.ta("tableView: ", "tabView.t:")
	local err = nil -- viewer.form9user("viewer_tableView_tests_title")
	local t = ""
	function tabView.tableView_form_one_case(case) -- Generally to define: Convert a case from testGroup to rowGroup.
		return case
	end
	tabView = viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
	tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
--	t = t .. tabView.t
	t = t .. tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return t, err, tabView
end -- function viewer.tableView(tabView)

function viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.
--	no memo = events.save_configs() -- Save global configuration before eventual changes.
	tabView.headers = tabView.headers or "viewer_tableView_tests_headers"
	tabView.headers = viewer.form9user(tabView.headers)
	tabView.tableStyle = tabView.tableStyle or " "
	tabView.testGroup = tabView.testGroup or {
		{ "abc", 123, "A1, B2, C3" },
		{ "equation", "y =", "a * x2 + b * x + c" },
		{ "Matrix:", { "2.1.0", "v4-6, ISO_8601, FC14 h6" } },
		{ "pi = ", 3.14, "circle / diameter" },
		{ "quadrature", "function end", "convert a square to a circle" },
	}
	tabView.t = (tabView.t or "") .. viewer.ta("tableView.tabView.testGroup: ", #tabView.testGroup) -- track to debug
	tabView.rowGroup = tabView.rowGroup or {}
	function tableView_form_one_case(case) -- Default: Convert a case from testGroup to rowGroup.
		local tocase = case -- {}
	--	for i, val in pairs(case) do table.insert( tocase, val) end
		tabView.t = tabView.t .. viewer.ta("tableView_form_one_case#tocase: ", #tocase) -- track to debug
		return tocase
	end
	tabView.tableView_form_one_case = tabView.tableView_form_one_case or tableView_form_one_case
	function tabView.add_Row(case) -- Default: Add a row to rowGroup.
		tabView.t = tabView.t .. viewer.ta("add_Row-#case: ", #case) -- track to debug
		table.insert( tabView.rowGroup, case)
	end -- In other tableView objects this function could, from each case, select or duplicate some rows.
	function tabView.tableView_form_all_cases() -- Default: Convert all tests cases from testGroup to rowGroup.
	--	local memo = events.save_configs() -- Save global configuration before eventual changes.
		for i, case in ipairs(tabView.testGroup) do
			tabView.t = tabView.t .. viewer.ta("i", i)
		--	local tocase = {}
			if type(case) ~= "table" then case = { case } end -- Cases must be tables of values.
			if type(tabView.tableView_form_one_case) == "function" then -- Convert all tests cases from testGroup to rowGroup.
				case = tabView.tableView_form_one_case(case) -- Form one case
				tabView.t = tabView.t .. viewer.ta("tocase", i)
			else
				case = case -- if tableView_form_one_case() is not defined, do not Form the case
			end
			if (type(case) == "table") and (type(tabView.add_Row) == "function") then -- Do not fail if case is nil or {}.
				tabView.add_Row(case)
				tabView.t = tabView.t .. viewer.ta("all.add_Row", i) -- track to debug
			end
		end
	--	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	end
	function tabView.tableView_form_whole_tableView() -- Form whole the viewer.tableView()
	--	local memo = events.save_configs() -- Save global configuration before eventual changes.
		local t = viewer.Th()
		local head = mw.text.split( viewer.form9user(tabView.headers), ";")
		for i, header in ipairs(head) do -- Form headers of the table.
			t = t .. viewer.Tc(header)
		end
		for row_i, columns_i in ipairs(tabView.rowGroup) do -- Form the content of each of rows.
			t = t .. viewer.Tr()
			for col, val in ipairs(columns_i) do
				t = t .. viewer.Td(val) -- each value in columns of row_i
			end
		end
		t = t .. viewer.Te()
	--	Do not events.restore_configs(memo) to not disturb adaptations
		return t
	end
	--	Do not events.restore_configs(memo) to not disturb adaptations
	return tabView
end -- function viewer.tableView_adapt_options(tabView) -- Adapt all options for all uses, before all other adaptations.

function viewer.tableView_test() -- Test viewer.tableView()
	return viewer.tableView() -- "viewer_tableView_tests_title" -- Form a table with lines and columns.
end

function tools.test_section(selector, section, style) -- Deprecated alias function
	versioning.deprecatedFunction("tools.test_section", "tools.docSection")
	return viewer.docSection(selector, section, style)
end
function viewer.docSection(selector, section, style)
--	res = res .. viewer.docSection(selector, "viewer_page_tests_h2_title", "h2") -- example
	-- selector = "allwaysview" -- To allways display one view.
	local res = ""
	if type(section) ~= "string" then section = "missing section" end
	if type(style) ~= "string" then style = "h3" end
	if not ( (selector == true) or (selector == section) or (selector == "allwaysview") or (selector == "alltestsview") or (selector == "enforcerun") ) then return "" end
	if selector == "allwaysview" then
		local sec = viewer.form9user(section) -- debug not translate
	--	res = res .. viewer.form9user("<%1>" .. sec .. "</%1>", style) -- debug style ok
		res = res .. "<" .. style .. ">" .. sec .. "</" .. style .. ">" -- debug style ok
	end
	viewer.docSection_t = res
	return res
end -- function viewer.docSection(selector, section, style)

function tools.dropbox(title, content, boxstyle) -- Deprecated alias function
	versioning.deprecatedFunction("tools.dropbox", "viewer.DropBox")
	return viewer.DropBox(title, content, boxstyle)
end
function viewer.DropBox(title, content, boxstyle) -- Form a drop box. Formar un cuadro desplegable. Former une boite déroulante.
	-- Easy Dropbox style from a list of named options.
	-- alignT, image, alignB, margin_bottom, width, border_radius, border_color, background_color)
	-- Original code from fr.wikisource : "{{Boîte déroulante/début|titre=" .. title .. "|alignT=" .. alignT .. "}}" .. content .. "{{Boîte déroulante/fin}}"
	if type(title) ~= "string" then title = "viewer_dropdown_missing_title" end
	title = viewer.form9user(title)
	local s = { -- options for style and other
		image = nil,
		alignT = alignT or "left", -- align
		alignB = alignB or "left", -- align
		margin_all = margin_all or "0px", -- margin
		margin_bottom = margin_bottom or "1em", -- margin-bottom
		width = width or "99%", -- width
		border_radius = border_radius or "0", -- -moz-border-radius
		text_color = text_color or "black", -- color
		background_color = background_color or "#FFFFFF", -- background-color
		border_color = "#AAAAAA", -- border-color
		height = "1.6em", -- height
		display = "block", -- not efficient, "block" to view, "none" to mask the list
		label = "&#x25bc; &nbsp; /&#x25b6; &nbsp; ", --	 = "Unwrap/Wrap",
		-- ▼ = &#x25bc; -- ▶ = &#x25b6; https://fr.wikipedia.org/wiki/Table_des_caract%C3%A8res_Unicode/U25A0
	--	label = label or viewer.form9user("viewer_DropBox_label_Unwrap"), -- = "Unwrap/Wrap", = "Unwrap/Wrap",
	}
	s.title = title
--	s.content = content or "no s.content"
	s.content = tostring(content)
	if type(boxstyle) == "table" then
		for key, val in pairs(boxstyle) do
			if type(val) == "string" then s[key] = val end
		end
	end
--	local txt = "{{Boîte déroulante/début|titre=" .. title .. "|alignT=" .. alignT .. "}}" .. content .. "{{Boîte déroulante/fin}}"
--	image = image or "Nuvola_apps_bookcase_2.svg" -- image
	if type(s.image) == "string" then
		s.image = ''
		.. '<div class="NavPic" style=" background-color:' .. s.background_color .. '; " >'
			.. '[[File:' .. s.image .. '|22px]]'
		.. '</div>' -- &nbsp;
	else
		s.image = ''
	end
--	local res = '<div>' -- try to debug task T20151215
--	local res = '</div><div>&nbsp;</div>' -- try to debug task T20151215
	local res = ''
	.. "\n------ " -- This code interact with DropBox and debug the task T20151215 2016-11-13 17:57.
	-- Original code from wikisource : "{{Boîte déroulante/début|titre=" .. title .. "|alignT=" .. alignT .. "}}" .. content .. "{{Boîte déroulante/fin}}"
	.. '<div align="'.. s.alignB ..'" >'
		.. '<div class="NavFrame" style="clear:both; margin-bottom:'.. s.margin_bottom ..'; width:'.. s.width ..'; border-style:solid; -moz-border-radius:'.. s.border_radius ..'; border-color:'.. s.border_color ..'; background-color:'.. s.background_color ..'; " title="'.. s.label ..'" >'
			.. '<div class="NavHead" align="'.. s.alignT ..'" style=" height:'.. s.height ..'; background-color:'.. s.background_color ..'; color:'.. s.text_color ..'; " >'
				.. s.title
			.. '</div>'
			.. '<div class="NavContent" align="'.. s.alignB ..'" style="margin:'.. s.margin_all ..'; background-color:'.. s.background_color ..'; display=block; " >'
				.. s.image
				.. s.content
			.. '</div>'
		.. '</div>'
	.. '</div>'
	--[[ -- alternative dropbox for template optimisation. Misses title.
	local res = ''
	.. '<div class="templatesUsed">' -- \n------
		.. '<div class="mw-templatesUsedExplanation mw-editfooter-toggler mw-icon-arrow-expanded" tabindex="0" role="button">'
			.. '<ul class="mw-editfooter-list mw-collapsible mw-crashd" style="display: none;"><p>'
				.. s.title
			.. '</p></ul>'
			.. '<li>'
				.. s.image
				.. s.content
			.. '</li>'
		.. '</div>'
	.. '</div>'
	--]]
	viewer.DropBox_t = res
	return res
end -- function viewer.DropBox(title, content, boxstyle)

--[==[ How to code a local note in html:
* content with a note <sup>[[#binop-note|†]]</sup>
<div id="binop-note" >content of the note</div>
--]==] -- Rical 2017-02-07 from https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Metatables
-- libraryUtil.makeCheckSelfFunction( libraryName, varName, selfObj, selfObjDesc )

function viewer.docDropBoxTitle() -- Title of the title box of the edit box.
--	local translate_main_i18n_languages_list		= "This module can translate <b>%5</b> sentences into <b>%3</b> languages: "
	local translate_main_i18n_languages_list = translate.main_i18n_languages_list() -- List available translations languages
	--
	local formDocBox_dropdown_title = viewer.form9user("versioning_support_desk_title") .. " - "
	.. versioning.report_main_discreet
--	.. " - "
--	.. translate_main_i18n_languages_list
	--
	viewer.docDropBoxTitle_t = formDocBox_dropdown_title
	return formDocBox_dropdown_title
end -- function viewer.docDropBoxTitle()

function tools.dropdownfunc(selector, title, content, ...) -- Deprecated alias function
	versioning.deprecatedFunction("tools.dropdownfunc", "viewer.docDropBox")
	return viewer.docDropBox(selector, title, content, ...)
end
function tools.dropdown_func(selector, title, content, ...) -- Deprecated alias function
	versioning.deprecatedFunction("tools.dropdown_func", "viewer.docDropBox")
	return viewer.docDropBox(selector, title, content, ...)
end
function viewer.docDropBox(selector, title, content_or_func, ...) -- Form a DropBox for a doc or a group of tests.
	--	Style options in boxstyle{}: alignT, image, alignB, margin_bottom, width, border_radius, border_color, background_color)
	--	selector = "allwaysview" -- To allways display one view. -- selector = "enforcerun" -- To debug one box with INTERNAL ERROR.
	local memo = events.save_configs() -- Save global configuration before eventual changes.
	if not ( (selector == true) or (selector == title) or string.is_in_sp(selector, "allwaysview alltestsview enforcerun failinref nocontent") ) then return "" end
	local args = { ... } -- optional arguments
	local boxstyle = {}
	for i, tab in ipairs(args) do -- Get the style options for the box,
		modes.recursiveLoop = (modes.recursiveLoop or 0) + 1
		if modes.recursiveLoop > 3 then return "" end
		if type(tab) == "table" and tab.boxstyle == "boxstyle" then
			boxstyle = mw.clone( args[i] )
			args[i] = nil -- then mask this style for functions giving the content.
		end
	end
	local success, result = true, ""
	if type(title) ~= "string" then title = "viewer_dropdown_missing_title" end
	local ac_opt = {} -- for antiCrash
	ac_opt.selector = selector
	ac_opt.title = title
	ac_opt.content = content_or_func
	ac_opt.success = true
	ac_opt.content_error = ""
	if type(content_or_func) == "function" then
		local memo = events.save_configs() -- Save global configuration before eventual changes.
		ac_opt.content, ac_opt.title_errors = content_or_func( ... )
		events.restore_configs(memo) -- Restore global configurations after eventual changes.
	end
	ac_opt.title = ac_opt.title .. ( ac_opt.title_errors or "" )
	ac_opt.resultKind = "ref"
	ac_opt.result = ""
--	local contt, ac_opt = versioning.antiCrash(ac_opt, content_or_func, ...) -- Form the display of a running error.
	local res = viewer.DropBox(title, ac_opt.content, boxstyle)
	if not ac_opt.success then
	--	ac_opt.content_error = "content_error<ref>" .. viewer.errorColor( ac_opt.content_error) .. "</ref>"
	--	ac_opt.content = mw.getCurrentFrame():preprocess( " ac_opt.success<ref>" .. (ac_opt.try_form_error or "ac_opt.try_form_error") ) -- .. "</ref>"
--		ac_opt.content_error = "content_error<ref>" .. viewer.errorColor(ac_opt.content_error) .. "</ref>"
--		ac_opt.content = mw.getCurrentFrame():preprocess( " ac_opt.success<ref>" .. viewer.errorColor(ac_opt.try_form_error or "ac_opt.try_form_error") ) -- .. "</ref>"
		ac_opt.content_ref = "content_error<ref>" .. viewer.errorColor( ac_opt.content_error) .. "</ref>"
		ac_opt.content = ac_opt.content_ref
	end
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function viewer.docDropBox(selector, title, content, ...)

function viewer.formResult(options_for_modes, mode) -- Forms the result using actual mode options
--	viewer.formResult(p.options_for_modes, "edit") -- example
--	local res = res .. versioning.init(frame, p.args_known, "tests", p.options_for_modes, "Q535") -- itemid
--	res = res .. translate.track_language("viewer.formResul()")
	local res, options = "\n* viewer.formResult : ", ""
	if type(options_for_modes) ~= "table" then options_for_modes = modes.options_for_modes end
	local opt_t, elem, module, normal, alias, name_I18N
	--	collect all options
	options = tostring(modes.mode_options) .. tostring(modes.invoke_options)
	res = res .. viewer.ta("options_for_modes", options_for_modes)
	res = res .. viewer.ta("mode", mode)
	res = res .. viewer.ta("options", options)
	if (type(options_for_modes) == "table") then
		local options_tab = mw.text.split(options, " ", true)
		for i, opt in ipairs(options_tab) do -- run all options
			res = res .. viewer.ta("i", i) .. tostring(opt)
			if (type(opt) == "function") and (type(opt) == "function") then
				opt_t = opt
				res = res .. viewer.value(opt) .. tostring(opt_t)
			elseif (type(opt) == "string") and (opt ~= "") and (type(viewer[opt]) == "function") then
				if opt == "docGroup"
				then opt_t = viewer.docGroup("allwaysview")
				else opt_t = viewer[opt] end
				res = res .. viewer.value(opt) .. viewer.ta("viewer[opt]", type(viewer[opt]))  .. tostring(opt_t)
			elseif (type(opt) == "string") and (opt ~= "") and (type(viewer[opt]) == "string") then
				opt_t = viewer[opt]
				res = res .. viewer.value(opt) .. viewer.ta("viewer[opt]", type(viewer[opt]))  .. tostring(opt_t)
			elseif (type(opt) == "string") and (opt ~= "") then
				opt_t = opt
				res = res .. viewer.ta("i", i) .. viewer.value(opt) .. tostring(opt_t)
			end
		end
	end
	res = res .. "<br/><br/>"
	viewer.formResult_t = res
	return res
end -- function viewer.formResult()

function viewer.docPage(selector, itemid) -- Form some dropboxes of tests to document a page.
	local memo = events.init_configs() -- Save global configuration before eventual changes.
	local res = ""
	res = res .. viewer.docSection(selector, "viewer_page_tests_h2_title", "h2")
	-- on viewer.docDropBox(
	res = res .. viewer.docDropBox(selector, "modes_used_options_title", modes.used_options_list() )
	res = res .. viewer.docDropBox(selector, "modes_list_all_args_main_title", modes.list_all_args_main)
	res = res .. "\n------ " -- This code interact with DropBox and debug the bug T20151215 2016-11-13 17:57.
	res = res .. viewer.docDropBox(selector, "translate_translationsCounts_title", translate.translationsCounts)
	res = res .. viewer.docDropBox(selector, "luaTable_table_args_source_title", luaTable.structure, modes.args_source, "modes.args_source")
	-- bind_i18
	res = res .. viewer.docDropBox(selector, "modes_args_known_structure_title", modes.args_known_structure)
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function viewer.docPage(selector, itemid)

function viewer.docModule(selector, itemid) -- Form some dropboxes of tests to document a module.
	local memo = events.init_configs() -- Init a new events group
	local res = ""
	res = res .. viewer.form9user("begin_follow_central_modules_title") .. " docModule "
--	global _G	modes.all_G_list_var_test : Variables in _G global space  = , br = Modulenn:Central-w-br/doc , br = Modulenn:Central-s-br/doc
	res = res .. viewer.docSection(selector, "viewer_module_tests_h2_title", "h2")
	res = res .. viewer.docDropBox(selector, "luaTable_table_args_unknown_title", luaTable.structure, modes.args_unknown, "modes.args_unknown",
		{ boxstyle = "boxstyle", image = "Gtk-dialog-info.svg"} ) -- "[[Fichier:Gtk-dialog-info.svg|15px]]"
	res = res .. viewer.docDropBox(selector, "versioning_support_desk_title", versioning.support_desk_report )
	res = res .. viewer.docDropBox(selector, "begin_follow_central_modules_title", begin.follow_central_modules )
	res = res .. viewer.docDropBox(selector, "modes_brief_options_mode_title", modes.options_from_mode_tests)
	res = res .. viewer.docDropBox(selector, "modes_all_categories_list_title", modes.all_categories_list)
	res = res .. viewer.docDropBox(selector, "modes_all_errors_list_title", modes.all_errors_list)
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function viewer.docModule(selector, itemid)

function viewer.docInternal(selector, itemid) -- Form some dropboxes of tests to document internal functions.
	local memo = events.init_configs() -- Init a new events group
	local res = ""
	res = res .. viewer.docSection(selector, "viewer_internal_tests_h2_title", "h2")
	--
	res = res .. viewer.docSection(selector, "Library:begin", "h3")
	res = res .. viewer.docDropBox(selector, "begin_follow_central_modules_title", begin.follow_central_modules )
	res = res .. viewer.docDropBox(selector, "begin_links_to_central_modules_title", begin.links_to_central_modules )
	--
	res = res .. viewer.docSection(selector, "Library:datas", "h3")
	res = res .. viewer.docDropBox(selector, "datas_wikidata_arbitrary_tests_title", datas.wikidata_arbitrary_tests )
	--	{ boxstyle = "boxstyle", image = "Gtk-dialog-info.svg"} )
	res = res .. viewer.docDropBox(selector, "datas_wikidata_time_details_title", luaTable.structure, datas.wikidata_time_details) --, "datas.wikidata_time_details.claims." .. (tools.TimeName or "Pxxx") )
	--
	res = res .. viewer.docSection(selector, "Library:events", "h3")
	res = res .. viewer.docDropBox(selector, "events_all_kinds_tests_title", events.all_kinds_test)
	res = res .. viewer.docDropBox(selector, "events_categ_Test_title", events.categ_Test)
	res = res .. viewer.docDropBox(selector, "events_selectLang_tests_title", events.selectLang_test)
	--
	res = res .. viewer.docSection(selector, "Library:luaTable", "h3")
	res = res .. viewer.docDropBox(selector, "luaTable_roughView_tests_title", luaTable.roughView_test )
	res = res .. viewer.docDropBox(selector, "luaTable_structure_nolimits_title", luaTable.structure, luaTable.tablim, "luaTable.tablim", {} )
	res = res .. viewer.docDropBox(selector, "luaTable_structure_limits_title", luaTable.structure, luaTable.tablim, "luaTable.tablim",
		{ boxstyle = "boxstyle", level_maxi = 2, max_n = 2, exclude1 = "hou" } ) -- , {width = "88%", text_color = "blue"}
	res = res .. viewer.docDropBox(selector, "luaTable_toList_tests_title", luaTable.toList_test)
	res = res .. viewer.docDropBox(selector, "luaTable_toTable_tests_title", luaTable.toTable_test)
	--
	-- In central modules, the Library:mathroman is a simple example of central modules support for any library.
	res = res .. viewer.docSection(selector, "Library:mathroman", "h3")
	res = res .. viewer.docDropBox(selector, "mathroman_roman2int_test_title", mathroman.roman2int_test)
	res = res .. viewer.docDropBox(selector, "mathroman_int2roman_test_title", mathroman.int2roman_test)
	--"enforcerun" "allwaysview"
	res = res .. viewer.docSection(selector, "Library:modes", "h3")
	res = res .. viewer.docDropBox(selector, "modes_options_from_args_test_title", modes.options_from_args_test)
	res = res .. viewer.docDropBox(selector, "modes_brief_options_mode_title", modes.options_from_mode_tests,
		{ boxstyle = "boxstyle", width = "80%", text_color="green", alignT="left", alignB="center", margin_all="2em",
		background_color="yellow", border_color="red", height="2em", image="Gtk-dialog-info.svg"} )
	res = res .. viewer.docDropBox(selector, "modes_options_from_mode_tests_title", modes.options_from_mode_tests,
		{ boxstyle = "boxstyle", width = "80%", text_color="green", alignT="left", alignB="center", margin_all="3em",
		background_color="yellow", border_color="red", height="2em", image="Gtk-dialog-info.svg"} ) -- "[[Fichier:Gtk-dialog-info.svg|15px]]"
	res = res .. viewer.docDropBox(selector, "modes_recursiveNormal_tests_title", modes.recursiveNormal_tests )
	-- arguments list
	res = res .. viewer.docDropBox(selector, "modes_all_categories_list_title", modes.all_categories_list)
	res = res .. viewer.docDropBox(selector, "modes_all_errors_list_title", modes.all_errors_list)
	-- arguments support
	res = res .. viewer.docDropBox(selector, "modes_all_G_list_var_tests_title", modes.all_G_list_var_test)
	res = res .. viewer.docDropBox(selector, "modes_multiple_selection_tests_title", modes.multiple_selection_tests)
	res = res .. viewer.docDropBox(selector, "modes_multiple_values_tests_title", modes.multiple_values_tests)
	res = res .. viewer.docDropBox(selector, "modes_similar_args_searchs_title", modes.similar_args_searchs)
	res = res .. viewer.docDropBox(selector, "modes_similar_levenshtein_tests_title", modes.similar_levenshtein_tests)
	res = res .. viewer.docDropBox(selector, "modes_spaces_page_names_title", modes.spacesPageNamesTest)
	-- "enforcerun"
	res = res .. viewer.docSection(selector, "Library:testsCases", "h3")
	res = res .. viewer.docDropBox(selector, "testsCases_group_Diffs_tests_title", testsCases.group_Diffs_tests ) -- Tests and report detailed differences, recursive.
	res = res .. viewer.docDropBox(selector, "testsCases_search_Diffs_tests_title", testsCases.search_Diffs_tests ) -- Tests and report detailed differences, recursive.
	res = res .. viewer.docDropBox(selector, "testsCases_tests_Cases_report_title", testsCases.tests_Cases_report ) -- Run a group of tests and returns one string.
	--
	-- translations support
	res = res .. viewer.docSection(selector, "Library:translate", "h3")
	res = res .. viewer.docDropBox(selector, "translate_translationsCounts_title", translate.translationsCounts)
	res = res .. viewer.docDropBox(selector, "translate_changing_translations_title", translate.changing_translations)
	res = res .. viewer.docDropBox(selector, "translate_missing_translations_title", translate.missing_translations)
	res = res .. viewer.docDropBox(selector, "translate_dummy_languages_title", translate.dummy_languages)
--	res = res .. viewer.docDropBox(selector, "translate_mixed_translations_title", translate.i18n_lister)
	res = res .. viewer.docDropBox(selector, "translate_abnormal_char_in_text_title", translate.abnormal_char_in_text )
	res = res .. viewer.docDropBox(selector, "translate_list_Mediawiki_languages_title", translate.list_Mediawiki_languages )
	--"allwaysview"
	-- versions support
	res = res .. viewer.docSection(selector, "Library:versioning", "h3")
	res = res .. viewer.docDropBox(selector, "versioning_versions_management_report", versioning.versions_management_report )
	res = res .. viewer.docDropBox(selector, "versioning_bind_modules_report_title", versioning.report_bind_modules_report_details )
	res = res .. viewer.docDropBox(selector, "versioning_deprecatedFunction_tests_title", versioning.deprecatedFunction_test)
	res = res .. viewer.docDropBox(selector, "versioning_tasks_report_title", versioning.versioning_tasks_report )
	res = res .. viewer.docDropBox(selector, "versioning_bind_modules_tests_title", versioning.bind_modules_test )
	res = res .. viewer.docDropBox("nocontent", "versioning.antiCrash_tests() selector=nocontent", viewer.simpleList_test ) -- Test antiCrash()
	res = res .. versioning.antiCrash_tests()
	res = res .. viewer.docDropBox(selector, "versioning_antiCrash_tests_title", versioning.antiCrash_tests ) -- Tests antiCrash()
	-- viewer formers
	res = res .. viewer.docSection(selector, "Library:viewer", "h3")
--	The Library:viewer forms some viewers for tables(in lines and columns), dropboxes, recursive luatables...
	res = res .. viewer.docDropBox(selector, "viewer_form9user_tests_title", viewer.form9user_test )
	res = res .. viewer.docDropBox(selector, "viewer_form99user_tests_title", viewer.form99user_test )
	res = res .. viewer.docDropBox(selector, "viewer_format_time_tests_title", viewer.format_time_tests)
	res = res .. viewer.docDropBox(selector, "viewer_parts_of_date_tests_title", viewer.parts_of_date_tests)
	res = res .. viewer.docDropBox(selector, "viewer_simpleList_tests_title", viewer.simpleList_test )
	res = res .. viewer.docDropBox(selector, "viewer_table_tabOptions_title", viewer.table_test )
	res = res .. viewer.docDropBox(selector, "viewer_tableView_tests_title", viewer.tableView_test )
	--
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function viewer.docInternal(selector, itemid)

function viewer.testsview(t) -- Deprecated alias function
	versioning.deprecatedFunction("viewer.testsview", "viewer.docGroup")
	return viewer.docGroup(t)
end
function viewer.docGroup(selector, itemid) -- Form a documentation of one dropbox or all.
	local memo = events.init_configs(memo_tests) -- Init a new events group
	local res = ""
	res = res .. viewer.docPage(selector, itemid)
	res = res .. viewer.docModule(selector, itemid)
	res = res .. viewer.docInternal(selector, itemid)
--	res = res .. versioning.antiLoop_list() -- List versioning.antiLoop().
	viewer.docGroup_t = res
	events.restore_configs(memo) -- Restore global configurations after eventual changes.
	return res
end -- function viewer.docGroup(selector, itemid)


-- - - - ------------------ - - - - ---------------------------------
--	Try part of Module:Central
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
--	At code level, try part tests show how to use libraries:
--	* To convert a module as centralisable using functions(frame).
--	* To form categories, errors and warnings, their collection and their activations.
--	* To form dropboxes and tableform with translatable headers.
--	* To display the structure of an internal luatable, to parameter this display and to get its counts of elements.
--
--	At page level, try part tests show how to use libraries:
--	* To get the wikitext resulting of a module in read or edit or tests or doc1 mode.
--	* In edit mode, a docbox display received arguments, errors and categories.
--	* In test mode, several dropboxes display many cases of tests that anybody can read and verify.
--	* These tests verify libraries and tools.
--
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------
-- - - - ------------------ - - - - ------------------ - - - - -----------------------------

-- - - - ------------------ - - - - ---------------------------------
-- Running internal tests and their documentations.
-- Ejecución de las pruebas internas y su documentación.
-- Exécution des tests internes et de leurs documentations.
--
-- Errors are needed to test their detection. Do not correct them.
-- Errores se necesitan para probar su detección. No corregirlos se.
-- Les erreurs sont nécessaires pour tester leur détection. Ne pas les corriger.
-- - - - ------------------ - - - - ---------------------------------

-- - - - ------------------ - - - - ---------------------------------
-- Tables of translations of settings and warnings in the languages : en, es, fr.
-- Mesas traducciones parámetros y mensajes en los idiomas : en, es, fr.
-- Tables des traductions des paramètres et warnings dans les langues : en, es, fr.
--
-- i18n translations tables to extend and update in calling modules
-- - - - ------------------ - - - - ---------------------------------

p.i18n = {} -- translations tables known in the module

p.i18n.en = {
	-- Arguments linked to the main module
	lastname						= 'name',
	lastname_descr					= "Family name. Please specify to correct the sort key.",
	firstname						= 'firstname',
	firstname_descr					= "First name. Please specify to correct the sort key.",
	firstname2						= 'firstname',
	firstname2_descr				= "First name. Please specify to correct the sort key.",
	initiale						= 'initial',
	initiale_descr					= "Initial to correct the category of authors.",
	title							= 'title',
	title_descr						= "Page title, automatic.",
	country							= 'country',
	flag_of_country					= 'Flag_of_England.svg',
	flag_of_country_descr			= 'Flag of the country',
	occupation_cat					= '%1',
	year_number_cat					= "Year %1",
	birthyear						= 'birthyear', -- birthyear P569 for test p_authors_birthyear_cat
	birthyear_descr					= "Year of birth.",
	p_authors_birthyear_cat			= "%1 births", -- en.wikisource 1802 births - 1885 deaths
	deathyear						= 'deathyear', -- deathyear P570 for test p_authors_deathyear_cat
	deathyear_descr					= "Year of death.",
	p_authors_deathyear_cat			= "%1 deaths", -- en.wikisource 1802 births - 1885 deaths
	description						= 'description',
	description_descr				= "Author's description, to clarify whether the automatic description does not fit.",

	-- Arguments limited to multiple values
	region							= 'region', -- argument with verified multiple values
	region_descr					= 'Region to determine the ancient periods.',
	region_values					= 'other;china;india;century', -- wiki_selectors

	rights							= 'rights',
	rights_descr					= "Necessary copyrights type: 70,50,mpf,ONU,non.",
	rights_values					= '70;50;mpf;ONU;none',

	sex								= 'sex',
	sex_descr						= 'Genre of the author',
	sex_values						= 'male;female',
} -- p.i18n.en

p.i18n.es = {
	-- Argumentos relacionados con el módulo principal
	occupation_cat					= '%1',
	p_authors_deathyear_cat			= "Autores-%1",
	year_number_cat					= "Año %1",
	-- Nombres y descripciones de los argumentos
	lastname						= 'nombre',
	lastname_descr					= "Nombre. Por favor, especifique para corregir la clave de ordenación.",
	firstname						= 'apellido',
	firstname_descr					= "Primero. Proponer para corregir la autómata.",
	firstname2						= 'primero',
	firstname2_descr				= "Primero. Proponer para corregir la autómata.",
	initiale						= 'inicial',
	initiale_descr					= "Inicial para corregir la categoría de los autores.",
	title							= 'titulo',
	title_descr						= "Título de la página, automático.",
	country							= 'país',
	flag_of_country					= 'Flag_of_Spain.svg',
	flag_of_country_descr			= 'Bandera del país',
	birthyear						= 'nacimiento',
	birthyear_descr					= "Año de nacimiento.",
	p_authors_birthyear_cat			= "N%1", -- es.wikisource N1802 - F1885
	deathyear						= 'muerte',
	deathyear_descr					= "Año de la muerte.",
	p_authors_deathyear_cat			= "F%1", -- es.wikisource N1802 - F1885
	description						= 'descripcion',
	description_descr				= "Descripción del autor, para aclarar si la descripción automática no encaja.",

	-- Argumentos limitados a múltiples valores
	region							= 'región',
	region_descr					= 'Región para determinar los tiempos antiguos.',
	region_values					= "otro;china;india;siglo",

	rights							= 'derechos',
	rights_descr					= "Tipo de derechos de autor necesario: 70,50,mpf,ONU,non.",
	rights_values					= '70;50;mpf;ONU;no',

	sex								= 'sex',
	sex_descr						= 'Género del autor',
	sex_values						= 'hombre;mujer',
} -- p.i18n.es

p.i18n.fr = {
	-- Arguments liés au module principal
	occupation_cat					= '%1',
	p_authors_deathyear_cat			= "Auteurs-%1",
	year_number_cat					= "Année %1",
	-- Noms et descriptions des arguments
	lastname						= 'nom',
	lastname_descr					= "Nom. A préciser pour corriger la clé de tri.",
	firstname						= 'prénom',
	firstname_descr					= "Prénom. A préciser pour corriger la clé de tri.",
	firstname2						= 'prenom',
	firstname2_descr				= "Prénom. A préciser pour corriger la clé de tri.",
	initiale						= 'initiale',
	initiale_descr					= "Initiale pour corriger les catégories d'ateurs.",
	title							= 'titre',
	title_descr						= "Titre de la page, automatique.",
	country							= 'pays',
	flag_of_country					= 'Flag_of_France.svg',
	flag_of_country_descr			= 'Drapeau du pays',
	birthyear						= 'anneeNaissance',
	birthyear_descr					= "Année de naissance",
	p_authors_birthyear_cat			= "Naissance en %1", -- fr.wikisource Naissance en 1802 Décès en 1885
	deathyear						= 'anneeDeces',
	deathyear_descr					= "Année de décès",
	p_authors_deathyear_cat			= "Décès en %1", -- fr.wikisource Naissance en 1802 Décès en 1885
	description						= 'description',
	description_descr				= "Description de l'auteur, à préciser si la description automatique ne convient pas",

	-- Arguments limités à des valeurs multiples
	region							= 'région',
	region_descr					= 'Région pour déterminer les époques anciennes.',
	region_values					= "autre;chine;inde;siècle", -- wiki_selectors

	rights							= 'droits',
	rights_descr					= "Type de droits d'auteur nécessaire parmi : 70,50,mpf,ONU,non.",
	rights_values					= '70;50;mpf;ONU;non',

	sex								= 'sexe',
	sex_descr						= "Sexe de l'auteur",
	sex_values						= 'homme;femme',
} -- p.i18n.fr

-- - - - ------------------ - - - - ---------------------------------
-- p.i18n tables end
-- - - - ------------------ - - - - ---------------------------------

-- - - - ------------------ - - - - ---------------------------------
-- Arguments table, to change in calling modules
-- - - - ------------------ - - - - ---------------------------------

p.args_known = { -- Table of the definitions of all known arguments at module level.

	-- Arguments in order without names, with their keyword for use as other arguments.
	-- Arguments dans l'ordre, non nommés, avec leur keyword pour traitement comme les autres arguments.
	[1] =			{need = 0, syn = 2,
		keyword = "mode"},
	-- Special arguments to modify the fonctions and outputs of this module.
	-- Arguments speciaux pour modifier le fonctionnement et les sorties de ce module.
	mode =			{typ = "config", need = 0,
		keyword = "mode"},
	c =				{typ = "config", need = 0,
		keyword = "c"},
	options =		{typ = "config", need = 0,
		keyword = "options"},
	-- The userlang argument permits at an administrator in his own langage (errors, warnings, catégories, tests) to help a wiki in any language.
	-- El userlang argumento permisos en administrador en su propia langage (errores, advertencias, categorías, pruebas) para ayudar a un wiki en cualquier idioma.
	-- L'argument userlang permet à un administrateur dans son propre langage (erreurs, warnings, catégories, tests) d'aider un wiki dans ne importe quelle langue.
--	userlang =		{typ = "config", need = 0,
--		keyword = "userlang"},

	-- The wikilang argument permits to verify in one unique wiki that a module can well adapt itself to any wiki language.
	-- Los argumento wikilang permisos para verificar en una wiki único que un módulo puede así adaptarse a cualquier idioma de wiki.
	-- L'argument wikilang permet de vérifier dans un wiki unique qu'un module peut s'adapter à n'importe quelle langue de wiki.
--	wikilang =		{typ = "config", need = 0,
--		keyword = "wikilang"},

--	allversions =	{typ = "config", need = 0,
--		keyword = "allversions"},
	knownversions = {typ = "config", need = 0,
		keyword = "knownversions"},

--	selectversions = {typ = "config", need = 0,
--		keyword = "selectversions"},
	soughtversions = {typ = "config", need = 0,
		keyword = "soughtversions"},

	debug =			{typ = "opt", need = 0,
		keyword = "debug"},

	category =		{typ = "ctr", need = 0,
		keyword = "category"},

	-- All arguments have a keyword identical to the registration name, except synonyms.
	-- Tous les arguments ont un keyword identique au nom d'enregistrement, sauf les synonymes.

	label =			{typ = "dat", need = 0,
		keyword = "label" , prop = "label"},

	country =		{typ = "dat", need = 0,
		keyword = "country", prop = "P27", },

	sitelink =		{typ = "dat", need = 0,
		keyword = "sitelink" , prop = "sitelink"},

	itemid =		{typ = "dat", need = 2,
		keyword = "itemid" , prop = "itemid"},

	itemid2 =		{typ = "dat", need = 2, syn = 2,
		keyword = "itemid" , prop = "itemid"},

	lastname =		{typ = "dat", need = 0,
		keyword = "lastname"},

	lastname2 =		{typ = "dat", need = 0, syn = 2,
		keyword = "lastname"},

	firstname =		{typ = "dat", need = 0,
		keyword = "firstname"},

	firstname2 =	{typ = "dat", need = 0, syn = 2,
		keyword = "firstname"},

	initiale =		{typ = "dat", need = 2,
		keyword = "initiale"},

	title =			{typ = "dat", need = 2,
		keyword = "title"},

	birthyear =		{typ = "dat", need = 0,
		keyword = "birthyear", prop = "P569", format = "year"},

	deathyear =		{typ = "dat", need = 0,
		keyword = "deathyear", prop = "P570", format = "year"},

	description =	{typ = "dat", need = 0,
		keyword = "description", prop = "description"},

	sex =			{keyword = "sex", typ = "dat", need = 0, prop = "P21",
		arg_values = "sex_values", key_values = ";", }, -- wiki_selectors

	region =		{keyword = "region", typ = "dat", need = 0,
		arg_values = "region_values", key_values = "other;china;india;century" }, -- wiki_selectors

	rights =		{keyword = "rights", typ = "dat", need = 2,
		arg_values = "rights_values", key_values = "no;none;ONU;none;cn;50;us;70;mpf", -- wiki_selectors
		delay_values = "0;0;0;0;50;50;70;70;95", arg_uses = "none;none;none;none;cn;cn;us;us;mpf" }, -- wiki_selectors

} -- p.args_known

-- - - - ------------------ - - - - ---------------------------------
-- Arguments sources examples
--
-- Errors are needed to test their detection. Do not correct them.
-- Errores se necesitan para probar su detección. No corregirlos se.
-- Les erreurs sont nécessaires pour tester leur détection. Ne pas les corriger.
-- - - - ------------------ - - - - ---------------------------------

p.WikidataEN = { label = "John Smith", deathyear = "1789",	country = "France" }

p.ArgtestEN = { "mode One", "Rimbaud 2", name = "Rimbaud", firstname = "Arnaud", rights = "70", deathyear = "MDCCCJL",	langue = "allemand,français,espagnol" }

p.ArgtestES = { nombre = "Rimbaud", apellido = "Arthur", optionsES = " ", derechoss = "70", anoMuerte = "MDCCCJL"}

p.ArgtestFR = { "mode Un", "Rimbaud 2", "Jonh", nom = "Smith", ["prénom"] = "Arnaud", anneeDeces = "1234", }

function p.trc(fn_mode, t)
	local res = ""
	-- to put in comment to desactivate
	--	res = res .. "<br>* " .. t .. viewer.ta("fn_mode", fn_mode) .. viewer.ta("modes.mode_name", modes.mode_name) .. viewer.ta("modes.mode_options", modes.mode_options) .. viewer.ta("modes.invoke_options", modes.invoke_options)
	res = res .. viewer.ta("trc try", viewer.form9user("viewer_internal_tests_title")) .. viewer.ta("MR", viewer.form9user("err_J_before_end")) .. viewer.ta("AT", viewer.form9user("deathyear"))
	return res
end

function p.list_all_args_sub(t, args_known) -- modes_list_all_args_main_title = List of all arguments, for try
	local t = t or "<br>* <b>list_all_args_sub :</b> "
--	t = t or "<br>* <b>List_all_args :</b> "
	if type(args_known) ~= "table" then args_known = modes.args_known end
	local descr, description = "", ""
	local args = mw.clone(args_known)
	local arglst = {}
	for key, elem in pairs(args) do
		elem.key = tostring(key)
		descr = key .. "_descr" -- key for description of an argument
		elem.description = translate.user_translations[descr] or "**missing translation**"
		elem.user_lang_key = translate.user_translations[elem.key] or elem.key or "**missing key**"
		elem.user_lang_keyword = translate.user_translations[elem.keyword] or elem.keyword or "**missing keyword**"
		elem.lev_arg_txt = ""
		local lst_lev = {}
		local lev_min = 99
		for arg_lev, elem_lev in pairs(args_known) do
			elem_lev.levenshtein = modes.levenshtein( translate.wiki_translations[arg_lev], translate.wiki_translations[key] ) or 99
			if elem_lev.levenshtein < lev_min then
				lev_min = elem_lev.levenshtein
				elem.levenshtein = elem_lev.levenshtein
				elem.lev_min = lev_min
				elem.arg_lev = arg_lev
				elem.lev_lang = translate.wiki_translations[arg_lev] or arg_lev
			end
			elem.arg_lev = arg_lev
			elem.lev_arg_txt = viewer.ta("lev", tostring(elem.lev_lang) .. ":" .. tostring(elem.lev_min) .. ":" .. elem.arg_lev )
		--	events.add_err("modes_too_unnamed_arguments_err", key_N, val_src .. " LLL lev=" .. tostring(arglst[1].levenshtein) )
		--	events.add_cat("versioning_module_usage_error_cat")
		end
		table.insert(arglst, elem)
	end -- insert in the arguments their own key
	table.sort(arglst, function (a, b) return (a.user_lang_key < b.user_lang_key) end )
	local gr_syn, gr_need, gr_other = {}, {}, {}
	for i, elem in ipairs(arglst) do -- group arguments in some groups
		if elem.need == 1 or elem.need == 2 then table.insert(gr_need, elem)
		else table.insert(gr_other, elem) end
	end
	local needed = viewer.smallCapsStyle(viewer.form9user("modes_needed_to_verify"))
	local function list_group( group, needed )
		needed = needed or ""
		local t = ""
		for key, elem in pairs(group) do
			if elem.syn
			then t = t .. "<br>* <b>" .. tostring(elem.user_lang_key) .. "</b> => <b>" .. elem.user_lang_keyword .. "</b> : " .. needed .. " " .. tostring(elem.description) .. elem.lev_arg_txt
			else t = t .. "<br>* <b>" .. tostring(elem.user_lang_key) .. "</b> : " .. needed .. " " .. tostring(elem.description) .. elem.lev_arg_txt end
		end
		return t
	end
	t = t .. "<br><br>* <b>" .. viewer.form9user("modes_list_needed_args") .. "</b> " .. list_group( gr_need, needed )
	t = t .. "<br><br>* <b>" .. viewer.form9user("modes_list_all_other_args") .. "</b> " .. list_group( gr_other )
	return t
end -- function p.list_all_args_sub(t, args_known) -- modes_list_all_args_main_title for try

function p.form_support_desk_report(args_final)
	-- Form the detailed Versions report
	t = "\n* Discreet main version: " .. (versioning.report_main_discreet or " report_main_discreet missing ")
	t = t .. "\n\n* All selected modules versions: " .. (versioning.report_bind_verif_modules or " report_bind_verif_modules missing ")
	t = t .. "\n\n* All <b>loaded modules</b> versions: " .. (versioning.report_loaded_pack or " report_loaded_pack missing ") -- loaded_modules
	t = t .. "\n* Start versions from main module: " .. (versioning.bind_verif_modules_report_start or " bind_verif_modules_report_start missing ")
	t = t .. "\n* effective versioning.bind_modules_start: " .. (versioning.bind_modules_start or " versioning.bind_modules_start missing ")
	versioning.support_desk_report = t -- versioning.bind_modules_report() -- Report all modules installation
	return t
end -- function p.form_support_desk_report(args_final)

-- Display the documentation in an infobox, similar to edit-boxs
-- Affichage de documentation dans un cadre (box), semblable aux boites d'edition
function p.formDocBox(args_final)
	-- modes.options = " docdata docmin docdef docmax docline docsrc docview : docafter docnotice docsrc" -- for documentation
	-- modes.options = " erron noerr nobox " -- without normal result
	-- modes.options = " debug tests en es fr " -- for debug or enforce language
	if type(args_final) ~= "table" then args_final = modes.args_final end -- optional arguments
--	local err = modes.verify_args_tables(modes.args_known, modes.args_source)
--	if err then return err end
	local res = "" -- on viewer.form9user(
	res = res .. viewer.errorColor("<center><b>" .. viewer.form9user("modes_delete_docbox_wng") .. "</b><br/></center>")
	viewer.formDocBox_dropdown_title = viewer.form9user("versioning_support_desk_title")
	.. " - " .. versioning.report_main_discreet -- .. " - " .. luaTable.formSubCounts("versioning.main_i18n")
	res = res .. viewer.DropBox(tostring(viewer.docDropBoxTitle()), versioning.support_desk_report )
--	modes.catView = ":"
--	modes.mode_options = " debug docview docdata docdef docline "
--	modes.invoke_options = ""
	if modes.option("debug")		then res = res .. "\n*" .. viewer.ta("modes.catView", modes.catView) .. viewer.ta("modes.invoke_options", modes.invoke_options) .. viewer.ta("modes.mode_options", modes.mode_options) end
	if modes.option("docdata")		then res = res .. "\n*" .. modes.generDoc(" docdef docline ", datas.args_wikidata, "Wikidata") end
	if modes.option("docview")		then res = res .. "\n*" .. modes.generDoc("", args_final, "Arguments") end
	if not modes.option("noerr")	then res = res .. "\n*" .. events.errors_lister() end
	--
	local t = "" -- Form events views
	local wng, err, cat = events.sort_typ() -- Sort events by type in wng, err, cat.
	for key, evt in pairs(wng) do
		evt.res, evt = events.form(evt)
		if evt.res then t = t .. "<br>* " .. tostring(evt.res) end
	end
	for key, evt in pairs(err) do
		evt.res, evt = events.form(evt)
		if evt.res then t = t .. "<br>* " .. tostring(evt.res) end
	end
	for key, evt in pairs(cat) do
		evt.res, evt = events.form(evt)
		if evt.res then t = t .. "<br>* " .. tostring(evt.res) end
	end
	res = res .. t
	local result = "\n------ " -- This code interact with DropBox and debug the bug T20151215 2016-11-13 17:57.
	result = result .. '<div style=" width=90%; border: 1px solid #AAAAAA; margin:1em; background-color:#F1F1F1; padding:0.3em; ">' .. res .. '</div>'
	result = result .. "\n* " .. events.categories_lister(":")
	return result
end -- function p.formDocBox(args_final)

function p.normal_box(args_final, title)
	local res = ""
	local warning_versions = tools.report_main_short
	if type(args_final) ~= "table" then args_final = modes.args_final end
	if not title then title = args_final.title end
	if not title then title = "TITLE" end
	res = res .. "<center><b><big>" .. title .. "</big></b> " .. versioning.report_main_discreet .. " - " .. (versioning.site_currentVersion_view or "currentVersion") .. "<br></center>"
	local flag_of_country = translate.trans9vars(translate.wiki_translations, "flag_of_country")
	if type(flag_of_country) == "string" then
		flag_of_country = '[[File:' .. flag_of_country .. '|frameless|40x40px||class=photo]] '
	else
		flag_of_country = "&nbsp;"
	end
	res = res .. flag_of_country
	local function arglingual(key)
		local t = ""
		if key and tools and translate.user_translations and translate.user_translations[key] then
			t = t .. viewer.tam(translate.user_translations[key], args_final[key] )
		else t = ", " end
		return t
	end
	res = res .. arglingual("label")
	res = res .. arglingual("description")
	res = res .. arglingual("occupation")
	res = res .. arglingual("firstname")
	res = res .. arglingual("lastname")
	res = res .. arglingual("birthyear")
	res = res .. arglingual("deathyear")
	modes.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
--	local i2r = mathroman.int2roman(modes.nowyear)
--	local r2i = mathroman.roman2int(i2r)
--	local r2r = mathroman.int2roman(r2i)
	res = res .. viewer.ta("int2roman", i2r) .. viewer.ta("roman2int", r2i) .. viewer.ta("int2roman", r2r)
	res = res .. "<br>" .. translate.main_i18n_languages_list() -- List available translations languages
	res = '<div style="margin-right:5px; box-shadow:0.2em 0.3em 0.2em #B7B7B7; background-color:#F1F1DE; padding:0.3em; width=90%; overflow-x:hidden; ">' .. res .. '</div>'
	return res
end -- function p.normal_box(args_final, title)

function p.form_tests_init(res, args_source)
-- Special init for the test mode
	if type(res) ~= "string" then res = "\n* Mode test : " end
	if type(args_source) ~= "table" then args_source = {} end
	if p.i18n and p.i18n.en then p.i18n.en.error_i18n_wanted_to_test_missing_translation = 'English error i18n wanted for tests missing translation' end
	if p.i18n and p.i18n.es then p.i18n.es.error_i18n_deseada_para_probar_traduccion_faltan = 'Espagnol error i18n deseada para probar traducción faltan' end
	if p.i18n and p.i18n.fr then p.i18n.fr.error_i18n_voulue_pour_test_de_traduction_manquante = 'Français erreur i18n voulue pour tests de traduction manquante' end
--	if not args_source.userlang then args_source.userlang = "en" end
--	if not args_source.wikilang then args_source.wikilang = "es" end
	if not args_source.name then args_source.name = "Jack Smith" end
	if not args_source.nom then args_source.nom = "Victor Hugo" end
	if not args_source.region then args_source.region = "india" end
	if not args_source["région"] then args_source["région"] = "chine" end
	if not args_source.description then args_source.description = "Victor Hugo est très connu." end
	if not args_source.langue then args_source.langue = "français,japonais" end
	if not args_source.occupation then args_source.occupation = "Académiciens,Personnalités politiques" end
	modes.args_source = args_source
	return res
end -- function p.form_tests_init(res, args_source)

-- Interact parameters in international args_final
function modes.interact_args_final(args_import)
	versioning.deprecatedFunction("modes.interact_args_final", "p.interact_args_final")
	return p.interact_args_final(args_import)
end
function p.interact_args_final(args_import)
	-- args_final = p.interact_args_final(args_import)
	local t = ""
	if type(args_import) ~= "table" then args_import = modes.args_import or {} end
	local args_final = mw.clone(args_import) -- do not disturb original args_import
	local a = args_import
	local i = {} -- interact
--	local t = "\n* begin :" .. viewer.ta("initiale", a.initiale) .. viewer.ta("firstname", a.firstname) .. viewer.ta("lastname", a.lastname) .. viewer.ta("title", a.title)
	--
	local tit = nil
	if not a.title then -- If title is undefined, enforce it.
		if a.lastname and a.firstname then
			tit = a.firstname .. " " .. a.lastname
		end
		i.title = a.label or tit or a.sitelink or a.lastname or modes.module_name
	end
	--
	if not a.initiale then -- If initiale is undefined, enforce it.
		-- if absent, default initiale come from the last word of title
		local title = a.title or i.title
		if title then
			local tab = mw.text.split(title, '%s') -- table of words
			local max = table.maxn( tab )
			i.initiale = tab[max] -- selector the last word
			i.initiale = string.sub( i.initiale, 1, 1 ) -- selector the first letter
			i.initiale = string.upper( i.initiale or "" )
		end
	end
	--
	-- if absent, synonym of basic arguments, syn = 2
	if not a.firstname then i.firstname = (i.firstname2 or a.firstname2) end
	if not a.lastname then i.lastname = (i.lastname2 or a.lastname2) end
	if not a.firstname2 then i.firstname2 = (i.firstname or a.firstname) end
	if not a.lastname2 then i.lastname2 = (i.lastname or a.lastname) end
	--
	if a.birth and not a.birthyear then
		local tt, err = viewer.date_to_part(a.birth, viewer.form9user("modes_date_to_part_format"), "yyyy")
		if tt then i.birthyear = tt else
			events.add_err(err, viewer.form9user("birthyear"), "yyyy")
			events.add_cat("modes_date_to_part_call_cat")
		end
	end
	-- memorize interactions in modes.args_final and show errors or warnings
	local n = 0
	for key, val in pairs(i) do
		local args_kwn = modes.args_known[key]
		if args_kwn then
			args_final[key] = val -- = i[key]
			args_kwn.src = "inter"
			args_kwn.trk = args_kwn.trk.." i"
			n = n + 1
			if (args_kwn.need == 2) and not a[key] then --
				-- need=2 necessary from argument or module interaction
				events.add_wng("modes_auto_val_warning_wng", translate.user_translations[key], val)
			end
		else
			events.add_err("modes_unknown_auto_arg_err", translate.wiki_translations[key], val)
		end
	end
	if modes.args_known and modes.args_known.title then modes.args_known.title.trk = (modes.args_known.title.trk or "").."i="..n end
	modes.args_final = args_final
	return args_final, t
end -- function p.interact_args_final(args_import)


-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------
-- Interfaces, alias and functions to templates
-- Interfaces, alias y funciones para modelos
-- Interfaces, alias et fonctions pour les modèles
-- - - - ------------------ - - - - ---------------------------------
-- - - - ------------------ - - - - ---------------------------------

function translate.track_language(where)
	local res = "\n* " .. viewer.form9user(where) .. " : "
	res = res .. viewer.ta("wiki_lang", translate.wiki_lang) .. viewer.ta("user_lang", translate.user_lang)
	res = res .. viewer.form9user("language") -- translate.i18n.en = { language = 'language',
	res = res .. viewer.form9user("begin_follow_central_modules_headers")
	return res
--	res = res .. translate.track_language("begin_follow_central_modules_title")
--	res = res .. translate.track_language("begin_follow_central_modules_title") .. " Central.read "
end                                                     

function Central.init(frame, mode_name, args_known, options_for_modes, itemid)
	local res = ""
	args_known = args_known or p.args_known
	options_for_modes = options_for_modes or p.options_for_modes
	res = res .. versioning.init(frame, mode_name, args_known, options_for_modes, itemid) -- on versioning.init(
--	translate.Central_x_y = p.Central_x_y -- To adapt the version of Module:Central in any translated text.
	translate.Module_Central_version = p.Module_Central_version -- To adapt the version of Module:Central in any translated text.
--	res = res .. versioning.site_currentVersion_view
	modes.frame = frame
	res = res .. versioning.detect_mediawiki_changes()
	return res
end -- function Central.init(frame, mode_name, args_known, options_for_modes, itemid)

function Central.read(frame) -- The read mode generates the normal result for read only users.
	-- Helpers or admins can add options to include edit or tests or user language...
	local res = ""
	res = res .. Central.init(frame, "read", p.args_known, p.options_for_modes, "Q535") -- itemid
--	res = res .. versioning.detect_mediawiki_changes()
	modes.args_final = p.interact_args_final(modes.args_import) -- Interactions between arguments
--	res = res .. versioning.site_currentVersion_view -- in debug phase
	res = res .. modes.form_result(modes.args_final)
--	res = res .. modes.running_times(true, "")
--	res = res .. versioning.site_currentVersion_view -- in debug phase
	res = res .. versioning.detect_mediawiki_changes()
	return res
end -- function Central.read(frame)

function Central.edit(frame) -- The edit mode verifies arguments, displays the edit panel with errors, warnings and categories.
	local res = ""
	res = res .. Central.init(frame, "edit", p.args_known, p.options_for_modes, "Q535") -- itemid
--	res = res .. Central.init(frame, "edit", p.args_known, p.options_for_modes) -- , "Q535"
--	res = res .. versioning.detect_mediawiki_changes()
	modes.args_final = p.interact_args_final(modes.args_import) -- Interactions between argumensts
--	res = res .. versioning.site_currentVersion_view
	translate.init_wiki_user_page_lang()
	res = res .. modes.form_result(modes.args_final)
--	res = res .. versioning.site_currentVersion_view
	res = res .. versioning.detect_mediawiki_changes()
	return res
end -- function Central.edit(frame)

function p.doc1(frame) -- Deprecated alias function
	versioning.deprecatedFunction("p.doc1", "Central.doc")
	return Central.doc(frame)
end
function Central.doc(frame) -- Form a documentation or a test in a dropbox.
	local res = ""
	res = res .. Central.init(frame, "doc", p.args_known, p.options_for_modes, "Q535") -- itemid
	modes.args_final = p.interact_args_final(modes.args_import) -- Interactions between arguments
--	res = res .. versioning.site_currentVersion_view
	res = res .. "\n*" .. viewer.ta("modes.mode_options", modes.mode_options)
	res = res .. viewer.ta("modes.invoke_options", modes.invoke_options)
	local dockey = modes.args_config.dockey or modes.args_source[1]
	local itemid = modes.args_config.itemid or modes.args_config.id or modes.args_source[2]
	modes.change_itemid() -- "Q41568"
	modes.args_final = p.interact_args_final(modes.args_import) -- Interactions between argumensts
	translate.init_wiki_user_page_lang()
	modes.time3 = os.clock()
	if dockey then
		res = res .. viewer.docGroup(dockey, itemid)
		return res
	else
		return res -- ""
	end
end -- function Central.doc(frame)

function Central.tests(frame)
	local res = ""
	res = res .. Central.init(frame, "tests", p.args_known, p.options_for_modes, "Q535") -- itemid
--	res = res .. versioning.detect_mediawiki_changes()
	modes.args_final = p.interact_args_final(modes.args_import) -- Interactions between arguments
--	res = res .. versioning.site_currentVersion_view
	res = res .. "\n* getEntityIdForCurrentPage = " .. tostring( mw.wikibase.getEntityIdForCurrentPage() ) -- Returns the Item id as string, like "Q42"
	p.form_tests_init()
	modes.args_final = p.interact_args_final(modes.args_import) -- Interactions between argumensts
	modes.mode_name = modes.args_final.mode or modes.args_import.mode or modes.args_config.mode or "tests"
	translate.init_wiki_user_page_lang()
	versioning.main_versions = versioning.main_versions or { versionName = "versionName", versionNumber = "0.0", }
	local res_function = "<br>* " .. versioning.main_versions.versionName .. ":" .. modes.mode_name .. ":" .. translate.wiki_lang .. " "
	-- If there is no MainModule, THIS version of Central is used alone
	if not versioning.main_versions then versioning.main_versions = versioning.versions or p.versions end
	local res_function = "<br/>\n" .. (versioning.main_versions.versionName or "MainModule_t") .. " " .. (versioning.main_versions.versionNumber or "0.0_t") .. ":" .. modes.mode_name .. ":" .. translate.wiki_lang .. " "
	res = res .. res_function .. " Begin:" .. "<br>"
	local loaded_pack, loaded_txt, modu = versioning.get_loaded_modules()
	versioning.loaded_modules_track = "p.tests : " .. loaded_txt
	res = res .. modes.form_result() -- Generate wikitext, categories, and others
	res = res .. res_function .. " End." .. "<br>"
--	res = res .. versioning.site_currentVersion_view
	-- frame:preprocess <pre>, <nowiki>, <gallery> and <ref>, will be replaced with "strip markers"
	res = res .. viewer.docSection(selector, viewer.form9user("versioning_antiCrash_reference_label"), "h2")
--	versioning_antiCrash_reference_label	= "References",
--	res = res .. frame:preprocess( "<br>== References ==<br><references><br>" )
--	res = res .. frame:preprocess( "<br>{{Références}}" ) -- antiCrash
--	res = res .. "\n== references ==<br><references><br>"
--	res = res .. versioning.bind_modules_report(t) -- Report all modules installation
--	res = res .. versioning.antiCrash_tests()
	res = res .. versioning.detect_mediawiki_changes()
	res = res .. luaTable.subname_to_subtable_test() -- Test: Get the last sub-table from a sub-table name.
	return res
end -- function Central.tests(frame)

function versioning.detect_mediawiki_changes()
	local t = t or "\n* <b>versioning.record_mw_changes()</b> : Detect, record and report all date-time changes of mediawiki, in each wiki."
--	T20170225 Rical: using 2 ways to use {{subst:}}, we could detect, record and report all date-time changes of mediawiki
--	in each wiki to help the gerrit team to debug mw.
--	See https://www.mediawiki.org/wiki/Manual:Substitution
	versioning.site_currentVersion = mw.site.currentVersion
	if mw.site.currentVersion == versioning.site_currentVersion_memo
	then
		versioning.site_currentVersion_view = viewer.discreetColor( versioning.site_currentVersion )
	else
		versioning.site_currentVersion_view = viewer.errorColor( versioning.site_currentVersion )
	end
	t = t .. "\n* See also the [https://www.mediawiki.org/wiki/Manual:Substitution Mediawiki Manual:Substitution]."
	return t
end -- function versioning.detect_mediawiki_changes()

--	Last Mediawiki-Wikisource update/upgrade versions:
--	20170221-23:50 : Mediawiki-Wikisource 1.29.0-wmf.12 (26fcd48)UTC-20170221-16:11.html
--	20170223-09:11 : Mediawiki-Wikisource 1.29.0-wmf.13 (c70f13a)UTC-20170222-20:22.html
--	20170225-00:09 : Mediawiki-Wikisource 1.29.0-wmf.13 (0276546)UTC-20170223-20:25.html
--	20170228-20:35 : Mediawiki-Wikisource 1.29.0-wmf.13 (c73a5e9)UTC-20170228-14:58.html
	versioning.site_currentVersion_memo = "1.29.0-wmf.13 (c73a5e9)" -- 123

return p
--	Usual pages for Translations
--	Modulenn:Central-s-br/doc				{{#invoke:Central-s-br|read}}	br = Breton = Brezhoneg
--	Modul:Central-s-de/Doku					{{#invoke:Central-s-de|read}}	de = German = Deutsch
--	Module:Central-s-en/Documentation		{{#invoke:Central-s-en|read}}	en = English = English
--	Module:Central-s-es/Documentación		{{#invoke:Central-s-es|read}}	es = Spanish = español
--	Module:Central-s-fr/Documentation		{{#invoke:Central-s-fr|read}}	fr = French = Français
--	Modul:Central-w-hu/Dokumentáció			{{#invoke:Central-w-hu|read}}	hu = Hungarian = Magyar
--	Mô đun:Central-w-vi/tài liệu			{{#invoke:Central-w-vi|read}}	vi = Vietnamese = Tiếng việt
--	MediaWiki:Scribunto/Central modules reference manual