Module:etymology/templates: Difference between revisions

From Linguifex
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
local require_when_needed = require("Module:utilities/require when needed")
local export = {}
 
local require_when_needed = require("Module:require when needed")


local concat = table.concat
local concat = table.concat
Line 7: Line 9:
local trim = mw.text.trim
local trim = mw.text.trim
local lower = mw.ustring.lower
local lower = mw.ustring.lower
local dump = mw.dumpObject


local export = {}
local etymology_module = "Module:etymology"
 
local etymology_specialized_module = "Module:etymology/specialized"
local m_internal = require("Module:etymology/templates/internal")
local parameter_utilities_module = "Module:parameter utilities"


-- For testing
-- For testing
local force_cat = false
local force_cat = false


local allowed_conjs = {"and", "or", ",", "/", "~", ";"}
local function parse_etym_args(parent_args, base_params, has_dest_lang)
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
}
local sourcearg, termarg
if has_dest_lang then
sourcearg, termarg = 2, 3
else
sourcearg, termarg = 1, 2
end
local terms, args = m_param_utils.parse_term_with_inline_modifiers_and_separate_params {
params = base_params,
param_mods = param_mods,
raw_args = parent_args,
termarg = termarg,
track_module = "etymology",
lang = function(args)
return args[sourcearg][#args[sourcearg]]
end,
sc = "sc",
-- Don't do this, doesn't seem to make sense.
-- parse_lang_prefix = true,
make_separate_g_into_list = true,
splitchar = ",",
subitem_param_handling = "last",
}
-- If term param 3= is empty, there will be no terms in terms.terms. To facilitate further code and for
-- compatibility,, insert one. It will display as <small>[Term?]</small>.
if not terms.terms[1] then
terms.terms[1] = {
lang = args[sourcearg][#args[sourcearg]],
sc = args.sc,
}
end
return terms.terms, args
end
function export.parse_2_lang_args(parent_args, has_text, no_family)
local boolean = {type = "boolean"}
local params = {
[1] = {
required = true,
type = "language",
default = "und"
},
[2] = {
required = true,
sublist = true,
type = "language",
family = not no_family,
default = "und"
},
[3] = true,
[4] = {alias_of = "alt"},
[5] = {alias_of = "t"},
["senseid"] = true,
["nocat"] = boolean,
["sort"] = true,
["sourceconj"] = true,
["conj"] = {set = allowed_conjs, default = ","},
}
if has_text then
params["notext"] = boolean
params["nocap"] = boolean
end
return parse_etym_args(parent_args, params, "has dest lang")
end
-- Implementation of deprecated {{etyl}}. Provided to make histories more legible.
function export.etyl(frame)
function export.etyl(frame)
local params = {
local params = {
Line 27: Line 108:
args = process_params({
args = process_params({
[1] = args[1],
[1] = args[1],
["sort"] = args["sort"]
["sort"] = args.sort
}, params)
}, params)
else
else
args = process_params(args, params)
args = process_params(args, params)
end
end
return require("Module:etymology").format_etyl(args[2], args[1], args["sort"])
return require(etymology_module).format_source {
lang = args[2],
source = args[1],
sort_key = args.sort,
force_cat = force_cat,
}
end
 
 
-- Implementation of {{derived}}/{{der}}.
function export.derived(frame)
local parent_args = frame:getParent().args
local terms, args = export.parse_2_lang_args(parent_args)
return require(etymology_module).format_derived {
lang = args[1],
sources = args[2],
terms = terms,
sort_key = args.sort,
nocat = args.nocat,
sourceconj = args.sourceconj,
conj = args.conj,
template_name = "derived",
force_cat = force_cat,
}
end
 
-- Implementation of {{borrowed}}/{{bor}}.
function export.borrowed(frame)
local parent_args = frame:getParent().args
local terms, args = export.parse_2_lang_args(parent_args)
return require(etymology_module).format_borrowed {
lang = args[1],
sources = args[2],
terms = terms,
sort_key = args.sort,
nocat = args.nocat,
sourceconj = args.sourceconj,
conj = args.conj,
force_cat = force_cat,
}
end
 
function export.inherited(frame)
local parent_args = frame:getParent().args
local terms, args = export.parse_2_lang_args(parent_args)
local sources = args[2]
if sources[2] then
-- Because this doesn't really make sense.
error("[[Template:inherited]] doesn't support multiple comma-separated sources")
end
return require(etymology_module).format_inherited {
lang = args[1],
terms = terms,
sort_key = args.sort,
nocat = args.nocat,
conj = args.conj,
force_cat = force_cat,
}
end
end


function export.cognate(frame)
local params = {
[1] = {
required = true,
sublist = true,
type = "language",
family = true,
default = "und"
},
[2] = true,
[3] = {alias_of = "alt"},
[4] = {alias_of = "t"},
sourceconj = true,
["conj"] = {set = allowed_conjs, default = ","},
sort = true,
}
local parent_args = frame:getParent().args
local terms, args = parse_etym_args(parent_args, params, false)
return require(etymology_module).format_cognate {
sources = args[1],
terms = terms,
sort_key = args.sort,
sourceconj = args.sourceconj,
conj = args.conj,
force_cat = force_cat,
}
end
function export.noncognate(frame)
return export.cognate(frame)
end


-- Supports various specialized types of borrowings, according to `frame.args.bortype`:
-- Supports various specialized types of borrowings, according to `frame.args.bortype`:
Line 47: Line 218:
--  "phono-semantic-matching" = {{psm}}/{{phono-semantic matching}}
--  "phono-semantic-matching" = {{psm}}/{{phono-semantic matching}}
function export.specialized_borrowing(frame)
function export.specialized_borrowing(frame)
local bortype = frame.args.bortype
local parent_args = frame:getParent().args
local args = frame:getParent().args
local terms, args = export.parse_2_lang_args(parent_args, "has text")
if args.gloss then
local m_etymology_specialized = require(etymology_specialized_module)
require("Module:debug").track("borrowing/" .. bortype .. "/gloss param")
return m_etymology_specialized.specialized_borrowing {
end
bortype = frame.args.bortype,
lang = args[1],
sources = args[2],
terms = terms,
sort_key = args.sort,
nocap = args.nocap,
notext = args.notext,
nocat = args.nocat,
sourceconj = args.sourceconj,
conj = args.conj,
senseid = args.senseid,
force_cat = force_cat,
}
end
 
 
-- Implementation of miscellaneous templates such as {{abbrev}}, {{back-formation}}, {{clipping}}, {{ellipsis}},
-- {{rebracketing}} and {{reduplication}} that have a single associated term.
function export.misc_variant(frame)
local iparams = {
["ignore-params"] = true,
text = {required = true},
oftext = true,
cat = {list = true}, -- allow and compress holes
conj = true,
}
 
local iargs = process_params(frame.args, iparams)
 
local boolean = {type = "boolean"}


-- More informative error message for {{calque}}, which used to support other params.
local params = {
if bortype == "calque" and (args["etyl lang"] or args["etyl term"] or args["etyl t"] or args["etyl tr"]) then
[1] = {required = true, type = "language", default = "und"},
error("{{[[Template:calque|calque]]}} no longer supports parameters beginning with etyl. " ..
[2] = true,
"The parameters supported are similar to those used by " ..
[3] = {alias_of = "alt"},
"{{[[Template:der|der]]}}, {{[[Template:inh|inh]]}}, " ..
[4] = {alias_of = "t"},
"{{[[Template:bor|bor]]}}. See [[Template:calque/documentation]] for more.")
end
local lang, term, sources
args, lang, term, sources = m_internal.parse_2_lang_args(frame, "has text")
local m_etymology_specialized = require("Module:etymology/specialized")
if sources then
return m_etymology_specialized.specialized_multi_borrowing(bortype, lang, term.sc, sources, term,
args.sort, args.nocap, args.notext, args.nocat, args.conj, args.senseid)
else
return m_etymology_specialized.specialized_borrowing(bortype, lang, term, args.sort,
args.nocap, args.notext, args.nocat, args.senseid)
end
end


nocap = boolean, -- should be processed in the template itself
notext = boolean,
nocat = boolean,
conj = {set = allowed_conjs},
sort = true,
}


-- Implementation of miscellaneous templates such as {{back-formation}}, {{clipping}},
-- |ignore-params= parameter to module invocation specifies
-- {{ellipsis}}, {{rebracketing}}, and {{reduplication}} that have a single
-- additional parameter names to allow  in template invocation, separated by
-- associated term.
-- commas. They must consist of ASCII letters or numbers or hyphens.
do
local ignore_params = iargs["ignore-params"]
local function get_args(frame)
if ignore_params then
local alias_of_t = {alias_of = "t"}
ignore_params = trim(ignore_params)
local boolean = {type = "boolean"}
if not ignore_params:match("^[%w%-,]+$") then
local plain = {}
error("Invalid characters in |ignore-params=: " .. ignore_params:gsub("[%w%-,]+", ""))
local params = {
end
[1] = {required = true, type = "language", default = "und"},
for param in ignore_params:gmatch("[%w%-]+") do
[2] = plain,
if params[param] then
[3] = {alias_of = "alt"},
error("Duplicate param |" .. param
[4] = alias_of_t,
.. " in |ignore-params=: already specified in params")
["alt"] = plain,
["gloss"] = alias_of_t,
["g"] = {list = true},
["id"] = plain,
["lit"] = plain,
["pos"] = plain,
["t"] = plain,
["tr"] = plain,
["ts"] = plain,
["sc"] = {type = "script"},
["nocap"] = boolean, -- should be processed in the template itself
["notext"] = boolean,
["nocat"] = boolean,
["sort"] = plain,
}
-- |ignore-params= parameter to module invocation specifies
-- additional parameter names to allow  in template invocation, separated by
-- commas. They must consist of ASCII letters or numbers or hyphens.
local ignore_params = frame.args["ignore-params"]
if ignore_params then
ignore_params = trim(ignore_params)
if not ignore_params:match("^[%w%-,]+$") then
error("Invalid characters in |ignore-params=: " .. ignore_params:gsub("[%w%-,]+", ""))
end
for param in ignore_params:gmatch("[%w%-]+") do
if params[param] then
error("Duplicate param |" .. param
.. " in |ignore-params=: already specified in params")
end
params[param] = plain
end
end
params[param] = true
end
end
return process_params(frame:getParent().args, params)
end
end
function export.misc_variant(frame)
local args = get_args(frame)
local lang = args[1]
local sc = args["sc"]


local parts = {}
local m_param_utils = require(parameter_utilities_module)
if not args["notext"] then
local param_mods = m_param_utils.construct_param_mods {
insert(parts, frame.args["text"])
{group = {"link", "q", "l", "ref"}},
end
}
if args[2] or args["alt"] then
 
if not args["notext"] then
local parent_args = frame:getParent().args
insert(parts, " ")
 
insert(parts, frame.args["oftext"] or "of")
local terms, args = m_param_utils.parse_term_with_inline_modifiers_and_separate_params {
insert(parts, " ")
params = params,
end
param_mods = param_mods,
insert(parts, require("Module:links").full_link(
raw_args = parent_args,
{
termarg = 2,
lang = lang,
track_module = "etymology",
sc = sc,
-- Don't set lang here as we want to know whether there was a lang prefix or not.
term = args[2],
sc = "sc",
alt = args["alt"],
parse_lang_prefix = true,
id = args["id"],
allow_multiple_lang_prefixes = true,
tr = args["tr"],
make_separate_g_into_list = true,
ts = args["ts"],
splitchar = ",",
genders = args["g"],
subitem_param_handling = "last",
gloss = args["t"],
}
pos = args["pos"],
lit = args["lit"],
nocont = true,
},
"term"))
end
-- Allow |cat=, |cat2=, |cat3=, etc. They must be sequential. If |cat=
-- is not defined, |cat2= will not be checked. Empty categories are ignored.
local categories = {}
if not args["nocat"] and frame.args["cat"] then
local cat_number
while true do
local cat = frame.args["cat" .. (cat_number or "")]
if not cat then break end
cat = trim(cat)
if cat ~= "" then
insert(categories, lang:getFullName() .. " " .. cat)
end
cat_number = (cat_number or 1) + 1
end
end
if #categories > 0 then
insert(
parts,
format_categories(categories, lang, args["sort"], nil, force_cat))
end


return concat(parts)
return require(etymology_module).format_misc_variant {
end
lang = args[1],
notext = args.notext,
text = iargs.text,
oftext = iargs.oftext,
terms = terms.terms,
sort_key = args.sort,
conj = args.conj or iargs.conj or "and",
nocat = args.nocat,
cats = iargs.cat,
force_cat = force_cat,
}
end
end




-- Implementation of miscellaneous templates such as {{unknown}} that have no
-- Implementation of miscellaneous templates such as {{doublet}} that can take multiple terms. Doesn't handle {{blend}}
-- associated terms.
-- or {{univerbation}}, which display + signs between elements and use compound_like in [[Module:affix/templates]].
function export.misc_variant_multiple_terms(frame)
local iparams = {
text = {required = true},
oftext = true,
cat = {list = true}, -- allow and compress holes
conj = true,
}
 
local iargs = process_params(frame.args, iparams)
 
local boolean = {type = "boolean"}
 
local params = {
[1] = {required = true, type = "language", template_default = "und"},
[2] = {list = true, allow_holes = true},
nocap = boolean, -- should be processed in the template itself
notext = boolean,
nocat = boolean,
conj = {set = allowed_conjs},
sort = true,
}
 
    local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
-- We want to require an index for all params.
{default = true, require_index = true},
{group = {"link", "q", "l", "ref"}},
}
 
local parent_args = frame:getParent().args
 
local terms, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 2,
parse_lang_prefix = true,
allow_multiple_lang_prefixes = true,
track_module = "etymology-templates-doublet",
disallow_custom_separators = true,
-- For compatibility, we need to not skip completely unspecified items. It is common, for example, to do
-- {{suffix|lang||foo}} to generate "+ -foo".
dont_skip_items = true,
-- Don't set lang here as we want to know whether there was a lang prefix or not.
sc = "sc.default",
}
 
return require(etymology_module).format_misc_variant {
lang = args[1],
notext = args.notext,
text = iargs.text,
oftext = iargs.oftext,
terms = terms,
sort_key = args.sort,
conj = args.conj or iargs.conj or "and",
nocat = args.nocat,
cats = iargs.cat,
force_cat = force_cat,
}
end
 
-- Implementation of miscellaneous templates such as {{unknown}} that have no associated terms.
do
do
local function get_args(frame)
local function get_args(frame)
local boolean = {type = "boolean"}
local boolean = {type = "boolean"}
local plain = {}
local params = {
local params = {
[1] = {required = true, type = "language", default = "und"},
[1] = {required = true, type = "language", default = "und"},


["title"] = plain,
["title"] = true,
["nocap"] = boolean, -- should be processed in the template itself
["nocap"] = boolean, -- should be processed in the template itself
["notext"] = boolean,
["notext"] = boolean,
["nocat"] = boolean,
["nocat"] = boolean,
["sort"] = plain,
["sort"] = true,
}
}
if frame.args["title2_alias"] then
if frame.args.title2_alias then
params[2] = {alias_of = "title"}
params[2] = {alias_of = "title"}
end
end
Line 205: Line 405:
function export.misc_variant_no_term(frame)
function export.misc_variant_no_term(frame)
local args = get_args(frame)
local args = get_args(frame)
local lang = args[1]
local parts = {}
if not args["notext"] then
insert(parts, args["title"] or frame.args["text"])
end
if not args["nocat"] and frame.args["cat"] then
local categories = {}
insert(categories, lang:getFullName() .. " " .. frame.args["cat"])
insert(parts, format_categories(categories, lang, args["sort"], nil, force_cat))
end


return concat(parts)
return require(etymology_module).format_misc_variant_no_term {
lang = args[1],
notext = args.notext,
title = args.title or frame.args.text,
nocat = args.nocat,
cat = frame.args.cat,
sort_key = args.sort,
force_cat = force_cat,
}
end
end


--This function works similarly to misc_variant_no_term(), but with some automatic linking to the glossary in `title`.
-- This function works similarly to misc_variant_no_term(), but with some automatic linking to the glossary in
-- `title`.
function export.onomatopoeia(frame)
function export.onomatopoeia(frame)
local args = get_args(frame)
local args = get_args(frame)


if args["title"] and (lower(args["title"]) == "imitative" or lower(args["title"]) == "imitation") then
local title = args.title
args["title"] = "[[Appendix:Glossary#imitative|" .. args["title"] .. "]]"
if title and (lower(title) == "imitative" or lower(title) == "imitation") then
title = "[[wikt:Appendix:Glossary#imitative|" .. title .. "]]"
end
end


local lang = args[1]
return require(etymology_module).format_misc_variant_no_term {
lang = args[1],
local parts = {}
notext = args.notext,
if not args["notext"] then
title = title or frame.args.text,
insert(parts, args["title"] or frame.args["text"])
nocat = args.nocat,
end
cat = frame.args.cat,
if not args["nocat"] and frame.args["cat"] then
sort_key = args.sort,
local categories = {}
force_cat = force_cat,
insert(categories, lang:getFullName() .. " " .. frame.args["cat"])
}
insert(parts, format_categories(categories, lang, args["sort"], nil, force_cat))
end
 
return concat(parts)
end
end
end
end


return export
return export

Revision as of 23:16, 24 March 2026



local export = {}

local require_when_needed = require("Module:require when needed")

local concat = table.concat
local format_categories = require_when_needed("Module:utilities", "format_categories")
local insert = table.insert
local process_params = require_when_needed("Module:parameters", "process")
local trim = mw.text.trim
local lower = mw.ustring.lower
local dump = mw.dumpObject

local etymology_module = "Module:etymology"
local etymology_specialized_module = "Module:etymology/specialized"
local parameter_utilities_module = "Module:parameter utilities"

-- For testing
local force_cat = false

local allowed_conjs = {"and", "or", ",", "/", "~", ";"}

local function parse_etym_args(parent_args, base_params, has_dest_lang)
	local m_param_utils = require(parameter_utilities_module)
	local param_mods = m_param_utils.construct_param_mods {
		{group = {"link", "q", "l", "ref"}},
	}

	local sourcearg, termarg
	if has_dest_lang then
		sourcearg, termarg = 2, 3
	else
		sourcearg, termarg = 1, 2
	end
	local terms, args = m_param_utils.parse_term_with_inline_modifiers_and_separate_params {
		params = base_params,
		param_mods = param_mods,
		raw_args = parent_args,
		termarg = termarg,
		track_module = "etymology",
		lang = function(args)
			return args[sourcearg][#args[sourcearg]]
		end,
		sc = "sc",
		-- Don't do this, doesn't seem to make sense.
		-- parse_lang_prefix = true,
		make_separate_g_into_list = true,
		splitchar = ",",
		subitem_param_handling = "last",
	}
	-- If term param 3= is empty, there will be no terms in terms.terms. To facilitate further code and for
	-- compatibility,, insert one. It will display as <small>[Term?]</small>.
	if not terms.terms[1] then
		terms.terms[1] = {
			lang = args[sourcearg][#args[sourcearg]],
			sc = args.sc,
		}
	end

	return terms.terms, args
end


function export.parse_2_lang_args(parent_args, has_text, no_family)
	local boolean = {type = "boolean"}
	local params = {
		[1] = {
			required = true,
			type = "language",
			default = "und"
		},
		[2] = {
			required = true,
			sublist = true,
			type = "language",
			family = not no_family,
			default = "und"
		},
		[3] = true,
		[4] = {alias_of = "alt"},
		[5] = {alias_of = "t"},

		["senseid"] = true,
		["nocat"] = boolean,
		["sort"] = true,
		["sourceconj"] = true,
		["conj"] = {set = allowed_conjs, default = ","},
	}
	if has_text then
		params["notext"] = boolean
		params["nocap"] = boolean
	end

	return parse_etym_args(parent_args, params, "has dest lang")
end


-- Implementation of deprecated {{etyl}}. Provided to make histories more legible.
function export.etyl(frame)
	local params = {
		[1] = {required = true, type = "language", default = "und"},
		[2] = {type = "language", default = "en"},
		["sort"] = {},
	}
	-- Empty language means English, but "-" means no language. Yes, confusing...
	local args = frame:getParent().args
	if args[2] and trim(args[2]) == "-" then
		params[2] = nil
		args = process_params({
			[1] = args[1],
			["sort"] = args.sort
		}, params)
	else
		args = process_params(args, params)
	end
	return require(etymology_module).format_source {
		lang = args[2],
		source = args[1],
		sort_key = args.sort,
		force_cat = force_cat,
	}
end


-- Implementation of {{derived}}/{{der}}.
function export.derived(frame)
	local parent_args = frame:getParent().args
	local terms, args = export.parse_2_lang_args(parent_args)
	return require(etymology_module).format_derived {
		lang = args[1],
		sources = args[2],
		terms = terms,
		sort_key = args.sort,
		nocat = args.nocat,
		sourceconj = args.sourceconj,
		conj = args.conj,
		template_name = "derived",
		force_cat = force_cat,
	}
end

-- Implementation of {{borrowed}}/{{bor}}.
function export.borrowed(frame)
	local parent_args = frame:getParent().args
	local terms, args = export.parse_2_lang_args(parent_args)
	return require(etymology_module).format_borrowed {
		lang = args[1],
		sources = args[2],
		terms = terms,
		sort_key = args.sort,
		nocat = args.nocat,
		sourceconj = args.sourceconj,
		conj = args.conj,
		force_cat = force_cat,
	}
end

function export.inherited(frame)
	local parent_args = frame:getParent().args
	local terms, args = export.parse_2_lang_args(parent_args)
	local sources = args[2]
	if sources[2] then
		-- Because this doesn't really make sense.
		error("[[Template:inherited]] doesn't support multiple comma-separated sources")
	end
	return require(etymology_module).format_inherited {
		lang = args[1],
		terms = terms,
		sort_key = args.sort,
		nocat = args.nocat,
		conj = args.conj,
		force_cat = force_cat,
	}
end

function export.cognate(frame)
	local params = {
		[1] = {
			required = true,
			sublist = true,
			type = "language",
			family = true,
			default = "und"
		},
		[2] = true,
		[3] = {alias_of = "alt"},
		[4] = {alias_of = "t"},
		sourceconj = true,
		["conj"] = {set = allowed_conjs, default = ","},
		sort = true,
	}

	local parent_args = frame:getParent().args
	local terms, args = parse_etym_args(parent_args, params, false)

	return require(etymology_module).format_cognate {
		sources = args[1],
		terms = terms,
		sort_key = args.sort,
		sourceconj = args.sourceconj,
		conj = args.conj,
		force_cat = force_cat,
	}
end

function export.noncognate(frame)
	return export.cognate(frame)
end

-- Supports various specialized types of borrowings, according to `frame.args.bortype`:
--   "learned" = {{lbor}}/{{learned borrowing}}
--   "semi-learned" = {{slbor}}/{{semi-learned borrowing}}
--   "orthographic" = {{obor}}/{{orthographic borrowing}}
--   "unadapted" = {{ubor}}/{{unadapted borrowing}}
--   "calque" = {{cal}}/{{calque}}
--   "partial-calque" = {{pcal}}/{{partial calque}}
--   "semantic-loan" = {{sl}}/{{semantic loan}}
--   "transliteration" = {{translit}}/{{transliteration}}
--   "phono-semantic-matching" = {{psm}}/{{phono-semantic matching}}
function export.specialized_borrowing(frame)
	local parent_args = frame:getParent().args
	local terms, args = export.parse_2_lang_args(parent_args, "has text")
	local m_etymology_specialized = require(etymology_specialized_module)
	return m_etymology_specialized.specialized_borrowing {
		bortype = frame.args.bortype,
		lang = args[1],
		sources = args[2],
		terms = terms,
		sort_key = args.sort,
		nocap = args.nocap,
		notext = args.notext,
		nocat = args.nocat,
		sourceconj = args.sourceconj,
		conj = args.conj,
		senseid = args.senseid,
		force_cat = force_cat,
	}
end


-- Implementation of miscellaneous templates such as {{abbrev}}, {{back-formation}}, {{clipping}}, {{ellipsis}},
-- {{rebracketing}} and {{reduplication}} that have a single associated term.
function export.misc_variant(frame)
	local iparams = {
		["ignore-params"] = true,
		text = {required = true},
		oftext = true,
		cat = {list = true}, -- allow and compress holes
		conj = true,
	}

	local iargs = process_params(frame.args, iparams)

	local boolean = {type = "boolean"}

	local params = {
		[1] = {required = true, type = "language", default = "und"},
		[2] = true,
		[3] = {alias_of = "alt"},
		[4] = {alias_of = "t"},

		nocap = boolean, -- should be processed in the template itself
		notext = boolean,
		nocat = boolean,
		conj = {set = allowed_conjs},
		sort = true,
	}

	-- |ignore-params= parameter to module invocation specifies
	-- additional parameter names to allow  in template invocation, separated by
	-- commas. They must consist of ASCII letters or numbers or hyphens.
	local ignore_params = iargs["ignore-params"]
	if ignore_params then
		ignore_params = trim(ignore_params)
		if not ignore_params:match("^[%w%-,]+$") then
			error("Invalid characters in |ignore-params=: " .. ignore_params:gsub("[%w%-,]+", ""))
		end
		for param in ignore_params:gmatch("[%w%-]+") do
			if params[param] then
				error("Duplicate param |" .. param
					.. " in |ignore-params=: already specified in params")
			end
			params[param] = true
		end
	end

	local m_param_utils = require(parameter_utilities_module)
	local param_mods = m_param_utils.construct_param_mods {
		{group = {"link", "q", "l", "ref"}},
	}

	local parent_args = frame:getParent().args

	local terms, args = m_param_utils.parse_term_with_inline_modifiers_and_separate_params {
		params = params,
		param_mods = param_mods,
		raw_args = parent_args,
		termarg = 2,
		track_module = "etymology",
		-- Don't set lang here as we want to know whether there was a lang prefix or not.
		sc = "sc",
		parse_lang_prefix = true,
		allow_multiple_lang_prefixes = true,
		make_separate_g_into_list = true,
		splitchar = ",",
		subitem_param_handling = "last",
	}

	return require(etymology_module).format_misc_variant {
		lang = args[1],
		notext = args.notext,
		text = iargs.text,
		oftext = iargs.oftext,
		terms = terms.terms,
		sort_key = args.sort,
		conj = args.conj or iargs.conj or "and",
		nocat = args.nocat,
		cats = iargs.cat,
		force_cat = force_cat,
	}
end


-- Implementation of miscellaneous templates such as {{doublet}} that can take multiple terms. Doesn't handle {{blend}}
-- or {{univerbation}}, which display + signs between elements and use compound_like in [[Module:affix/templates]].
function export.misc_variant_multiple_terms(frame)
	local iparams = {
		text = {required = true},
		oftext = true,
		cat = {list = true}, -- allow and compress holes
		conj = true,
	}

	local iargs = process_params(frame.args, iparams)

	local boolean = {type = "boolean"}

	local params = {
		[1] = {required = true, type = "language", template_default = "und"},
		[2] = {list = true, allow_holes = true},
		nocap = boolean, -- should be processed in the template itself
		notext = boolean,
		nocat = boolean,
		conj = {set = allowed_conjs},
		sort = true,
	}

    local m_param_utils = require(parameter_utilities_module)
	local param_mods = m_param_utils.construct_param_mods {
		-- We want to require an index for all params.
		{default = true, require_index = true},
		{group = {"link", "q", "l", "ref"}},
	}

	local parent_args = frame:getParent().args

	local terms, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
		params = params,
		param_mods = param_mods,
		raw_args = parent_args,
		termarg = 2,
		parse_lang_prefix = true,
		allow_multiple_lang_prefixes = true,
		track_module = "etymology-templates-doublet",
		disallow_custom_separators = true,
		-- For compatibility, we need to not skip completely unspecified items. It is common, for example, to do
		-- {{suffix|lang||foo}} to generate "+ -foo".
		dont_skip_items = true,
		-- Don't set lang here as we want to know whether there was a lang prefix or not.
		sc = "sc.default",
	}

	return require(etymology_module).format_misc_variant {
		lang = args[1],
		notext = args.notext,
		text = iargs.text,
		oftext = iargs.oftext,
		terms = terms,
		sort_key = args.sort,
		conj = args.conj or iargs.conj or "and",
		nocat = args.nocat,
		cats = iargs.cat,
		force_cat = force_cat,
	}
end

-- Implementation of miscellaneous templates such as {{unknown}} that have no associated terms.
do
	local function get_args(frame)
		local boolean = {type = "boolean"}
		local params = {
			[1] = {required = true, type = "language", default = "und"},

			["title"] = true,
			["nocap"] = boolean, -- should be processed in the template itself
			["notext"] = boolean,
			["nocat"] = boolean,
			["sort"] = true,
		}
		if frame.args.title2_alias then
			params[2] = {alias_of = "title"}
		end
		return process_params(frame:getParent().args, params)
	end

	function export.misc_variant_no_term(frame)
		local args = get_args(frame)

		return require(etymology_module).format_misc_variant_no_term {
			lang = args[1],
			notext = args.notext,
			title = args.title or frame.args.text,
			nocat = args.nocat,
			cat = frame.args.cat,
			sort_key = args.sort,
			force_cat = force_cat,
		}
	end

	-- This function works similarly to misc_variant_no_term(), but with some automatic linking to the glossary in
	-- `title`.
	function export.onomatopoeia(frame)
		local args = get_args(frame)

		local title = args.title
		if title and (lower(title) == "imitative" or lower(title) == "imitation") then
			title = "[[wikt:Appendix:Glossary#imitative|" .. title .. "]]"
		end

		return require(etymology_module).format_misc_variant_no_term {
			lang = args[1],
			notext = args.notext,
			title = title or frame.args.text,
			nocat = args.nocat,
			cat = frame.args.cat,
			sort_key = args.sort,
			force_cat = force_cat,
		}
	end
end

return export