Module:Central

From Laserwiki
Revision as of 15:26, 12 July 2020 by Admin (talk | contribs) (1 revision imported)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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

-- Module:Central {{#invoke:Central|read}}

local p = {}

local try = p -- try part of Module:Central
--	local CA = p -- try part of Module:Central (deprecated ControlArgs)

--[ [ Module:Central
p.versions = { -- Modules dependencies. Dependencias del módulo. Dépendances du module.
	versionName = "Central", versionNumber = "1.03", versionDate = "2016-05-12 12:0",
	sought = "Central", -- Sought module and submodules versions
	known = "Central", -- Known module and submodules versions
	loaded_list = ",Central,",-- Module:Central
}
--] ]

--[[ Module:Central
p.versions = { -- Modules dependencies. Dependencias del módulo. Dépendances du module.
	versionName = "Central", versionNumber = "1.02", versionDate = "2016-02-21 23:53",
	sought = "Central,Central01,cen tre,mathroman", -- Sought module and submodules versions
	known = "Central,Central000	 ", -- Known module and submodules versions
	loaded_list = ",Central,Central 01,centre,tools,mathroman,",
}
--]]

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

-- Because the bug T122752 : #invoke seems do not record the module, for some weeks.
-- https://phabricator.wikimedia.org/T122752 Rical created this task.Via Web ·Sat, Jan 2, 2016-01-02 23:08
-- Replace the missing of record of the module by : (Todo also in other modules)
-- _G[p.ModuleNS .. p.versions.versionName] = p -- like _G["Module:Central1"] = p
-- in _G to DEBUG T122752 : #invoke do not record the main module in package.loaded

------------------------------------------------------------------------------
-- Development guide line
-- 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 seems do not record the module, for some weeks.
-- 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 Library:tools, Library:centre and Library:mathroman.
-- We simulate them here, to faster and easier install and coordinate them and their functions.
--	if not centre		then start.new_library("centre", options)		end -- Install Library:centre as a true temporary library.
--	if not tools		then start.new_library("tools", options)		end -- Install Library:tools as a true temporary library.
--	if not mathroman	then start.new_library("mathroman", options)	end -- Install Library:mathroman as a true temporary library.
------------------------------------------------------------------------------

------------------------------------------------------------------------------
--	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:centre manages the versioning of modules, and the mix of translations from libraries, modules and their /I18N sub-modules.
--	A central module and its translations can inpedendently change. For this goal a module contains only one or some tranlations, but its /I18N sub-module contains translations in some or all languages.
--	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.
--
--	A library contains internal autotests cases.
--	But the view of tests cases for users, to verify or understand the functions or the uses, are in a tools library or in some modules.
--
--	The Library:tools manages the translations used for received arguments names, errors, messages, categories, arguments lists...
--	The Library:tools forms easy translatable dropboxes, tables views...
------------------------------------------------------------------------------

------------------------------------------------------------------------------
--	Install Library:centre
--	This library support modules to:
--	* Collect and bind modules and sub-modules
--	* Collect and bind translations from /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 from main, like Module:Sub, Module:Sub2, Module:SubPlus.
--
--	This library do not support the bilingual categories, see Library:tools.
--	Libraries dependencies:
--	This Library:centre needs the Library:mw
--	The Library:mathroman and the Library:tools and the Module:Author3 depend of this library.
------------------------------------------------------------------------------

-- Translations for centre library
centre = {}
centre.i18n = {} -- Translations not used on 2016-05-17
centre.i18n.en = {
	centre_luatable_counts				= "%1 variables, %2 functions, %3 sub-tables",
	centre_table_dont_exists			= "The table <b>%1</b> does not exist.",
	centre_table_listlimit_levelmaxi	= "Level limit levelmaxi = <b>%1</b> ",
	centre_table_listlimit_max_n		= "Length limit max_n = <b>%1</b> ",
} -- centre.i18n.en

centre.i18n.es = {
	centre_luatable_counts				= "%1 variables, %2 funciones, %3 subtablas",
	centre_table_dont_exists			= "La tabla <b>%1</b> no existe.",
	centre_table_listlimit_levelmaxi	= "Límite de nivel levelmaxi = <b>%1</b>",
	centre_table_listlimit_max_n		= "Límite de longitud max_n = <b>%1</b>",
} -- centre.i18n.es

centre.i18n.fr = {
	centre_luatable_counts				= "%1 variables, %2 fonctions, %3 sous-tables",
	centre_table_dont_exists			= "La table <b>%1</b> n'existe pas.",
	centre_table_listlimit_levelmaxi	= "Limite de niveau levelmaxi = <b>%1</b> ",
	centre_table_listlimit_max_n		= "Limite de longueur max_n = <b>%1</b> ",
} -- centre.i18n.fr
centre.wiki_translations = centre.i18n.en -- wiki's default translations
centre.user_translations = centre.i18n.en -- user's default translations

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

function centre.trackallversions(where, txt)
	centre.report_trackallversions = (centre.report_trackallversions or "function report_trackallversions ") .. tools.ta(where, txt)
	return centre.report_trackallversions -- search .all_versions
end

function centre.pcallrequire( version ) -- require() must not fail and block the page.
	if type(version) == "string" then
		version = string.gsub( version, centre.ModuleNS, "" ) -- normalize for always one "Module:"
		version = centre.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 centre.get_module_and_time(title, given_module) -- Get a descriptor of one loaded object
--	local get = centre.get_module_and_time("Module:Central")
	local get = {}
	local module, required, loaded
	if type(title) ~= "string" then
		get.title = title
		get.isNil = true
		return get
	end -- If not loaded, return only an empty get
	if given_module then -- of any type
		module = given_module
	else -- to require a new module
		loaded = centre.pcallrequire(title) -- require() must not fail and block the page if the module do not exists.
		if loaded 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
	get.module = module
	get.isNil = not module
	get.isLoaded = true and package.loaded[title]
	get.isFunction = (type(module) == "function")
	get.isTable = (type(module) == "table")
	get.i18n = module.i18n
	get.versions = module.versions
	get.isLibrary = ( not tools.is_in(centre.ModuleNS, title) ) and ( (type(module) == "table") or (type(module) == "function") )
	-- T000000 Rical task: Add module and library types in scribunto ?
	get.hasi18n = (type(module) == "table") and (type(get.i18n) == "table")
	get.versionName = string.gsub(get.title or "", centre.ModuleNS, "") -- full version name with perhaps /I18N
	get.versionTitle = centre.ModuleNS .. get.versionName -- normalize title like Module:module
	get.isModule = string.find(get.title, centre.ModuleNS) -- is module from title
	get.isI18N = true and string.find(get.title, "/I18N") -- is /I18N
	get.lessI18N = string.gsub(get.versionName, "/I18N", "") -- version without /I18N
--	get.versionI18N = get.lessI18N .. "/I18N" -- eventual /I18N module to search
	get.nowstamp = os.date("%Y%m%d%H%M%S") -- YYYYMMDDhhmmss from module
	
	if get.isLibrary then
		get.revistamp = "20010101000000" -- default libraries timestamp at 2000-01-01T00:00:00Z, see ISO 8601 time format
	else
		get.revistamp = mw.getCurrentFrame():preprocess( "{{REVISIONTIMESTAMP:" .. get.title .. "}}" )
	end
	-- Special for isModule, isLibrary, isFunction
	get.versionName = string.gsub(get.title or "", centre.ModuleNS, "") -- full version name with perhaps /I18N
	get.simplename = string.gsub(get.title or "", centre.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, centre.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 = centre.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.subI18N = centre.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
	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 = tools.str_vars("%1-%2-%3 %4:%5",
			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
	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 st, vr, fn, tb = tools.luatablecount(get.i18n, "i18n") -- count of internal .i18n
		get.i18n_text = st
		get.i18n_vars = vr
		get.i18n_func = fn
		get.i18n_tabs = tb
		-- get.i18n_varstabs = tools.str_vars("%1 texts in %2 languages", get.i18n_vars, get.i18n_tabs )
		local st2, vr2, fn2, tb2 = tools.luatablecount(centre.maini18n, "maini18n") -- count of maini18n
		get.maini18n_text = st2
		get.maini18n_vars = vr2
		get.maini18n_func = fn2
		get.maini18n_tabs = tb2
		vr2 = vr2 or 0
		tb2 = tb2 or 0
		local vr_tb = 0
		if tb2 == 0 then vr_tb = 0 else vr_tb = tonumber(vr2 / tb2) end
		vr2 = math.floor( vr_tb ) -- texts number in one language
	--	if type(txt) ~= "string" then txt = "tools_maini18n_languages_list" end
		get.i18n_varstabs = tools.str_vars("%1 texts in %2 languages, (all %3/%4)", get.i18n_vars, get.i18n_tabs, vr2, tb2)
	end --] ]
	centre.trackallversions("get_module_and_time " .. 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 centre.get_module_and_time(title) -- Get a descriptor of one loaded object

function centre.get_loaded_modules(tab) -- List all package.loaded modules
	local loaded_txt = ""
	local loaded_pack = {}
	local elem = {}
	local tab_to_report = package.loaded
	centre.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, centre.ModuleNS)
		if (type(module) == "table") then --  md and
			local get = centre.get_module_and_time(title, module)
			loaded_txt = loaded_txt .. " <b>" .. (get.title or "") .. "</b>, "
		--	elem.title = title
		--	centre.trackallversions("loaded_modules", get.known)
			loaded_pack = centre.add_tab_elem_to_sort(loaded_pack, title, get)
		end
	end
	if loaded_txt == loaded_txt_memo then loaded_txt = loaded_txt .. "The input table contains no module." end
	-- centre.loaded_descriptors = loaded_pack
	return loaded_pack, loaded_txt
end -- function centre.get_loaded_modules()

function centre.transdiff(trans1, trans2) -- Collect all differences of translations
	centre.translations_differences = centre.translations_differences or {}
	if trans1.dtrans and trans2.dtrans and (trans1.dlang == trans2.dlang) and (trans1.dkey == trans2.dkey) and (trans1.dtrans ~= trans2.dtrans) then
		table.insert(centre.translations_differences, { ["trans1"] = trans1, ["trans2"] = trans2 }) -- build a table with only languages codes
	end -- Only fast collect to treat later if needed.
end

--	Cumulate translations from modules or their /I18N sub-modules.
--	Add or replace translations texts or new languages in the centre.maini18n table.
function centre.addi18n(t, module_name, module_tab)
	t = t or ""
	centre.maini18n = centre.maini18n or {}
	local i18n, lang_N, text_N = {} ,0, 0
	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
	--	st, vr3, fn, tb = tools.luatablecount(i18n, "tab 3")
		lang_N, text_N = 0, 0
		for lang, trans in pairs(i18n) do -- For all languages of the mixed module or library
			if centre.maini18n[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
						centre.transdiff( -- Collect all differences of translations
							{ dlang = lang, dkey = key, dfile = "previous", dtrans = centre.maini18n[lang][key] },
							{ dlang = lang, dkey = key, dfile = module_name, dtrans = val} )
						centre.maini18n[lang][key] = val -- add or replace a field and its translation
						text_N = text_N + 1
					end
				end
				t = t .. tools.str_vars(", 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 centre.maini18n
				if mw.language.isKnownLanguageTag(lang) then -- only for languages known in wikis
					lang_N = lang_N + 1
					centre.maini18n[lang] = mw.clone(trans)
					text_N = text_N + #trans
					t = t .. tools.str_vars(", lang:<b>%1:%2:%3</b> ", lang, lang_N, text_N)
				end
			end
		end
	end
	local st, vr, fn, tb = tools.luatablecount(centre.maini18n, "centre.maini18n")
	t = t .. tools.str_vars(", 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 centre.addi18n(t, module_name, module_tab)

function centre.new_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 new_library below.
	else -- RunOnce
		return nil, "INTERNAL ERROR : new_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 centre "INTERNAL ERROR : new_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 centre.new_library(name, library, options)

function centre.bindlibraries() -- 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.
	tools.tracki18n("start.bind_libraries")
	centre.new_library("centre", centre) -- RunOnce
	tools.tracki18n("new_library:centre", centre)
	centre.new_library("tools", tools) -- RunOnce
	tools.tracki18n("new_library:tools", tools)
	centre.new_library("mathroman", mathroman) -- RunOnce
	tools.tracki18n("new_library:mathroman", mathroman)
	local name, name_I18N, tt, lang_N, text_N
	local nm = "<br>Libraries after new_library in bind_libraries() : "
	for name, library in pairs(package.loaded) do nm = nm .. name .. ", " end
	centre.tracki18n_new_libraries123 = nm or "no library in package.loaded"
	local t = ""
	t = t .. "\n* effective centre.bindlibraries : "
	centre.loaded_modules = centre.get_loaded_modules() -- List all libraries with internal .i18n translations
	for title, get in pairs(centre.loaded_modules) do
	--	if package.loaded[name] and package.loaded[name].i18n then
		if get.hasi18n and get.isLibrary then
			t = t .. "\n*" .. tools.ta("get.libraryName", get.libraryName) -- .. tools.ta("library", get.module)
			-- tools.ta("get.title", get.title) .. tools.ta("get.moduleName", get.moduleName) ..
			tt, lang_N, text_N = centre.addi18n(t, get.libraryName, get.module)
			t = t .. tools.form_i18n_counts(get.i18n, " <b>+ %1/%2</b>, ") -- N translations/languages
			-- (get.libraryName or get.moduleName or "missing") ..
			t = t .. tools.form_i18n_counts(centre.maini18n, " <b>all = %1/%2</b>, ") -- N translations/languages
			t = t .. "\n*" .. tools.ta("get.subI18N", get.subI18N) -- .. tools.ta("name", get.libraryName) -- .. tools.ta("name_I18N", name_I18N)
			local getI18N = centre.get_module_and_time(get.subI18N)
			tt, lang_N, text_N = centre.addi18n(t, getI18N.subI18N, getI18N.module)
			if getI18N.libraryName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
				t = t .. "\n*" .. tools.ta("I18N", getI18N.libraryName) -- .. tools.ta("name_I18N", name_I18N)
				t = t .. tools.form_i18n_counts(getI18N.i18n, getI18N.libraryName .. " <b>LB18+ %1/%2</b>, ") -- Count available translations and languages
				t = t .. tools.form_i18n_counts(centre.maini18n, " <b>all+ %1/%2</b>, ") -- N translations/languages
			end
		end
	end
	tools.tracki18n("end.bind_libraries")
	centre.bind_i18n_report = (centre.bind_i18n_report or ".") .. (t or ".")
	return t
end -- function centre.bindlibraries()

-- local tt, vers_test = centre.bindmodules(p, vers_test) -- The main module can bind all its sub-modules versions, and their /I18N sub-modules.
function centre.bindmodules(main_p, vers_test)
	local centre_maini18n_memo = centre.maini18n
	local tools_args_source_memo = tools.args_source
	local tools_args_config_memo = tools.args_config
	tools.args_source = new_source
	tools.args_config = args_config
	-- Bind all modules and versions, and their translations
	-- vers_test and loaded_versions are only for tests
	-- centre.bind_libraries() -- Bind translatable libraries before bind modules and versions.
	centre.T122752_detect_correct(t) -- Correct the bug T122752 : #invoke do not record the main module in package.loaded
	local t = "\n*<b>centre.bindmodules() 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
--	centre.main_versions = nil
--	centre.main_module = nil
	local main_module = nil -- To get first
	local main_versions = nil
	local versions = nil
	local title = nil
	local gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
	local package_loaded_gettitle = package.loaded[gettitle]
	centre.main_gettitle = gettitle
	t = t .. tools.ta("gettitle", gettitle)
	main_p = main_p or centre.T122752_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 .. tools.ta("main_p", main_p)
		if type(main_p) == "table" and type(main_p.versions) == "table" then -- For main modules using versions management
			title = centre.ModuleNS .. main_p.versions.versionName
			centre.main_p_versions_txt = "main_p.versions : "
			.. tools.ta("main_p.title", title) .. tools.ta("main_p.versionNumber", main_p.versions.versionNumber)
			.. tools.ta("main_p.sought", main_p.versions.sought) .. tools.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 maini18n_memo = mw.clone(centre.maini18n)
		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
			centre.gettitle_versions_txt = "getTitle.versions : "
			.. tools.ta("getTitle.versionName", main_p.versions.versionName) .. tools.ta("getTitle.versionNumber", main_p.versions.versionNumber)
			.. tools.ta("getTitle.sought", main_p.versions.sought) .. tools.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 = centre.pcallrequire(gettitle) -- require() must not fail and block the page.
		if module then-- Alert by a message in edit mode: the bug is already fixed.
			local maini18n_memo = centre.maini18n
			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 maini18n_memo = centre.maini18n
			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
		t = t .. tools.ta("centre.main_module", centre.main_module)
		main_versions = main_p.versions
		t = t .. tools.ta("centre.main_versions", centre.main_versions)
	end
	if not main_module then -- The main module is not found.
		return tools.error_color( tools.str_vars("tools_main_module_missing_err") )
	end
	centre.maini18n = centre_maini18n_memo -- Restore from centre.bindlibraries()
	tools.args_source = tools_args_source_memo -- Restore from tools.args_config_init()
	tools.args_config = tools_args_config_memo -- Restore from tools.args_config_init()
	-- In normal cases
	if type(centre) == "table" and type(centre.main_gettitle) == "string" then
		main_versions.versionName = string.gsub(centre.main_gettitle, centre.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>" .. tools.ta("main_module", main_module) .. tools.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 .. tools.ta("main_module", main_module) .. tools.ta("main_versions", main_versions)
		t = t .. tools.ta("sought", sought) .. tools.ta("known", known)
		t = t .. tools.ta("versionName", versionName) .. tools.ta("versionNumber", versionNumber)
	end
	t = t .. "<br>AFTER sought, known, versionName, versionNumber, version, main_module : "
	centre.bindmodules_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*bindmodules() 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 centre.bindmodules : " .. tools.ta("main_versions.versionName", main_versions.versionName)
	.. tools.ta("main_versions.sought", main_versions.sought) .. tools.ta("sought_maxn", sought_maxn)
	.. "<br>" .. tools.ta("main_versions.known", main_versions.known) .. tools.ta("known_max", known_max)
	for i = known_maxn, 1, -1 do -- alternatives = known_tab[i]
		alternatives = known_tab[i]
		alternatives = "," .. mw.text.trim(alternatives) .. "," -- normalize versions between ","
		alternatives = tools.compact_comma_list(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 tools.is_in_sp(altern, sought, ",") then
				normal = altern
				kind = "normal"
			end
			if v > 1 and tools.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 tools.is_in_sp(altern, vers_test.loaded_list, ",") then
					used = (used or "- ") .. altern .. ", "
				end
				name_I18N = altern .. "/I18N"
			else -- In normal run
				-- addi18n from te module itself
				get_i18n = centre.get_module_and_time(centre.ModuleNS .. altern)
				tt, lang_N, text_N = centre.addi18n(t, get_i18n.subI18N, get_i18n.module)
				if get_i18n.moduleName then -- get_i18n.i18n and	 -- addi18n eventual sub-module "/I18N"
					t = t .. "\n*" .. tools.ta("I18N", get_i18n.moduleName) -- .. tools.ta("name_I18N", name_I18N)
					t = t .. tools.form_i18n_counts(get_i18n.i18n, get_i18n.moduleName .. " <b>MD+ %1/%2</b>, ") -- Count available translations and languages
					t = t .. tools.form_i18n_counts(centre.maini18n, " <b>all+ %1/%2</b>, ") -- N translations/languages
				end
			end
			if vers_test then -- In versions management test
				if tools.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
				getI18N = centre.get_module_and_time(get_i18n.subI18N)
				alias = getI18N.alias
				tt, lang_N, text_N = centre.addi18n(t, getI18N.subI18N, getI18N.module)
				if getI18N.moduleName then -- getI18N.i18n and	 -- addi18n eventual sub-module "/I18N"
					t = t .. "\n*" .. tools.ta("I18N", getI18N.moduleName) -- .. tools.ta("name_I18N", name_I18N)
					t = t .. tools.form_i18n_counts(getI18N.i18n, getI18N.moduleName .. " <b>MDI18N+ %1/%2</b>, ") -- Count available translations and languages
					t = t .. tools.form_i18n_counts(centre.maini18n, " <b>all+ %1/%2</b>, ") -- N translations/languages
				end
			end
			centre.track_i18n_vers = (centre.track_i18n_vers or "") .. tools.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 = tools.compact_comma_list(vers_test.sought .. "," .. vers_test.known .. "," .. vers_test.used) -- .. "," .. loaded_report)
	--	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 tools.is_in_sp(name, vers_test.sought, ",") and not tools.is_in_sp(name, vers_test.known, ",") then
				errs = errs .. tools.err_add("tools_missing_versions_err", name, vers_test.versionName ) -- new
			--	tools_missing_versions_err		= "The module <b>%1</b> misses in the main module <b>%2</b>.",
			end
			if tools.is_in_sp(name, vers_test.used, ",") and not tools.is_in_sp(name, vers_test.sought, ",") then
				vers_test.replaced = vers_test.replaced .. name .. "," -- sought but not found
				errs = errs .. tools.err_add("tools_replaced_versions_err", name, vers_test.versionName ) -- new
			--	tools_replaced_versions_err		= "The module <b>%1</b> replaces the missing module in the main module <b>%2</b>.",
			--	tools_select_unknown_module_err = "Internal error: The missing Module:<b>%1</b> is replaced by the normal Module:<b>%2</b>.", -- versionsmanagement
			end
			if tools.is_in_sp(name, vers_test.alloncesort, ",") and not tools.is_in_sp(name, vers_test.known, ",") then
				vers_test.unknown = vers_test.unknown .. name .. "," -- sought but not found
				errs = errs .. tools.err_add("tools_unknown_versions_err", name, vers_test.versionName ) -- new
			--	tools_unknown_versions_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
		centre.main_module = main_module -- In normal cases and not in tests cases
		centre.main_versions = mw.clone(main_versions) -- In normal cases and not in tests cases
	end
	--
	local loaded_pack = loaded_pack or centre.get_loaded_modules() -- List all package.loaded modules			normal
	t = t .. "\n* centre.bindmodules END : " .. tools.ta("main_versions.used", main_versions.used) .. tools.ta("#loaded_pack", #loaded_pack)
	centre.bindmodules_start = t
	centre.required_descriptors = loaded_pack
	centre.bind_i18n_report = (centre.bind_i18n_report or ".") .. (t or ".")
	return t, main_versions -- loaded_pack, centre.track_i18n_vers
end -- function centre.bindmodules(main_p, vers_test) -- Bind all modules and versions and translations

function centre.add_tab_elem_to_sort(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 centre.add_tab_elem_to_sort(tab, name, elem)

-- for loaded_descriptors and other
function centre.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 centre.sort_table_in_insert_order(tab)

function centre.tab_elem_report(tab, key) -- simple text report of versions elements
	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* " .. tools.ta("i", i) .. tools.ta("title", elem.title)
			t = t .. tools.ta("versionName", elem.versionName) .. tools.ta("versionNumber", elem.versionNumber)
			t = t .. tools.ta("versionDate", elem.versionDate) .. tools.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* " .. tools.ta(key, k)
				t = t .. tools.ta("versionName", elem.versionName) .. tools.ta("versionNumber", elem.versionNumber)
				t = t .. tools.ta("versionDate", elem.versionDate) .. tools.ta("i18n_varstabs", elem.i18n_varstabs)
			end
		end
	end
	return t
end -- function centre.tab_elem_report(tab, key)

centre.track_i18n_vers = (centre.track_i18n_vers or "")

centre.trcPVG_txt = "<br><b>trcPVG</b> loaded_pack/loaded_vers/loaded_G "
function centre.trcPVG(id, loaded_pack, loaded_vers, loaded_G)
--	centre.i18n_trc = centre.i18n_trc .. "<br><b>" .. (id or "") .. "</b> t=" .. (t or "")
	local p, P, v, V, g, G = 0, 0, 0, 0, 0, 0
	loaded_pack = loaded_pack or centre.loaded_pack
	loaded_vers = loaded_vers or centre.loaded_vers
	loaded_G = loaded_G or centre.loaded_G
	for i, elem in pairs(loaded_pack or centre.loaded_pack or {} ) do
		p = p + 1
		if elem.isI18N then P = P + 1 end
	end
	for i, elem in pairs(loaded_vers or centre.loaded_pack or {} ) do
		v = v + 1
	--	if elem.isI18N then V = V + 1 end
	end
	for i, elem in pairs(loaded_G or centre.loaded_pack or {} ) do
	--	g = g + 1
	--	if elem.isI18N then G = G + 1 end
	end
	centre.trcPVG_txt = (centre.trcPVG_txt or "") .. tools.str_vars("%1 = <b>%2.%3/%4.%5/%6.%7</b> ", id, p, P, v, "", g, "")
	return
end -- function centre.trcPVG(id, loaded_pack, loaded_vers, loaded_G)

function centre.version_warning_normal_list(d) -- From alternatives forms version, title, alias, warning, normal
	local d = d.versions or centre.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 tools.is_in(altern, d.selector) then normal = altern ; version = altern end
		if v == 2 and tools.is_in(altern, d.selector) then normal = altern ; version = altern end
		if v > 2 and tools.is_in(altern, d.selector) then version = altern end
	end
	if version then
		d.version = string.gsub(version, centre.ModuleNS, "") -- version without Module:
		d.title = centre.ModuleNS .. d.version
		d.normal = normal
		d.normal_I18N = centre.ModuleNS .. normal .. "/I18N"
		d.normal_I18N = string.gsub(d.normal_I18N, "/I18N/I18N", "/I18N")
		d.version_I18N = centre.ModuleNS .. version .. "/I18N"
		d.version_I18N = string.gsub(d.version_I18N, "/I18N/I18N", "/I18N")
		d.alias = alias
		if d.normal and not tools.is_in(d.normal, d.norm or " ") then d.norm = (d.norm or " ") .. " " .. d.normal .. " " end
		if d.version and not tools.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 tools.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 tools.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 centre.version_warning_normal_list(d)

function centre.modules_binder_raz(calling_module) -- raz excess _G
	local v, d = centre.versions, d or centre.d or {} -- descriptor of submodule
	local all_versions
	-- Along the init process : First step, the main_versions execute : require ("Module:XYZ")
	d.N_submodules = 0
	if type(d.all_versions) == "string" then -- Cut and trim
		d.all_versions_split = mw.text.split(d.all_versions, "*", true)
		d.N_submodules = table.maxn(d.all_versions_split)
	end
	for i = 1, d.N_submodules do
		d.alternates = d.all_versions[i]
		d.alternates = mw.text.trim(d.alternates)
		d = v.version_warning_normal_list(d) -- From alternatives forms version, title, alias, warning, normal
		if d.title then -- if submodule realy required
			_G[d.alias] = nil
			_G[d.title] = nil
			_G[d.version] = nil
		end
	end
	d.missings = "" ; d.unknowns = "" ; d.normals = "" ; d.listall = "" ; d.selectall = "" ;
	d.warnings = "" ; d.loaded_list = "" ; d.excess = "" ;	d.aliases = "" ;
	v.d = d
	return d
end -- function centre.modules_binder_raz(calling_module)

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

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

-- To locate and to debug the bug T122752 in time, Rical lists here the versions of fr.wikisource.org/wiki/Module:ControlArgs1
-- 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)

function centre.T122752_detect_correct(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	 centre.T122752_detect_correct() verify centre.T122752_status
	-- before centre.bindmodules() bind all modules and versions, and their translations
	local t = t or ""
	local gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
	centre.main_gettitle = gettitle
	local module
	if type(package.loaded[gettitle]) == "table" then
		centre.T122752_status = "Resolved"
	else
		centre.T122752_status = "getmodule" -- "Open"
		module = centre.pcallrequire(gettitle) -- require() must not fail and block the page if the module do not exists.
		if module then -- Alert? by a message in edit mode: the bug is already fixed.
			centre.T122752_main_module = module
			centre.T122752_status = "module found" -- "Open"
		--	t = t .. tools.str_vars("tools_try_waits_debug_T122752", tostring(gettitle) ) .. "#invoke T122752 replaced by pcallrequire."
			t = t .. "\n* T122752_detect_correct: 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 centre.T122752_status = "get corrected Resolved" end
		else
			centre.T122752_status = "module missing" -- "Open" -- Main error, there is no main module.
		end
	end
	centre.T122752_status = centre.T122752_status or "nil status" -- "Open"
	return t
end -- function centre.T122752_detect_correct(t) -- Bypass the bug T122752 : #invoke do not record the main module in package.loaded

function centre.init(frame, args_known, mode_name, options_for_modes, itemid)
	-- Initializes translations, bindlibraries, bindmodules, get arguments, modes, options.
	-- Imports arguments from wikidata, module and template with increasing priorities.
	local res = ""
	tools.time1 = os.clock()
	tools.module_init(frame) -- Get tools.args_source with tools.args_config
	tools.init_wiki_user_lang(tools.args_config.wikilang, tools.args_config.userlang) -- init user and the wiki languages from args
	tools.init_tools(frame, options_for_modes) -- Needed to init categories and errors
	res = res .. centre.bindlibraries() -- Install central libraries, waiting true central libraries.
--	centre.bindmodules(p)
--	tools.tasks_table_report_full = tools.tasks_table_report()
--	tools.tasks_table_report_short = "\n*" .. tools.tasks_table_report("Tasks list and their states:", "short")
	centre.bindmodules()
	tools.module_init(frame) -- Get tools.args_source with tools.args_config
	res = res .. "\n* p.init centre.bindlibraries centre.bindmodules : "
	res = res .. "\n* " .. (centre.bind_i18n_report or ".")
	res = res .. tools.tracki18n("after centre.bindmodules", tools)
	local mode_name, source_key, try_lang =
	tools.get_arg_mode(mode_name, source_key, try_lang, args_source)
	tools.mode_name = mode_name or tools.mode_name or "read"
	tools.init_wiki_user_lang(tools.args_config.wikilang, tools.args_config.userlang) -- init user and the wiki languages from args
	local args_known = args_known or tools.args_known
	if type(args_known) == "table" then tools.args_known = args_known end
	local dockey = tools.args_config.dockey -- or tools.args_config[1]
	local itemid = itemid or tools.args_config.itemid -- or tools.args_config.id or tools.args_config[2]
	tools.init_tools(frame, options_for_modes) -- Raz categories and errors before full translations
	local mode_name = tools.get_arg_mode(mode_name, source_key)
	tools.change_itemid() -- "Q41568"
	tools.args_wikidata, t = tools.import_wikidata(tools.args_known, itemid)
	tools.tracki18n("after import_wikidata", tools.main_module)
	tools.time2 = os.clock()
	tools.options_for_modes = options_for_modes
	if type(tools.args_config.c) == "string" then
		tools.invoke_options = tools.args_config.c
	end
	if type(tools.args_config.mode) == "string" then
		mode_name = tools.args_source.mode
	end
	tools.invoke_options = tools.args_source.c or ""
	tools.mode_options = tools.options_from_mode(source_mode_name or mode_name)
	tools.args_import, t = tools.import_arguments()
	local mode_name = tools.get_arg_mode() -- (mode_name, source_key)
--	tools.report_bind_verif_modules_details = tools.bindmodules_report() -- Report all modules installation
	tools.bindmodules_report() -- Report all modules installation
	tools.tasks_table_report_full = tools.tasks_table_report()
	tools.tasks_table_report_short = "\n*" .. tools.tasks_table_report("Tasks list and their states:", "short")
	return res
end -- function centre.init(frame, args_known, mode_name, options_for_modes, itemid)

------------------------------------------------------------
-- End of Library:centre.
------------------------------------------------------------

------------------------------------------------------------------------------
--	Install Library:tools
--	This library support modules to:
--	* Form parametrable strings
--	* Translate parametrable texts, in wiki and user languages
--	* Add a language needs only to add its own i18n subtable, like i18n.en = {}
--	* Errors and messages: form one, collect them, display one or them
--	* Categories: form one or a group of categories. Collect, display and activate categories
--	* Support the bilingual categories, displaying in user language and linking in wiki language.
--
--	Libraries dependencies:
--	This Library:tools needs the Library:centre
--	The Library:mathroman and the Module:Author3 depend of this library.
------------------------------------------------------------------------------

--	local ctz = centre -- Share it under a local shortcut to distinct the definition and the use.

tools = {}
tools.i18n = {}

tools.i18n.en = {
	-- 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.",
	-- Groups of arguments
	tools_needed_to_verify			= "(required, to be checked)",
	tools_list_needed_args			= "List of needed arguments:",
	tools_list_all_config_arguments	= "List of all config arguments:",
	tools_list_all_system_arguments	= "List of all system arguments:",
	tools_list_all_other_args		= "List of all other arguments:",

	-- Main texts, errors and categories of tools
	language							= 'language',
	tools_user_wiki_lang_msg			= "Languages: user: %1, wiki: %2.",
	tools_DropBox_label_Unwrap			= "Unwrap/Wrap",
	tools_language_cat					= 'Speaking %1',
	tools_date_months_names				= "January, February, March, April, May, June, July, August, September, October, November, December",
	tools_date_to_part_format			= "dd yyyy mmmm",
	tools_date_to_part_call_err			= "Internal Error: Abnormal calling arguments in date <b>%1</b>.",
	tools_date_to_part_call_cat			= "Module with internal error",
	tools_date_to_part_not_found_err	= "Internal Error: No part found in date <b>%1</b>.",
	tools_test_OK_cat					= "Test category generation OK",
	tools_module_usage_error_cat		= "Module with usage error",
	tools_module_internal_error_cat		= "Module with internal error",
	tools_module_with_error_cat			= "Module with error",
	tools_module_with_error_err			= "Module with error",

	-- Arguments management
	tools_error_list_header_err			= "User support about parameters:",
	tools_need_arg_value_err			= "Error: This argument is required but absent : <b>%1</b>. Should define it.",
	tools_none_value_err				= "Error: No argument has been defined.",
	tools_unknown_argument_err			= "Error: parameter <b>%1</b> is unknown in this template. Check the name or flag this gap.",
	tools_too_unnamed_arguments_err 	= "Error: This unnamed argument is too many: <b>%1</b> = <b>%2</b>.",
	tools_internal_notice_wsid_err		= "Internal Error: Notify the developer that the internal argument <b>%1</b> is unknown in the records.",
	tools_without_translation_err		= "Known argument, but not translated: <b>%1</b>.",
	tools_without_translation_N_err 	= "There are %1 arguments untranslated.",
	tools_is_defined_err				= "The argument %1:<b>%2</b> is defined.",
	tools_is_undefined_err				= "The argument %1:<b>%2</b> is not defined.",
	tools_args_values_err				= "Abnormal value of the argument <b>%1 = %2</b> including (%3) ",
	
	-- Wikidata
	tools_wikidata_wikibase_err			= "Error: Wikibase is not available.",
	tools_wikidata_getEntity_err		= "Error: getEntity Wikidata is not available.",
	tools_wikidata_getEntityObject_err	= "Error: Element Wikidata <b>%1</b> is not found.",
	tools_wikidata_property_err			= "Error: Wikidata property <b>%1</b> is not found.",
	tools_wikidata_error_err			= "Error Wikidata: <b>%1</b> ",
--	tools_wikidata_cat_err				= "Error Wikidata",
	tools_wikidata_cat_err				= "Module with internal error",	 -- tools_module_internal_error_cat
	tools_structured_data_txt			= 'Datas from Wikidata',
	tools_wikidata_any_page_title		= "Wikidata for any page:",
	tools_wikidata_details_test_title	= "Tests and imported datas from wikidata",
	tools_wikidata_arbitrary_test_title = "Test of Wikidata arbitrary access",
	tools_wikidata_arbitrary_access_text= "Wikidata for another Title: ",
	tools_wikidata_time_details			= "Wikidata time properties for more details",
	
	-- Versions management
	tools_versions_no_select_altern_err = "Versions error: no selection in the alternative <b>%1</b> of the module <b>%2</b>.",
	tools_versions_too_select_altern_err= "Versions error: <b>%1</b> selections in the alternative <b>%2</b> of the module <b>%3</b>.",
	tools_versions_select_not_used_err	= "Versions error: <b>%1</b> selector is not in alternative <b>%2</b> of the module <b>%3</b>.",
	tools_versions_missing_module_err	= "Versions error: missing module for the version <b>%1</b> of the module <b>%2</b>.",
	tools_main_module_missing_err		= "The main module is not found.",
	tools_select_unknown_module_err 	= "Internal error: The missing Module:<b>%1</b> is replaced by the normal Module:<b>%2</b>.",
	tools_I18N_module_no_base_err		= "The <b>%1</b> translations module has no basic version.",
	tools_no_versions_module_err		= "The module <b>%1</b> is not in the system of versions.",
	tools_all_versions_tests			= "Versions warning: <b>%1</b>, normal: <b>%2</b>, listall: <b>%3</b>.",
	tools_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>.",
	tools_missing_versions_err			= "The module <b>%1</b> misses in the main module <b>%2</b>.",
	tools_replaced_versions_err			= "The module <b>%1</b> replaces the missing sub-module in the main module <b>%2</b>.",
	tools_unknown_versions_err			= "The module <b>%1</b> is unknown in the main module <b>%2</b>.",

	-- Miscellaneous messages and errors
	tools_maini18n_languages_list		= "This module can translate <b>%1</b> sentences in <b>%2</b> languages: ",
	tools_nearest_argument_err			= "Error: Do you need the known argument <b>%1</b> ?",
	tools_max_nearest_argument_msg		= "A longer name argument accepts more letter errors.",
	tools_value_re_defined_err			= "Error: The value of the argument <b>%1</b> is already defined. Choose only one value of a single synonymous.",
	tools_table_listlimit_levelmaxi 	= "Level limit levelmaxi = <b>%1</b> ",
	tools_table_listlimit_max_n			= "Length limit max_n = <b>%1</b> ",
	tools_i18n_list_all_texts			= "This list show all the texts, but cannot replace the original declarations.",
	tools_languages_nbr_and_list		= "\n* There are %1 tables of translations in these languages : %2 <br>",
	tools_module_miss_i18n_txt_err		= "Internal Error: The text <b>%1</b> lack of translation in <b>%2</b> language, and/or others.",
	tools_module_miss_i18n_count_err	= "Internal Error: There are <b>%1</b> missings in <b>%2</b> translations.",
	tools_module_miss_i18n_none_err 	= "OK, none missing translations.",
	tools_texts_translated_in_langs 	= "There are %1 texts translated in %2 languages.",
	tools_module_miss_i18n_trad_err 	= "Internal Error: Module missing i18n translation for the argument <b>%1</b> ",
--	tools_err_module_miss_i18n_cat		= "Module missing i18n translation",
	tools_err_module_miss_i18n_cat		= "Module with internal error",	 -- tools_module_internal_error_cat
	tools_module_miss_i18n_mini_err 	= "Error: I18n table has only '''1%''' translations.",
--	tools_no_args_known_err				= "Error: Module without known arguments table.",
--	tools_no_args_source_err			= "Error: Module without source arguments table.",
	tools_no_wiki_translations_err		= "Error: Module without translated arguments table.",
	tools_no_args_wikidata_err			= "Error: Module without wikidata arguments table.",
	tools_lang_table_err				= "Error: The <b>%1</b> language or its table is incorrect.",
--	tools_lang_table_err_cat			= "Module with erroneous language arguments",
	tools_lang_table_err_cat			= "Module with internal error",	 -- tools_module_internal_error_cat
	tools_lang_not_exist_err			= "Error: The language <b>%1</b> is not available.",
	tools_lang_not_translated_err		= "Error: The language <b>%1</b> is not translated.",
	tools_generDoc1_paramName_err		= "Internal Error: in generDoc1, bad argument <b>%1</b>.",
	tools_sources_of_datas				= "Informations from: /Wikidata, /template or module, /other, /message, /error",
	tools_list_in_translated_lang		= "\n* %1 translations in language %2 : %3 : %4",
	tools_luatable_counts				= "The table <b>%1</b> counts <b>%2</b> texts, <b>%3</b> functions and <b>%4</b> sub-tables.",
	tools_table_dont_exists_err			= "The table <b>%1</b> does not exist.",
	tools_auto_val_unknown_err			= "Internal Error: Unknown automatic argument: %1 = <b>%2</b>.",
	--
	tools_delete_docbox_msg				= "You must remove this documentation before recording.<br>Remove all modes to return to read mode.",
	tools_auto_val_warning_msg			= "Verify the automatic argument: %1 = <b>%2</b>.",
	--
	tools_assist_user_param_err			= "User support for checking the settings:",
	tools_module_error_err				= "Module with error.",
	tools_no_known_arguments_err		= "Module without known arguments table.",
	tools_unknown_auto_arg_err			= "Internal Error: Unknown automatic argument: %1 = <b>%2</b>.",
	tools_args_values_err				= "Abnormal value of the argument <b>%1 = %2</b> (%3)",
	--
	tools_no_known_arguments_err		= "Internal Error: Module without known arguments table.",
--	tools_tools_no_known_args_cat		= "Module without known arguments table.",
	tools_tools_no_known_args_cat		= "Module with internal error",	 -- tools_module_internal_error_cat
	tools_no_source_arguments_err		= "Internal Error: Module without source arguments table.",
--	tools_no_source_arguments_cat		= "Module without source arguments table.",
	tools_no_source_arguments_cat		= "Module with internal error",	 -- tools_module_internal_error_cat

	-- Titles of tests
	tools_page_tests_h3_title			= "Tests of this page",
	tools_page_tests_title				= "Testing and information related to this page:",
	tools_internal_tests_h3_title		= "Internal tests",
	tools_internal_tests_title			= "Internal regression tests:",
	tools_dropdown_missing_title		= "Missing title for this dropbox",
	tools_used_options_title			= "Options and their uses:",
	tools_options_from_mode_title		= "Test options from modes",
	tools_options_from_args_title		= "Test options from arguments",
	tools_spaces_page_names_title		= "Module, namespaces, and page names:",
	tools_luatables_counts_title		= "Counts of contents in tables",
	tools_list_all_args_title			= "List of all accepted arguments",
	tools_support_desk_title			= "Support desk",
	tools_bindmodules_report_title		= "Detailed effective versions",
	tools_versions_manage_tests_title	= "Versions management test",
--	tools_unused_modules_tests_title	= "Tests of UNUSED selection of modules versions",
	tools_versions_management_title		= "Versions management test",
	tools_versionsmanagement_report		= "Versions management report",
	tools_bindmodules_test_title		= "Test for management of modules versions",
	tools_verif_bindmodules_report		= "Binding of modules and versions control",
	tools_bindmodules_test_headers		= "Sought versions, Used versions, Known versions, ACTUAL: Errors in the active module, SIMULATION: Errors in the active module",
	tools_transdiff_report_title		= "Compare all differences in translations",
	tools_transdiff_report_headers		= "Language, Key, Previous translation, New translation, Module",
	tools_documentations_changes_report = "Documentations, tasks and changes",
	tools_list_loaded_modules_headers	= "Title, Version, Date, i18n, Translations / Languages",
	tools_date_to_part_test_title		= "Tests of date to day, month, year or era",
	tools_cat_add_test_title			= "Test the creation of categories in some languages",
	tools_levenshtein_test_title		= "Test the distances between words '''[https://en.wikipedia.org/wiki/Levenshtein_distance Levenshtein]''':",
	tools_table_args_source_title		= "Table of received arguments, args_source:",
	tools_table_args_unknown_title		= "Table of unknown arguments, args_unknown:",
	tools_args_known_structure_title	= 'Table structure of arguments',
	tools_missing_translations_title	= "Translations missings and changes in i18n tables:",
	tools_similar_args_test_title		= "Test close words similar_args:",
	tools_test_luatable_lister_title	= "List a table, test without limits:",
	tools_luatable_tests_limits_title	= "List a table, test with limits:",
	tools_mixed_translations_title		= "Combined i18n translations tables:",
	tools_dummy_languages_title			= "Language Definitions:",
	tools_time_format_test_title		= "Tests of coding and conversion of dates",
	tools_normal_box_main_text_1		= "Simple infobox: page title: <b>%1</b>, description: <b>%2</b>",
	tools_normal_box_main_text_2		= "<br/>He was <b>%1</b>, named <b>%2</b> <b>%3</b> was dead in <b>%4</b>.",
	tools_try_waits_debug_T122752		= "#invoke has not loaded <b>%1</b>, but the debug of T122752 has loaded it.",
	tools_tasks_table_report_title		= "States table of known tasks",
	tools_tasks_table_report_short		= "Short states of known tasks",
	tools_tasks_table_report_headers	= "Number, Status, Title",

	-- These texts are used to title some tests.
	tools_list_wiki_selectors_title		= "List of this wiki selectors:",
	tools_list_all_categories_title		= "List of all eventual categories of this wiki:",
	tools_list_all_errors_title			= "List of all detectable errors in this wiki:",
	tools_multiple_values_tests_title	= "Test of multiple values arguments",
	tools_multiple_selection_test_title	= "Multiple selection test",
	tools_multiple_selection_test_header= "Options,Selector,To select,Selected",
	tools_multiple_selection_test_select= "2,nobel,president,deputy,price",

} -- tools.i18n.en

tools.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.",
	-- Grupos de argumentos
	tools_needed_to_verify			= "(obligatorio, se debe comprobar)",
	tools_list_needed_args			= "Lista de argumentos necesarios:",
	tools_list_all_config_arguments	= "Lista de argumentos de configuración:",
	tools_list_all_system_arguments	= "Lista de argumentos del sistema:",
	tools_list_all_other_args		= "Lista de los otros argumentos:",
	
	-- Textos principales, errores y categorías de instrumentos
	language						= 'lenguaje',
	tools_user_wiki_lang_msg		= "Idiomas: usuario: %1, wiki: %2.",
	tools_DropBox_label_Unwrap		= "Desenvolver/Envolver",
	tools_language_cat				= 'Hablando %1',
	tools_date_months_names			= "Enero, Febrero, Marzo, Abril, Mayo, Junio​​, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre",
	tools_date_to_part_format			= "dd mmmm yyyy",
	tools_date_to_part_call_err			= "Error interno: argumentos de llamadas anómala en fecha <b>%1</b>.",
	tools_date_to_part_call_cat			= "Módulo con error interno",
	tools_date_to_part_not_found_err	= "Error interno: No se encuentra en fecha <b>%1</b>.",
	tools_test_OK_cat					= "Prueba generación categoría en OK",
	tools_module_usage_error_cat		= "Módulo con error del uso",
	tools_module_internal_error_cat		= "Módulo con error interno",
	tools_module_with_error_cat			= "Módulo con error",
	tools_module_with_error_err			= "Módulo con error",

	-- Control de argumentos
	tools_error_list_header_err			= "Asistencia de los parámetros de este modelo:",
	tools_need_arg_value_err			= "Error: Usted tiene que fijar este argumento necesario, pero falta: <b>%1</b>.",
	tools_none_value_err				= "Error: No hay argumento se ha definido.",
	tools_unknown_argument_err			= "Error: El parámetro <b>%1</b> es desconocido en este modelo. Compruebe el nombre o reportar esta falta.",
	tools_too_unnamed_arguments_err 	= "Error: Demasiado argumento sin nombre: <b>%1</b> = <b>%2</b>.",
	tools_internal_notice_wsid_err		= "Error interno: Informar al el desarrollador que el argumento interno <b>%1</b> es desconocido en los registros.",
	tools_without_translation_err		= "Argumento conocido, pero no traducido: <b>%1</b>.",
	tools_without_translation_N_err 	= "Hay 1% argumentos no traducidos.",
	tools_is_defined_err				= "El argumento %1:<b>%2</b> está definido.",
	tools_is_undefined_err				= "El argumento %1:<b>%2</b> no está definido.",
	tools_args_values_err				= "Valor anormal del argumento <b>%1 = %2</b> entre: (%3) ",
	
	-- Wikidata
	tools_wikidata_wikibase_err			= "Erreur : Wikibase n'est pas disponible.",
	tools_wikidata_getEntity_err		= "Erreur : getEntity Wikidata n'est pas disponible.",
	tools_wikidata_getEntityObject_err	= "Error: Elemento <b>%1</b> de Wikidata no se encuentra.",
	tools_wikidata_property_err			= "Erreur : La propriété <b>%1</b> de Wikidata n'est pas trouvée.",
	tools_wikidata_error_err			= "Erreur Wikidata : <b>%1</b> ",
	tools_wikidata_cat_err				= "Module with internal error",	 -- tools_module_internal_error_cat
	tools_structured_data_txt			= 'Datos de Wikidata',
	tools_wikidata_any_page_title		= "Wikidata para cualquier página:",
	tools_wikidata_details_test_title	= "Pruebas y datos importados de wikidata",
	tools_wikidata_arbitrary_test_title = "Prueba de Wikidata acceso arbitrario",
	tools_wikidata_arbitrary_access_text= "Wikidata por otro título: ",
	tools_wikidata_time_details			= "Wikidata propiedades de tiempo para más detalles",

	-- Gestión de versiones
	tools_versions_no_select_altern_err = "Error de versiones: no hay selección en alternativa <b>%1</b> del módulo <b>%2</b>.",
	tools_versions_too_select_altern_err= "Error de versiones: <b>%1</b>  selecciones en la alternativa <b>%2</b> del módulo <b>%3</b>.",
	tools_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>.",
	tools_versions_missing_module_err	= "Error de versiones: módulo ausente para la versión <b>%1</b> del módulo <b>%2</b>.",
	tools_main_module_missing_err		= "El módulo principal no es encontrado.",
	tools_select_unknown_module_err 	= "Internal error: El Modulo:<b>%1</b> faltante se reemplaza por lo normal Module:<b>%2</b>.",
	tools_I18N_module_no_base_err		= "El módulo de traducciones <b>%1</b> no tiene ninguna versión básica.",
	tools_no_versions_module_err		= "El <b>%1</b> módulo no está en el sistema de versiones.",
	tools_all_versions_tests			= "Versiones aviso: <b>%1</b>, normal: <b>%2</b>, lista: <b>%3</b>.",
	tools_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>.",
	tools_missing_versions_err			= "El módulo <b>%1</b> no disponible en el módulo principal <b>%2</b>.",
	tools_replaced_versions_err			= "El módulo <b>%1</b> reemplaza el sub-módulo ausente en el módulo principal <b>%2</b>.",
	tools_unknown_versions_err			= "El módulo <b>%1</b> es desconocido en el módulo principal <b>%2</b>.",
	
	-- Titulos de pruebas
	tools_support_desk_title			= "Escritorio de ayuda",
	tools_bindmodules_report_title		= "Versiones efectivas detalladas",
	tools_versions_manage_tests_title	= "Prueba de la gestión de versiones",
--	tools_unused_modules_tests_title	= "Pruebas de selección NO UTILIZADA de versiones de módulos",
	tools_versionsmanagement_report		= "Informe de gestión del versiones",
	tools_versions_management_title		= "Prueba de gestión de versiones",
	tools_bindmodules_test_title		= "Prueba de la gestión de versiones de módulos",
	tools_verif_bindmodules_report		= "Enlace de los módulos y de control de versións",
	tools_bindmodules_test_headers		= "Versiones solicitadas, Versiones utilizadas, Versiones conocidas, REALES: Errores en el módulo activo, SIMULACIÓN: Errores en el módulo activo",

	-- Diversos mensajes y errores
	tools_maini18n_languages_list		= "Este módulo puede traducir <b>%1</b> frases en <b>%2</b> idiomas: ",
	tools_nearest_argument_err			= "Error : ¿Es usted conocido argumento <b>%1</b> ?",
	tools_max_nearest_argument_msg		= "Un argumento nombre más largo acepta más letras errores.",
	tools_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",
	tools_language_cat					= 'Habla %1',
	occupation_cat						= '%1',
	tools_sources_of_datas				= "Informacións de: /Wikidata, /modelo o módulo, /otros, /mensaje, /error",
	tools_languages_nbr_and_list		= "\n* Hay %1 mesas de traducciones en estas idiomas: %2 <br>",
	tools_list_in_translated_lang		= "\n* %1 traducciones en idioma %2 : %3 : %4",
	tools_module_miss_i18n_none_err 	= "OK, ninguno traducciones faltan.",
	tools_texts_translated_in_langs 	= "Hay %1 textos traducidos en 2% idiomas.",
	tools_module_miss_i18n_trad_err 	= "Error interno: falta de traducción para el argumento <b>%1</b> ",
--	tools_err_module_miss_i18n_cat		= "Módulo falta de traducción i18n",
	tools_err_module_miss_i18n_cat		= "Módulo con error interno", -- tools_module_internal_error_cat
	tools_i18n_list_all_texts			= "Esta lista muestra todos los textos, pero no puede sustituir a las declaraciones originales.",
	tools_luatable_counts				= "La tabla <b>%1</b> cuenta <b>%2</b> textos, <b>%3</b> funciones y <b>%4</b> sub-tablas.",
	tools_table_dont_exists_err			= "La tabla <b>%1</b> no existe.",
	--
	tools_delete_docbox_msg				= "Debe quitar esta documentación antes de grabar.<br>Retire todos los modos para volver al modo read.",
	tools_auto_val_warning_msg			= "Verifique el argumento automático: %1 = <b>%2</b>.",
	--
	tools_assist_user_param_err			= "Soporte al usuario para comprobar la configuración:",
	tools_module_error_err				= "Módulo con error.",
	tools_auto_val_unknown_err			= "Error interno: Argumento desconocido automático: %1 = <b>%2</b>.",
	tools_no_known_arguments_err		= "Módulo sin argumentos tabla conocida.",
	tools_unknown_auto_arg_err			= "Error interno: Argumento desconocido automático: %1 = <b>%2</b>.",
	tools_args_values_err				= "Valor anormal del argumento <b>%1 = %2</b> (%3)",
	--
	tools_no_known_arguments_err		= "Error interno: Módulo sin tabla de argumentos conocidos.",
	tools_tools_no_known_args_cat		= "Módulo con error interno", -- tools_module_internal_error_cat
	tools_no_source_arguments_err		= "Error interno: Módulo sin tabla de argumentos fuentes.",
	tools_no_source_arguments_cat		= "Módulo con error interno", -- tools_module_internal_error_cat

	-- Titres des pruebas
	tools_page_tests_h3_title			= "Pruebas de esta página",
	tools_page_tests_title				= "Pruebas y información relacionada con esta página:",
	tools_internal_tests_h3_title		= "Pruebas internas",
	tools_internal_tests_title			= "Pruebas de no regresión internas:",
	tools_dropdown_missing_title		= "Falta el título para este dropbox",
	tools_used_options_title			= "Opciones y sus usos:",
	tools_options_from_mode_title		= "Prueba de opciones de modos",
	tools_options_from_args_title		= "Prueba de opciones de argumentos",
	tools_spaces_page_names_title		= "Módulo, namespaces, y nombres de páginas:",
	tools_luatables_counts_title		= "Condes des contenidos en tablas",
	tools_list_all_args_title			= "Lista de todos los argumentos aceptados",
	
	
	tools_transdiff_report_title		= "Comparar todas las diferencias en las traducciones",
	tools_transdiff_report_headers		= "Idioma, Clave, Traducción anterior, Nueva traducción, Módulo",
	tools_documentations_changes_report = "Documentación, parches y modificaciones",
	tools_list_loaded_modules_headers	= "Título, Versión, Fecha, i18n, Traducciones / Idiomas",
	tools_date_to_part_test_title		= "Pruebas de fecha a día, mes, año o época",
	tools_cat_add_test_title			= "Pruebe la creación de categorías en algunos idiomas",
	tools_levenshtein_test_title		= "Prueba de las distancias entre las palabras '''[https://es.wikipedia.org/wiki/Distancia_de_Levenshtein Levenshtein]''':",
	tools_table_args_source_title		= "Tabla de argumentos recibido, args_source:",
	tools_table_args_unknown_title		= "Tabla de argumentos desconocidos, args_unknown:",
	tools_args_known_structure_title	= 'Estructura de la tabla del argumentos',
	tools_missing_translations_title	= "Traducciones que faltan o cambiando en las tablas i18n:",
	tools_similar_args_test_title		= "Prueba de palabras cercanos similar_args:",
	tools_test_luatable_lister_title	= "Enumerar una tabla, prueba sin límites:",
	tools_luatable_tests_limits_title	= "Enumerar una tabla, prueba con límites:",
	tools_mixed_translations_title		= "Tablas traducciones i18n combinadas:",
	tools_dummy_languages_title			= "Definiciones de idiomas:",
	tools_time_format_test_title		= "Pruebas de codificación y conversión fechas",
	tools_normal_box_main_text_1		= "Simple caja de información: Título de la página: <b>%1</b>, descripción: <b>%2</b>",
	tools_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>.",
	tools_try_waits_debug_T122752		= "#invoke no ha cargado <b>%1</b>, pero la depuración de T122752 ha lo cargó.",
	tools_tasks_table_report_title		= "Tabla de estados de tareas conocidas",
	tools_tasks_table_report_short		= "Estados breves del tareas conocidas",
	tools_tasks_table_report_headers	= "Número, Estado, Título",

	-- Estos textos se utilizan para titular algunas pruebas.
	tools_list_wiki_selectors_title		= "Lista de las selectores de este wiki:",
	tools_list_all_categories_title		= "Lista de todas las posibles categorías de esta wiki:",
	tools_list_all_errors_title			= "Lista de todos los errores detectables de este wiki:",
	tools_multiple_values_tests_title	= "Prueba de múltiples valores argumentos",
	tools_multiple_selection_test_title	= "Prueba de selección múltiple",
	tools_multiple_selection_test_header= "Opciones,Selector,Para seleccionar,Seleccionadas",
	tools_multiple_selection_test_select= "2,nobel,presidente,diputado,precio",

} -- tools.i18n.es

tools.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.",
	-- Groupes d'arguments
	tools_needed_to_verify			= "(obligatoire, à vérifier)",
	tools_list_needed_args			= "Liste des arguments nécessaires :",
	tools_list_all_config_arguments	= "Liste des arguments de configuration :",
	tools_list_all_system_arguments	= "Liste des arguments système :",
	tools_list_all_other_args		= "Liste des autres arguments :",
	
	-- Principaux textes, erreurs et catégories des outils
	language							= 'langue',
	tools_user_wiki_lang_msg			= "Langues : utilisateur : %1, wiki : %2.",
	tools_DropBox_label_Unwrap			= "Dérouler/Enrouler",
	tools_language_cat					= 'Parle %1',
	tools_date_months_names				= "Janvier, Février, Mars, Avril, Mai, Juin, Juillet, Août, Septembre, Octobre, Novembre, Décembre",
	tools_date_to_part_format			= "dd yyyy mmmm",
	tools_date_to_part_call_err			= "Erreur interne : Argument anormal de définition de date <b>%1</b>.",
	tools_date_to_part_call_cat			= "Module avec erreur interne",
	tools_date_to_part_not_found_err	= "Erreur interne : partie non définie de date <b>%1</b>.",
	tools_test_OK_cat					= "Test de génération de catégorie OK",
	tools_module_usage_error_cat		= "Module avec erreur d'utilisation",
	tools_module_internal_error_cat		= "Module avec erreur interne",
	tools_module_with_error_cat			= "Module avec erreur",
	tools_module_with_error_err			= "Module avec erreur",

	-- Gestion des arguments
	tools_error_list_header_err			= "Assistance sur les paramètres de ce modèle :",
	tools_need_arg_value_err			= "Erreur : Vous devez définir cet argument nécessaire mais absent : <b>%1</b>.",
	tools_none_value_err				= "Erreur : Aucun argument n'a été défini.",
	tools_unknown_argument_err			= "Erreur : Le paramètre <b>%1</b> est inconnu dans ce modèle. Vérifier ce nom ou signaler ce manque.",
	tools_too_unnamed_arguments_err 	= "Erreur : Ce paramètre non nommé est en trop : <b>%1</b> = <b>%2</b>.",
	tools_internal_notice_wsid_err		= "Erreur interne : Signaler au développeur que l'argument interne <b>%1</b> est inconnu dans les notices.",
	tools_without_translation_err		= "Argument connu, mais non traduit : <b>%1</b>.",
	tools_without_translation_N_err 	= "Il y a %1 arguments non traduits.",
	tools_is_defined_err				= "L'argument %1:<b>%2</b> est défini.",
	tools_is_undefined_err				= "L'argument %1:<b>%2</b> n'est pas défini.",
	tools_args_values_err				= "Valeur anormale de l'argument <b>%1 = %2</b> parmi : (%3) ",
	
	-- Wikidata
	tools_wikidata_wikibase_err			= "Erreur : Wikibase n'est pas disponible.",
	tools_wikidata_getEntity_err		= "Erreur : getEntity Wikidata n'est pas disponible.",
	tools_wikidata_getEntityObject_err	= "Erreur : L'élément <b>%1</b> de Wikidata n'est pas trouvé.",
	tools_wikidata_property_err			= "Erreur : La propriété <b>%1</b> de Wikidata n'est pas trouvée.",
	tools_wikidata_error_err			= "Erreur Wikidata : <b>%1</b> ",
	tools_wikidata_cat_err				= "tools_module_internal_error_cat",
--	tools_wikidata_cat_err				= "Module with internal error",	 -- tools_module_internal_error_cat
	tools_structured_data_txt			= 'Données de Wikidata',
	tools_wikidata_any_page_title		= "Wikidata pour une autre page :",
	tools_wikidata_details_test_title	= "Tests et données importées de wikidata",
	tools_wikidata_arbitrary_test_title = "Test de Wikidata accès arbitraire",
	tools_wikidata_arbitrary_access_text= "Wikidata pour une autre page: ",
	tools_wikidata_time_details			= "Wikidata propriétés de temps pour plus de détails",

	-- Gestion des versions
	tools_versions_no_select_altern_err = "Erreur de versions : pas de sélection dans l'alternative <b>%1</b> du module <b>%2</b>.",
	tools_versions_too_select_altern_err= "Erreur de versions : <b>%1</b> sélections dans l'alternative <b>%2</b> du module <b>%3</b>.",
	tools_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>.",
	tools_versions_missing_module_err	= "Erreur de versions : module absent pour la version <b>%1</b> du module <b>%2</b>.",
	tools_main_module_missing_err		= "Le module principal est introuvable.",
	tools_select_unknown_module_err 	= "Internal error: Le Module:<b>%1</b> manquant est remplacé par le module normal Module:<b>%2</b>.",
	tools_I18N_module_no_base_err		= "Le module de traductions <b>%1</b> n'a pas de version de base.",
	tools_no_versions_module_err		= "Le module <b>%1</b> n'est pas dans le système des versions.",
	tools_all_versions_tests			= "Versions avertissements : <b>%1</b>, normaux : <b>%2</b>, liste : <b>%3</b>.",
	tools_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>.",
	tools_missing_versions_err			= "Le module <b>%1</b> manque dans le module principal <b>%2</b>.",
	tools_replaced_versions_err			= "Le module <b>%1</b> remplace le sous-module absent dans le module principal <b>%2</b>.",
	tools_unknown_versions_err			= "Le module <b>%1</b> est inconnu dans le module principal <b>%2</b>.",

	-- Messages et erreurs divers
	tools_maini18n_languages_list		= "Ce module peut traduire <b>%1</b> phrases en <b>%2</b> langues: ",
	tools_nearest_argument_err			= "Erreur : Voulez vous l'argument connu <b>%1</b> ?",
	tools_max_nearest_argument_msg		= "Un nom d'argument plus long accepte plus d'erreurs de lettres.",
	tools_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.",
	tools_table_listlimit_levelmaxi 	= "Limite de niveau levelmaxi = <b>%1</b> ",
	tools_table_listlimit_max_n			= "Limite de longueur max_n = <b>%1</b> ",
	tools_i18n_list_all_texts			= "Cette liste montre tous les textes, mais ne peut remplacer les déclarations originales.",
	tools_languages_nbr_and_list		= "\n* Il y a %1 tables de traductions dans ces langues : %2 <br>",
	tools_module_miss_i18n_txt_err		= "Erreur interne : Le texte <b>%1</b> manque de traduction en langue <b>%2</b>, et/ou d'autres.",
	tools_module_miss_i18n_count_err	= "Erreur interne : Il y a <b>%1</b> manques parmi <b>%2</b> traductions.",
	tools_module_miss_i18n_none_err 	= "OK, aucune traduction manquante.",
	tools_texts_translated_in_langs 	= "Il y a %1 textes traduits en %2 langues.",
	tools_module_miss_i18n_trad_err 	= "Erreur interne : manque de traduction pour l'argument <b>%1</b> ",
--	tools_err_module_miss_i18n_cat		= "Module manquant de traduction i18n",
	tools_err_module_miss_i18n_cat		= "Module avec erreur interne", -- tools_module_internal_error_cat
	tools_module_miss_i18n_mini_err 	= "Erreur interne : La table i18n n'a que <b>%1</b> traductions.",
--	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.",
	tools_no_wiki_translations_err		= "Erreur interne : Module sans table d'arguments traduits.",
	tools_no_args_wikidata_err			= "Erreur interne : Module sans table d'arguments wikidata.",
	tools_lang_table_err				= "Erreur interne : La langue <b>%1</b> ou sa table est erronée.",
--	tools_lang_table_err_cat			= "Module avec langue d'arguments erronée",
	tools_lang_table_err_cat			= "Module avec erreur interne", -- tools_module_internal_error_cat
	tools_lang_not_exist_err			= "Erreur : La langue <b>%1</b> n'est pas disponible.",
	tools_lang_not_translated_err		= "Erreur : La langue <b>%1</b> n'est pas traduite.",
	tools_generDoc1_paramName_err		= "Erreur interne : en generDoc1, mauvais argument <b>%1</b>.",
	tools_sources_of_datas				= "Informations de : /Wikidata, /modèle ou module, /autres, /message, /erreur",
	tools_list_in_translated_lang		= "\n* %1 traductions en langage %2 : %3 : %4",
	tools_luatable_counts				= "La table <b>%1</b> compte <b>%2</b> textes, <b>%3</b> fonctions, <b>%4</b> sous-tables.",
	tools_table_dont_exists_err			= "La table <b>%1</b> n'existe pas.",
	tools_auto_val_unknown_err			= "Erreur interne: Argument automatique inconnu : %1 = <b>%2</b>.",
	--
	tools_delete_docbox_msg				= "Vous devez supprimer cette documentation avant d'enregistrer.<br>Supprimez tous les modes pour revenir en mode read.",
	tools_auto_val_warning_2_msg		= "Vérifiez l'argument automatique BIS : %1 = <b>%2</b>.",
	tools_auto_val_warning_msg			= "tools_auto_val_warning_2_msg",
	--
	tools_assist_user_param_err			= "Support aux utilisateurs pour vérifier les paramètres :",
	tools_module_error_err				= "Module avec erreur",
	tools_no_known_arguments_err		= "Module sans table d'arguments connus.",
	tools_unknown_auto_arg_err			= "Erreur interne : Argument automatique inconnu : %1 = <b>%2</b>.",
	tools_args_values_err				= "Valeur anormale de l'argument <b>%1 = %2</b> (%3)",
	--
	tools_no_known_arguments_err		= "Erreur interne : Module sans table d'arguments connus.",
--	tools_tools_no_known_args_cat		= "Module sans table d'arguments connus.",
	tools_tools_no_known_args_cat		= "Module avec erreur interne", -- tools_module_internal_error_cat
	tools_no_source_arguments_err		= "Erreur interne : Module sans table d'arguments sources.",
--	tools_no_source_arguments_cat		= "Module sans table d'arguments sources.",
	tools_no_source_arguments_cat		= "Module avec erreur interne", -- tools_module_internal_error_cat

	-- Tests titles
	tools_page_tests_h3_title			= "Tests de cette page",
	tools_page_tests_title				= "Tests et informations liées à cette page :",
	tools_internal_tests_h3_title		= "Tests internes",
	tools_internal_tests_title			= "Tests internes de non régression :",
	tools_dropdown_missing_title		= "Titre manquant pour cette boite déroulante",
	tools_used_options_title			= "Options et leurs utilisations :",
	tools_options_from_mode_title		= "Test des options de modes",
	tools_options_from_args_title		= "Test des options des arguments",
	tools_spaces_page_names_title		= "Module, namespaces, et noms de pages :",
	tools_luatables_counts_title		= "Comptages des contenus de tables",
	tools_list_all_args_title			= "Liste de tous les arguments acceptés",
	tools_support_desk_title			= "Bureau d'aide",
	tools_bindmodules_report_title		= "Versions effectives détaillées",
	tools_versions_manage_tests_title	= "Test de la gestion des versions",
	tools_versionsmanagement_report		= "Rapport de gestion des versions",
	tools_versions_management_title		= "Test de gestion des versions",
	tools_bindmodules_test_title		= "Test de la gestion des versions des modules",
	tools_verif_bindmodules_report		= "Liaison des modules et contrôle des versions",
	tools_bindmodules_test_headers		= "Versions demandées, Versions utilisées, Versions connues, ACTUEL : Erreurs dans le module actif, SIMULATION : Erreurs dans le module actif",
	tools_transdiff_report_title		= "Comparez toutes les différences dans les traductions",
	tools_transdiff_report_headers		= "Langue, Clé, Traduction précédente, Nouvelle traduction, Module",
	tools_documentations_changes_report = "Documentations, tâches et modifications",
	tools_list_loaded_modules_headers	= "Titre, Version, Date, i18n, Traductions / Langues",
	tools_date_to_part_test_title		= "Tests de date vers jour, mois, année ou ère",
	tools_cat_add_test_title			= "Tester la création des catégories dans quelques langues",
	tools_levenshtein_test_title		= "Test des distances entre mots de '''[https://fr.wikipedia.org/wiki/Distance_de_Levenshtein Levenshtein]''':",
	tools_table_args_source_title		= "Table des arguments reçus, args_source :",
	tools_table_args_unknown_title		= "Table des arguments inconnus, args_unknown :",
	tools_args_known_structure_title	= 'Structure de la table des arguments',
	tools_missing_translations_title	= "Traductions manquantes ou changeantes dans les tables i18n:",
	tools_similar_args_test_title		= "Test des mots proches similar_args :",
	tools_test_luatable_lister_title	= "Lister une table, test sans limites :",
	tools_luatable_tests_limits_title	= "Lister une table, test avec limites :",
	tools_mixed_translations_title		= "Tables de traductions i18n combinées :",
	tools_dummy_languages_title			= "Définitions de langues :",
	tools_time_format_test_title		= "Tests de codage et conversions de dates",
	tools_normal_box_main_text_1		= "Boîte d'information simple : titre de cette page <b>%1</b>, description : <b>%2</b>",
	tools_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>.",
	tools_try_waits_debug_T122752		= "#invoke n'a pas chargé <b>%1</b>, mais le débogage de T122752 l'a chargé.",
	tools_tasks_table_report_title		= "Table des états de tâches connues",
	tools_tasks_table_report_short		= "États brefs de tâches connues",
	tools_tasks_table_report_headers	= "Numéro, État, Titre",

	-- Ces textes sont utilisés pour titrer des tests.
	tools_list_wiki_selectors_title		= "Liste des sélecteurs de ce wiki :",
	tools_list_all_categories_title		= "Liste de toutes les catégories éventuelles de ce wiki :",
	tools_list_all_errors_title			= "Liste de toutes les erreurs détectables de ce wiki :",
	tools_multiple_values_tests_title	= "Test des arguments à valeurs multiples",
	tools_multiple_selection_test_title	= "Test de sélection multiple",
	tools_multiple_selection_test_header= "Options,Sélecteur,À sélectionner,Sélectionnés",
	tools_multiple_selection_test_select= "2,nobel,président,député,prix",

} -- tools.i18n.fr

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

--	For translations
tools.wiki_lang = "en" -- wiki default language
tools.wiki_translations = tostring(mw.language.getContentLanguage().code) -- wiki default language
-- language of usual wiki for args and categories
tools.user_lang = "en" -- user default language
tools.user_translations = tostring(mw.language.getContentLanguage().code) -- wiki default language
tools.msgs_list = tools.user_translations -- = tools.i18n[tools.user_lang] -- translations in the user language of identifiers, errors and messages
--	https://www.mediawiki.org/wiki/Extension:UniversalLanguageSelector
--	Flexible and easy way to select a language from a large set of languages.
-- For reports

tools.report_main_short = "tools.report_main_short is missing."
tools.report_main_discreet = "tools.report_main_discreet is missing."
tools.report_REVISIONTIMESTAMP = "tools.report_REVISIONTIMESTAMP is missing."
tools.report_actual_versions = "tools.report_actual_versions is missing."
tools.recursivity = 11111
tools.tracks_level = 1 -- none=0, normal=1, details=2, full=3

function tools.args_config_init(args_source)
	-- Memorize direct configuration arguments, without translation, for helpers.
	if type(args_source) ~= "table" then args_source = tools.args_source end
	local new_source = mw.clone(args_source)
	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 -- known_versions argument
	new_source.knownversions = nil
	tools.args_source = new_source
	tools.args_config = args_config
	return args_config, new_source
end -- function tools.args_config_init()

function tools.change_itemid(id_in)
	if id_in then -- Local function parameter have priority.
		id = id_in
	else -- Else use the template arguments or inforce itemid here.
		id = tools.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 tools.is_in_sp(ns, "10,11,828,829", ",") then
		-- Change itemid only where title cannot be in wikidata.
		tools.args_config.id = id
		tools.args_config.itemid = id
	end
--	tools.args_config.id = id -- always inforce
--	tools.args_config.itemid = id -- always inforce
	return id
end -- function tools.change_itemid(id_in)

function tools.maini18n_lang_v_t(i18n, txt) -- Count available translations and languages
return tools.form_i18n_counts(i18n, txt) end

function tools.form_i18n_counts(i18n, txt) -- Count available translations and languages
	local st, vr, fn, tb = tools.luatablecount(i18n) -- or centre.maini18n
	local vr_tb = 0
	if tb == 0 then vr_tb = 0 else vr_tb = tonumber(vr / tb) end
--	if (vr_tb > 0) and (vr_tb < 1) then vr_tb = 1 end
	vr = math.floor( vr_tb ) -- texts number in one language
	if type(txt) ~= "string" then txt = "tools_maini18n_languages_list" end -- ", %1 sentences in %2 languages, "
	t = tools.str_vars(txt, vr, tb)
	return t
end -- function tools.form_i18n_counts(i18n, txt)

function tools.maini18n_languages_list() -- List available translations languages
	local t = tools.form_i18n_counts(centre.maini18n, "tools_maini18n_languages_list") -- tools_maini18n_languages_list
	local tab_to_sort = {}
	centre.maini18n = centre.maini18n or {}
	for lang, modname in pairs(centre.maini18n) 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 )
	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 .. tools.str_vars("<b>%1</b>(%2=%3), ", nativename, lang, englishname)
	end
	return t
end -- function tools.maini18n_languages_list()

function tools.group_translations(group_type, group_name_key, group_names_list, group_selected_languages) -- Todo later
	-- Enable to rename translations by cascading keys. Example: tools_err_XYZ = tools_user_error = "User error"
	-- Todo : That permit to group some detailed categories in only one to simplify lookup for helpers.
	-- Todo : That can be different in each projet and each language. Or automatic in all languages if the main module.
	-- group_translations is able without this function, only in i18n translations tables
	-- translations in the group_name_list are grouped in group_name_key, following the rules of group_type
	-- group_type could be "in_all_languages,in_selected_languages"
	return
end -- function tools.group_translations()

--	Tests : class ClassNameTest extends Scribunto_LuaEngineTestBase
--	protected static $moduleName = 'ClassNameTest';
function tools.str_test_case(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9) -- db
	-- add -v1 to -v9 to the ref
	local wt = nil
	if type(ref) == "string" then -- Use ref as reference of text.
		wt = ref
		if v1 then wt = wt .. "-" .. tostring(v1) end
		if v2 then wt = wt .. "-" .. tostring(v2) end
		if v3 then wt = wt .. "-" .. tostring(v3) end
		if v4 then wt = wt .. "-" .. tostring(v4) end
		if v5 then wt = wt .. "-" .. tostring(v5) end
		if v6 then wt = wt .. "-" .. tostring(v6) end
		if v7 then wt = wt .. "-" .. tostring(v7) end
		if v8 then wt = wt .. "-" .. tostring(v8) end
		if v9 then wt = wt .. "-" .. tostring(v9) end
		return wt
	end
	return wt or ""
end -- function tools.str_test_case(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)

function tools.string_vars(translations, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	-- replace %1 to %9 by v1 to v9 in the user translation of ref, else in ref
	local wt = nil
	if translations and translations[ref] then -- Use ref as reference of text.
		ref = tostring(translations[ref]) -- See also tools.group_translations()
		if translations and translations[ref] then -- Use ref as reference of text.
			-- Enable to rename translations by cascading keys.
			-- Example: tools_err_XYZ = "tools_user_error",
			-- Then :   tools_internal_error = "User error",
			-- Todo : That permit to group some detailed categories in only one to simplify lookup for helpers.
			-- Todo : That can be different in each projet and each language. Or automatic in all languages by the main module.
			--	tools_module_usage_error_cat		= "Module with usage error",
			--	tools_module_internal_error_cat		= "Module with internal error",
			--	tools_module_with_error_err			= "Module with error",
			--	tools_module_with_error_cat			= "Module with error",
			ref = tostring(translations[ref])
		end
		wt = ref .. " "
	elseif type(ref) == "string" then -- Else use ref as the text itself.
		wt = ref .. " "
	end
	local percent = string.find(ref or "", "%", 1, true)
	if wt and percent then -- If reference has %1 to %9
		v1 = v1 or " 1"
		v2 = v2 or " 2"
		v3 = v3 or " 3"
		v4 = v4 or " 4"
		v5 = v5 or " 5"
		v6 = v6 or " 6"
		v7 = v7 or " 7"
		v8 = v8 or " 8"
		v9 = v9 or " 9"
		if v1 then wt = string.gsub(wt, "%%1", tostring(v1) ) end
		if v2 then wt = string.gsub(wt, "%%2", tostring(v2) ) end
		if v3 then wt = string.gsub(wt, "%%3", tostring(v3) ) end
		if v4 then wt = string.gsub(wt, "%%4", tostring(v4) ) end
		if v5 then wt = string.gsub(wt, "%%5", tostring(v5) ) end
		if v6 then wt = string.gsub(wt, "%%6", tostring(v6) ) end
		if v7 then wt = string.gsub(wt, "%%7", tostring(v7) ) end
		if v8 then wt = string.gsub(wt, "%%8", tostring(v8) ) end
		if v9 then wt = string.gsub(wt, "%%9", tostring(v9) ) end
		return wt
	elseif type(ref) == "string" then -- Else use ref as the text itself.
		wt = tools.str_test_case(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
		--[[
		wt = ref
		if v1 then wt = wt .. "-" .. tostring(v1) end
		if v2 then wt = wt .. "-" .. tostring(v2) end
		if v3 then wt = wt .. "-" .. tostring(v3) end
		if v4 then wt = wt .. "-" .. tostring(v4) end
		if v5 then wt = wt .. "-" .. tostring(v5) end
		if v6 then wt = wt .. "-" .. tostring(v6) end
		if v7 then wt = wt .. "-" .. tostring(v7) end
		if v8 then wt = wt .. "-" .. tostring(v8) end
		if v9 then wt = wt .. "-" .. tostring(v9) end
		--]]
		wt = wt .. " "
		return wt
	else -- Else internal error.
		wt = "string_vars.noref "
		return wt
	end
	return wt or ""
end -- function tools.string_vars(translations, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)

function tools.str_vars(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	-- replace %1 to %7 by v1 to v7 in the user translation of ref, else in ref
	return tools.string_vars(tools.user_translations, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
end -- function tools.str_vars(ref, v1, v2, v3, v4, v5, v6, v7)

function tools.str_test(ref, v1, v2, v3)
	local t = ""
	t = t .. "tools.i18n: " .. tools.string_vars(tools.i18n.fr, ref or "tools_auto_val_warning_msg", v1 or "str_test", v2 or 22) .. " "
	t = t .. "centre.maini18n: " .. tools.string_vars(centre.maini18n.fr, ref or "tools_auto_val_warning_msg", v1 or "str_test", v2 or 22) .. " "
	return t
end

function tools.wiki_vars(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	-- replace %1 to %7 by v1 to v7 in the wiki translation of ref, else in ref
	return tools.string_vars(tools.wiki_translations, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
end -- function tools.wiki_vars(ref, v1, v2, v3, v4, v5, v6, v7)

function tools.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 tools.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.
	word = word or ""
	text = text or ""
	local i = string.find(sep .. text .. sep, sep .. (word or "") .. sep, 1, true)
	return (i and 1)
end

function tools.is_version(module) -- A module is it in version management system?
	return (type(module) == "table") and module.versions
end

------------------------------------------------------------
-- 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 tools.luatable_lister_recursive(tbl, uppername, name, recursive, countonly, levelmaxi, 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 countonly
	local tobreak = nil
	-- limit the number of the level of sub-tables
	if type(tools.recursivity) ~= "number" then tools.recursivity = 11111 end
	if type(recursive) ~= "number" then recursive = 1 end
	levelmaxi = tonumber(levelmaxi)
	if type(levelmaxi) ~= "number" then levelmaxi = 99 end
	if recursive > levelmaxi then
		local t_levelmaxi = tools.str_vars("tools_table_listlimit_levelmaxi", levelmaxi)
		return tools.message_color( tostring(t_levelmaxi) ), tot_vars, tot_func, tot_tabs
	end
	max_n = tonumber(max_n) or 999
	shift = string.rep("*", recursive)
	--
	-- Do not list if exclude1, exclude2 or exclude3 are in the table name.
	if type(exclude1) == "string" and exclude1 ~= "" then
		if string.find(name, exclude1) ~= nil then
			return "", tot_vars, tot_func, tot_tabs
		end
	end
	if type(exclude2) == "string" and exclude2 ~= "" then
		if string.find(name, exclude2) ~= nil then
			return "", tot_vars, tot_func, tot_tabs
		end
	end
	if type(exclude3) == "string" and exclude3 ~= "" then
		if string.find(name, exclude3) ~= nil then
			return "", tot_vars, tot_func, tot_tabs
		end
	end
	-- display table error
	if type(tbl) ~= "table" then
		return '<br>The variable "' .. tostring(name) .. '" is not a table.<br>', tot_vars, tot_func, tot_tabs
	end
	--
	-- List and count vars, functions and sub tables
	-- All named elements, including [1] and ["1"].
	-- Tous les élements, y compris [1] et ["1"].
	for k, v in pairs(tbl) do
		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
			-- List recursively (or no) each sub-table
			if recursive < tools.recursivity then
				st, vr, fn, tb = tools.luatable_lister_recursive(v, levelname, newname, recursive+1, countonly, levelmaxi, 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
			end
			if recursive >= levelmaxi then
				local tools_table_listlimit_levelmaxi = tools.str_vars("tools_table_listlimit_levelmaxi", levelmaxi)
				if list then res = res .. " " .. tools.message_color( tools_table_listlimit_levelmaxi ) end
				break
			end
			local sep = ""
			if max == max_n then
				local tools_table_listlimit_max_n = tools.str_vars("tools_table_listlimit_max_n", max_n)
				if list then res = res .. "\n" .. shift .. " Table <b>" .. levelname .. "</b> " .. tools.message_color(tools_table_listlimit_max_n) end
				break
			end
--			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 tools.luatable_lister_recursive(tbl, uppername, name, recursive, countonly, levelmaxi, max_n, exclude1, exclude2, exclude3)

function tools.luatable_lister(table, tablename, opt)
	local res = "\n* Content of the <b>" .. tostring(tablename) .. "</b> table, begin:"
	-- test : check mw content
	if not opt then opt = { levelmaxi = 99 } end
	local levelmaxi = opt.levelmaxi or 99
	local max_n = opt.max_n or 9999
	local exclude1 = opt.exclude1 or ""
	local exclude2 = opt.exclude2 or ""
	local exclude3 = opt.exclude3 or ""
	local countonly = opt.countonly and true
	local tot_vars, tot_func, tot_tabs = 0, 0, 0
	-- limit the number of the level of sub-tables
	if type(recursive) ~= "number" then recursive = 1 end
	if type(levelmaxi) ~= "number" then levelmaxi = 99 end
	if recursive > levelmaxi then
		return "", tot_vars, tot_func, tot_tabs
	end
	res = res .. " ( " .. tools.ta("levelmaxi", levelmaxi) .. tools.ta("max_n", max_n) .. tools.ta("exclude1", exclude1) .. tools.ta("exclude2", exclude2) .. tools.ta("exclude3", exclude3) .. " ) "
	local st, vr, fn, tb = tools.luatable_lister_recursive(table, "", tablename, 1, countonly, levelmaxi, 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 .. tools.str_vars(" %1 variables, %2 functions, %3 sub-tables.\n", vr, fn, tb)
	return res, tot_vars, tot_func, tot_tabs
end -- function tools.luatable_lister(table, tablename, opt)

function tools.luatablecount(tab, tabname, recursive)
	if type(tools.recursivity) ~= "number" then tools.recursivity = 11111 end
	if type(recursive) ~= "number" then recursive = 1 end
	recursive = math.floor( recursive )
	local t = ""
	local tot_vars, tot_func, tot_tabs = 0, 0, 0
	local nbr_vars, nbr_func, nbr_tabs = 0, 0, 0
	local txt, vr, fn, tb = "", 0, 0, 0
	local level_max = 1000
	if (type(tab) == "table") and (type(tabname) == "string") then -- for any tables
		-- normal case OK, do nothing
	elseif (type(tab) == "table") and (type(tabname) ~= "string") then -- for abnormal or missing tabname
		tabname = "tabname=" .. tostring(tab) .. "?"
		tabname = tools.error_color(tabname)
	elseif (type(tab) ~= "table") and (type(tabname) == "string") then -- for abnormal table
		tabname = "tabname=" .. tostring(tabname) .. "?"
		tabname = tools.error_color(tabname)
	elseif type(tab) == "string" then -- for tools.tables
		tabname = "tools." .. tab
		tab = tools[tab]
	else
		tabname = "tabname=" .. tostring(tab) .. "?"
		tabname = tools.error_color(tabname)
	--	tabname = tostring(tabname)
	end
	if type(tab) == "table" then
		for key, var in pairs(tab) do -- List and count vars, functions and sub tables
			key = tostring(key)
			if type(var) == "table" then
				nbr_tabs = nbr_tabs + 1
				if recursive < tools.recursivity then
					txt, vr, fn, tb, recursive = tools.luatablecount(var, tabname, recursive + 1)
					tot_vars = tot_vars + vr
					tot_func = tot_func + fn
					tot_tabs = tot_tabs + tb + 1
				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
	t = t .. "<b>" .. tabname .. "</b>: "
	t = tools.str_vars("tools_luatable_counts", tabname, tot_vars, tot_func, tot_tabs)
	return t, tot_vars, tot_func, tot_tabs, recursive
--	result example: The table args_known counts 161 texts, 0 functions and 24 sub-tables., 161, 0, 24, 3
end -- function tools.luatablecount(tab, tabname, recursive)

function tools.luatables_counts(t, ...)
	local t = t or "\n* Counts of contents of tables: "
	t = t .. "<br>" .. tools.luatablecount(centre.i18n, "centre.i18n")
	t = t .. "<br>" .. tools.luatablecount(tools.i18n, "tools.i18n")
	t = t .. "<br>" .. tools.luatablecount(mathroman.i18n, "mathroman.i18n")
--	t = t .. "<br>" .. tools.luatablecount(p.i18n, "p.i18n") -- not available in any module
	t = t .. "<br>" .. tools.luatablecount(centre.maini18n, "centre.maini18n")
--	t = t .. "<br>" .. tools.luatablecount("i18n")
	t = t .. "<br>" .. tools.luatablecount("wiki_translations")
	t = t .. "<br>" .. tools.luatablecount("user_translations")
	t = t .. "<br>" .. tools.luatablecount("args_known")
	t = t .. "<br>" .. tools.luatablecount("args_wikidata")
--	t = t .. "<br>" .. tools.luatablecount("args_invoke")
--	t = t .. "<br>" .. tools.luatablecount("args_template")
	t = t .. "<br>" .. tools.luatablecount("args_source")
	t = t .. "<br>" .. tools.luatablecount("args_config")
	t = t .. "<br>" .. tools.luatablecount("args_import")
	t = t .. "<br>" .. tools.luatablecount("args_final")
	t = t .. "<br>" .. tools.luatablecount("args_unknown")
--	t = t .. "<br>" .. tools.luatablecount("args_selected") -- not available in any module
	t = t .. "<br>" .. tools.luatablecount("errors_list")
	t = t .. "<br>" .. tools.luatablecount("categories_list")
	t = t .. "<br>" .. tools.luatablecount("loaded_pack")
	t = t .. "<br>" .. tools.luatablecount("loaded_vers")
--	t = t .. "<br>" .. tools.luatablecount({}, "empty table") -- do not fail in errors cases
--	t = t .. "<br>" .. tools.luatablecount({}) -- do not fail in errors cases
--	t = t .. "<br>" .. tools.luatablecount(123.456) -- do not fail in errors cases
--	t = t .. "<br>" .. tools.luatablecount() -- do not fail in errors cases
	return t
end -- function tools.luatables_counts(t)

--	tools.i18n_trc = "-"
function tools.i18n_trac(id, t, count) -- track for i18n DEBUG
--	tools.i18n_trac("base_func", "end", 1) -- example of use
	t = t or ""
	t = t .. "<br><b>" .. (id or "") .. "</b> t=" .. t
	if count then
		t = t .. "<br>* " .. tools.luatablecount("i18n")
		t = t .. "<br>* " .. tools.luatablecount("wiki_translations")
		t = t .. "<br>* " .. tools.luatablecount("user_translations")
	end
--	tools.i18n_trc = t
	return t
end

------------------------------------------------------------
-- Manage errors and messages. Administrar errores y mensages. Gérer les erreurs et messages.
------------------------------------------------------------

tools.errors_list = {} -- Table to collect errors and messages
tools.erron = true -- Activated or not errors. Errores activado o no. Erreurs activées ou non.

-- Add a message, error or category to le lists.
function tools.error_add(typ, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	--	tools.err_add("tools_value_re_defined_err", k, key, xyz)
	if not tools.erron then return "" end
	local msg = { ["typ"] = typ, ["ref"] = ref, ["v1"] = v1, ["v2"] = v2, ["v3"] = v3, ["v4"] = v4, ["v5"] = v5, ["v6"] = v6, ["v7"] = v7, ["v8"] = v8, ["v9"] = v9 }
	local str = tools.str_vars(msg.ref, msg.v1, msg.v2, msg.v3, msg.v4, msg.v5, msg.v6, msg.v7, msg.v8, msg.v9)
	local do_it = true
	for k, reg in ipairs(tools.errors_list) do -- If the new error was previously registered, do not add it.
		local reg_txt = tools.str_vars(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(tools.errors_list, msg) end
	local res = ""
	if msg.typ == "msg" then
		res = '<br>⦁ ' .. tools.message_color(str)
	end
	if msg.typ == "err" then
		res = '<br>⦁ ' .. tools.error_color(str)
	end
	return res
end -- function tools.error_add(typ, ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)

function tools.err_add(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	return tools.error_add("err", ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
end

function tools.msg_add(ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	return tools.error_add("msg", ref, v1, v2, v3, v4, v5, v6, v7, v8, v9)
end

function tools.errors_lister(title, v1, v2, v3, v4, v5, v6, v7, v8, v9)
	local res, msgref = "", ""
--	res = res .. centre.luatable_lister(tools.errors_list, "tools.errors_list")
	local mwtitle = mw.title.getCurrentTitle()
	local page = tostring(mwtitle.nsText) .. ":" .. tostring(mwtitle.text)
	if type(title) ~= "string" then title = "tools_error_list_header_err" end
	res = res .. '\n*' .. tools.str_vars(title, page, v1, v2, v3, v4, v5, v6, v7, v8, v9) -- .. ' - ' .. page
	local n = 0
	for k, msg in ipairs(tools.errors_list) do
		msgref = tools.str_vars(msg.ref, msg.v1, msg.v2, msg.v3, msg.v4, msg.v5, msg.v6, msg.v7, msg.v8, msg.v9) -- texte traduit ou direct
		if msg.typ == "msg" then
			res = res .. '<br>⦁ ' .. tools.message_color(msgref)
		end
	end
	for k, msg in ipairs(tools.errors_list) do
		msgref = tools.str_vars(msg.ref, msg.v1, msg.v2, msg.v3, msg.v4, msg.v5, msg.v6, msg.v7, msg.v8, msg.v9) -- texte traduit ou direct
		if msg.typ == "err" then
			res = res .. '<br>⦁ ' .. tools.error_color(msgref)
			n = n + 1
		end
	end
	if n > 0 then
		tools.cat_add("tools_module_with_error_err")
	end
	return res
end -- function tools.errors_lister(title, v1, v2, v3, v4, v5, v6, v7, v8, v9)

--	Manage categories
tools.categories_list = {} -- Table to collect all categories to generate in wikitext
tools.catView = "" -- = ":" to document a category rather than truly categorize it

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

function tools.fenv_test(t) -- Test the stack to search names of modules.
	local t = t or "\n* fenv1 :"
--	local env1 = getfenv(1) -- getfenv(1) fails when called
	t = t .. tools.luatable_lister(env1, "env1", { boxstyle = "boxstyle", levelmaxi = 1, max_n = 2 } )
	t = ""
	return t
--	It should be possible to get entity id from page title. See EntityIdLookup::getEntityIdForTitle. Project mediawiki/extensions/Wikibase
--	res = res .. tools.dropdownfunc(select, "tools_luatable_tests_limits_title", tools.luatable_lister, tools.tablim, "tools.tablim",
--		{ boxstyle = "boxstyle", levelmaxi = 2, max_n = 2, exclude1 = "hou" } ) -- , {width = "88%", text_color = "blue"}
--	if not sep then sep = "=" end
end -- function tools.fenv_test(t)

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

function tools.tam(txt, val, sep) -- Formats an argument and its value in a documentation. The text is "nil" if the value is nil.
	if not val then return "" end
	return tools.ta(txt, val, sep)
end

function tools.all_versions_text(all_versions) -- DEBUG
	all_versions = all_versions or centre.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 tools.track_i18n_t(t)
	tools.track_i18n_tt = (tools.track_i18n_tt or "track_i18n_tt ") .. (t or "track_i18n_tt") .. ":" .. tools.str_vars("err_J_before_end") .. " - "
	return tools.track_i18n_tt -- .. " " .. tools.i18n_trc, see also track_i18n_vers
end

function tools.warning_short()
--	t = t .. tools.track_i18n_vers
	return tools.report_main_short
end

function tools.warning_small(d) -- samll display of the main module version
	local v, d, t = tools.versions, d or centre.d, ""
--	t = t .. tools.warning_short(d)
	t = tools.str_vars("<small><small>" .. tools.report_main_short .. "</small></small>")
	return t
end

function tools.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 .. tools.ta("Modules", tostring(mw.site.stats.pagesInNamespace( 828 ) ) )
	t = t .. tools.ta("activeUsers", tostring(mw.site.stats.activeUsers ) )
	t = t .. tools.ta("Administrators(sysop)", tostring(mw.site.stats.usersInGroup( "sysop" ) ) )
	t = t .. tools.ta("bots(bot)", tostring(mw.site.stats.usersInGroup( "bot" ) ) )
	t = t .. tools.ta("patrollers(patroller)", tostring(mw.site.stats.usersInGroup( "patroller" ) ) )
	t = t .. tools.ta("bureaucrats(bureaucrat)", tostring(mw.site.stats.usersInGroup( "bureaucrat" ) ) )
--	t = t .. tools.ta("checkuser(checkuser)", tostring(mw.site.stats.usersInGroup( "checkuser" ) ) )
--	t = t .. tools.ta("autoconfirmed(autoconfirmed)", tostring(mw.site.stats.usersInGroup( "autoconfirmed" ) ) )
--	t = t .. tools.ta("Autopatrol users(autoreview)", tostring(mw.site.stats.usersInGroup( "autoreview" ) ) )
--	t = t .. tools.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 .. tools.ta("REVISIONTIMESTAMP", tools.frame:preprocess( "{{REVISIONTIMESTAMP}}" ) )
	t = t .. tools.ta("CONTENTLANGUAGE", tools.frame:preprocess( "{{CONTENTLANGUAGE}}" ) )
	t = t .. tools.ta("REVISIONUSER", tools.frame:preprocess( "{{REVISIONUSER}}" ) )
	t = t .. tools.ta("USERLANG", tools.frame:preprocess( "{{USERLANG}}" ) )
	t = t .. tools.ta("UILANGCODE", tools.frame:preprocess( "{{UILANGCODE}}" ) )
	t = t .. tools.ta("USERIFCODE", tools.frame:preprocess( "{{USERIFCODE}}" ) )
	t = t .. tools.ta("USERLANGUAGE", tools.frame:preprocess( "{{USERLANGUAGE}}" ) )
	return t
end -- function tools.wiki_modules_users(t)

function tools.warning_report(t) -- DEBUG: finish the migration by alias of "deprecated" fonctions.
	return tools.bindmodules_report(t) -- Test of binding of the modules and versions control tools_verif_bindmodules_report
end -- function tools.warning_report(t)

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

-- relire Central/Auteur : module code in english

------------------------------------------------------------
-- Miscelanous functions. Fonctions utilitaires.
------------------------------------------------------------

function tools.table_iterator(t) -- iterator to use all elements of a table tree, one by one
	-- for noticeN in tools.table_iterator(notices_properties) do
	-- modele : function list_iter (t)
	if type(t) ~= "table" then return {}, 0 ,0 end
	local i = 0
	local n = table.getn(t)
	return function ()
		i = i + 1
		n = table.getn(t)
		if t[i] ~= nil then return t[i], i ,n end
	end
end -- function tools.table_iterator(t)

function tools.spaces_page_names_test(t)
	if type(t) ~= "string" then t = nil end
	local t = t or "\n* '''spaces_page_names_test''' :"
	local mwtitle = mw.title.getCurrentTitle()
	t = t .. tools.ta("mwtitle", mwtitle)
	local nsText = mwtitle.nsText
	t = t .. tools.ta("nsText", nsText)
	local baseText = mwtitle.baseText -- namespace for the page
	t = t .. tools.ta("baseText", baseText)
	local url = tostring(mwtitle:canonicalUrl( ))
	t = t .. tools.ta("url", url)
	--
	t = t .. "\n* Module namespace : "
	local ns = mw.site.namespaces
	if ns and ns[828] then
		t = t .. tools.ta("id828", ns[828].id)
		t = t .. tools.ta("canonicalName828", ns[828].canonicalName)
		t = t .. tools.ta("name828", ns[828].name)
		t = t .. tools.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 .. tools.ta(ns, ins)
		end
	end
	return t
end -- function tools.spaces_page_names_test()

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

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

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

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

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

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

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

function tools.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

function tools.tab_fields(tab) -- List the fields of a table
	if type(tab) ~= "table" then return tostring(tab) end
	local t = ""
	for key, val in pairs(tab) do
		t = t .. key .. ", "
	end
	t = string.sub( t, 1, -3 )
	return t
end -- function tools.tab_fields(tab)

function tools.tab_pairs(tab) -- List a pairs table: Lister une table de pairs :"
	local t = ""
	for key, val in pairs(tab) do -- Pour tous les mots
		t = t .. tostring(key) .. " = '''" .. tostring(val) .. "''', "
	end
	t = string.sub( t, 1, -3 )
	return t
end -- function tools.tab_pairs(tab)

-- 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-collapse:collapse"
-- {| class="wikitable sortable" border="1" mw-collapsible mw-collapsed"
-- |} end of table
function tools.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 tools.Tc(t) return '\n! scope="col" | ' .. tostring( t or " " ) end -- Table repeated or different columns headers
function tools.Tr(t) return "\n|- " .. tostring( t or " " ) end -- Table -- row
function tools.Td(t) return "\n| " .. tostring( t or " ") end -- Table data -- or " "
function tools.Te(t) return "\n|}" end -- Table end

-- To do debug : Dropbox plus souple a traduire et parametrer par "style" une table de parametres nommés.

function tools.dropbox(title, content, boxstyle) -- alignT, image, alignB, margin_bottom, width, border_radius, border_color, background_color)
	-- Form a drop box. Formar un cuadro desplegable. Former une boite déroulante.
	local s = { -- options for style and other
		title = tools.str_vars(title or "tools_dropdown_missing_title"),
		content = content,
		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 = border_color or "#AAAAAA", -- border-color
		height = height or "1.6em", -- height
		label = "&#x25bc; &nbsp; /&#x25b6; &nbsp; ", --	 = "Unwrap/Wrap",
		-- ▼ = &#x25bc; -- ▶ = &#x25b6; https://fr.wikipedia.org/wiki/Table_des_caract%C3%A8res_Unicode/U25A0
	--	label = label or tools.str_vars("tools_DropBox_label_Unwrap"), --	= "Unwrap/Wrap", = "Unwrap/Wrap",
	}
	if type(boxstyle) == "table" then
		for key, val in pairs(boxstyle) do
			if type(val) == "string" then s[key] = val end
		end
	end
	if type(title) ~= "string" then title = "tools_dropdown_missing_title" end
	s.title = tools.str_vars(title or "tools_dropdown_missing_title")
	if type(content) ~= "string" then content = "" end
	s.content = tools.str_vars(content)
--	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 align="'.. s.alignB ..'" >' -- \n------
		.. '<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-collapsed" style="display: none;"><p>'
				.. s.title
			.. '</p></ul>'
			.. '<li>'
				.. s.image
				.. s.content
			.. '</li>'
		.. '</div>'
	.. '</div>'
	--]]
	return res
end -- function tools.DropBox(title, content, boxstyle)

function tools.dropdown_func(select, title, content, ...)
  return tools.dropdownfunc (select, title, content, ...)
end

function tools.dropdownfunc(select, title, content, ...) -- alignT, image, alignB, margin_bottom, width, border_radius, border_color, background_color)
	-- select = "alltestsview" -- Deprecated, to view all boxes.
	-- select = "allwaysview" -- To allways display one view. -- select = "enforcerun" -- To debug one box with INTERNAL ERROR.
	if not ( (select == true) or (select == title) or (select == "allwaysview") or (select == "alltestsview") or (select == "enforcerun") ) then return "" end
	local func = content
	local t = ""
	local args = { ... } -- optional arguments
	local boxstyle = { }
	local success, result
	title = tools.str_vars(title or "tools_dropdown_missing_title")
	if type(content) == "string" then
		t = t .. content
	elseif type(func) == "function" then
		-- A function produces the wikitext.
		-- Get the style options for the box, then mask this style for functions giving the content.
		for i, tab in ipairs(args) do --
			if type(tab) == "table" and tab.boxstyle == "boxstyle" then
				boxstyle = mw.clone( args[i] )
				args[i] = nil
			end
		end
	-- select = "alltestsview" -- Deprecated, to view all boxes.
	-- select = "allwaysview" -- To allways display one view. -- select = "enforcerun" -- To debug one box with INTERNAL ERROR.
		if select == "enforcerun" then -- enforce run for one dropbox only.
			result = func( ... ) -- enforce run including in blocking cases, to debug it.
			success = true
		else
			success, result = pcall( func, ... ) -- replaces result = func( ... ) without blocking cases.
		--	title = title .. tools.error_color(" INTERNAL ERROR ")
		end
		if success then
			t = t .. result
		else -- In case of internal error, alert by a category and a clear not blocking display.
			tools.cat_add("tools_module_internal_error_cat")
			tools.categories_lister("")
			title = title .. tools.error_color(" function INTERNAL ERROR ")
			t = t .. tools.error_color(" See the internal error category. ")
		end
		--[[
		In some cases the dropbox is not displayed for unknown reason, perhaps a bug in mediawiki.
		After several tries to debug, Rical uses pcall to bypass the issue on 2015-12-15 14:38
		res = res .. tools.dropdownfunc(1, "tools_luatables_counts_title", tools.luatables_counts)
		res = res .. tools.dropdownfunc(1, "tools_spaces_page_names_title", tools.spaces_page_names_test)
		Erreur Lua dans Module:Author3 à la ligne 2826 : attempt to concatenate field '?' (a nil value).
		Pile des appels :
		Module:Author3:2826 : dans la fonction « func »
		Module:Central1:2638 : dans la fonction « dropdownfunc »
		Module:Author3:4096 : dans la fonction « form_tests »
		Module:Author3:4437 : dans la fonction « form_result »
		Module:Author3:4804 : dans la fonction « chunk »
		--]]
	else
		t = ""
		tools.cat_add("tools_module_internal_error_cat")
		tools.categories_lister("")
		title = title .. tools.error_color(" INTERNAL ERROR ")
		t = t .. tools.error_color(" See the internal error category. ")
	end
	-- Search if the last arg is a table of args, then return it for DropBox style options.
--	local maxn = table.maxn(args)
--	if type(args[maxn]) == "table" then args = args[maxn] end
	--
	return tools.dropbox( tostring(title), tostring(t), boxstyle) or "dropdownfunc"
end -- function tools.dropdownfunc(select, title, content, ...)

-- Extract a part of date following a pre-defined format which starts with separator
function tools.date_split(date, format)
	-- local dd, mmmm, yyyy, era = tools.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 tools.date_split(date, format)

-- Extract a part of date following a pre-defined format which starts with separator
function tools.date_to_part(date, part)
	-- local t, err = tools.date_to_part(a.birthyear, " dd mmmm yyyy", "yyyy")
	part = part or "yyyy"
	local dd, mmmm, yyyy, era
	local found = false
	--	local err = "tools_date_to_part_not_found_err"
	if not found then
		dd, mmmm, yyyy, era = tools.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 = tools.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 = tools.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 = tools.date_split(date, "mmmm yyyy")
		if not dd and mmmm and yyyy and not era then
			--	tools_date_months_names				= "January, February, March, April, May, June, July, August, September, October, November, December",
			--	tools_date_months_names				= "Enero, Febrero, Marzo, Abril, Mayo, Junio​​, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre",
			--	tools_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 = tools.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 tools.date_to_part(date, part)

-- relire Central/Auteur : module code in english

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

function tools.trans(key) -- give the wiki translation of indentified texts, or arguments indentifier, or errors messages
	local t = tools.wiki_translations[key]
	if t == nil then t="" end
	return t
end

function tools.get_IETF_fr()
	-- Chargement de la base de donnée des langues avec gestion d'erreur.
	-- voir, see : https://fr.wikipedia.org/wiki/Module:Langue
	local dataLangue
	local success, resultat = pcall (mw.loadData, 'Module:Langue/Data' )
	if success then
		dataLangue = resultat
	else
		-- Base de donnée à minima en cas de bug sur le Module:Langue/Data
		dataLangue = {
			en = { code = 'en', nom = 'anglais' },
			fr = { code = 'fr', nom = 'français' },
			de = { code = 'de', nom = 'allemand' },
			es = { code = 'es', nom = 'espagnol' },
			it = { code = 'it', nom = 'italien'	 },
			la = { code = 'la', nom = 'latin'	 },
--			['rtl script'] = { Arab = true }, -- debug : risque d'anomalie, anomaly risc
		}
		dataLangue.anglais = dataLangue.en
		dataLangue['français'] = dataLangue.fr
		dataLangue.francais = dataLangue.fr
		dataLangue.allemand = dataLangue.de
		dataLangue.espagnol = dataLangue.es
		dataLangue.italien = dataLangue.it
	end
	return dataLangue
end -- function tools.get_IETF_fr()

function tools.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 : ' ..  tools.ta("isTag", "isKnownLanguageTag(xx)") .. tools.ta("isLang", "isSupportedLanguage(xx)") .. tools.ta("isBuilt", "isValidBuiltInCode(xx)")
	t = t .. tools.Th() .. tools.Tc("lang") .. tools.Tc("isTag") .. tools.Tc("isLang") .. tools.Tc("isBuilt")
	t = t .. tools.Tc("langname") .. tools.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")
		local native = mw.language.fetchLanguageName(lang)
		local space_name = tostring(mw.site.namespaces.Module.name)
		t = t .. tools.Tr() .. tools.Td(lang) .. tools.Td(isTag) .. tools.Td(isLang) .. tools.Td(isBuilt)
		t = t .. tools.Td(langname) .. tools.Td(native)
	end
	t = t .. tools.Te()
	return t
end -- function tools.dummy_languages()

function tools.list_loaded_modules(t)
	t = t or ""
	t = t .. "\n* list_loaded_modules: "
--	local loaded_libs = centre.get_loaded_modules() -- List all libraries with internal .i18n translations
	for name, library in pairs(package.loaded) do
		t = t .. tools.ta(type(library), name )
	end
	return t .. "<br>"
end -- function tools.list_loaded_modules(t)

tools.trac_lang_t = "" -- "track_lang: "
function tools.trac_lang(where, args_tab) -- res = res .. tools.trac_lang("tools.init_args:", args_source) -- DEBUG
	tools.trac_lang_t = tools.trac_lang_t .. " <b>" .. where .. "</b>"
	tools.trac_lang_t = tools.trac_lang_t .. tools.ta(".c", args_tab.c)
	tools.trac_lang_t = tools.trac_lang_t .. tools.ta(".userlang", args_tab.userlang) .. tools.ta(".wikilang", args_tab.wikilang)
	.. ":u." .. tools.user_lang .. "=" .. (tools.user_translations["title"] or "x")
	.. ":w." .. tools.wiki_lang .. "=" .. (tools.wiki_translations["title"] or "x") .. " "
end

-- en : initialize or change the user and the wiki languages and their tables
-- es : inicializar o cambiar el idiomas del usuario y del wiki y su tablas
-- fr : initialiser ou modifier la langue de l'utilisateur et du wiki et leurs tables
function tools.init_wiki_user_lang(wiki_lang, user_lang)
	-- initialize or change the user and the wiki languages and their tables
	local get_lang = mw.language.getContentLanguage().code -- default wiki_lang
	local wiki_lang = wiki_lang or tools.args_config.wikilang
	local user_lang = user_lang or tools.args_config.userlang
	-- WIKI
	if wiki_lang and centre.maini18n and centre.maini18n[wiki_lang] then -- Activate the wiki language for errors, messages and categories.
		tools.wiki_lang = wiki_lang
	else -- if asked is unavailable uses wiki_lang.
		tools.err_add("tools_lang_not_exist_err", wiki_lang)
		tools.wiki_lang = get_lang -- wiki_lang or tools.wiki_lang or get_lang
	end
	if tools.wiki_lang and centre.maini18n and centre.maini18n[tools.wiki_lang] then
		centre.wiki_translations = centre.maini18n[tools.wiki_lang]
		tools.wiki_translations = centre.wiki_translations
	end
	-- USER
	local get_lang = mw.language.getContentLanguage().code -- default user_lang
	if user_lang and centre.maini18n and centre.maini18n[user_lang] then -- Activate the wiki language for errors, messages and categories.
		tools.user_lang = user_lang
	else -- if asked is unavailable uses user_lang.
		tools.err_add("tools_lang_not_exist_err", user_lang)
		tools.user_lang = get_lang -- user_lang or tools.user_lang or get_lang
	end
	if tools.user_lang and centre.maini18n and centre.maini18n[tools.user_lang] then
		centre.user_translations = centre.maini18n[tools.user_lang]
		tools.user_translations = centre.user_translations
	end
end -- function tools.init_wiki_user_lang(wiki_lang, user_lang)

function tools.init_user_lang(user_lang, wiki_lang)
--	t = "\n* init_user_lang : sought user_lang = " .. tostring(user_lang)
	-- user_lang ok if i18n ok, else wiki language
	if type(user_lang) ~= "string" then user_lang = nil end
	if user_lang and centre.maini18n and centre.maini18n[user_lang] then -- Activate the user language for errors, messages and categories.
		tools.user_lang = user_lang or mw.language.getContentLanguage().code
	else -- degraded mode : = wiki_lang can replace missing user_lang.
		tools.err_add("tools_lang_not_exist_err", user_lang) -- Error if the language is not a wikipedia language
		tools.user_lang = user_lang or mw.language.getContentLanguage().code or "en"
	end
	centre.user_translations = centre.maini18n[tools.user_lang]
	tools.user_translations = centre.user_translations
	-- DEBUG wait for : T68051 Give at modules the user language to display errors and categories names for him
	local mwtitle = mw.title.getCurrentTitle()
	local mwuri = mw.uri.canonicalUrl(mwtitle.prefixedText, { host, authority, user, password, } )
--	tools.trac_lang_t = tools.trac_lang_t .. tools.ta("host", tostring(mwuri.host)) .. tools.ta("authority", tostring(mwuri.authority))
	return -- t .. "<br>"
end -- function tools.init_user_lang(user_lang)

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

function tools.catGen(ref, v1, v2, v3, v4, v5) -- add a category to the categories_list
	table.insert(tools.categories_list, { ["typ"] = "cat", ["ref"] = ref, ["v1"] = v1, ["v2"] = v2, ["v3"] = v3, ["v4"] = v4, ["v5"] = v5, ["v6"] = v6, ["v7"] = v7, ["v8"] = v8, ["v9"] = v9 })
end

-- Record in categories_list and genrate the wikitext of a category
function tools.cat_add(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 = tools.str_vars(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(tools.categories_list) do -- If the new category was previously registered, do not add it.
		local reg_txt = tools.str_vars(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(tools.categories_list, cat) end
	--
	local c = tools.catView or tools.catView or ""
--	if tools.catView == true or tools.catView == true then c = ":" else c = "" end
	local wiki_catspace = mw.site.namespaces.Category.name -- tools.wiki_translations.category or
	local user_catspace = mw.site.namespaces.Category.name -- tools.user_translations.category or
	local user_cat = tools.string_vars(tools.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 = tools.string_vars(tools.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 .. "]] "
	return res
end -- function tools.cat_add(ref, v1, v2, v3, v4, v5)

-- Generate categories from plural values in only one argument
function tools.catGroup(groupCat, groupList) -- generate some categories
	-- catGroup("Country %1", "France,Italie") -> [[Category:Country France]] [[Category:Country Italie]]
	if type(groupCat) ~= "string" then groupCat = "%1" end
	if type(groupList) ~= "string" then return "" end
	local cats = ""
	local t = ""
	t = t .. tools.ta("groupList", groupList)
	local splitxt = mw.text.split(groupList, ",", true)
--	t = t .. " splitxt = " .. table.concat(splitxt, "+")
	for str in mw.text.gsplit(groupList, ",", true) do
	--	t = t .. " , s=" .. tools.cat_add(groupCat, str)
		cats = cats .. tools.cat_add(groupCat, str)
	end
	return cats, t
end -- function tools.catGroup(groupCat, groupList)

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

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

-- generate the wikitext of the list of categories to only display them
function tools.categories_lister(c)
	local res = "" -- "\n* Catégories : "
	local wiki_cat = ""
	local user_cat = ""
	tools.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 tools.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(tools.categories_list) do
		user_cat = tools.string_vars(tools.user_translations, cat.ref, cat.v1, cat.v2, cat.v3, cat.v4, cat.v5, cat.v6, cat.v7, cat.v8, cat.v9)
		wiki_cat = tools.string_vars(tools.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
--	res = res .. tools.track_i18n_vers
	return res
end -- function tools.categories_lister(c)

-- Initialize the categories list. Initialise la liste des categories.
function tools.categories_init(catName, catText) -- initialize the category list
	tools.categories_list = {} -- init the collect of categories
	return
end

function tools.tasks_table_report(t, short) -- List the state of known bugs or tasks.
	t = t or ""
	t = t .. " <b>*</b> =  ( detected state )"
	local head = mw.text.split( tools.str_vars("tools_tasks_table_report_headers") , ',')
	--
	function one_task_short(task, detect, state, name, shortext)
		local task = task or "T0000"
		if not state then return "<br><b>" .. task .. "</b>: " end
		if (state == "Resolved") or (state == "OK") then shortext = tools.message_color(shortext)
		elseif state == "args" then shortext = tools.other_color(shortext)
		else shortext = tools.error_color(shortext) end
		if detect == 1 then shortext = shortext .. " <b>*</b>" end
		local phabtask = "[[phab:" .. task .. "|" .. task .. "]]"
		local t = phabtask .. ":<b>" .. shortext .. "</b>, "
		return t
	end
	--
	function one_task(short, task, detect, state, name, shortext)
		local task = tostring(task) or "T0000" --  task or
		if short then return one_task_short(task, detect, state, name, shortext) end
	--	local head = mw.text.split( tools.str_vars("tools_tasks_table_report_headers") , ',')
		local phabtask = "[[phab:" .. task .. "|" .. task .. "]]"
		if (state == "Resolved") or (state == "OK") then name = tools.message_color(name)
		elseif state == "args" then name = tools.other_color(name)
		elseif name then name = tools.error_color(name) end
		if detect == 1 then name = name .. " - ( detected state )" end
		local t = ""
	--	t = t .. one_task(short, "T122752", 1, centre.T122752_status, "#invoke do not record the main module in package.loaded", "invoke# main not loaded")
		if name and phabtask then
			t = t .. tools.Tr() .. tools.Td(phabtask) .. tools.Td(state) .. tools.Td(name)
		elseif task and state and name then
			t = t .. tools.Tr() .. tools.Td(task) .. tools.Td(state) .. tools.Td(name)
		elseif (type(task) == "string") and (not state) then
			t = t .. tools.Tr() .. tools.Tc(task) .. tools.Tc("-") .. tools.Tc("-")
		elseif task and (not state) then
			t = t .. tools.Tr() .. tools.Tc(task) .. tools.Tc("...") .. tools.Tc("...")
		end
		return t
	end
	--
	t = t .. "<br>Used colors: "
	.. tools.ta("Resolved", tools.message_color("task already completed") )
	.. tools.ta("args", tools.other_color("task replaced by arguments or by the module") )
	.. tools.ta("other", tools.error_color("other tasks") )
	if not short then
		t = t .. tools.Th() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[3])
	end
	t = t .. one_task(short, "Important")
	t = t .. one_task(short, "T53660", 0, "Open", "Detect the edit state to adapt help messages to user errors", "edit state")
	centre.T68051_status = mw.uri.new().userLanguage
	if centre.T68051_status then centre.T68051_status = "Resolved" else centre.T68051_status = "args" end
	-- T68051 Any title with datas in wikibase give its item.id to any module. Probably across mw.uri.new().userLanguage
	t = t .. one_task(short, "T68051", 1, centre.T68051_status, "Give at modules the user language to display errors and categories names for him", "user language")
	t = t .. one_task(short, "T119978", 0, "Invalid", "Get the own module name and last record date-time from each module", "own module name")
	t = t .. one_task(short, "T85419", 0, "Open", "User groups should be exposed to Scribunto", "user groups") -- to adapt errors and warning for administrators or modules coders
	t = t .. one_task(short, "T122752", 1, centre.T122752_status, "#invoke do not record the main module in package.loaded", "invoke# main not loaded")
	t = t .. one_task(short, "T67507", 0, "Closed", "It should be possible to get entity id from page title. See EntityIdLookup::getEntityIdForTitle.", "entity id from title")
	-- T67507 = Wikibase\Store\EntityIdLookup::getEntityIdForTitle can do this.
	t = t .. one_task(short, "T123196", 0, "Resolved", "Access to item from talk page", "Access from talk page")
	-- T123196 description = There is a function in the mw.wikibase module (client/includes/DataAccess/Scribunto/mw.wikibase.lua, around line 65) which gets the item ID based on mw.title.getCurrentTitle().prefixedText, this means for me that we could change it to content page name (since talk pages won’t have own connected items, so it won’t cause conflict).
	local mwtitle = mw.title.getCurrentTitle()
	local url = tostring(mwtitle:canonicalUrl( ))
--	centre.T133498_status = mw.uri.canonicalUrl() -- mw.uri.canonicalUrl
	local title_object = mw.title.new(url) -- .itemid -- mw.uri.canonicalUrl
	if title_object then centre.T133498_status = title_object.itemid end -- mw.uri.canonicalUrl
	if centre.T133498_status then centre.T133498_status = "Resolved" else centre.T133498_status = "Open" end
	-- Any title with datas in wikibase give its item.id to any module. Probably across mw.title.new().itemid
	t = t .. one_task(short, "T133498", 1, centre.T133498_status, "Any title in wikibase can give its item id to modules", "wikidata from title.itemid")
	t = t .. one_task(short, "Central")
	t = t .. one_task(short, "T121470", 0, "Open", "Central Global Repository for Templates, Lua modules, and Gadgets", "central repository")
	t = t .. one_task(short, "T135845", 0, "Open", "Convert any module as central or centralisable", "Convert as central")
	t = t .. one_task(short, "T52329", 0, "Stalled", "We need a common repository for Scribunto modules and templates", "common repository")
	t = t .. one_task(short, "T41610", 0, "Resolved", "Scribunto should support global module invocations", "global invocations")
	t = t .. one_task(short, "Marginal")
	t = t .. one_task(short, "T63958", 0, "Open", "Use existing $dateFormats to format dates on Wikidata", "existing $dateFormats")
	t = t .. one_task(short, "T85412", 0, "Open", "The formatPropertyValues needs an option to format data from date-time, angle or geographic coordinates", "format date-time")
	t = t .. one_task(short, "T75460", 0, "Resolved", "[Story] Make Lua functions default to the user's language on multilingual wikis", "multilingual wikis")
	if not short then
		t = t .. tools.Te()
	end
	return t
end -- function tools.tasks_table_report(t, short)

------------------------------------------------------------
-- Manage options. Administrar opciones. Gérer les options.
------------------------------------------------------------

--	Options de maitrise du fonctionnement de ce module

tools.invoke_options = "" -- Options normales venant du modèle. Aucune par defaut. Normal options from the template.
	--	In the template : options = "params docview docmin docmax docdef docnotice docafter docline docsrc"

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

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

tools.used_options = {} -- table to collect tested options then list them at end of tests

--	if tools.option(":") or tools.option("catview") then tools.catView = ":" ; tools.catView = ":" else tools.catView = "" ; tools.catView = "" end

function tools.option(key, opt)
	-- if option("nocat") then cat = "" end -- utilisation exemple simple
	-- Si le mot key est parmi les mots options, repondre true
	-- Chercher dans invoke_options, mode_options ou opt, voir ci-dessous
	-- tools.options = " : docdata docmin docdef docmax docline docview docsrc docafter docnotice " -- for documentation
	-- tools.options = " erron noerr nobox nocat " -- without normal result
	-- tools.options = " debug tests en es fr " -- for debug or enforce language
	-- Veiller à toujours séparer les mots par des espaces
	-- Les identifiants de langues permettent de forcer certaines langues.
	-- Les erreurs n'apparaissent que dans les espaces de noms Modèle ou Module, en attendant la réalisation du Bug 51660.
	if type(key) ~= "string" then key = false end
	if type(opt) ~= "string" then opt = false end
	local available_options = " " .. (tools.invoke_options or "") .. " "
	if opt -- opt param can replace tools.mode_options
		then available_options = " " .. opt .. " " .. available_options .. " "
		else available_options = " " .. tools.mode_options .. " " .. available_options .. " "
	end -- options du modèle et de debug sinon
	-- Chercher le mot clef exact, non inclus dans un autre.
	-- Search the exact key, not included in another.
	local key2 = " " .. (key or "				  ") .. " "
	-- Le mot cle est-il parmi les options definies ?
	-- The searched keyword is it among the options words?
	local n = string.find(available_options, key2)
	local ifyes = n and (n > 0)
	if not ifyes then ifyes = false else ifyes = true end
	-- collect options tested along the execution of the module
	if ifyes then tools.used_options[key] = "a" else tools.used_options[key] = "x" end
	return ifyes, available_options -- ( yes ~= nil )
end -- function tools.option(key, opt)

function tools.init_options(opt)
	-- tools.invoke_options = init_options(args.options)
	-- tools.invoke_options = init_options("fr params docview docmin docmax docdef docnotice docafter docline docsrc")
	--
	if (type(opt) == "string") then tools.invoke_options = opt end --	 and (new ~= "")
	--
	-- Early effects on options which modify other ones.
	-- Effets précosses pour des options qui agissent sur d'autres options.
	-- tools.options = " : docdata docmin docdef docmax docline docview docafter docnotice docsrc" -- for documentation
	-- tools.options = " erron noerr nobox nocat " -- without normal result
	-- tools.options = " debug tests en es fr " -- for debug or enforce language
	tools.options_to_catView()
	if tools.option("noerr") then tools.erron = false end
	if tools.option("erron") then tools.erron = true end
	if tools.option("docolor") then tools.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.
	local _lang
	for lang, tab in pairs(tools.i18n) do
		if tools.option(lang) then
	--		tools.init_user_lang(lang, tools.wiki_lang) -- tools.user_lang, tools.wiki_lang
	--		_lang = lang
		end
	end
--	if _lang then tools.option(_lang) end
--	tools.init_user_lang(nil) -- , tools.args_final.wikilang)
	return opt
end -- function tools.init_options(opt)

function tools.used_options_list(t, used_options)
	local t = "List of modes:" or t
	for md, opt in pairs(tools.options_for_modes) do t = t .. "<br>- " .. tools.ta(md, opt) end
	-- List of used options after collect them
	used_options = used_options or tools.used_options
	t = t or ""
	t = t .. "\n* " .. tools.str_vars("tools_user_wiki_lang_msg", tools.user_lang, tools.wiki_lang) -- ( or tools.str_vars("tools_user_wiki_lang_msg", tools.user_lang, tools.wiki_lang) or " missing user_wiki_lang_msg " )
	t = t .. "\n* Actual mode used: <b>" .. tools.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 opt = ""
	for key, x in pairs(tools.used_options) do
		if x == "a" then opt = opt .. ", <b>" .. tostring(key) .. "</b> "
		else opt = opt .. ", " .. tostring(key) .. " " end
	end
	t = t .. opt
--	t = t .. "<br>* " .. tools.get_editstate()
--	t = t .. "\n* import_arguments_track : " .. tools.import_arguments_track
	return t, opt
end -- function tools.used_options_list(t, used_options)

function tools.options_from_args_test(t)
	local mode_options_memo = tools.mode_options -- save
	local invoke_options_memo = tools.invoke_options -- save
	local used_options_memo = mw.clone(tools.used_options) -- save
	tools.used_options = {}
	local t = "options_from_args_test:" or t
	t = t .. tools.Th() .. tools.Tc("mode_options") .. tools.Tc("invoke_options") .. tools.Tc("opt can replace mode_options") .. tools.Tc("available options") .. tools.Tc("option result")
	local function options_from_args(mode_options, invoke_options, opt, key)
		tools.mode_options = mode_options
		tools.invoke_options = invoke_options
		local ifyes, available_options = tools.option(key, opt)
		return tools.Tr() .. tools.Td(mode_options or "") .. tools.Td(invoke_options or "") .. tools.Td(opt or "")
		.. tools.Td( tostring(available_options) ) .. tools.Td(tools.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 .. tools.Te()
	t = t .. tools.used_options_list(nil, tools.used_options)
	t = t .. "\n* After these tests, anterior options are restored. Après ces tests, les options antérieures sont restaurés."
	tools.mode_options = mode_options_memo -- restore
	tools.invoke_options = invoke_options_memo -- restore
	tools.used_options = used_options_memo -- restore
	return t
end -- function tools.options_from_args_test(t)

function tools.wordstotable(txt, opt) -- convertit un texte en table de mots
	local t = ""
	local tab = {}
	local function inserer(ti)
		if ti ~= "" then table.insert(tab, ti.."") end
	end
	local xyz = string.gsub( txt, "(%S*%-*%S*)", inserer ) -- "(%w*%-*%'*%w*)", "(%S*%-*%S*)"
	for key, val in pairs(tab) do -- Pour tous les mots
		t = t .. " ( " .. tostring(key) .. " = " .. tostring(val) .. " ) "
	end
	t = "\n* wordstotable txt = " .. tostring(xyz) .. " " .. t
	return tab, t, opt
end -- function tools.wordstotable(txt, opt)

------------------------------------------------------------
-- Argts : Manage arguments. Gestione argumentos. Gérer les arguments.
------------------------------------------------------------

-- Compute the Levenshtein distance between 2 ASCII words.
function tools.levenshtein(word1, word2)
	-- prevent exceptions
	local cout = 0
	if (word1 == nil) or (word2 == nil) then
		return 999, "<br>lev: " .. tools.ta("word1", word1) .. tools.ta("word2", word2)
	end
	local len1 = string.len(word1)
	local len2 = string.len(word2)
	local lev = 0
	local t = "<br>lev: " .. tools.ta("word1", word1) .. tools.ta("word2", word2)
	if (type(word1) ~= "string") or (type(word2) ~= "string") or (word1 == "") or (word2 == "") then
		lev = len1 + len2
		return lev, t .. tools.ta("lev", lev)
	end
	-- simple case
	if (word1 == word2) then
		lev = 0
		return lev, t .. tools.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 .. tools.ta("lev", lev)
end -- function tools.levenshtein(word1, word2)

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

function tools.levenshtein_test(res, c)
	if type(res) ~= "string" then res = nil end
	local res = res or ("\n* " .. tools.str_vars("tools_max_nearest_argument_msg") )
	local errors = ""
	local n, t = tools.levenshtein_test_1( "nom", "nom")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "nom", "Nom")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "top", "pot")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "ami", "amis")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "nom", "name")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "m", "mu")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "m", "mur")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "mur", "m")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "c", "C")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "c", "cf")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "c", "long")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "xxx", "")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "", "xyz")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "xxx", "xyz")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "prénom", "Prenom")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "catégorie", "Category")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "description", "Description")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "anneeDeces", "anneeNaissance")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "anoNacimiento", "anneeNaissance")
	res = res .. tostring(t)
	local n, t = tools.levenshtein_test_1( "avant-après", "après-avant")
	res = res .. tostring(t)
	if errors ~= "" then res = res .. "\n* '''levenshtein_test''' errors = " .. tools.error_color(errors) end
	return res
end -- function tools.levenshtein_test( res, c)

tools.constants = tools.constants or {}
-- Similar words search : diff on length
-- Cerrar de palabras puscadas: diff en longitud
-- Recherche de mots proches: diff sur longueur
tools.constants.near_word_search_diff_coef = 0.30 -- Access to change these constants from anywhere.
tools.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 tools.similar_args_diffmaxi(length, t) -- diffmaxi from length of arg arglingual name
	local coef = tools.constants.near_word_search_diff_coef or 0.30
	local constant = tools.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 tools.similar_args_diffmaxi(length, t)

-- Pour un argument inconnu, cherche le nom d'argument le plus proche, parmi les arguments connus traduits
function tools.similar_args_list(args_known)
	local list, arglingual = {}, "xxx"
	if type(args_known) ~= "table" then args_known = tools.args_known end
	if type(args_known) ~= "table" then return "similar_args_list", 1 end
	-- faire la liste des arguments possibles, c'est a dire connus
	for key, argm in pairs(args_known) do
		if not tonumber(key) then -- Pour les arguments nommés seulement
			key = tostring(key)
			arglingual = tostring(tools.wiki_translations[key])
			list[key] = arglingual
		end
	end
	return list
end -- function tools.similar_args_list(args_known)

-- For an unknown argument, seeking the name of the nearest argument among the known arguments
-- Para un argumento desconocido, buscando el nombre del argumento más cercana entre los argumentos conocidos
-- Pour un argument inconnu, cherche le nom d'argument le plus proche, parmi les arguments connus
function tools.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 = tools.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 = tools.levenshtein_test_1(tostring(cherche), tostring(mot)) -- obsolete
		dist, tlev = tools.levenshtein(search, arglingual)
	--	if type(dist) ~= "number" then dist = 9 end
		if (dist <= min1) then
			trouve3 = trouve2
			min3 = min2
			trouve2 = trouve1
			min2 = min1
			min1 = dist
			trouve1 = tostring(arglingual)
		end
	end
	t = t .. "<br>" .. tools.ta("cherche", cherche) .. tools.ta("trouve1", trouve1) .. tools.ta("min1", min1) .. tools.ta("trouve2", trouve2) .. tools.ta("min2", min2) .. " " .. t
	if trouve1 and min1 == 0 then t = t .. tools.wikidata_color(tools.ta("connu", trouve1)) end
	if trouve1 and min1 <= diffmaxi then t = t .. tools.wikidata_color(tools.ta("min1 "..min1.."<="..diffmaxi, trouve1)) end
	if trouve2 and min2 <= diffmaxi then t = t .. tools.wikidata_color(tools.ta("min2 "..min2.."<="..diffmaxi, trouve2)) end
	if trouve3 and min3 <= diffmaxi then t = t .. tools.wikidata_color(tools.ta("min3 "..min3.."<="..diffmaxi, trouve3)) end
	return trouve1, min1, trouve2, min2, t
end -- function tools.similar_args_search(search, list)

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

function tools.similar_args_test( t, args_known)
	if type(args_known) ~= "table" then args_known = tools.args_known end
	local err = tools.verify_args_tables(args_known, tools.args_source)
	if err then return err end
	--
	local key, argsyn, arglingual, txt
--	local coef = tools.constants.near_word_search_diff_coef
--	local constant = tools.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 = tools.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 = tools.similar_args_diffmaxi(length)
		t = t .. ", " .. tostring(diffmaxi) .. " / " .. tostring(length) .. " "
	end
	t = t .. "\n* List of known arguments and '''synonyms''' : "
	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
	--	tools.user_translations = tools.i18n[lang] or tools.user_translations
	--	tools.wiki_translations = mw.language.getContentLanguage().code
		lingual = tools.wiki_translations[key] or "-" -- importer un argument source
		txt = argsyn .. key .. "/" .. lingual
		if argm.syn == 1 then
			t = t .. ", '''" .. txt .. "''' " -- '''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 = tools.similar_args_test1( t, "anneedece", {["a"]="but", ["b"]="porter", ["c"]="anneedeces"})
	t = t .. "\n* Test similar arguments 2."
	t = tools.similar_args_test1( t, "porte", {["a"]="but", ["b"]="porter", ["c"]="pot"})
	return t
end -- function tools.similar_args_test( 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 tools.multiple_values(argmt, argvalue, args_final, args_known)
	local args_final = args_final or tools.args_final or {}
--	local args_known = args_known or tools.args_known
	if type(args_known) ~= "table" then args_known = tools.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 = tools.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
end -- function tools.multiple_values(argmt, argvalue, args_final, args_known)

function tools.multiple_values_tests(t)
	t = t or "\n* Test '''multiple_values''' :"
	t = t .. tools.Th() .. tools.Tc("argm") .. tools.Tc("argvalue") .. tools.Tc("args_final") .. tools.Tc("keyword") .. tools.Tc("keyval") .. tools.Tc("argval") .. tools.Tc("rank")
	local function multiple_values_tests1(t, argm, argvalue, args_final)
		local keyword, keyval, argval, rank = tools.multiple_values(argm, argvalue, args_final)
		t = (t or "") .. tools.Tr() .. tools.Td(argm) .. tools.Td(argvalue) .. tools.Td(args_final) .. tools.Td(keyword) .. tools.Td(keyval) .. tools.Td(argval) .. tools.Td(rank)
		return t
	end -- function multiple_values_tests1(t, argm, argval)
	t = multiple_values_tests1(t, "region", "inde")
	t = multiple_values_tests1(t, "region", "inconnue")
	t = multiple_values_tests1(t, "rights", "mpf")
	t = multiple_values_tests1(t, "rights", "non")
	t = multiple_values_tests1(t, "rights", "aucun")
	t = multiple_values_tests1(t, "sex", "femme")
	t = multiple_values_tests1(t, "sex", "homme")
	t = multiple_values_tests1(t, "sex", "enfant")
	args_final = { region = "india", rights = "non", sex = "femme", }
	t = (t or "") .. tools.Tr() .. tools.Tc() .. tools.Tc() .. tools.Tc('args_final = {') .. tools.Tc('region = "india"') .. tools.Tc('rights = "non"') .. tools.Tc('sex = "femme"') .. tools.Tc('}')
	t = t .. tools.Tr() .. tools.Tc("argm") .. tools.Tc("argvalue") .. tools.Tc("args_final") .. tools.Tc("keyword") .. tools.Tc("keyval") .. tools.Tc("argval") .. tools.Tc("rank")
	t = multiple_values_tests1(t, "region", "inde", args_final)
	t = multiple_values_tests1(t, "region", nil, args_final)
	t = multiple_values_tests1(t, "rights", "mpf", args_final)
	t = multiple_values_tests1(t, "rights", "non", args_final)
	t = multiple_values_tests1(t, "rights", nil, args_final)
	t = multiple_values_tests1(t, "sex", nil, args_final)
	t = multiple_values_tests1(t, "sex", nil, args_final)
	t = multiple_values_tests1(t, "sex", "enfant", args_final)
	t = t .. tools.Te()
	return t
end -- function tools.multiple_values_tests(t)

function tools.multiple_selection(opt, selector, to_select)
	-- Select items to select 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, select in ipairs(selector_tab) do -- select authorities only following selectors
		if Nsel >= maxi then break end
		select = mw.text.trim(select)
		N = tonumber(select)
		if select == "+" then -- select all items
			for key, val in pairs(to_select_tab) do
				Nsel = Nsel + 1
				selected_tab[key] = val
			end
		elseif select == "-" 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(select, 1, 1) == "+") then -- select +N more items
			maxi = Nsel + N
		elseif N then -- select N maximum total items
			maxi = N
		else -- select ONE item from to_select_tab if it matches select ( not - or + or +N or N )
			for key, val in pairs(to_select_tab) do
				local select_t, val_t = select, val
				if not tools.is_in("U", opt) then select_t = string.lower(select_t) end -- recognize lowercase and uppercase
				if not tools.is_in("t", opt) then select_t = mw.text.trim(select_t) end -- recognize after trim
				if not tools.is_in("U", opt) then val_t = string.lower(val_t) end -- recognize lowercase and uppercase
				if not tools.is_in("t", opt) then val_t = mw.text.trim(val_t) end -- recognize after trim
				equal = tools.is_in("=", opt) -- recognize only equal string
				if equal then equal = (select == val) else equal = tools.is_in(select_t, val_t) end
			--	if tools.is_in(select, val) then
				if equal then -- recognize if select is equal or is inside an item from to_select_tab
					t = t .. tools.ta(select, val)
					selected_tab[select] = 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 tools.multiple_selection(opt, selector, to_select)

function tools.multiple_selection_test(t)
	t = (t or "") .. "\n* '''multiple_selection''' options: " .. tools.ta("=", "equal only") .. tools.ta("t", "not trim before and after") .. tools.ta("U", "not lowercase and uppercase")
	local function multiple_selection_test1(t, opt, selector, to_select)
		local selected_txt, selected_tab, txt = tools.multiple_selection(opt, selector, to_select)
		t = t .. tools.Tr() .. tools.Td(opt) .. tools.Td(selector) .. tools.Td(to_select) .. tools.Td( txt .. tools.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( tools.str_vars("tools_multiple_selection_test_header") , ',')
	t = t .. tools.Th() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[3]) .. tools.Tc(head[4])
	-- Todo ? P39 = fonction = "président de Pologne, député à l'Assemblée, Prix Nehru, Nobel de la paix"
	-- tools_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 .. tools.Te()
	return t
end -- function tools.multiple_selection_test(t)

function tools.bindmodules_test(t) return tools.versions_management_test(t) end -- Deprecated alias function
function tools.versionsmanagement_test(t) return tools.versions_management_test(t) end -- Deprecated alias function
function tools.versions_management_test(t) -- Test : List all loaded modules tools_bindmodules_test_title
	local t = (t or "") .. "\n* <b>tools.versions_management_test</b> : The selector = <b> Box3, Group, Item1 </b> means: check these modules versions among those available."
	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 bindmodules_test1(t, vers_test)
		local tt, vers = centre.bindmodules(main_p, vers_test) -- Bind all modules and versions
		local vers_err = tools.versions_management_report(vers) -- Detect versions management errors and warnings after bindmodules
		vers.sought = tools.compact_comma_list(vers.sought)
	--	local vers_sought = tools.compact_comma_list(vers.used)
		local vers_sought = "," ..	string.gsub( (vers.sought or "-s-"), ",", ",<br>" ) .. "," -- names in column
		local vers_used = tools.compact_comma_list(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 = tools.compact_comma_list(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 "") .. tools.Tr() .. tools.Td(vers_sought) .. tools.Td(vers_used) .. tools.Td(vers_results)
		return t -- tools.Td(vers.used) ..
	end
	t = t .. "\n* '''loaded_vers''' : 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( tools.str_vars("tools_bindmodules_test_headers") , ',') -- tools_bindmodules_test_title -- tools_versions_management_title
	t = t .. tools.Th() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[4])
	t = bindmodules_test1( t, nil )
	t = t .. tools.Tr() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.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 tools.compact_comma_list(vers_test.loaded_list)
	--	vers_test.i18n = { en = { txt = "txt" } } --
		if tools.is_in("/I18N", vers_test.key) then vers_test.isI18N = true end
		loaded_vers[i] = vers_test
		t = bindmodules_test1( t, vers_test)
	--	t = t .. "<b>" .. vers_test.key .. "</b>, "
	end
	t = t .. tools.Tr() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[5])
	t = t .. tools.Te()
	local t2 = ""
	local head = mw.text.split( tools.str_vars("tools_list_loaded_modules_headers") , ',')
	t2 = t2 .. "\n* The table below simulates loaded modules for behind tests cases."
	t2 = t2 .. tools.Th() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[3]) .. tools.Tc(head[4]) .. tools.Tc(head[5])
	for key, elem in pairs(loaded_tests) do
		t2 = t2 .. tools.Tr() .. tools.Td(elem.key) .. tools.Td(elem.versionName) .. tools.Td(elem.versionDate) .. tools.Td( type(elem.i18n) ) .. tools.Td(elem.comment)
	end
	t2 = t2 .. tools.Te()
	t2 = t2 .. "\n* " .. tools.ta("Number of versions simulated:", #loaded_tests)
	t = t .. t2
	return t
end -- function tools.bindmodules_test(t)

function tools.compact_comma_list(list) -- Standardize list like "Central01,centre 2.4,tools,two words"
	local list = "," .. string.gsub(list or "", "*", "," ) .. "," -- mask "*"
	local list = "," .. string.gsub(list or "", "<br>", "," ) .. "," -- mask new line
	local listtab = mw.text.split(list or "", ',') -- names between commas
	local listtab2 = {}
	for i, name in ipairs(listtab) do -- for all names
		name = mw.text.trim(name) -- keep spaces in each name
		listtab2[i] = name
	end
	table.sort(listtab2, function (a, b) return (a < b) end ) -- sort in alphabetic order
	local list2 = ""
	for i, name in ipairs(listtab2) do -- list all names, only one each, in alphabetic order
		name = mw.text.trim(name)
		if (name ~= "") and not tools.is_in_sp(name, list2, ",") then list2 = list2 .. name .. "," end
	end
	local list = string.sub( list2, 1, -2 ) -- without last ","
	return list
end -- function tools.compact_comma_list(list)

function tools.verif_i18n(i18n) return tools.verifyinit(i18n) end
function tools.verifyinit(i18n) -- check coherence of translations, wiki_lang, user_lang,
	-- tools_missing_translations_title
	if type(i18n) ~= "table" then i18n = centre.i18n end
	if type(i18n) ~= "table" then return " verifyinit MISSING. " end
	local nerr, trad, t, err = 0, "", "", ""
	local nbr, list = 0, ""
	-- tools_missing_translations_title
	-- List any gaps of translations in i18n tables.
	-- Liste cualquier las lagunas traducciones en tablas i18n.
	-- Lister tous les manques de traductions dans les tables i18n.
	local prec_tab, suiv_tab, max, nk = nil, nil, 0, 0
	local lang, prec_lang, suiv_lang, cats = "", "", "", ""
	local all_txts = {}
	for lang, texts in pairs(i18n) do -- Pour toutes les langues et tous les textes à traduire
		suiv_lang = lang -- table suivante de traduction d'une langue
		suiv_tab = 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 k, v in pairs(texts) do -- Lister tous les textes traduits au moins dans une langue
				all_txts[k] = "x"
			end
			if prec_tab ~= nil and suiv_tab ~= nil then -- pour chaque couple de langues à comparer
				t = t .. "\n* Comparer les langues : '''" .. prec_lang .. "/" .. suiv_lang .. "''', "
				nk = 0
				for k, x in pairs(all_txts) do -- Pour tous les textes à traduire
					trad = suiv_tab[k]
					nk = nk + 1
					if trad == nil then -- and k ~= nil then -- args vers un argument nommé
						err = tools.err_add("tools_module_miss_i18n_trad_err", k, suiv_lang) -- tools_module_miss_i18n_txt_err
						t = t .. err .. " " -- .. "<br>"
						nerr = nerr + 1
					end
				end
				if nk > max then max = nk end
			end
		end
		prec_lang = suiv_lang
		prec_tab = suiv_tab
	end
	if nerr > 0 then
		err = tools.err_add("tools_module_miss_i18n_count_err", nerr, max)
		t = t .. "<br>" .. err .. " "
		cats = cats .. tools.cat_add("tools_err_module_miss_i18n_cat")
--		cats = cats .. tools.cat_add("tools_module_internal_error_cat")
	else
		err = tools.msg_add("tools_module_miss_i18n_none_err")
		t = t .. "<br>" .. err .. " "
	end
	-- tester vr le nombre total de variables de traductions
	local st, vr, fn, tb = tools.luatable_lister(i18n, "i18n")
	if vr < 33 then
		err = tools.err_add("tools_module_miss_i18n_mini_err", vr)
		t = t .. "<br>" .. err .. " "
		cats = cats .. tools.cat_add("tools_err_module_miss_i18n_cat")
--		cats = cats .. tools.cat_add("tools_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 -- Pour toutes les langues à importer
	--	if tools.i18n[lng] then -- la langue existe déja, y ajouter ou y remplacer les champs importés
		if i18n[lng] then -- la langue existe déja, y ajouter ou y remplacer les champs importés
			t = t .. tools.str_vars(", '''Langue : %1</b> ", lng)
			ln = ln + 1
			if type(texts) == "table" then -- pour chaque couple de langues à comparer
				for argn, val in pairs(argmts) do -- For all imported fields
					if tools.i18n[lng][argn] then Nchange = Nchange + 1
					else Nadd = Nadd + 1 end
					-- Add or replace a field and its translation. Ajouter ou remplacer un champs et sa traduction
					tools.i18n[lng][argn] = val --	t = t .. tools.str_vars(", %1 ", argn)
					an = an + 1
				end
			end
		else -- ajouter la table et tous ses champs si elle n'est pas encore dans tools.i18n
			if mw.language.isKnownLanguageTag(lng) then
				tools.i18n[lng] = i18n[lng]
			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(tools.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 .. tools.str_vars("tools_list_in_translated_lang", Ntotal, lang, langname, native)
			-- tools_list_in_translated_lang	= "\n* %1 translations in language %2 : %3 : %4",
		end
	end
	tools.user_wiki_lang_msg = tools.str_vars("tools_user_wiki_lang_msg", tools.user_lang, tools.wiki_lang)
	tools.user_wiki_lang_err = tools.user_wiki_lang_msg -- deprecated for Module:Author3
	tools.Lang_list = lst_lng
	t = t .. tools.str_vars("tools_languages_nbr_and_list", nbr, list) .. lst_lng
	t = t .. "\n* Same example without translation for a test case : " .. tools.str_test_case("tools_languages_nbr_and_list", nbr, list)
	t = t .. "\n\n"
	t = t .. tools.transdiff_report() -- Compare all differences of translations
	return t, cats, lst_lng
end -- function tools.verifyinit(i18n)

function tools.transdiff_report(t) -- Compare all differences of translations
	local t = t or ("\n* '''transdiff_report''': Compare all differences of translations:")
	local head = mw.text.split( tools.str_vars("tools_transdiff_report_headers") , ',')
	t = t .. tools.Th("wikitable sortable") .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[3]) .. tools.Tc(head[4]) .. tools.Tc(head[5])
	for key, trans in pairs(centre.translations_differences or {} ) do
		local trans1 = trans.trans1
		local trans2 = trans.trans2
		t = t .. tools.Tr() .. tools.Tc(trans1.dlang) .. tools.Tc(trans1.dkey) .. tools.Tc(trans1.dtrans) .. tools.Tc(trans2.dtrans) .. tools.Tc(trans2.dfile)
	end
	t = t .. tools.Te()
	return t -- tools_transdiff_report_title
end -- function tools.transdiff_report(t)

function tools.versionsmanagement_report(vers) return tools.versions_management_report(vers) end -- Deprecated alias function
function tools.versions_management_report(vers) -- Detect versions management errors and warnings after bindmodules
	--[[
	The versionning could have 2 meanings. One is successive states of a page or a module.
	Another is a group of modules with similar names and functions. Example: job, job01, job02.
	The versioning of group management that I experiment is managed by a user, which defines in a main_module a list of soughtversions included in a list of known_versions.
	Example: soughtversions = "Author3 MathRoman2 Central1"
	known_versions = " Author,Author3 * MathRoman,MathRoman2 * Central,Central1 "
	The user changes sought and known at module level or at Args level.
	And a versions_library supports the module and the user.
	The main_module can display messages like "MathRoman44 is not in known versions, normal MathRoman replaces it." or "Central345 is missing".

	tools_all_versions_tests		= "Versions warning: <b>%1</b>, normal: <b>%2</b>, listall: <b>%3</b>.", -- versionsmanagement
	tools_select_unknown_module_err = "Internal error: The missing Module:<b>%1</b> is replaced by the normal Module:<b>%2</b>.", -- versionsmanagement
	tools_I18N_module_no_base_err	= "The <b>%1</b> translations module has no basic version.", -- versionsmanagement
	tools_no_versions_module_err	= "The module <b>%1</b> is not in the system centre.", -- versionsmanagement
	--]]
	local errors_list_memo = tools.errors_list
	tools.errors_list = {} -- Table to collect errors and messages
	local categories_list_memo = tools.categories_list
	tools.categories_list = {} -- Table to collect all categories to generate in wikitext
	local t, errs, cats = "", "", ""
	if type(vers) ~= "table" then vers = tools.main_versions end
	if type(vers) ~= "table" then vers = centre.main_versions end
--	if type(vers) ~= "table" then return t end
	--[[
	p.versions_example = { -- Modules dependencies. Dependencias del módulo. Dépendances du module.
		versionName = "Author3", versionNumber = "3.03", versionDate = "2016-04-25 10:31",
		sought = " Author3, Central, 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 = tools.compact_comma_list(vers.sought)
	local known_report = tools.compact_comma_list(vers.known)
	local loaded_report = tools.compact_comma_list(vers.loaded_list)
	local used_report = tools.compact_comma_list(vers.used)
	local alloncesort = tools.compact_comma_list(sought_report .. "," .. known_report .. "," .. used_report) -- .. "," .. loaded_report)
--	t = t .. "\n* <b>Versions management report</b>, second: " .. alloncesort
	local argm = tools.args_known
	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 tools.is_in_sp(name, sought_report, ",") and not tools.is_in_sp(name, known_report, ",") and not name_I18N then
			missing_report = missing_report .. name .. "," -- sought but not found
			errs = errs .. tools.err_add("tools_missing_versions_err", name, vers.versionName )
			cats = cats .. tools.cat_add("tools_module_usage_error_cat")
		end
		if tools.is_in_sp(name, used_report, ",") and not tools.is_in_sp(name, sought_report, ",") and not name_I18N then
			replaced_report = replaced_report .. name .. "," -- missing version replaced by normal
			errs = errs .. tools.err_add("tools_replaced_versions_err", name, vers.versionName )
			cats = cats .. tools.cat_add("tools_module_usage_error_cat")
		end
		if tools.is_in_sp(name, alloncesort, ",") and not tools.is_in_sp(name, known_report, ",") and not name_I18N then
			unknown_report = unknown_report .. name .. "," -- found but not known
			errs = errs .. tools.err_add("tools_unknown_versions_err", name, vers.versionName )
			cats = cats .. tools.cat_add("tools_module_usage_error_cat")
		end
		--[[
		tools_no_versions_module_err	= "Le module <b>%1</b> n'est pas dans le système des versions.",
		tools_all_versions_tests		= "Versions avertissements : <b>%1</b>, normaux : <b>%2</b>, liste : <b>%3</b>.",
		tools_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>.",
		tools_missing_versions_err		= "Le module <b>%1</b> manque dans le module <b>%2</b>.",
		tools_replaced_versions_err		= "Le module <b>%1</b> remplace le module absent <b>%2</b>.",
		tools_unknown_versions_err		= "Le module <b>%1</b> est inconnu dans le module <b>%2</b>.",
		--]]
	end
	missing_report = tools.compact_comma_list(missing_report)
	replaced_report = tools.compact_comma_list(replaced_report)
	unknown_report = tools.compact_comma_list(unknown_report)
--	t = t .. "\n* Versions management report "
--	t = t .. "<br/><b>Sought</b> modules: <b>" .. sought_report .. "</b>"
--	t = t .. "<br/><b>Known</b> modules: <b>" .. known_report .. "</b>"
	t = t .. errs -- "<br/>" ..
	--
--	t = t .. "<br/><b>Replaced</b> modules, missing version replaced by normal: <b>" .. replaced_report .. "</b>"
--	t = t .. "<br/><b>Selected</b> modules, sought and found: <b>" .. used_report .. "</b>"
--	t = t .. "<br/><b>Missing</b> modules, sought but not found: <b>" .. missing_report .. "</b>"
--	t = t .. "<br/><b>Unknown</b> modules, found but not known: <b>" .. unknown_report .. "</b>"
--	t = t .. "<br/><b>All used names</b>, in alphabetic order: <b>" .. alloncesort .. "</b>"
--	t = t .. "<br/>" .. cats
	tools.errors_list = errors_list_memo -- Table to collect errors and messages
	tools.categories_list = categories_list_memo -- Table to collect all categories to generate in wikitext
	return t
end -- function tools.versions_management_report(vers)

------------------------------------------------------------
-- Datas wikidata from mw.wikibase
------------------------------------------------------------

function tools.import_wikidata(args_known, id) -- id = itemid
--	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
	if type(id) ~= "string" then 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 .. tools.ta("wd_error", wd_mng.wd_error)
		wd_mng.t = t
		tools.args_wikidata = wd
		return wd, t, wd_mng
	end
	function pcall_getEntityObject( 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
	if id then -- Wikidata item available ?
		wd_mng.wd_entity = pcall_getEntityObject( id )
	else
		wd_mng.wd_entity = mw.wikibase.getEntityObject()
	--	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_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_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_PREFERRED then
				wd_mng.formatPropertyValues_RANK_PREFERRED = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_PREFERRED } )
			end
			if wd_mng.wd_RANK_NORMAL then
				wd_mng.formatPropertyValues_wd_RANK_NORMAL = wd_mng.wd_entity:formatPropertyValues(	 pp, { wd_mng.wd_RANK_NORMAL } )
			end
			if wd_mng.wd_RANK_DEPRECATED 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 .. "=" .. tools.ta(proplabel, val)
			-- .. "/" .. tools.ta(proplabel, wd_mng.formatPropertyNORMAL)
			if pp == "P569" then -- birthyear P569 for test DEBUG
			tools.TimeName = pp .. ".1.mainsnak" ; tools.WikidataTimeDetails = wd_mng.wd_entity.claims[pp][1].mainsnak end
			if pp == "P570" then -- deathyear P570 for test DEBUG
			tools.TimeName = pp .. ".1.mainsnak" ; tools.WikidataTimeDetails = 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 .. tools.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 = tools.str_vars("tools_wikidata_error_err", "wikidata_props") end
		if wd_mng.wd_error == "wd_base" then err = tools.str_vars("tools_wikidata_wikibase_err") end
		if wd_mng.wd_error == "wd_entity" then err = tools.str_vars("tools_wikidata_getEntity_err", wd_mng.wd_entity) end
		if wd_mng.wd_error == "wd_property" then err = tools.str_vars("tools_wikidata_property_err", wd_mng.wd_property) end
		-- elem -> id
		if wd_mng.wd_error == "wd_id" then err = tools.str_vars("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
		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_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 .. tools.ta(key, val) -- .. tools.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 tools.import_wikidata(args_known, id)

function tools.wikidata_details_test(t)
	local t = t or "\n* '''wikidata_details_test''' :"
	local wd, tw, wd_mng
	--	t = t .. tools.tools_wikidata_arbitrary_test()
	wd, tw, wd_mng = tools.import_wikidata( tools.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 tools_structured_data_txt = tools.str_vars("tools_structured_data_txt")
		t = t .. ' <span style="color:#232388; font-size:140%; line-height:120%; ">⦁&nbsp;&nbsp;</span>[[d:' .. wd_mng.wd_entity.id .. '|' .. tools_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 "")
	return t
end -- function tools.wikidata_details_test(t)

function tools.tools_wikidata_arbitrary_test(t)
-- T49930 Allow accessing data from a Wikidata item not connected to the current page - arbitrary access (tracking)
	t = "" -- tostring(t) -- t or ""
	local function one_arbitrary_access(id)
		local wd, tw, wd_mng = tools.import_wikidata(tools.args_known, id) -- tools.args_known, id) -- example Q535 = Victor Hugo = Victor Marie Hugo
	--	local args_import, tx = tools.import_arguments() -- tools.args_known, tools.args_source )
		if wd_mng and wd_mng.wd_entity and wd_mng.wd_entity.id then
			t = "\n* " .. tools.str_vars("tools_wikidata_arbitrary_access_text")
		--	.. (wd.itemid or "") .. ", " .. (wd.sitelink or "") .. " ( " .. (wd.birthyear or "") .. " - " .. (wd.deathyear or "") .. " ) "
			.. (wd.itemid or "") .. ", " .. (wd.label or "") .. " ( " .. (wd.birthyear or "") .. " - " .. (wd.deathyear or "") .. " ) "
		--	.. (wd_mng.wd_id or "") .. ", " .. (wd_mng.title or "") .. " ( " .. (wd.birthyear or "") .. " - " .. (wd.deathyear or "") .. " ) "
			local tools_structured_data_txt = tools.str_vars("tools_structured_data_txt")
			t = t .. ' <span style="color:#232388; font-size:140%; line-height:120%; ">⦁&nbsp;&nbsp;</span>[[d:' .. wd_mng.wd_entity.id .. '|' .. tools_structured_data_txt .. ']]'
			t = t .. ", " .. (wd_mng.props_maxn or "") .. " properties. " -- "\n* Properties number maxn: "
		--	wd, tw, wd_mng = tools.import_wikidata( tools.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 tools_structured_data_txt = tools.str_vars("tools_structured_data_txt")
		--		t = t .. ' <span style="color:#232388; font-size:140%; line-height:120%; ">⦁&nbsp;&nbsp;</span>[[d:' .. wd_mng.wd_entity.id .. '|' .. tools_structured_data_txt .. ']]<br>'
			end
			t = t .. "\n* Sought properties: " .. tw
		--	t = t .. "\n* Properties txt: " .. (wd_mng.props_txt or "")
		--	t = t .. "\n* Properties list: " .. (wd_mng.props_list or "")
		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
	return t
end -- function tools.tools_wikidata_arbitrary_test(t)

function tools.verify_args_tables(_known, _source)
	-- initialize all the values to "" in arg table
	if type(_known) == "table" then
		tools.args_known = _known
	end
	if type(tools.args_known) ~= "table" then
		tools.err_add("tools_no_known_arguments_err")
--		tools.cat_add("tools_tools_no_known_args_cat")
		return tools.error_color(" Internal error : no source or no known arguments ! ")
	end
	if type(_source) == "table" then
		tools.args_source = _source
	end
	if type(tools.args_source) ~= "table" then
		tools.err_add("tools_no_source_arguments_err")
--		tools.cat_add("tools_no_source_arguments_cat")
		return tools.error_color(" Internal error : no source or no known arguments ! ")
	end
	return
end -- function tools.verify_args_tables(_known, _source)

function tools.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 = tools.args_known end
	if type(args_known) ~= "table" then args_known = tools.args_known end
	if type(args_source) ~= "table" then args_source = tools.args_source end
	wiki_translations = wiki_translations or tools.wiki_translations
	--
	if type(args_wikidata) ~= "table" then args_wikidata = tools.args_wikidata end
	tools.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
	args_import.nowyear = tools.nowyear
	tools.import_arguments_err = ""
	tools.import_arguments_track = ""
	local err = tools.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
--	tools.erron = true -- Errors actived or no. Errores activas o no. Erreurs activées ou non.
--	tools.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
	--
	tools.args_unknown = mw.clone(tools.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
		tools.import_arguments_track = tostring(tools.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"
			tools.import_arguments_track = tostring(tools.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
			tools.import_arguments_track = tools.import_arguments_track .. "='''" .. tostring(argval) .. "''' "
		end
		-- import a source argument. importer un argument source.
		arglingual = tools.wiki_translations[key_known]
		tools.import_arguments_track = tools.import_arguments_track .. "/" .. tostring(arglingual)
		if arglingual then -- The argument name has a translation in wiki language
		--	if tools.frame[arglingual] then
		--		argval = tools.frame[arglingual]
		--		argm.src = "inv" -- arg comes from invoke
		--	end
			if tools.frame.args[arglingual] then
	--			argval = tools.frame.args[arglingual]
	--			argm.src = "inv" -- arg comes from invoke
	--			arg_found = true
			end
			if tools.frame:getParent().args then
	--			argval = tools.frame:getParent().args[arglingual]
	--			argm.src = "tpl" -- arg comes from template
	--			arg_found = true
			end

			if args_source[arglingual] and not tools.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
						tools.err_add("tools_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
				tools.import_arguments_track = tools.import_arguments_track .. "='''" .. tostring(argval) .. "''' "
			end -- not args_source[arglingual] is normal
		else -- internal error and category
		--	tools.err_add("tools_module_miss_i18n_trad_err", key_known)
			-- Generate a category to list all modules with missing translation
			cats = cats .. tools.cat_add( "tools_err_module_miss_i18n_cat" )
		end
		--
		key_N = tonumber(key_known_init)
		if key_N and not args_known[key_N] then
--			tools.err_add("tools_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 tools.args_unknown[arglingual] then
				tools.args_unknown[arglingual] = nil -- unknown arguments are source arguments without known arguments.
			end
		end
	end
	-- tools.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
	--		tools.err_add("tools_value_re_defined_err", argm["keyword"])
	--		cats = cats .. tools.cat_add("tools_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
				tools.err_add("tools_value_re_defined_err", (argm["keyword"] or "**") ) --.. ">2")
				cats = cats .. tools.cat_add("tools_module_usage_error_cat")
			end
		else
			if argm.found and (argm.found > 1) then
				tools.err_add("tools_value_re_defined_err", (argm["keyword"] or "**") ) --.. ">1")
				cats = cats .. tools.cat_add("tools_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
				tools.err_add("tools_need_arg_value_err", arglingual)
				cats = cats .. tools.cat_add("tools_module_usage_error_cat")
			end
		end
	end
	-- All arguments sources have they been used?
	--	tools.args_unknown -- unknown arguments are source arguments without known arguments.
	local args_list = tools.similar_args_list(tools.args_known)
	for key_src, val_src in pairs(tools.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
			tools.err_add("tools_unknown_argument_err", arglingual, val_src)
			cats = cats .. tools.cat_add("tools_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 = tools.similar_args_diffmaxi( string.len(arglingual) )
			local trouve1, min1, trouve2, min2 = tools.similar_args_search(arglingual, args_list)
			if min1 and (min1 <= diffmaxi) then
				tools.err_add("tools_nearest_argument_err", trouve1)
			end
			if min2 and (min2 <= diffmaxi) then
				tools.err_add("tools_nearest_argument_err", trouve2)
			end
		end
		key_N = tonumber(arglingual)
		if key_N and not args_known[key_N] then
			tools.err_add("tools_too_unnamed_arguments_err", key_N, val_src)
			tools.cat_add("tools_module_usage_error_cat")
		end
	end -- For all unknown source arguments.
	tools.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
	args_import.nowyear = tools.nowyear
	tools.args_import = args_import
--	tools.trac_lang("tools.import_arguments:", args_import) -- DEBUG
	return tools.args_import
end -- function tools.import_arguments(args_known, args_source, wiki_translations, args_wikidata)

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

function tools.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
		tools.err_add("tools_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 tools.option("docline", opt) then newline = "" end
	local argm = tools.args_known[paramName]
	if argm then
		--[ [
		if tools.docolor then --	Document the data sources by colors
			if argm.src == "wd" then
				paramData = tools.wikidata_color(paramData)
			elseif argm.src == "args" then
				paramData = tools.invoke_color(paramData)
		--	elseif argm.src == "inter" then
		--		paramData = tools.inter_color(paramData)
			else
				paramData = tools.other_color(paramData)
			end
		end
		--] ]
		paramData = paramData -- .. (argm.trk or "")
	end
	return " | " .. paramName .. " = <b>" .. paramData .. "</b>" .. newline
end -- function tools.generDoc1(paramName, paramId, opt)

function tools.generDoc(opt, args_final, module_name)
	-- List of paramètres for the module documentation
	-- Lister des paramètres pour la documentation du module
	-- "docview" 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
	-- tools.options = " : docdata docmin docdef docmax docline docview docafter docnotice docsrc" -- for documentation
	-- tools.options = " erron noerr nobox nocat " -- without normal result
	-- tools.options = " debug tests en es fr " -- for debug or enforce language
	local args_known = tools.args_known
	local err = tools.verify_args_tables(args_known, tools.args_source)
	if err then return args_import, err end
	if type(module_name) ~= "string" then module_name = tools.module_name end
	if type(module_name) ~= "string" then module_name = tools.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 = tools.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(tools.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 tools.isDef(val) then val = "" end
		lst = false
		opt = opt .. " docdef " -- optional display
		if tools.option("docmin", opt) and (argknw["list"] == 1) then lst = true end
		if tools.option("docdef", opt) and (val ~= "") then lst = true end
		if tools.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 = tools.generDoc1(arglingual, n, val, opt)
			table.insert(lst_doc, {lst_t = lst_t, key = key, user_lang_key = tools.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 tools.generDoc(opt, args_final, module_name)

function tools.sources_of_datas_colors()
	local res = ""
	if tools.docolor then --	Document the data sources by colors
		local tools_sources_of_datas = tools.str_vars("tools_sources_of_datas")
		--	tools_sources_of_datas = "Informations from: /Wikidata, /template or module, /other, /message, /error",
		local splitxt = mw.text.split(tools_sources_of_datas, "/", true)
		res = res .. splitxt[1]	 .. " ''' " .. tools.wikidata_color(splitxt[2]) .. tools.invoke_color(splitxt[3]) .. tools.other_color(splitxt[4]) .. tools.message_color(splitxt[5]) .. tools.error_color(splitxt[6]) .. ".''' <br>"
	else -- Document the data sources in black on white
--		local tools_sources_of_datas = tools.str_vars("tools_sources_of_datas")
--		res = res .. string.gsub(tools_sources_of_datas, "/", "") .. ".''' <br>"
	end
	return res
end -- function tools.sources_of_datas_colors()

function tools.docbox_namespace_error_and_cat()
	-- T53660 Detect the edit state to adapt help messages to user errors
	-- We cannot replace this need by another test.
	-- If DocBox is displayed out of Module or Template namespace, generate an error and a category "Module with usage error"
	-- Si DocBox est affiché hors de l'espace de nom Module ou Modèle, générer une erreur et une catégorie "erreur d'utilisation"
	local namespace = mw.title.getCurrentTitle().namespace
	local nsText = mw.title.getCurrentTitle().nsText
	if namespace ~= 10 and namespace ~= 828 then -- ns:Template and ns:Module
		tools.err_add("docbox_namespace_error_err", nsText)
		tools.cat_add("docbox_namespace_error_cat")
	end
	return
end -- function tools.docbox_namespace_error_and_cat()

function tools.get_editstate()
	local t = ""
	local mwtitle = mw.title.getCurrentTitle()
	local url = tostring(mwtitle:canonicalUrl() )
	local n = string.find(url, "/w/")
	tools.EditState = false
	if tonumber( n ) then tools.EditState = true end
	return tools.ta("EditState", tools.EditState) .. " - " .. url
end -- function tools.get_editstate()

function tools.module_init(frame) -- Get tools.args_source with tools.args_config
	frame = frame or tools.frame or mw.getCurrentFrame()
	tools.frame = frame
	tools.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
	tools.args_source = args_tab
--	tools.args_config = tools.args_config_init() -- Get tools.args_config
	-- Default values of main module version.
	if tools.main_versions then
		tools.main_versions.versionName = tools.main_versions.versionName or "Central0"
		tools.main_versions.versionNumber = tools.main_versions.versionNumber or "0.00"
		tools.main_versions.versionDate = tools.main_versions.versionDate or "2013-03-24"
	end
--	tools.init_user_lang()
	tools.args_config_init() -- Extract and put apart args_config from args_source
	tools.verifyinit()
	return -- tools.args_config
end -- function tools.module_init(frame)

------------------------------------------------------------
-- Main interface to templates
-- Interfaz principales de modelos
-- Interface principal avec les modèles
------------------------------------------------------------

------------------------------------------------------------
------------------------------------------------------------
-- TEST part, with use of options, modes and resulting text
-- TEST parte, con el uso de opciones, modos y texto resultante
-- TEST partie, avec l'utilisation d'options, modes et texte résultant
------------------------------------------------------------
------------------------------------------------------------

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

function tools.cat_add_test_1(t, test, user_lang, wiki_lang, key,vals )
	tools.catView = ":"
--	tools.init_wiki_lang(wiki_lang) -- res = res ..
--	tools.init_user_lang(user_lang) -- res = res ..
	tools.init_wiki_user_lang(wiki_lang, user_lang)
	if test == "cat_add" then catext = tools.cat_add(key, vals)
	elseif test == "catGroup" then catext = tools.catGroup(key, vals) end
	tools.options_to_catView()
	return t .. tools.Tr() .. tools.Td(test or "-") .. tools.Td(user_lang or "-") .. tools.Td(wiki_lang or "-") .. tools.Td(catext or "-")
end

function tools.cat_add_test(t) -- tester les categories
	t = t or "\n* Test the function '''cat_add''' : " .. tools.ta("user_lang", tools.user_lang) .. tools.ta("wiki_lang", tools.wiki_lang)
	local cat_memo, catView, user_lang, wiki_lang = tools.categories_list, tools.catView, tools.user_lang_memo, tools.wiki_lang_memo
	local wiki_memo, user_memo = tools.wiki_lang, tools.user_lang
--	tools.categories_init()
	tools.categories_list = {} -- init the collect of categories
	tools.catView = ":"
	t = t .. tools.Th() .. tools.Tc("Test type") .. tools.Tc("user language") .. tools.Tc("wiki language") .. tools.Tc("Categories")
	t = tools.cat_add_test_1(t, "cat_add", nil, nil, "tools_err_module_miss_i18n_cat")
	t = tools.cat_add_test_1(t, "cat_add", nil, nil, "tools_module_usage_error_cat")
	t = tools.cat_add_test_1(t, "cat_add", "en", nil, "tools_module_internal_error_cat")
	t = tools.cat_add_test_1(t, "cat_add", "en", "es", "tools_err_module_miss_i18n_cat")
	t = tools.cat_add_test_1(t, "catGroup", "en", "fr", "tools_language_cat", "french,italian,chinese")
	t = tools.cat_add_test_1(t, "catGroup", "en", "en", "occupation_cat", "Académiciens,Personnalités politiques")
	t = tools.cat_add_test_1(t, "cat_add", "fr", "en", "tools_module_internal_error_cat")
	t = tools.cat_add_test_1(t, "cat_add", "es", "en", "tools_module_internal_error_cat")
	t = tools.cat_add_test_1(t, "cat_add", "en", "x-y-z", "tools_module_internal_error_cat")
	t = tools.cat_add_test_1(t, "cat_add", "x-y-z", "en", "tools_module_internal_error_cat")
	t = t .. tools.Te()
	tools.init_wiki_user_lang(wiki_memo, user_memo)
	t = t .. "\n* Test the function <b>categories_lister</b> : " .. tools.categories_lister(":") -- generate the categories wikitext
	t = t .. "\n* This test reset categories before the test."
	tools.options_to_catView()
	return t
end -- function tools.cat_add_test(tst)

function tools.i18n_lister(t, i18n_tables)
	if type(i18n_tables) ~= "table" then i18n_tables = tools.i18n end
	t = t or "\n* <b>i18n_lister</b> :"
	t = t .. tools.error_color("\n* <b>This list show all the texts, but cannot replace the original declarations.</b> ")
	t = t .. tools.error_color("\n* <b>" .. tools.str_vars("tools_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 tools.i18n_lister(t, i18n_tables)

-- 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 tools.day_to_UTC(jj, mm, aaaa)
	local t = "@" .. tostring( jj*86400 + mm*30*86400 + (aaaa-1970)*31556926 )
	return t
end

function tools.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
--	mm = tostring( mm or "1" )
--	aaaa = tostring( aaaa or "1" )
--	if jj == 1 and tools.language_obj:getCode() == "fr" then t = string.gsub(t, "1 ", "1<sup>er</sup> " ) end
--	if jj == 1 and tools.language_obj:getCode() == "en" then t = string.gsub(t, "1 ", "1<sup>st</sup> " ) end
end

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

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

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

function tools.date_to_part_test(t)
	function date_to_part_test_1(t, nom, date, part)
		t = t .. tools.Tr() .. tools.Td(nom) .. tools.Td(date) .. tools.Td(part) .. tools.Td(tostring(tools.date_to_part(date, part)))
		return t
	end
	t = t or "\n* '''date_to_part''' :"
	t = t .. "\n* Verify each value of part of date:"
	t = t .. tools.Th() .. tools.Tc("Example") .. tools.Tc("date") .. tools.Tc("part") .. tools.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 .. tools.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
	return t
end -- function tools.date_to_part_test(t)

function tools.new_library_test(t) -- Show some use cases on centre.new_library(name, library, options)
	function new_library_test_1(t, name, library, options)
		libout, err = centre.new_library(name, library, options)
		if err ~= "OK" then err = tools.error_color(err) end
		t = t .. tools.Tr() .. tools.Td(name) .. tools.Td(library) .. tools.Td( type(libout) ) .. tools.Td(err)
		return t
	end
	t = t or "\n* <b>new_library_test</b> :"
	t = t .. "\n* Show some tests of use cases of centre.new_library(name, library, options) :"
	t = t .. tools.Th() .. tools.Tc("library name") .. tools.Tc("library input") .. tools.Tc("library output") .. tools.Tc("error")
	t = new_library_test_1(t, "emptyTable", {}, "")
	t = new_library_test_1(t, "emptyFunction", function()end, "")
	t = new_library_test_1(t, "simpleNumber", 123, nil)
	t = new_library_test_1(t, "simpleString", "ABC", nil)
	t = new_library_test_1(t, "libraryExample", { a = "a", fctn = function(i)end }, "")
	t = new_library_test_1(t, "Module:Main/I18N", {}, "")
	t = new_library_test_1(t, "text/slash", {} )
	t = new_library_test_1(t, "version.2.3", {} )
	t = new_library_test_1(t, "available:non/alphanumeric.chars", {} )
	t = new_library_test_1(t, "Module:Main/sub-version23", {}, "")
	t = new_library_test_1(t, "name with spaces", {} )
	t = new_library_test_1(t, "semi;column", {} )
	t = t .. tools.Te()
	return t
end -- function tools.new_library_test(t)

function tools.table_n_vars(tab, tab_name)
	local st, vr, fn, tb = tools.luatablecount(tab, tab_name)
	return vr, fn, tb
end

function tools.debug_traceback(t)
	if type(t) ~= "string" then t = "\n* '''debug.traceback()''' : " end
	t = t .. debug.traceback()
	t = string.gsub(t, "Module", "\n* Module" )
	return t
end

function tools.bindmodules_report(t, tracks_level) -- Report all modules installation
	tools.main_module = centre.main_module
	tools.main_versions = centre.main_versions
	tools.main_gettitle = centre.main_gettitle
	tools.bindmodules_start = centre.bindmodules_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 = (t or "") .. "\n* tools.tools_verif_bindmodules_report</b> : effective List and verify all modules tools."
	-- Get previous loaded modules and record tables for further uses and count
--	local loaded_pack, loaded_txt = centre.get_loaded_modules() -- get the modules state
	tools.loaded_txt = (loaded_txt or "")
--	local loaded_to_sort = centre.loaded_descriptors -- or centre.loaded_vers or centre.loaded_pack or {x=x}
	tools.report_bind_modules_init_i18n = (tools.report_bind_modules_init_i18n or "")
	tools.required_sorted = centre.sort_table_in_insert_order(centre.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 tools.main_versions then vers = tools.main_versions end
	local main_versions = vers -- tools.main_versions
	local main_versionNumber = vers -- tools.main_versions
	-- Use main_versions rather than simulate it
	--
	-- Search the main module which has the most known and sought centre.
	local loaded_pack = centre.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 -- tools.main_versions.versionNumber
	for title, get in pairs(loaded_pack) do -- DEBUG reverse order
		t = t .. tools.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 .. tools.ta("loaded_max", loaded_max)
		end
		if get.title == centre.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 .. tools.str_vars(
		"<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 .. tools.ta("modules number", #loaded_pack)
	tools.bind_verif_modules_report_start = t
	--
	-- Form the report of centre.loaded_pack = package.loaded modules
	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/centre/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: "
	t2 = ""
	local head = mw.text.split( tools.str_vars("tools_list_loaded_modules_headers") , ',')
	t = t .. tools.Th("wikitable sortable") .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[3]) .. tools.Tc(head[4]) .. tools.Tc(head[5])
	-- A triable table start with : {| class="wikitable sortable"
	-- A column become fix and not triable with : ||class="unsortable"|
--	for key, elem in pairs(tools.loaded_sorted) do
	for key, elem in ipairs(loaded_sorted) do
	--	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
		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/centre/I18N
			t = t .. tools.Tr() .. tools.Td( (elem.alias or "") ) .. tools.Td(elem.versionName or "")
			.. tools.Td( (elem.versionDate or "date") .. " , " .. (elem.shortVersion or "vers") ) .. tools.Td(vers18 or "")
			.. tools.Td( tools.form_i18n_counts(elem.i18n, "+ %1 sentences in %2 languages") .. tools.form_i18n_counts(centre.maini18n, ", (all %1/%2)") )
			if elem.versionName and elem.alias then
				t2 = t2 .. tools.str_vars("<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 .. tools.Te()
	tools.report_loaded_pack = t
	tools.report_loaded_pack_alias_versions = t2 .. " effective " .. (tools.bindmodules_start or "")
	-- t = t .. "\n\n\n* effective tools.bindmodules_start: " .. (tools.bindmodules_start or " tools.bindmodules_start missing ")
	tools.report_loaded_txt = (loaded_txt or " loaded_txt ")
	--
	-- Form the report of loaded_sorted
--	local CentralModule, loaded_vers, err, select_txt, alias_txt = tools.bindmodules_verif(main, "list", centre.loaded_pack)
	tools.report_bind_verif_modules_list = select_txt
	local main_versions
	if type(centre.main_module) == "table" and type(centre.main_versions) == "table" then
		main_versions = centre.main_versions or { versionName = "versionName", versionNumber = "0.0", }
		tools.report_main_short = main_versions.versionName .. " " .. main_versions.versionNumber
		tools.report_main_discreet = tools.discreet_color( tools.report_main_short )
		tools.report_REVISIONTIMESTAMP = tools.frame:preprocess( "{{REVISIONTIMESTAMP}}" )
		tools.report_actual_versions = (tools.report_main_discreet or "tools.report_main_discreet") -- .. tools.report_REVISIONTIMESTAMP .. " "
	else
		tools.report_main_short = "tools.report_main_short is missing."
		tools.report_main_discreet = "tools.report_main_discreet is missing."
		tools.report_REVISIONTIMESTAMP = "tools.report_REVISIONTIMESTAMP is missing."
		tools.report_actual_versions = "tools.report_actual_versions is missing."
	end
	--
	t = ""
	local head = mw.text.split( tools.str_vars("tools_bindmodules_test_headers") , ',') -- tools_bindmodules_test_title -- tools_versions_management_title
	t = t .. tools.Th() .. tools.Tc(head[1]) .. tools.Tc(head[2]) .. tools.Tc(head[4])
	if type(main_versions) == "table" then
		t = t .. tools.Tr() .. tools.Td(main_versions.sought) .. tools.Td(main_versions.known) .. tools.Td(select_txt) .. tools.Td(alias_txt) .. tools.Td(err)
	end
	t = t .. tools.Te()
	tools.report_bind_verif_modules = t
	--
	tools.report_loaded_descriptors = centre.tab_elem_report(centre.loaded_descriptors, "title")
	tools.report_required_descriptors = centre.tab_elem_report(centre.required_descriptors, "title")
	tools.report_loaded_descriptors_loaded_sorted = centre.tab_elem_report(tools.loaded_sorted, 1)
	--
	centre.trcPVG("tools_verif_bindmodules_report") -- track for tools.bindmodules_verif
	-- Form the detailed Versions report
	t = "\n* Discreet main version: " .. (tools.report_main_discreet or " report_main_discreet missing ")
	t = t .. "\n* effective tools.bindmodules_start: " .. (tools.bindmodules_start or " tools.bindmodules_start missing ")
	t = t .. "<br/>\n\n* Start versions from main module: " .. (tools.bind_verif_modules_report_start or " bind_verif_modules_report_start missing ")
	t = t .. "<br/>\n\n* All versions from main module: " .. (tools.report_main_select_all or " report_main_select_all missing ")
	t = t .. "<br/>\n\n* Modules alias and versions: " .. (tools.report_loaded_pack_alias_versions or " report_loaded_pack_alias_versions missing ")
	t = t .. "\n\n\n* All selected modules versions: " .. (tools.report_bind_verif_modules or " report_bind_verif_modules missing ")
	t = t .. "\n\n\n* All <b>loaded modules</b> versions: " .. (tools.report_loaded_pack or " report_loaded_pack missing ") -- loaded_modules
	tools.support_desk_report = t -- tools.bindmodules_report() -- Report all modules installation
	--
	t = "" -- tools.report_bind_verif_modules_details
	t = t .. "\n* tools.bindmodules_start: " .. (tools.bindmodules_start or " tools.bindmodules_start missing ")
	t = t .. "\n\n\n* effective report_find_main_module: " .. (tools.report_find_main_module or " report_find_main_module missing ")
	t = t .. "<br/>\n* bind_verif_modules_recursive: " .. (tools.ta("bind_verif_modules_recursive", tools.bind_verif_modules_recursive) or " bind_verif_modules_recursive missing ")
	t = t .. "<br/>\n* effective list loaded descriptors: " .. (tools.report_loaded_descriptors or " report_loaded_descriptors missing ")
	t = t .. "<br/>\n* List required descriptors: " .. (tools.report_required_descriptors or " report_required_descriptors missing ")
	t = t .. "<br/>\n* List loaded descriptors sizes: " .. (tools.report_loaded_descriptors_loaded_sorted or " report_loaded_descriptors_loaded_sorted missing ")
	t = t .. "<br/>\n* Search the MainModule: " .. (tools.report_find_main_module or " report_find_main_module missing ")
	t = t .. "<br/>\n* List elem.alias : " .. (tools.alias_txt or " alias_txt missing ")
	t = t .. "<br/>\n\n* " .. (tools.report_bind_verif_modules_list or " report_bind_verif_modules_list missing ")
--	t = t .. "<br/>\n\n* All used modules versions: " .. (tools.report_main_used or " report_main_used missing ")
	t = t .. "\n* Text report of <b>package.loaded</b> modules: " .. (tools.report_loaded_txt or " report_loaded_txt missing ") -- loaded_modules
	t = t .. "\n\n\n* Text report of <b>_G global</b> modules: " .. (tools.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 : " .. (tools.report_bind_modules_init_i18n or " report_bind_modules_init_i18n missing ")
--	t = t .. centre.get_edit_state() -- Detect if the wiki page using the module is in edit state
	t = t .. "<br/>\n* " .. centre.trcPVG_txt
--	t = t .. "<br/>\n* " .. tools.i18n_trc
	tools.report_bind_verif_modules_details = t
	return t
end -- function tools.bindmodules_report(t, tracks_level) -- Report all modules installation

function tools.versions_manage_report(t) -- Report some aspects of the versions management
	local t = t or "<br/>* <b>tools.versions_manage_report</b>:"
	t = t .. "<br/>\n* Discreet main version: " .. (tools.report_main_discreet or " report_main_discreet missing ")
	t = t .. "<br/>\n* All selected modules versions: " .. (tools.report_bind_verif_modules or " report_bind_verif_modules missing ")
	t = t .. "<br/>" .. tools.report_loaded_pack
	t = t .. "<br/>\n* " .. tools.list_loaded_modules()
	t = t .. "<br/>\n* " .. (tools.report_bind_verif_modules_list or " report_bind_verif_modules_list missing ")
	t = t .. "<br/>\n* Modules alias and versions: " .. (tools.report_loaded_pack_alias_versions or " report_loaded_pack_alias_versions missing ") -- long
	t = t .. "<br/>\n* Start versions from main module: " .. (tools.bind_verif_modules_report_start or " bind_verif_modules_report_start missing ")
	t = t .. "<br/>\n* All versions from main module: " .. (tools.report_main_select_all or " report_main_select_all missing ")
	t = t .. "<br/>\n* " .. tools.wiki_modules_users() -- Report some aspects of the versions management
	t = t .. "<br/>\n* tools.bindmodules_start: " .. (tools.bindmodules_start or " tools.bindmodules_start missing ")
	t = t .. "<br/>\n* List required descriptors: " .. (tools.report_required_descriptors or " report_required_descriptors missing ")
--	t = t .. "<br/>\n* Search the MainModule: " ..ki18 (tools.report_find_main_module or " report_find_main_module missing ")
--	t = t .. tools.tools_verif_bindmodules_report() -- Test of binding of the modules and versions control tools_verif_bindmodules_report
	t = t .. "<br>Track number of translations along bindmodules process" .. tools.tracki18n_all
--	t = t .. (tools.report_bind_verif_modules_details or "")
--	t = "\n------" .. t
	return t
end -- function tools.versions_manage_report(t)
-- {-{Central|doc1|versions_manage_report_title}-}

function tools.documentations_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 .. tools.tasks_table_report_short
	res = res .. "\n* List of blocking tasks: " .. tools.tasks_table_report_full
	return res
end -- function tools.documentations_changes_report(t)

function tools.running_times( if_view, res, time1, time2, time3, time4)
	-- Display example :
	-- Execution and test_time : 2014-02-22 18:09:15 UTC , url = http://fr.wikisource.org/wiki/Module:Central/Documentation ,
	-- start = 84 mS , import + 8 mS , generate page + 0 mS , tests + 127 mS , total = 221 mS Central:tests:fr
	tools.time4 = os.clock()
	if not if_view then return "" end
	time1 = time1 or tools.time1 or os.clock()
	time2 = time2 or tools.time2 or time1
	time3 = time3 or tools.time3 or time1
	time4 = time4 or tools.time4 or time1
	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 or ""
	res = res .."\n* <b>Execution and test_time</b>: " .. nowtime .. " UTC" .. tools.ta("url", url) -- prefixedText
	res = res .. tools.ta("<br>start in page", time1) .. tools.ta("import", time2d, "+")
	res = res .. tools.ta("form result", time3d, "+") .. tools.ta("tests", time4d, "+") .. tools.ta("duration", duration)
	--[ [
	if tools.frame then
		res = res .. "<br/>\n* <b>Revision time and user language</b>: "
		res = res .. tools.ta("REVISIONTIMESTAMP:Module:Central", tools.frame:preprocess( "{{REVISIONTIMESTAMP:Module:Central}}" ) )
		res = res .. tools.ta("REVISIONTIMESTAMP", tools.frame:preprocess( "{{REVISIONTIMESTAMP}}" ) )
		res = res .. tools.ta("CONTENTLANGUAGE", tools.frame:preprocess( "{{CONTENTLANGUAGE}}" ) )
		res = res .. tools.ta("REVISIONUSER", tools.frame:preprocess( "{{REVISIONUSER}}" ) )
		res = res .. tools.ta("USERLANG", tools.frame:preprocess( "{{USERLANG}}" ) )
		res = res .. tools.ta("UILANGCODE", tools.frame:preprocess( "{{UILANGCODE}}" ) )
		res = res .. tools.ta("USERIFCODE", tools.frame:preprocess( "{{USERIFCODE}}" ) )
		res = res .. tools.ta("USERLANGUAGE", tools.frame:preprocess( "{{USERLANGUAGE}}" ) )
	end
	--] ]
	-- 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
	-- {{REVISIONTIMESTAMP}}=20151101023804 Timestamp as of time of edit.
	-- {{REVISIONYEAR}}=2015, {{REVISIONMONTH}}=07, {{REVISIONDAY2}}=01
	-- DEBUG wait for : T68051 Get the user language to help admins to maintain a module or a template (userInfo: String user and password)
	local mwuri = mw.uri.canonicalUrl(mwtitle.prefixedText, { host, authority, user, password, } )
--	res = res .. tools.ta("mwtitle.prefixedText", mwtitle.prefixedText) .. tools.ta("mwuri", mwuri)
	--[[
	res = res .. "\n\n* <b>Page title details</b>: " .. tools.ta("host", tostring(mwuri.host)) .. tools.ta("authority", tostring(mwuri.authority))
	res = res .. tools.ta("user", tostring(mwuri.user)) .. tools.ta("user_language", tostring(mwuri.language))
	--	spaces_page_names_test : , mwtitle = Auteur:Nelson Mandela , nsText = Auteur , baseText = Nelson Mandela , url =
	-- subpageText: If this is a subpage, just the subpage name. Otherwise, the same as title.text.
	res = res .. tools.ta("text", tostring(mwtitle.text)) .. tools.ta("baseText", tostring(mwtitle.baseText))
	res = res .. tools.ta("rootText", tostring(mwtitle.rootText)) .. tools.ta("subpageText", tostring(mwtitle.subpageText))
	--]]
	return res
end -- function tools.running_times( if_view, res, time1, time2, time3, time4)

-- local object = {}
local php
function tools.setupInterface( options )
	-- Remove setup function
	tools.setupInterface = nil
	-- 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.tools = tools
	-- Indicate that we're loaded
	package.loaded['mw.ext.tools'] = tools
	return tools
end

function tools.invoke_central_doc1(title)
--	res = res .. tools.invoke_central_doc1("tools_options_from_mode_title") -- calling example
	local res =					  "<br>* <b>funct = doc1, [1] = " .. title .. "</b>"
	res = res ..			"<br><nowiki>{{#invoke:Central|doc1|" .. title .. "}}</nowiki> "
	res = res .. tools.frame:preprocess("{{#invoke:Central|doc1|" .. title .. "}}")
--	res = res .. tools.frame:preprocess("{{#invoke:Central|doc1|tools_options_from_mode_title}}") -- prototype
	return res
end -- function tools.invoke_central_doc1(title)

function tools.tracki18n(where, module)
	-- List number of available translations and languages, and their central collection.
	tools.tracki18n_all = tools.tracki18n_all or ""
	local res = "\n* trk: <b>" .. where .. "</b>, "
	if type(module) == "table" and type(module.i18n) == "table" then
		res = res .. tools.form_i18n_counts(module.i18n, "module=<b>%1/%2</b>, ")
	end
	res = res .. tools.form_i18n_counts(centre.maini18n, "maini18n=<b>%1/%2</b>, ")
	res = res .. tools.form_i18n_counts(centre.maini18n, "mathroman_char_X_in_N_err")
	tools.tracki18n_all = tools.tracki18n_all .. res
	return res
--	t = t .. "<br>* " .. tools.form_i18n_counts(i18n, txt)
--	function tools.tracki18n(where, module)
--	local st, vr, fn, tb = tools.luatablecount(module.maini18n)
end -- function tools.tracki18n(where, module)

function tools.init_tools(frame, options_for_modes)
	local res = ""
	frame = frame or mw.getCurrentFrame()
	tools.frame = frame
	tools.errors_list = {} -- Table to collect errors and messages
	tools.categories_list = {} -- init the collect of categories
--	tools.categories_init() -- initialize the category list
	tools.options_for_modes = options_for_modes or tools.options_for_modes
	return res
end -- function tools.init_tools(frame, options_for_modes)

------------------------------------------------------------
--	Start of Simulation of Library:mathroman from Module:MathRoman
--	_G["mathroman"] = {}
------------------------------------------------------------

------------------------------------------------------------------------------
-- Install Library:mathroman													--
-- Libraries dependencies:
-- For a right display of the errors messages, this Library:mathroman needs the Library:tools
------------------------------------------------------------------------------

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

mathroman = {}
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_rom2dig_testtitle 			= "Test from roman to digital numbers",
	mathroman_dig2rom_testtitle 			= "Test from digital to roman numbers",
	mathroman_recursive_TestsCases_title	= "Mediawiki recursive TestsCases",
} -- mathroman.i18n.en db

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_rom2dig_testtitle 			= "Prueba de números romanos a números decimales",
	mathroman_dig2rom_testtitle 			= "Prueba de números decimales a números romanos",
	mathroman_recursive_TestsCases_title	= "Mediawiki TestsCases recursivos",
} -- 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_rom2dig_testtitle 			= "Test des nombres romains en nombres décimaux",
	mathroman_dig2rom_testtitle 			= "Test des nombres décimaux en nombres romains",
	mathroman_recursive_TestsCases_title	= "Mediawiki TestsCases recursifs",
} -- 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.

mathroman.TestCaseOK = "OK"
mathroman.TestCaseERR = "ERR"
mathroman.TestCaseErrorsCode = ","

function mathroman.TestCaseErrors_add(ref, v1, v2, v3, v4, v5) -- DEBUG : mathroman.int2roman() can fail without blocking page.
	mathroman.TestCaseErrorsCode = tostring(mathroman.TestCaseErrorsCode) .. "," .. tostring( tools.str_test_case(tostring(ref), v1, v2, v3, v4, v5) ) -- db
	return mathroman.TestCaseErrorsCode
end

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, testcase)
--	tools.erron = true
--	if type(rm) == "table" then rm = rm[1] end
--	if type(rm) == "table" then rm = rm[rm] end -- NO
--	if ( rm == nil ) then rm = '+' end
--	if ( rm == '' ) then return '+' end
	if type(rm) == "table" then rm = rm[rm] end
	if type(testcase) ~= "table" then testcase = nil end
	if ( rm == nil ) then rm = '' end
	if ( rm == '' ) then return '' end
	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 testerrs = ",X,"
	local errs = errs or ""
	errs = ""
	local lst = '-MDCLXVIJ' -- caracteres autorises
	x = string.sub(rm, i, i) or ''
	while (x ~= '') do
		v3 = v2
		v2 = v1
		v1 = 0
	--	x = string.sub(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 .. tools.err_add("mathroman_J_before_end_err") -- e4 = ' erreur caractere J avant la fin.'
			testerrs = testerrs .. mathroman.TestCaseErrors_add("mathroman_J_before_end_err", rm, i)
		end
		if ( v1 == 0 ) then
			errs = errs .. tools.err_add("mathroman_char_X_in_N_err", x, i) -- e3 = ' erreur caractere '..x..' en '..i..'.'
			testerrs = testerrs .. mathroman.TestCaseErrors_add("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 -- ajuster 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 .. tools.err_add("mathroman_char_increase_err") -- e2 = ' caracteres croissants.'
			testerrs = testerrs .. mathroman.TestCaseErrors_add("mathroman_char_increase_err", rm, i) -- db
		end
		i = i + 1
		x = string.sub(rm, i, i) or ''
	end
	if ( v == 0 ) then --	e0 = ' valeur nulle.'
		errs = errs .. tools.err_add("mathroman_null_value_err")
		testerrs = testerrs .. mathroman.TestCaseErrors_add("mathroman_null_value_err", rm, v)
	--	v = 1
	end
	if ( v > 4999 ) then -- e1 = ' valeur > 4999.'
		errs = errs .. tools.err_add("mathroman_greater_4999_err")
		testerrs = testerrs .. mathroman.TestCaseErrors_add("mathroman_greater_4999_err", rm, v)
	--	v = 4999
	end
--	if testcase then testcase.testerrs = testerrs end
--	{ name = 'mathroman.roman2int (4)', func = mathroman.roman2int, args = { rm = "VI", }, expect = { 123 } },
	if testcase then
		testcase.testcode = mathroman.TestCaseErrors_add(testcase.subkey .. "_testcode", rm, v)
		testcase.testerrs = testerrs
		errs = testerrs
	end
--	if testcase then
--		testcase.testcode = mathroman.TestCaseErrors_add(testcase.subkey .. "_testcode", i, roman)
--		errs = testerrs
--	end
	return v, errs, cats
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(i, testcase)
	local n = 0
	if ( i == nil ) then n = 0 else n = i end -- anti nill
	if ( type(n) ~= 'number' ) then n = tonumber(n) end
	if ( type(n) ~= 'number' ) then n = 0 end
	n = math.floor(n) -- input:89: bad argument #1 to 'floor' (number expected, got nil)
	if type(errs) ~= 'string' then errs = "" end -- anti nill, to text
	errs = ""
	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
	local errs = "" -- errs or ""
	if type(testcase) ~= "table" then testcase = nil end
	local testerrs = ",*,"
	reste = n
	if ( n > 4999 ) then
		errs = errs .. tools.err_add("mathroman_greater_4999_err") -- e1 = ' valeur > 4999.'
		testerrs = testerrs .. mathroman.TestCaseErrors_add("mathroman_greater_4999_err", n) -- db
		reste = 0
		roman = "" -- 'ERROR'
	end
	if ( n < 1 ) then
	--	n = 0
		errs = errs .. tools.err_add("mathroman_null_value_err") -- e2 = ' valeur < 1.'
		testerrs = testerrs .. mathroman.TestCaseErrors_add("mathroman_null_value_err", n) -- db
		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
--	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 444 }, expect = { "CDXLIV" } },
	if testcase then
		testcase.testcode = mathroman.TestCaseErrors_add(testcase.subkey .. "_testcode", i, roman)
		errs = testerrs
	end
	return roman, errs -- avec ou sans erreurs
end -- function mathroman.int2roman(i, testcase)

function mathroman.roman_to_digital_test(t, word) -- Unitary tests of mathroman.roman2int
	t = t or "roman_to_digital_test"
	t = "\n:*<b>" .. t .. "</b>:"
--	t = t .. tools.Th() .. tools.Tc("Nombre romain") .. tools.Tc("Valeur décimale (et corrigée)") .. tools.Tc("Erreur")
	t = t .. tools.Th() .. tools.Tc("Roman number") .. tools.Tc("Digital value (and corrected)") .. tools.Tc("Error")
	function roman_to_digital_test1(t, word)
	--	local val, errs = mathroman.pcall_roman2int(word) -- DEBUG : mathroman.roman2int() can fail without blocking page.
		local val, errs = mathroman.roman2int(word) -- DEBUG : mathroman.roman2int() can fail without blocking page.
	--	if not val then errs = errs .. " p " .. tools.err_add(errs) end
		local wordX, errsX = mathroman.int2roman(val) -- DEBUG : mathroman.roman2int() can fail without blocking page.
		if wordX then val = tostring(val) .. " ( = " .. tostring(wordX) .. " ) " end
		t = (t or "") .. tools.Tr() .. tools.Td(word) .. tools.Td(val or "") .. tools.Td(errs or "")
		return t
	end
	t = roman_to_digital_test1( t, "-X")
	t = roman_to_digital_test1( t, "0")
	t = roman_to_digital_test1( t, "MCXI")
	t = roman_to_digital_test1( t, "XIJ")
	t = roman_to_digital_test1( t, "XJI")
	t = roman_to_digital_test1( t, "XIA")
	t = roman_to_digital_test1( t, "VLD")
	t = roman_to_digital_test1( t, "IXC")
	t = roman_to_digital_test1( t, "MMMMCMXCIX")
	t = roman_to_digital_test1( t, "MMMMM")
	t = roman_to_digital_test1( t, "MMMMMYJXC")
	t = t .. tools.Te()
	return t
end -- function mathroman.roman_to_digital_test( t, args_known)

function mathroman.digital_to_roman_test(t, val) -- Unitary tests of mathroman.int2roman
	t = t or "digital_to_roman_test"
	t = "\n:*<b>" .. t .. "</b>:"
--	t = t .. tools.Th() .. tools.Tc("Nombre décimal") .. tools.Tc("Nombre romain") .. tools.Tc("Erreur")
	t = t .. tools.Th() .. tools.Tc("Digital value") .. tools.Tc("Roman number") .. tools.Tc("Error")
	function digital_to_roman_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 "") .. tools.Tr() .. tools.Td(val) .. tools.Td(word or "") .. tools.Td(errs or "")
		return t
	end
	t = digital_to_roman_test1( t, -10)
	t = digital_to_roman_test1( t, 0)
	t = digital_to_roman_test1( t, 12)
	t = digital_to_roman_test1( t, 17)
	t = digital_to_roman_test1( t, "18")
	t = digital_to_roman_test1( t, "19")
	t = digital_to_roman_test1( t, 111)
	t = digital_to_roman_test1( t, 444)
	t = digital_to_roman_test1( t, 555)
	t = digital_to_roman_test1( t, "777")
	t = digital_to_roman_test1( t, "1111")
	t = digital_to_roman_test1( t, "4999")
	t = digital_to_roman_test1( t, "5000")
	t = t .. tools.Te()
	return t
end -- function mathroman.digital_to_roman_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.

------------------------------------------------------------
--	End of Simulation of Library:mathroman from Module:MathRoman
------------------------------------------------------------


--	Tests : class ClassNameTest extends Scribunto_LuaEngineTestBase
--	protected static $moduleName = 'ClassNameTest';
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 tools.str_test_case. db
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 1, }, expect = { "I" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 2, }, expect = { "II" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 3, }, expect = { "III" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 4, }, expect = { "IX" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 5, }, expect = { "V" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 6, }, expect = { "VI" } },
	--
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "I" }, expect = { 1 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "II" }, expect = { 2 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "III", }, expect = { 6 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "VI", }, expect = { 123 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "V", }, expect = { 1 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "VI", }, expect = { 1 } },
}

--	Tests : class ClassNameTest extends Scribunto_LuaEngineTestBase
--	protected static $moduleName = 'ClassNameTest';
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 tools.str_test_case.
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 1 }, expect = { "I" }, },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 3 }, expect = { "III" }, },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 6 }, expect = { "VIII" }, },
	--
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "IV", }, expect = { 4 }, },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "V", }, expect = { 5 }, },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "III", }, expect = { 6 }, },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "VI", }, expect = { 6 }, },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "I" }, expect = { 22 }, },
}

--	Tests : class ClassNameTest extends Scribunto_LuaEngineTestBase db
--	protected static $moduleName = 'ClassNameTest';
mathroman.Tests_int2roman = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- each test_case defines a name, a function, an input, an output. See also tools.str_test_case.
--	{ subkey = 'int2roman', name = '_1', group = mathroman.TestsCasesGroup, args = { [1] =1 }, expect = { [1] = "I" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = -11 }, expect = { "" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 0 }, expect = { "" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 12 }, expect = { "XII" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 17 }, expect = { "XVII" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 18 }, expect = { "XVIII" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 19 }, expect = { "XIX" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 111 }, expect = { "CXI" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 444 }, expect = { "CDXLIV" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 555 }, expect = { "DLV" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 777 }, expect = { "DCCLXXVII" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 1111 }, expect = { "MCXI" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 4999 }, expect = { "MMMMCMXCIX" } },
	{ subkey = 'int2roman', func = mathroman.int2roman, args = { i = 5000 }, expect = { "0" } },
}
mathroman.Tests_roman2int = { -- Autotest cases to validate the mathroman library at mediawiki level.
	-- each test_case defines a name, a function, an input, an output. See also tools.str_test_case.
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "-X" }, expect = { 10 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "0" }, expect = { 0 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "" }, expect = { 0 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "MCXI" }, expect = { 1111 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "XIJ" }, expect = { 12 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "XJI" }, expect = { 12 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "XIA" }, expect = { 11 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "VLD" }, expect = { 445 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "IXC" }, expect = { 89 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "MMMMCMXCIX" }, expect = { 4999 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "MMMMM" }, expect = { 5000 } },
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "MMMMMYJXC" }, expect = { 5089 } },
	{ module = mathroman, subkey = 'roman_group', group = mathroman.TestsCasesGroup, }, -- name = 'mathroman_roman2int', 
}
mathroman.TestsRecursive = { -- Autotest cases to validate the mathroman library at mediawiki level.
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "XIJ" }, expect = { 12 } },
	{ module = mathroman, subkey = 'TestsRecursive1', group = mathroman.TestsRecursive, }, -- name = 'mathroman_int2roman', 
	{ subkey = 'roman2int', func = mathroman.roman2int, args = { rm = "MCXI" }, expect = { 1111 } },
	{ module = mathroman, subkey = 'TestsRecursive2', group = mathroman.TestsRecursive, }, -- name = 'mathroman_int2roman', 
	-- each test_case defines a name, a function, an input, an output. See also tools.str_test_case.
}
mathroman.TestsGroups = { -- Autotest cases to validate the mathroman library at mediawiki level.
	{ module = mathroman, subkey = 'groups', group = mathroman.Tests_int2roman, }, -- name = 'mathroman_int2roman', 
	{ module = mathroman, subkey = 'groups', group = mathroman.Tests_roman2int, }, -- name = 'mathroman_roman2int', 
	{ module = mathroman, subkey = 'tests_recursive', group = mathroman.TestsRecursive, }, -- To test recursive limit
	-- each test_case defines a name, a function, an input, an output. See also tools.str_test_case.
}

-- Define ClassCentralMathromanTests.lua

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

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

function mathroman.run( n, testcase ) -- generic testcase for mathroman.TestsCases
--	Extension:Scribunto/Lua reference manual : run( n ): Function that runs test n and returns one string.
	local ok = mathroman.TestCaseOK
	local t = "-", "" -- intern report to display for humans
	res = testcase.func( testcase.args )
	if res ~= mathroman.TestCaseOK then res_all = res_all .. ", " .. res end
	return ok, t -- t is for trace
end

function mathroman.TestsCases_recursive( test_name, group, recursive, count_only ) -- Run a group of tests and returns one string.
	local t = "" -- intern report to display for humans
	if type(tools.recursivity) ~= "number" then tools.recursivity = 111 end
	if type(recursive) ~= "number" then recursive = 1 end
	recursive = math.floor( recursive )
	if recursive < 1 then recursive = 1 end
	if recursive > tools.recursivity then
		local t_levelmaxi = tools.str_vars("tools_table_listlimit_levelmaxi", recursive - 1)
		return tools.message_color( tostring(t_levelmaxi) ), tot_vars, tot_func, tot_tabs
	end
	local really_tests = not count_only
	local n, ok, outest, res, err, res2, name, args, tt, xxx, sub_ok, sub_t, sub_count
	local sub_ok, sub_t, sub_count
	ok = mathroman.TestCaseOK
	err = mathroman.TestCaseERR
	test_name = test_name or "_"
	res_all = 0
	sub_count = 0
--	t = t .. "\n**; TestsCases " .. tools.ta("recursive", recursive) .. tools.ta("group.countonly", group.countonly)
	if group.countonly then
		group.count_all = group.count_all or 0 -- group.countonly or 
	end
	group.count_one = 0
	mathroman.count = sub_count -- group.count_one
	for k, testcase in ipairs(group) do -- For all sub-tests in the group
		testcase.subkey = testcase.subkey or "subkey"
		testcase.subkey_n = testcase.subkey .. "_" .. (group.count_one + 1)
		name = test_name .. "_" .. testcase.subkey_n
		if testcase.group then -- db
			testcase.group.count_one = group.count_one
			if not count_only then
				testcase.group.countonly = 0
				testcase.group.count_all = 0
				sub_ok, sub_t, sub_count = mathroman.TestsCases_recursive( test_name, testcase.group, recursive, count_only ) -- Run a group of tests and returns one string.
				testcase.group.count_all = testcase.group.count_all + sub_count
				if not testcase.group.countonly then
			--		testcase.group.countonly = sub_count
				else
			--		testcase.group.countonly = testcase.group.countonly + sub_count
			--		testcase.group.count_all = testcase.group.count_all + sub_count
				end
			end
			testcase.group.count_all = testcase.group.count_all or group.count_all
			sub_ok, sub_t, sub_count = mathroman.TestsCases_recursive( name, testcase.group, recursive + 1 ) -- Run a group of tests and returns one string.
			group.countonly = testcase.group.countonly + sub_count
			group.count_one = testcase.group.count_one
			t = t .. "\n**; TestsCases " .. tools.ta("recursive level", recursive) .. tools.str_vars("n/N = <b>%1/%2</b>", group.count_one, testcase.group.count_all) .. tools.ta("group", name)
			t = t .. sub_t
		else
			group.count_one = group.count_one + 1
			args = testcase.args
			local errs = "-*-"
			if args then
				mathroman.TestCaseErrorsCode = ","
				--	testcase.group.count_one = group.count_one
				if really_tests then
					mathroman.count = group.count_all
					n_, name_, ok_ = mathroman.provide(group.count_one, name, ok) -- generic testcase for mathroman.TestsCases
					res, tt = mathroman.run( group.count_one, testcase ) -- transmit any number of named args
					errs = errs .. tt
				end
				local a = ""
				for k, val in pairs(args) do -- For all sub-tests in the group
					a = a .. tools.ta(k, val)
				end
				t = t .. "\n* " .. tools.str_vars("n/N = <b>%1/%2</b>, countonly=%3, sub_count=%4, ", group.count_one, group.count_all, group.countonly, sub_count)
				t = t .. tools.str_vars(" name = <b>%1</b>(%2), ", name, a)
				if testcase.expect then
					if res == testcase.expect[1] then
						t = t .. tools.ta(" errs", errs) .. tools.ta("testcode", testcase.testcode) .. tools.ta("argserrs", mathroman.TestCaseErrorsCode)
					else
						mathroman.TestCaseErrorsCode = tools.compact_comma_list(mathroman.TestCaseErrorsCode)
						t = t .. tools.ta("testcode", testcase.testcode) .. tools.ta("argserrs", tools.error_color(mathroman.TestCaseErrorsCode) )
					end
				end
			end
			group.countonly = group.countonly + 1
		end
	end
	return ok, t, group.countonly or 1
--	Extension:Scribunto/Lua reference manual : run( n ): Function that runs test n and returns one string.
end -- function mathroman.TestsCases_recursive( test_name, group, recursive )

function mathroman.example(t) -- db
	local t = t or "<b>TestsCases</b> mathroman: "
	local i, r = 2013, "-"
	r = mathroman.int2roman(i)
	t = "\n*" .. t .. "int2roman(" .. i .. ") = " .. r .. ", "
	local i, r = 0, "MMXVI"
	i = mathroman.roman2int(r)
	t = t .. "roman2int(" .. r .. ") = " .. i .. ", "
	return t
end -- function mathroman.example(t)

function mathroman.TestsCases_recursive_tests()
	local t, tt, ok = "", ""
	t = t .. mathroman.example() -- "mathroman:tests ")
	ok, tt = mathroman.TestsCases_recursive( "mathroman", mathroman.TestsGroups ) -- Run a group of tests and returns one string.
	t = t .. tt
	return t
end -- function mathroman.example(t)

--[[
Extension:Scribunto/Lua reference manual : Test cases in another extension
There are (at least) two ways to run PHPUnit tests:
    Run phpunit against core, allowing the tests/phpunit/suites/ExtensionsTestSuite.php to find the extension's tests using the UnitTestsList hook. If your extension's test class names all contain a unique component (e.g. the extension's name), the --filter option may be used to run only your extension's tests.
    Run phpunit against the extension directory, where it will pick up any file ending in "Test.php".
Either of these will work fine if Scribunto is loaded in LocalSettings.php. And it is easy for method #1 to work if Scribunto is not loaded, as the UnitTestsList hook can easily be written to avoid returning the Scribunto test when $wgAutoloadClasses['Scribunto_LuaEngineTestBase'] is not set.
But Jenkins uses method #2. For Jenkins to properly run the tests, you will need to add Scribunto as a dependency for your extension. See Gerrit change 56570 for an example of how this is done.
If for some reason you need the tests to be able to run using method #2 without Scribunto loaded, one workaround is to add this check to the top of your unit test file:
if ( !isset( $GLOBALS['wgAutoloadClasses']['Scribunto_LuaEngineTestBase'] ) ) {
    return;
}
--]]

------------------------------------------------------------
--	End of Simulation of Library:mathroman from Module:MathRoman
------------------------------------------------------------


------------------------------------------------------------
--	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 messages, 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.
--
--	Libraries dependencies:
--	This try part needs the Library:centre, the Library:tools and the Library:mathroman.
------------------------------------------------------------------------------

------------------------------------------------------------
-- 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 des traductions des paramètres et messages dans les langues : en, es, fr.
-- Tables of translations of settings and messages in the languages : en, es, fr.
-- Mesas traducciones parámetros y mensajes en los idiomas : 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_image					= 'Flag_of_England.svg',
	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
	rights							= 'rights',
	rights_descr					= "Necessary copyrights type: 70,50,mpf,ONU,non.",
	rights_values					= '70,50,mpf,ONU,none',
	region							= 'region',
	region_values					= 'other,china,india,century',
}

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_image					= 'Flag_of_Spain.svg',
	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
	rights							= 'derechos',
	rights_descr					= "Tipo de derechos de autor necesario: 70,50,mpf,ONU,non.",
	rights_values					= '70,50,mpf,ONU,no',
	region							= 'región',
	region_values					= "otro,china,india,siglo",
--	debug							= "debug",
}

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_image					= 'Flag_of_France.svg',
	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
	rights							= 'droits',
	rights_descr					= "Type de droits d'auteur nécessaire parmi : 70,50,mpf,ONU,non.",
	rights_values					= '70,50,mpf,ONU,non',
	region							= 'région',
	region_values					= "autre,chine,inde,siècle",
}

------------------------------------------------------------
-- 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, messages, catégories, tests) to help a wiki in any language.
	-- El userlang argumento permisos en administrador en su propia langage (errores, mensajes, categorías, pruebas) para ayudar a un wiki en cualquier idioma.
	-- L'argument userlang permet à un administrateur dans son propre langage (erreurs, messages, 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"},

} -- p.args_known

------------------------------------------------------------
-- Main tables of arguments. Principales tablas de argumentos. Principales tables d'arguments.
------------------------------------------------------------

p.args_source_example = { "Hugo", "Victor", "arg3", c = ' docdef docview docsrc erron ', nom = 'Voltaire', nomm = 'Voltaire', anneenaissance = '1987', BNF = '123456789' }

-- p.args_known = nil -- Table of the definitions of all known arguments at module level.
p.args_wikidata = nil -- Table of present arguments values from wikidata
p.args_source = nil -- Table of source arguments from calling template, based on argument names in wiki language
p.args_unknown = nil -- unknown arguments are source arguments without known arguments.
p.args_import = nil -- Table of values of all imported arguments, including wikidata, based on international english keys
p.args_final = nil -- Table of values of arguments after interactions between them
p.args_wikidata_import = nil -- Table of the first complete import from wikidata
p.args_wikidata_selected = nil -- Table of imported arguments from wikidata after any selection

p.args_test_errors = { "aaa", "bbb", "ccc", "ddd", ["prénom"] = "Arthur",
	options = ' docdef docview docsrc ', lastXXname = 'Voltaire', birthyear = '1999', birthyear = '2000',
} -- Arguments pour auto-test

------------------------------------------------------------
-- 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 .. tools.ta("fn_mode", fn_mode) .. tools.ta("tools.mode_name", tools.mode_name) .. tools.ta("tools.mode_options", tools.mode_options) .. tools.ta("tools.invoke_options", tools.invoke_options)
	res = res .. tools.ta("trc try", tools.str_vars("tools_internal_tests_title")) .. tools.ta("MR", tools.str_vars("err_J_before_end")) .. tools.ta("AT", tools.str_vars("deathyear"))
	return res
end

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

function tools.options_from_mode_test(t)
	local t = "options_from_mode_test:" or t
	for md, opt in pairs(tools.options_for_modes) do t = t .. "<br>- " .. tools.ta(md, opt) end
	t = t .. tools.Th() .. tools.Tc("Mode") .. tools.Tc("List of options") .. tools.Tc("noerr value") .. tools.Tc("docview value") .. tools.Tc("tests value")
	local function test_options_from_mode(md, op1, op2, op3)
		local opstest = tools.options_from_mode(md) or ""
		return tools.Tr() .. tools.Td(md) .. tools.Td(opstest) .. tools.Td(tools.option(op1, opstest)) .. tools.Td(tools.option(op2, opstest)) .. tools.Td(tools.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 .. tools.Te()
	return t
end -- function tools.options_from_mode_test(t)

function tools.get_arg_mode(mode_name, source_key, lang, args_source)
	local mode_key = "mode"
	local args_source = args_source or tools.args_source
--	tools.wikilang = 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(centre.maini18n) == "table" and type(centre.maini18n[tools.wikilang]) == "table" then tools.wiki_translations = centre.maini18n[tools.wikilang] 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 tools.mode_name = mode_name or tools.mode_name or "read" end
--	local mode_name = mode_name or args_source[source_key] or "read"
--	tools.mode_name = mode_name or tools.mode_name or "read"
	return mode_name, source_key, try_lang
end

function tools.list_all_categories(t) -- list_all_categories_title
	t = "\n* " .. (t or " <b>List all eventual categories of this wiki:</b>")
	for key, txt in pairs(tools.user_translations) do
		if tools.is_in("cat_", key) or tools.is_in("_cat", key) then
			txt = tools.str_vars(txt, "**", "**", "**", "**", "**", "**")
			t = t .. "<br>" .. tools.ta(key, txt)
		end
	end
	return t
end -- function tools.list_all_categories(t)

function tools.list_all_errors(t) -- list_all_errors_title
	t = "\n* " .. (t or " <b>List all detectable errors of this wiki:</b>")
	for key, txt in pairs(tools.user_translations) do
		if tools.is_in("err_", key) or tools.is_in("_err", key) then
			txt = tools.str_vars(txt, "**", "**", "**", "**", "**", "**")
			t = t .. "<br>" .. tools.ta(key, txt)
		end
	end
	return t
end -- function tools.list_all_errors(t)

function p.list_all_args_sub(t, args_known) -- tools_list_all_args_title = List of all arguments, for try
	local t = t or "<br>* <b>List_all_args :</b> "
	t = t or "<br>* <b>List_all_args :</b> "
	if type(args_known) ~= "table" then args_known = tools.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 = tools.user_translations[descr] or "**missing translation**"
		elem.user_lang_key = tools.user_translations[elem.key] or "**missing translation**"
		elem.user_lang_keyword = tools.user_translations[elem.keyword] or "**missing translation**"
		elem.lev_arg_txt = ""
		local lst_lev = {}
		local lev_min = 99
		for arg_lev, elem_lev in pairs(args_known) do
			elem_lev.levenshtein = tools.levenshtein( tools.wiki_translations[arg_lev], tools.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 = tools.wiki_translations[arg_lev] or arg_lev
			end
			elem.arg_lev = arg_lev
			elem.lev_arg_txt = tools.ta("lev", tostring(elem.lev_lang) .. ":" .. tostring(elem.lev_min) .. ":" .. elem.arg_lev )
		--	tools.err_add("tools_too_unnamed_arguments_err", key_N, val_src .. " LLL lev=" .. tostring(arglst[1].levenshtein) )
		--	tools.cat_add("tools_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 = tools.small_caps_style(tools.str_vars("tools_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>" .. tools.str_vars("tools_list_needed_args") .. "</b> " .. list_group( gr_need, needed )
	t = t .. "<br><br>* <b>" .. tools.str_vars("tools_list_all_other_args") .. "</b> " .. list_group( gr_other )
	return t
end -- function p.list_all_args_sub(t, args_known) -- tools_list_all_args_title for try

function p.list_all_args_main(t, args_known) -- tools_list_all_args_title = List of all arguments for main
	local t = t or "\n* <b>List_all_args :</b> "
	if type(args_known) ~= "table" then args_known = tools.args_known end
	t = t .. tools.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 = tools.user_translations[descr] or "**missing translation**"
		elem.user_lang_key = tools.user_translations[elem.key] or "**missing translation**"
		elem.user_lang_keyword = tools.user_translations[elem.keyword] or "**missing translation**"
	--	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 = tools.small_caps_style(tools.str_vars("tools_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>" .. tools.str_vars("tools_list_needed_args") .. "</b> " .. list_group( gr_need, needed )
	t = t .. "<br><br>* <b>" .. tools.str_vars("tools_list_all_other_args") .. "</b> " .. list_group( gr_other )
	t = t .. "<br><br>* <b>" .. tools.str_vars("list_all_authorities") .. "</b> " .. list_group( gr_authority )
	t = t .. "<br><br>* <b>" .. tools.str_vars("tools_list_all_config_arguments") .. "</b> " .. list_group( gr_config )
	t = t .. "<br><br>* <b>" .. tools.str_vars("tools_list_all_system_arguments") .. "</b> " .. list_group( gr_sys )
	return t
end -- function p.list_all_args_main(t, args_known) -- tools_list_all_args_title for main

-- 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)
	-- tools.options = " : docdata docmin docdef docmax docline docsrc docview docafter docnotice docsrc" -- for documentation
	-- tools.options = " erron noerr nobox " -- without normal result
	-- tools.options = " debug tests en es fr " -- for debug or enforce language
	if type(args_final) ~= "table" then args_final = tools.args_final end -- optional arguments
	local err = tools.verify_args_tables(tools.args_known, tools.args_source)
	if err then return err end
	res = ""
--	res = res .. "\n------"
	res = res .. tools.error_color("<center><b>" .. tools.str_vars("tools_delete_docbox_msg") .. "</b><br/></center>")
	local gettitle = mw.getCurrentFrame():getTitle() -- get the mainmodule title
	if type(package.loaded[gettitle]) == "table" then -- If #invoke has already loaded the main module in package.loaded
	else
	end
	tools.formDocBox_dropdown_title = tools.str_vars("tools_support_desk_title")
	.. " - " .. tools.report_main_discreet .. " - " .. tools.form_i18n_counts(centre.maini18n, "tools_maini18n_languages_list")
	-- select = "alltestsview" -- Deprecated, to view all boxes.
	-- select = "allwaysview" -- To allways display one view. -- select = "enforcerun" -- To debug one box with INTERNAL ERROR.
	res = res .. tools.dropdownfunc("allwaysview", tools.formDocBox_dropdown_title, tools.support_desk_report )
	if tools.docolor then res = res .. "\n:.\n: " .. tools.sources_of_datas_colors() end
	if tools.option("debug")		then res = res .. "\n*" .. tools.ta("tools.catView", tools.catView) .. tools.ta("tools.invoke_options", tools.invoke_options) .. tools.ta("tools.mode_options", tools.mode_options) end
	if tools.option("docdata")		then res = res .. "\n*" .. tools.generDoc(" docdef docline ", tools.args_wikidata, "Wikidata") end
	if tools.option("docview")		then res = res .. "\n*" .. tools.generDoc("", args_final, "Arguments") end
	if not tools.option("noerr")	then res = res .. "\n*" .. tools.errors_lister() end
	res = res .. "\n* " .. tools.categories_lister(":")
	res = '<div style=" width=90%; border: 1px solid #AAAAAA; margin:1em; background-color:#F1F1F1; padding:0.3em; ">' .. res .. '</div>'
	return res
end -- function p.formDocBox(args_final)

function p.normal_box(args_final, title)
	local res = ""
	local warning_versions = tools.report_main_short -- tools.warning_short()
	if type(args_final) ~= "table" then args_final = tools.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> " .. tools.report_main_discreet .. "<br></center>"
	local flag_of_image = tools.str_vars("flag_of_image")
	if type(flag_of_image) == "string" then -- and image ~= ""
		flag_of_image = '[[File:' .. flag_of_image .. '|frameless|40x40px||class=photo]] '
	else
		flag_of_image = "&nbsp;"
	end
	res = res .. flag_of_image
	function arglingual(key)
		local t = ""
		if key and tools and tools.user_translations and tools.user_translations[key] then
			t = t .. tools.tam(tools.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")
	tools.nowyear = tonumber(os.date("%Y") ) -- now_date = os.date("%Y-%m-%d %H:%M:%S")
--	local i2r = mathroman.int2roman(tools.nowyear)
--	local r2i = mathroman.roman2int(i2r)
--	local r2r = mathroman.int2roman(r2i)
	res = res .. tools.ta("int2roman", i2r) .. tools.ta("roman2int", r2i) .. tools.ta("int2roman", r2r)
	res = res .. "<br>" .. tools.maini18n_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)

-- Normal result of the module
-- Resultado normal del módulo
-- Résultat normal du module
function p.form_result(args_final)
	if type(args_final) ~= "table" then args_final = tools.args_final end -- optional arguments
	local res = ""
--	res = res .. tools.dropdownfunc(1, "tools_cat_add_test_title", tools.cat_add_test)
	tools.gener_categories(args_final) -- Produire les catégories sans les activer
	if tools.option("docview") then
		res = res .. "\n------"
		res = res .. p.formDocBox(args_final)
	end
	if not tools.option("nobox") then res = res .. p.normal_box(args_final) end
	-- Display categories. Afficher les catégories.
	if ( not tools.option("nobox") ) and ( tools.option("catview") or tools.option(":") ) then res = res .. tools.categories_lister(":") end
	tools.time3 = os.clock()
	if tools.option("tests") or (tools.args_final.mode == "tests") then res = res .. p.testsview("allwaysview") end
	tools.time4 = os.clock()
	if tools.option("tests") or (tools.args_final.mode == "tests") then res = res .. tools.running_times(true, "") end
	return res
end -- function p.form_result(args_final)

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
	tools.args_source = args_source
	return res
end -- function p.form_tests_init(res, args_source)

-- Interact parameters in international args_final
function tools.interact_args_final(args_import)
	return p.interact_args_final(args_import)
end
function p.interact_args_final(args_import)
	-- args_final = p.interact_args_final(args_import)
	if type(args_import) ~= "table" then args_import = tools.args_import end
	local args_final = mw.clone(args_import)

	local a = args_import
	local i = {} -- interact
--	t = "\n* begin :" .. tools.ta("initiale", a.initiale) .. tools.ta("firstname", a.firstname) .. tools.ta("lastname", a.lastname) .. tools.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 tools.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] -- select the last word
			i.initiale = string.sub( i.initiale, 1, 1 ) -- select 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 = tools.date_to_part(a.birth, tools.str_vars("tools_date_to_part_format"), "yyyy")
		if tt then i.birthyear = tt else
			tools.err_add(err, tools.str_vars("birthyear"), "yyyy")
			tools.cat_add("tools_date_to_part_call_cat")
		end
	end
	--
	if a.userlang then
--		i.userlang = a.userlang
--		tools.init_user_lang(a.userlang, tools.wiki_lang)
	end
	if a.wikilang then
--		i.wikilang = a.wikilang
--		tools.init_user_lang(tools.user_lang, a.wikilang)
	end
	--
	-- memorize interactions in tools.args_final and show errors or messages
	local n = 0
	for key, val in pairs(i) do
		local args_kwn = tools.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
				tools.msg_add("tools_auto_val_warning_msg", tools.user_translations[key], val)
			end
		else
			tools.err_add("tools_auto_val_unknown_err", tools.wiki_translations[key], val)
		--	tools.err_add("tools_unknown_auto_arg_err", tools.wiki_translations[key], val)
		end
	end
	if tools.args_known and tools.args_known.title then tools.args_known.title.trk = (tools.args_known.title.trk or "").."i="..n end
	tools.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, allias et fonctions pour les modèles
------------------------------------------------------------
------------------------------------------------------------

p.options_for_modes = { -- default init
	normal	= " noerr ",
	read	= " noerr ",
	edit	= " : catview docview docdef docline docsrc docdata ",
	doc1	= " nobox noerr nocat ",
	tests	= " : catview docview docdef docline docsrc docdata tests ",
	-- Option nocat means "Do not categorize and do not show categories."
}

function p.init(frame, mode_name, args_known, options_for_modes, itemid)
	return centre.init(frame, args_known, mode_name, options_for_modes, itemid)
end

function p.normal(frame) return p.read(frame) end -- Deprecated alias function
function p.read(frame)
	-- The read function imports, translates and verifies arguments.
	-- It generates read text and uses options to include edit or tests
	local res, t = "", ""
--	res = res .. '<div style="float:right;margin-left:1em;"> __TOC__ </div>' -- t = t .. tools.form_i18n_counts()
	centre.init(frame, p.args_known, "read", p.options_for_modes, itemid)
--	res = res .. tools.tracki18n("read mode")
	tools.init_wiki_user_lang()
	tools.args_final = p.interact_args_final(tools.args_import) -- Interactions between arguments
	tools.tracki18n("read:interact_args_final", tools)
	tools.time3 = os.clock()
	centre.trcPVG("<br>* read : ") -- track for tools.bindmodules_verif
--	res = res .. tools.dropdownfunc("versions_manage_report_title", "versions_manage_report_title", tools.versions_manage_report) -- Report some aspects of the versions management
	tools.tracki18n("read before form_result", try)
	res = res .. p.form_result()
--	tools.trac_lang("read args_final", tools.args_final) --
--	res = res .. "<br>read end : " .. tools.luatablecount("args_source")
	tools.tracki18n("read end", try)
--	res = res .. tools.tracki18n_all
	return res
end -- function p.read(frame)

-- function p.doc(frame) return p.edit(frame) end -- Deprecated alias function
function p.edit(frame)
	-- The edit function imports, translates and verifies arguments.
	-- It generates edit panel and edit text.
	local res = "", ""
	centre.init(frame, p.args_known, "edit", p.options_for_modes) -- , "Q535"
--	res = res .. mathroman.example() -- "mathroman:read ")
--	local ok, t = mathroman.TestsCases_recursive( "mathroman", mathroman.TestsCasesGroup ) -- Run a group of tests and returns one string.
--	res = res .. t
--	res = res .. mathroman.example() -- "mathroman:read ")
--	local ok, t = mathroman.TestsCases_recursive( "mathroman", mathroman.TestsGroups, 1 ) -- Run N groups of tests and returns one string.
--	res = res .. t
--	res = res .. tools.tracki18n("edit mode")
	tools.init_wiki_user_lang()
	tools.init_tools() -- frame, p.options_for_modes)
	tools.args_final = p.interact_args_final(tools.args_import) -- Interactions between argumensts
	tools.init_wiki_user_lang()
	tools.tracki18n("edit:interact_args_final")
	tools.time3 = os.clock()
	centre.trcPVG("<br>* doc : ") -- track for tools.bindmodules_verif
	tools.tracki18n("edit:before form_result")
--	res = res .. tools.tracki18n("before edit form_result")
	res = res .. p.form_result(tools.args_final)
	tools.tracki18n("edit end")
--	res = res .. tools.tracki18n_all
	return res
end -- function p.edit(frame)

function p.doc1(frame) return p.doc(frame) end -- Deprecated alias function
function p.doc(frame)
	-- Form as documentation, a test in a dropbox.
	local res, t = "", ""
	centre.init(frame, p.args_known, "doc", p.options_for_modes) -- , "Q535"
	local dockey = tools.args_config.dockey or tools.args_source[1]
	local itemid = tools.args_config.itemid or tools.args_config.id or tools.args_source[2]
--	res = res ..
--	tools.args_config_init() -- Get tools.args_config
	tools.change_itemid() -- "Q41568"
	tools.args_final = p.interact_args_final(tools.args_import) -- Interactions between argumensts
	tools.time3 = os.clock()
	if dockey then
		res = res .. p.testsview(dockey, itemid)
		return res
	else
		return ""
	end
end -- function p.doc(frame)

function tools.test_section(select, section)
	-- select = "alltestsview" -- Deprecated, to view all boxes.
	-- select = "allwaysview" -- To allways display one view. -- select = "enforcerun" -- To debug one box with INTERNAL ERROR.
	local res = ""
	if select == "allwaysview" then
		res = res .. "<h3>" .. tools.str_vars(section) .. "</h3>"
		res = res .. tools.report_main_short -- "tools.report_main_short is missing."
	end
	return res
end -- function tools.test_section(select, section)

function p.testsview(select, itemid) -- Form a documentation of one dropbox or all.
	-- select = "alltestsview" -- Deprecated, to view all boxes.
	-- select = "allwaysview" -- To allways display one view. -- select = "enforcerun" -- To debug one box with INTERNAL ERROR.
	local res, t = "", ""
	--
	res = res .. tools.test_section(select, "tools_page_tests_h3_title")
	--
	tools.support_desk_title = tools.report_main_discreet .. " - " .. tools.str_vars("tools_support_desk_title") -- tools.warning_short()
	res = res .. tools.dropdownfunc(select, "tools_support_desk_title", tools.support_desk_report )
	res = res .. tools.dropdownfunc(select, "tools_used_options_title", tools.used_options_list() )
	res = res .. tools.dropdownfunc(select, "tools_list_all_args_title", p.list_all_args_main)
	--
	res = res .. tools.dropdownfunc(select, "tools_luatables_counts_title", tools.luatables_counts)
	res = res .. tools.dropdownfunc(select, "tools_spaces_page_names_title", tools.spaces_page_names_test)
	res = res .. tools.dropdownfunc(select, "tools_table_args_source_title", tools.luatable_lister, tools.args_source, "tools.args_source")
	res = res .. tools.dropdownfunc(select, "tools_table_args_unknown_title", tools.luatable_lister, tools.args_unknown, "tools.args_unknown",
		{ boxstyle = "boxstyle", image = "Gtk-dialog-info.svg"} ) -- "[[Fichier:Gtk-dialog-info.svg|15px]]"
	res = res .. tools.dropdownfunc(select, "tools_args_known_structure_title", tools.args_known_structure)
	--
	res = res .. tools.test_section(select, "tools_internal_tests_h3_title")
	--
	res = res .. tools.dropdownfunc(select, "tools_list_all_categories_title", tools.list_all_categories)
	res = res .. tools.dropdownfunc(select, "tools_list_all_errors_title", tools.list_all_errors)
	res = res .. tools.dropdownfunc(select, "tools_transdiff_report_title", tools.transdiff_report)
	res = res .. tools.dropdownfunc(select, "tools_options_from_mode_title", tools.options_from_mode_test,
		{ 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 .. tools.dropdownfunc(select, "tools_options_from_args_title", tools.options_from_args_test)
	--
	-- versions management
--	res = res .. tools.dropdownfunc(select, "tools_unused_modules_tests_title", centre.select_modules_test )
--	res = res .. tools.dropdownfunc(select, "tools_verif_bindmodules_report", tools.bindmodules_report ) -- binding of the modules
--	tools.report_bind_verif_modules_details = tools.bindmodules_report() -- Report all modules installation
	res = res .. tools.dropdownfunc(select, "tools_bindmodules_report_title", tools.report_bind_verif_modules_details )
	res = res .. tools.dropdownfunc(select, "tools_missing_translations_title", tools.verifyinit, centre.maini18n) -- tools.i18n
--	res = res .. tools.dropdownfunc(select, "tools_versionsmanagement_report", tools.versions_management_report )
	--
	res = res .. tools.dropdownfunc(select, "tools_similar_args_test_title", tools.similar_args_test)
	res = res .. tools.dropdownfunc(select, "tools_levenshtein_test_title", tools.levenshtein_test)
	res = res .. tools.dropdownfunc(select, "tools_cat_add_test_title", tools.cat_add_test)
	res = res .. tools.dropdownfunc(select, "tools_multiple_values_tests_title", tools.multiple_values_tests)
	res = res .. tools.dropdownfunc(select, "tools_multiple_selection_test_title", tools.multiple_selection_test)
--	res = res .. tools.dropdownfunc(select, "tools_mixed_translations_title", tools.i18n_lister)
	res = res .. tools.dropdownfunc(select, "tools_wikidata_arbitrary_test_title", tools.tools_wikidata_arbitrary_test,
		{ boxstyle = "boxstyle", image = "Gtk-dialog-info.svg"} ) -- "[[Fichier:Gtk-dialog-info.svg|15px]]"
	res = res .. tools.dropdownfunc(select, "mathroman_rom2dig_testtitle", mathroman.roman_to_digital_test)
	res = res .. tools.dropdownfunc(select, "mathroman_dig2rom_testtitle", mathroman.digital_to_roman_test)
	res = res .. tools.dropdownfunc(select, "tools_date_to_part_test_title", tools.date_to_part_test)
	res = res .. tools.dropdownfunc(select, "tools_time_format_test_title", tools.time_format_test)
	res = res .. tools.dropdownfunc(select, "tools_wikidata_time_details", tools.luatable_lister, tools.WikidataTimeDetails, "tools.WikidataTimeDetails.claims." .. (tools.TimeName or "Pxxx") )
	res = res .. tools.dropdownfunc(select, "tools_tasks_table_report_short", ( tools.tasks_table_report_short or "tasks_table_report_short missing") ) -- "enforcerun"
	res = res .. tools.dropdownfunc(select, "tools_tasks_table_report_title", ( tools.tasks_table_report_full or "tasks_table_report_full missing") ) -- "enforcerun"
	--
--	res = res .. tools.dropdownfunc(select, "tools_versions_management_title", centre.bindmodules_start ) -- "enforcerun"
	res = res .. tools.dropdownfunc(select, "tools_bindmodules_test_title", tools.bindmodules_test() ) -- "enforcerun"
	--
	res = res .. tools.dropdownfunc(select, "tools_dummy_languages_title", tools.dummy_languages)
	res = res .. tools.dropdownfunc(select, "tools_test_luatable_lister_title", tools.luatable_lister, tools.tablim, "tools.tablim", {} )
	res = res .. tools.dropdownfunc(select, "tools_luatable_tests_limits_title", tools.luatable_lister, tools.tablim, "tools.tablim",
		{ boxstyle = "boxstyle", levelmaxi = 2, max_n = 2, exclude1 = "hou" } ) -- , {width = "88%", text_color = "blue"}
	res = res .. tools.dropdownfunc(select, "tools_documentations_changes_report", tools.documentations_changes_report )
	res = res .. tools.dropdownfunc("enforcerun", "mathroman_recursive_TestsCases_title", mathroman.TestsCases_recursive_tests )
	return res
end -- function p.testsview(select)

function p.tests(frame)
	local res = ""
	centre.init(frame, p.args_known, "tests", p.options_for_modes) -- , "Q535"
--	local res = mathroman.example() -- "mathroman:tests ")
--	local ok, t = mathroman.TestsCases_recursive( "mathroman", mathroman.TestsGroups ) -- Run a group of tests and returns one string.
--	res = res .. ok .. t
	tools.tracki18n("tests mode")
	tools.init_wiki_user_lang()
	tools.args_final = p.interact_args_final(tools.args_import) -- Interactions between arguments
	p.form_tests_init()
	tools.errors_list = {} -- Table to collect errors and messages
	tools.categories_list = {} -- init the collect of categories
--	tools.categories_init() -- initialize the category list
	tools.init_tools() -- frame, p.options_for_modes)
	tools.args_final = p.interact_args_final(tools.args_import) -- Interactions between argumensts
	tools.mode_name = tools.args_final.mode or tools.args_import.mode or tools.args_config.mode or "tests"
	tools.mode_options = tools.options_from_mode(tools.mode_name)
	tools.tracki18n("tests:options_from_mode")
	tools.init_wiki_user_lang()
	tools.tracki18n("tests:init_wiki_user_lang")
	tools.time3 = os.clock()
	tools.main_versions = tools.main_versions or { versionName = "versionName", versionNumber = "0.0", }
	local res_function = "<br>* " .. tools.main_versions.versionName .. ":" .. tools.mode_name .. ":" .. tools.wiki_lang .. " "
	-- If there is no MainModule, THIS version of Central is used alone
	if not tools.main_versions then tools.main_versions = tools.versions or p.versions end
	local res_function = "<br/>\n" .. (tools.main_versions.versionName or "MainModule_t") .. " " .. (tools.main_versions.versionNumber or "0.0_t") .. ":" .. tools.mode_name .. ":" .. tools.wiki_lang .. " "
	res = res .. res_function .. " Begin:" .. "<br>"
	local loaded_pack, loaded_txt, modu = centre.get_loaded_modules()
	tools.loaded_modules_track = "p.tests : " .. loaded_txt
	centre.trcPVG("<br>* tests form_result : ") -- track for tools.bindmodules_verif
	res = res .. p.form_result() -- Generate wikitext, categories, and others
	res = res .. res_function .. " End." .. "<br>"
	tools.tracki18n("tests:end")
	return res
end -- function p.tests(frame)

return p