muca and Module:etymology/templates: Difference between pages

From Wiktionary, the free dictionary
(Difference between pages)
Jump to navigation Jump to search
Content deleted Content added
+cat
 
new {{desc}}/{{desctree}} implementation supporting multiple terms, misc cleanups
Tag: Reverted
 
Line 1: Line 1:
local export = {}
==Romani==
===Noun===
{{rom-noun|muc|f|a|i}}


local m_languages = require("Module:languages")
# [[cat]]
local m_etymology = require("Module:etymology")


local rsplit = mw.text.split
[[Category:rom:Cats]]
local rsubn = mw.ustring.gsub
[[Category:rom:Mammals]]


-- version of rsubn() that discards all but the first return value
[[io:muca]]
local function rsub(term, foo, bar)
[[scn:muca]]
local retval = rsubn(term, foo, bar)
return retval
end

local function track(page)
return require("Module:debug").track(page)
end


local function fetch_script(sc)
if sc then
return require("Module:scripts").getByCode(sc, true)
else
return nil
end
end


function export.etyl(frame)
local params = {
[1] = {required = true, default = "und"},
[2] = {},
["sort"] = {},
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local source = m_languages.getByCode(args[1], 1, "allow etym", "allow family")
local lang = args[2]
local sort_key = args["sort"]
-- Empty language means English, but "-" means no language. Yes, confusing...
if not lang then
lang = "en"
elseif lang == "-" then
lang = nil
end
if lang then
lang = m_languages.getByCode(lang, 2)
end
if lang and (lang:getCode() == "la" or lang:getCode() == "nl") then
track("etyl/" .. lang:getCode())
track("etyl/" .. lang:getCode() .. "/" .. source:getCode())
end
return m_etymology.format_etyl(lang, source, sort_key)
end


function export.cognate(frame)
local args = frame:getParent().args
if args.gloss then
track("cognate/gloss param")
end
local params = {
[1] = {required = true, default = "und"},
[2] = {},
[3] = {alias_of = "alt"},
[4] = {alias_of = "t"},
["alt"] = {},
["g"] = {list = true},
["id"] = {},
["lit"] = {},
["pos"] = {},
["t"] = {},
["gloss"] = {alias_of = "t"},
["tr"] = {},
["ts"] = {},
["sc"] = {},
["sort"] = {},
}
args = require("Module:parameters").process(args, params)
local source = m_languages.getByCode(args[1], 1, "allow etym", "allow family")
local sc = fetch_script(args["sc"])

return m_etymology.format_cognate(
{
lang = source,
sc = sc,
term = args[2],
alt = args["alt"],
id = args["id"],
genders = args["g"],
tr = args["tr"],
ts = args["ts"],
gloss = args["t"],
pos = args["pos"],
lit = args["lit"]
},
args["sort"])
end


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

local function parse_2_lang_args(frame, has_text, no_family)
local params = {
[1] = {required = true, default = "und"},
[2] = {required = true, default = "und"},
[3] = {},
[4] = {alias_of = "alt"},
[5] = {alias_of = "t"},
["alt"] = {},
["g"] = {list = true},
["gloss"] = {alias_of = "t"},
["id"] = {},
["lit"] = {},
["pos"] = {},
["t"] = {},
["tr"] = {},
["ts"] = {},
["sc"] = {},

["nocat"] = {type = "boolean"},
["sort"] = {},
}

if has_text then
params["notext"] = {type = "boolean"}
params["nocap"] = {type = "boolean"}
end

local args = require("Module:parameters").process(frame:getParent().args, params)
local lang = m_languages.getByCode(args[1], 1)
local source = m_languages.getByCode(args[2], 2, "allow etym", not no_family and "allow family")
local sc = fetch_script(args["sc"])

return args, lang, {
lang = source,
sc = sc,
term = args[3],
alt = args["alt"],
id = args["id"],
genders = args["g"],
tr = args["tr"],
ts = args["ts"],
gloss = args["t"],
pos = args["pos"],
lit = args["lit"]
}
end

function export.derived(frame)
local args, lang, term = parse_2_lang_args(frame)
return m_etymology.format_derived(lang, term, args["sort"], args["nocat"], "derived")
end

function export.inherited(frame)
local args, lang, term = parse_2_lang_args(frame, nil, "no family")
return m_etymology.format_inherited(lang, term, args["sort"], args["nocat"])
end

function export.borrowed(frame)
local args, lang, term = parse_2_lang_args(frame)
return m_etymology.format_borrowed(lang, term, args["sort"],
false, true, args["nocat"], "plain")
end

function export.learned_borrowing(frame)
if frame:getParent().args.gloss then
track("learned_borrowing/gloss param")
end

local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.format_borrowed(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"], "learned")
end

function export.semi_learned_borrowing(frame)
if frame:getParent().args.gloss then
track("semi_learned_borrowing/gloss param")
end

local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.format_borrowed(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"], "semi-learned")
end

function export.orthographic_borrowing(frame)
if frame:getParent().args.gloss then
track("orthographic_borrowing/gloss param")
end

local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.format_borrowed(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"], "orthographic")
end

function export.unadapted_borrowing(frame)
if frame:getParent().args.gloss then
track("unadapted_borrowing/gloss param")
end

local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.format_borrowed(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"], "unadapted")
end

function export.calque(frame)
local args = frame:getParent().args
-- More informative error message.
if args["etyl lang"] or args["etyl term"] or args["etyl t"] or args["etyl tr"] then
error("{{[[Template:calque|calque]]}} no longer supports parameters beginning with etyl. " ..
"The parameters supported are similar to those used by " ..
"{{[[Template:der|der]]}}, {{[[Template:inh|inh]]}}, " ..
"{{[[Template:bor|bor]]}}. See [[Template:calque/documentation]] for more.")
end
local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.calque(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"])
end

function export.partial_calque(frame)
if frame:getParent().args.gloss then
track("partial_calque/gloss param")
end

local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.partial_calque(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"])
end

function export.semantic_loan(frame)
local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.semantic_loan(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"])
end

function export.psm(frame)
local args, lang, term = parse_2_lang_args(frame, "has text")
return m_etymology.phono_semantic_matching(lang, term, args["sort"],
args["nocap"], args["notext"], args["nocat"])
end

local function qualifier(content)
if content then
return table.concat{
'<span class="ib-brac qualifier-brac">(</span>',
'<span class="ib-content qualifier-content">',
content,
'</span>',
'<span class="ib-brac qualifier-brac">)</span>'
}
end
end


local function add_tooltip(text, tooltip)
return '<span class="desc-arr" title="' .. tooltip .. '">' .. text .. '</span>'
end


local function desc_or_desc_tree(frame, desc_tree)
local params
if desc_tree then
params = {
[1] = {required = true, default = "gem-pro"},
[2] = {required = true, list = "term", allow_holes = true, default = "*fuhsaz"},
["notext"] = { type = "boolean" },
["noalts"] = { type = "boolean" },
["noparent"] = { type = "boolean" },
}
else
params = {
[1] = { required = true, default = "en" },
[2] = { list = "term", allow_holes = true, default = "word" },
["alts"] = { type = "boolean" }
}
end

for k, v in pairs({
[3] = { alias_of = "alt" },
["alt"] = { list = true, allow_holes = true },
[4] = { alias_of = "t" },
-- FIXME, currently all genders refer to first term, for historical reasons
["g"] = { list = true, allow_holes = true },
["gloss"] = { alias_of = "t", list = true, allow_holes = true },
["id"] = { list = true, allow_holes = true },
["lit"] = { list = true, allow_holes = true },
["pos"] = { list = true, allow_holes = true },
["t"] = { list = true, allow_holes = true },
["tr"] = { list = true, allow_holes = true },
["ts"] = { list = true, allow_holes = true },
["sc"] = { list = true, allow_holes = true },
["inh"] = { type = "boolean" },
["partinh"] = { type = "boolean", list = "inh", allow_holes = true, require_index = true },
["bor"] = { type = "boolean" },
["partbor"] = { type = "boolean", list = "bor", allow_holes = true, require_index = true },
["lbor"] = { type = "boolean" },
["partlbor"] = { type = "boolean", list = "lbor", allow_holes = true, require_index = true },
["slb"] = { type = "boolean" },
["partslb"] = { type = "boolean", list = "slb", allow_holes = true, require_index = true },
["der"] = { type = "boolean" },
["partder"] = { type = "boolean", list = "der", allow_holes = true, require_index = true },
["clq"] = { type = "boolean" },
["partclq"] = { type = "boolean", list = "clq", allow_holes = true, require_index = true },
["cal"] = { alias_of = "clq", type = "boolean" },
["partcal"] = { alias_of = "partclq", type = "boolean", list = "cal", allow_holes = true, require_index = true },
["calq"] = { alias_of = "clq", type = "boolean" },
["partcalq"] = { alias_of = "partclq", type = "boolean", list = "calq", allow_holes = true, require_index = true },
["calque"] = { alias_of = "clq", type = "boolean" },
["partcalque"] = { alias_of = "partclq", type = "boolean", list = "calque", allow_holes = true, require_index = true },
["pclq"] = { type = "boolean" },
["partpclq"] = { type = "boolean", list = "pclq", allow_holes = true, require_index = true },
["sml"] = { type = "boolean" },
["partsml"] = { type = "boolean", list = "sml", allow_holes = true, require_index = true },
["unc"] = { type = "boolean" },
["partunc"] = { type = "boolean", list = "unc", allow_holes = true, require_index = true },
["sclb"] = { type = "boolean" },
["nolb"] = { type = "boolean" },
["q"] = {},
["partq"] = { list = "q", allow_holes = true, require_index = true },
["sandbox"] = { type = "boolean" },
}) do
params[k] = v
end

local namespace = mw.title.getCurrentTitle().nsText

local parent_args
if frame.args[1] then
parent_args = frame.args
else
parent_args = frame:getParent().args
end

if parent_args[3] then
track("descendants/arg3")
end
if parent_args[4] then
track("descendants/arg4")
end

local args = require("Module:parameters").process(parent_args, params)

if args.sandbox then
if namespace == "" or namespace == "Reconstruction" then
error("The sandbox module, Module:descendants tree/sandbox, should not be used in entries.")
end
end

local m_desctree
if desc_tree or args["alts"] then
if args.sandbox or require("Module:yesno")(frame.args.sandbox, false) then
m_desctree = require("Module:descendants tree/sandbox")
else
m_desctree = require("Module:descendants tree")
end
end

-- FIXME! Remove this after converting existing instances of multiple genders to a single gender param, comma-separated.
if args["g"].maxindex > 0 then
local genders = {}
for i = 1, args["g"].maxindex do
if args["g"][i] then
table.insert(genders, args["g"][i])
end
end
args["g"] = {table.concat(genders, ",")}
args["g"].maxindex = 1
end

-- Check for any remaining places where g2= might be used for the first term's second gender.
-- (Applies after the FIXME code just above is removed.)
if args["g"].maxindex > args[2].maxindex then
track("descendant/gender-no-term")
end

local lang = args[1]
lang = m_languages.getByCode(lang, 1, "allow etym")
local entryLang = m_languages.getNonEtymological(lang)
if not desc_tree and entryLang:getType() == "family" then
error("Cannot use language family code in [[Template:desc]].")
end
if lang:getCode() ~= entryLang:getCode() then
-- [[Special:WhatLinksHere/Template:tracking/descendant/etymological]]
track("descendant/etymological")
track("descendant/etymological/" .. lang:getCode())
end
local languageName = lang:getCanonicalName()
languageName = mw.ustring.gsub(languageName, "^Proto%-", "")

local label
-- Find the maximum index among any of the list parameters.
local maxmaxindex = 0
for k, v in pairs(args) do
if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
maxmaxindex = v.maxindex
end
end

local function get_arrow(index)
local function val(arg)
if index == 0 then
return args[arg]
else
return args["part" .. arg][index]
end
end

local arrow

if val("bor") then
arrow = add_tooltip("→", "borrowed")
elseif val("lbor") then
arrow = add_tooltip("→", "learned borrowing")
elseif val("slb") then
arrow = add_tooltip("→", "semi-learned borrowing")
elseif val("clq") then
arrow = add_tooltip("→", "calque")
elseif val("pclq") then
arrow = add_tooltip("→", "partial calque")
elseif val("sml") then
arrow = add_tooltip("→", "semantic loan")
elseif val("unc") and not val("der") then
arrow = add_tooltip(">", "inherited")
else
arrow = ""
end
-- allow der=1 in conjunction with bor=1 to indicate e.g. English "pars recta"
-- derived and borrowed from Latin "pars".
if val("der") then
arrow = arrow .. add_tooltip("⇒", "reshaped by analogy or addition of morphemes")
end
if val("unc")then
arrow = arrow .. add_tooltip("?", "uncertain")
end

if arrow ~= "" then
arrow = arrow .. " "
end

return arrow
end

local function get_post_qualifiers(index)
local function val(arg)
if index == 0 then
return args[arg]
else
return args["part" .. arg][index]
end
end

local postqs = {}
if val("inh") then
table.insert(postqs, qualifier("inherited"))
end
if val("lbor") then
table.insert(postqs, qualifier("learned"))
end
if val("slb") then
table.insert(postqs, qualifier("semi-learned"))
end
if val("clq") then
table.insert(postqs, qualifier("calque"))
end
if val("pclq") then
table.insert(postqs, qualifier("partial calque"))
end
if val("sml") then
table.insert(postqs, qualifier("semantic loan"))
end
-- FIXME, should we use the qualifier support in full_link() (in which case the qualifier precedes the term)?
if val("q") then
table.insert(postqs, require("Module:qualifier").format_qualifier(val("q")))
end
if #postqs > 0 then
return " " .. table.concat(postqs, " ")
else
return ""
end
end

local parts = {}
local descendants = {}
local saw_descendants = false
local terms = {}

for i = 1, maxmaxindex do
local term = args[2][i]
local alt = args["alt"][i]
local id = args["id"][i]
local sc = args["sc"][i] and require("Module:scripts").getByCode(args["sc"][i], "sc" .. (i == 1 and "" or i))
local tr = args["tr"][i]
local ts = args["ts"][i]
local gloss = args["t"][i]
local pos = args["pos"][i]
local lit = args["lit"][i]
local g = args["g"][i] and rsplit(args["g"][i], "%s*,%s*") or {}

if i == 1 then
if args["sclb"] then
if sc then
label = sc:getCanonicalName()
elseif not term then
error("When sclb=1, must specify sc= or 2=")
else
label = require("Module:scripts").findBestScript(term, lang):getCanonicalName()
end
else
label = languageName
end
end
local link = ""
if term ~= "-" then
link = require("Module:links").full_link(
{
lang = entryLang,
sc = sc,
term = term,
alt = alt,
id = id,
tr = tr,
ts = ts,
genders = g,
gloss = gloss,
pos = pos,
lit = lit,
},
nil,
true)
elseif ts or gloss or #g > 0 then
-- [[Special:WhatLinksHere/Template:tracking/descendant/no term]]
track("descendant/no term")
link = require("Module:links").full_link(
{
lang = entryLang,
sc = sc,
ts = ts,
gloss = gloss,
genders = g,
},
nil,
true)
link = link
:gsub("<small>%[Term%?%]</small> ", "")
:gsub("<small>%[Term%?%]</small>&nbsp;", "")
:gsub("%[%[Category:[^%[%]]+ term requests%]%]", "")
else -- display no link at all
-- [[Special:WhatLinksHere/Template:tracking/descendant/no term or annotations]]
track("descendant/no term or annotations")
end

local arrow = get_arrow(i)
local postqs = get_post_qualifiers(i)
local alts
if desc_tree and term and term ~= "-" then
table.insert(terms, term)
descendants[i] = m_desctree.getDescendants(entryLang, term, id, maxmaxindex > 1)
if descendants[i] then
saw_descendants = true
end
end

descendants[i] = descendants[i] or ""
if desc_tree and not args["noalts"] or not desc_tree and args["alts"] then
-- [[Special:WhatLinksHere/Template:tracking/desc/alts]]
track("desc/alts")
alts = m_desctree.getAlternativeForms(entryLang, term)
else
alts = ""
end

local linktext = table.concat{link, alts, postqs}
if not args["notext"] then
linktext = arrow .. linktext
end
if linktext ~= "" then
table.insert(parts, linktext)
end
end

if desc_tree and not saw_descendants then
if #terms == 0 then
error("{{desctree}} invoked but no terms to retrieve descendants from")
elseif #terms == 1 then
error("No Descendants section was found in the entry [[" .. terms[1] ..
"]] under the header for " .. entryLang:getCanonicalName() .. ".")
else
for i, term in ipairs(terms) do
terms[i] = "[[" .. term .. "]]"
end
error("No Descendants section was found in any of the entries " ..
table.concat(terms, ", ") .. " under the header for " .. entryLang:getCanonicalName() .. ".")
end
end

descendants = table.concat(descendants)
if args["noparent"] then
return descendants
end

local initial_arrow = get_arrow(0)
local final_postqs = get_post_qualifiers(0)

local all_linktext = table.concat(parts, ", ") .. final_postqs .. descendants

if args["notext"] then
return all_linktext
elseif args["nolb"] then
return initial_arrow .. all_linktext
else
return table.concat{initial_arrow, label, ":", linktext ~= "" and " " or "", all_linktext}
end
end
function export.descendant(frame)
return desc_or_desc_tree(frame, false) .. require("Module:TemplateStyles")("Module:etymology/style.css")
end

function export.descendants_tree(frame)
return desc_or_desc_tree(frame, true)
end

-- Implementation of miscellaneous templates such as {{back-formation}}, {{clipping}},
-- {{ellipsis}}, {{rebracketing}}, and {{reduplication}} that have a single
-- associated term.
function export.misc_variant(frame)
local params = {
[1] = {required = true, default = "und"},
[2] = {},
[3] = {alias_of = "alt"},
[4] = {alias_of = "t"},
["alt"] = {},
["gloss"] = {alias_of = "t"},
["g"] = {list = true},
["id"] = {},
["lit"] = {},
["pos"] = {},
["t"] = {},
["tr"] = {},
["ts"] = {},
["sc"] = {},
["nocap"] = {type = "boolean"}, -- should be processed in the template itself
["notext"] = {type = "boolean"},
["nocat"] = {type = "boolean"},
["sort"] = {},
}
-- |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 = mw.text.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] = {}
end
end
local args = require("Module:parameters").process(frame:getParent().args, params)
local lang = m_languages.getByCode(args[1], 1)
local sc = fetch_script(args["sc"])

local parts = {}
if not args["notext"] then
table.insert(parts, frame.args["text"])
end
if args[2] or args["alt"] then
if not args["notext"] then
table.insert(parts, " ")
table.insert(parts, frame.args["oftext"] or "of")
table.insert(parts, " ")
end
table.insert(parts, require("Module:links").full_link(
{
lang = lang,
sc = sc,
term = args[2],
alt = args["alt"],
id = args["id"],
tr = args["tr"],
ts = args["ts"],
genders = args["g"],
gloss = args["t"],
pos = args["pos"],
lit = args["lit"],
},
"term",
true))
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 = mw.text.trim(cat)
if cat ~= "" then
table.insert(categories, lang:getCanonicalName() .. " " .. cat)
end
cat_number = (cat_number or 1) + 1
end
end
if #categories > 0 then
table.insert(
parts,
require("Module:utilities").format_categories(categories, lang, args["sort"]))
end

return table.concat(parts)
end


local function get_parsed_part(template, lang, args, terms, i)
local term = terms[i]
local alt = args["alt"][i]
local id = args["id"][i]
local sc = fetch_script(args["sc"][i])

local tr = args["tr"][i]
local ts = args["ts"][i]
local gloss = args["t"][i]
local pos = args["pos"][i]
local lit = args["lit"][i]
local g = args["g"][i]

if not (term or alt or tr or ts) then
track(template .. "/no term or alt or tr")
return nil
else
return require("Module:links").full_link(
{ term = term, alt = alt, id = id, lang = lang, sc = sc, tr = tr,
ts = ts, gloss = gloss, pos = pos, lit = lit,
genders = g and rsplit(g, ",") or {}
}, "term", true)
end
end


local function get_parsed_parts(template, lang, args, terms)
local parts = {}

-- Find the maximum index among any of the list parameters.
local maxmaxindex = 0
for k, v in pairs(args) do
if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
maxmaxindex = v.maxindex
end
end

for index = 1, maxmaxindex do
table.insert(parts, get_parsed_part(template, lang, args, terms, index))
end
return parts
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:compound/templates]].
function export.misc_variant_multiple_terms(frame)
local params = {
[1] = {required = true, default = "und"},
[2] = {list = true, allow_holes = true},

["t"] = {list = true, allow_holes = true, require_index = true},
["gloss"] = {list = true, allow_holes = true, require_index = true, alias_of = "t"},
["tr"] = {list = true, allow_holes = true, require_index = true},
["ts"] = {list = true, allow_holes = true, require_index = true},
["g"] = {list = true, allow_holes = true, require_index = true},
["id"] = {list = true, allow_holes = true, require_index = true},
["alt"] = {list = true, allow_holes = true, require_index = true},
["lit"] = {list = true, allow_holes = true, require_index = true},
["pos"] = {list = true, allow_holes = true, require_index = true},
["sc"] = {list = true, allow_holes = true, require_index = true},

["nocap"] = {type = "boolean"}, -- should be processed in the template itself
["notext"] = {type = "boolean"},
["nocat"] = {type = "boolean"},
["sort"] = {},
}

local args = require("Module:parameters").process(frame:getParent().args, params)
local lang = m_languages.getByCode(args[1], 1)

local parts = {}
if not args["notext"] then
table.insert(parts, frame.args["text"])
end
if #args[2] > 0 or #args["alt"] > 0 then
if not args["notext"] then
table.insert(parts, " ")
table.insert(parts, frame.args["oftext"] or "of")
table.insert(parts, " ")
end
local formatted_terms = get_parsed_parts(mw.ustring.lower(
-- Remove link and convert uppercase to lowercase to get an
-- approximation of the original template name.
rsub(rsub(frame.args["text"], "^%[%[.*|", ""), "%]%]$", "")),
lang, args, args[2])
table.insert(parts, require("Module:table").serialCommaJoin(formatted_terms))
end
if not args["nocat"] and frame.args["cat"] then
local categories = {}
table.insert(categories, lang:getCanonicalName() .. " " .. frame.args["cat"])
table.insert(parts, require("Module:utilities").format_categories(categories, lang, args["sort"]))
end

return table.concat(parts)
end


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

["title"] = {},
["nocap"] = {type = "boolean"}, -- should be processed in the template itself
["notext"] = {type = "boolean"},
["nocat"] = {type = "boolean"},
["sort"] = {},
}

if frame.args["title2_alias"] then
params[2] = {alias_of = "title"}
end
local args = require("Module:parameters").process(frame:getParent().args, params)
local lang = m_languages.getByCode(args[1], 1)

local parts = {}
if not args["notext"] then
table.insert(parts, args["title"] or frame.args["text"])
end
if not args["nocat"] and frame.args["cat"] then
local categories = {}
table.insert(categories, lang:getCanonicalName() .. " " .. frame.args["cat"])
table.insert(parts, require("Module:utilities").format_categories(categories, lang, args["sort"]))
end

return table.concat(parts)
end

return export

Revision as of 02:46, 27 November 2020

This module generates content for several etymology templates. They include:

Also, the following specialized borrowing templates (not including {{bor}}/{{borrowed}} itself):

Miscellaneous etymology templates that take a single term parameter:

Miscellaneous etymology templates that take no term parameter:

Some templates that used to be here but have now been moved to submodules:


local export = {}

local m_languages = require("Module:languages")
local m_etymology = require("Module:etymology")

local rsplit = mw.text.split
local rsubn = mw.ustring.gsub

-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
	local retval = rsubn(term, foo, bar)
	return retval
end

local function track(page)
	return require("Module:debug").track(page)
end


local function fetch_script(sc)
	if sc then
		return require("Module:scripts").getByCode(sc, true)
	else
		return nil
	end
end


function export.etyl(frame)
	local params = {
		[1] = {required = true, default = "und"},
		[2] = {},
		["sort"] = {},
	}
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	
	local source = m_languages.getByCode(args[1], 1, "allow etym", "allow family")
	local lang = args[2]
	local sort_key = args["sort"]
	
	-- Empty language means English, but "-" means no language. Yes, confusing...
	if not lang then
		lang = "en"
	elseif lang == "-" then
		lang = nil
	end
	if lang then
		lang = m_languages.getByCode(lang, 2)
	end
	if lang and (lang:getCode() == "la" or lang:getCode() == "nl") then
		track("etyl/" .. lang:getCode())
		track("etyl/" .. lang:getCode() .. "/" .. source:getCode())
	end
	
	return m_etymology.format_etyl(lang, source, sort_key)
end


function export.cognate(frame)
	local args = frame:getParent().args
	
	if args.gloss then
		track("cognate/gloss param")
	end
	
	local params = {
		[1] = {required = true, default = "und"},
		[2] = {},
		[3] = {alias_of = "alt"},
		[4] = {alias_of = "t"},
		
		["alt"] = {},
		["g"] = {list = true},
		["id"] = {},
		["lit"] = {},
		["pos"] = {},
		["t"] = {},
		["gloss"] = {alias_of = "t"},
		["tr"] = {},
		["ts"] = {},
		["sc"] = {},
		
		["sort"] = {},
	}
	
	args = require("Module:parameters").process(args, params)
	
	local source = m_languages.getByCode(args[1], 1, "allow etym", "allow family")
	local sc = fetch_script(args["sc"])

	return m_etymology.format_cognate(
		{
			lang = source,
			sc = sc,
			term = args[2],
			alt = args["alt"],
			id = args["id"],
			genders = args["g"],
			tr = args["tr"],
			ts = args["ts"],
			gloss = args["t"],
			pos = args["pos"],
			lit = args["lit"]
		},
		args["sort"])
end


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

local function parse_2_lang_args(frame, has_text, no_family)
	local params = {
		[1] = {required = true, default = "und"},
		[2] = {required = true, default = "und"},
		[3] = {},
		[4] = {alias_of = "alt"},
		[5] = {alias_of = "t"},
		
		["alt"] = {},
		["g"] = {list = true},
		["gloss"] = {alias_of = "t"},
		["id"] = {},
		["lit"] = {},
		["pos"] = {},
		["t"] = {},
		["tr"] = {},
		["ts"] = {},
		["sc"] = {},

		["nocat"] = {type = "boolean"},
		["sort"] = {},
	}

	if has_text then
		params["notext"] = {type = "boolean"}
		params["nocap"] = {type = "boolean"}
	end

	local args = require("Module:parameters").process(frame:getParent().args, params)
	
	local lang = m_languages.getByCode(args[1], 1)
	local source = m_languages.getByCode(args[2], 2, "allow etym", not no_family and "allow family")
	local sc = fetch_script(args["sc"])

	return args, lang, {
		lang = source,
		sc = sc,
		term = args[3],
		alt = args["alt"],
		id = args["id"],
		genders = args["g"],
		tr = args["tr"],
		ts = args["ts"],
		gloss = args["t"],
		pos = args["pos"],
		lit = args["lit"]
	}
end
	

function export.derived(frame)
	local args, lang, term = parse_2_lang_args(frame)
	return m_etymology.format_derived(lang, term, args["sort"], args["nocat"], "derived")
end

function export.inherited(frame)
	local args, lang, term = parse_2_lang_args(frame, nil, "no family")
	return m_etymology.format_inherited(lang, term, args["sort"], args["nocat"])
end

function export.borrowed(frame)
	local args, lang, term = parse_2_lang_args(frame)
	return m_etymology.format_borrowed(lang, term, args["sort"],
		false, true, args["nocat"], "plain")
end

function export.learned_borrowing(frame)
	if frame:getParent().args.gloss then
		track("learned_borrowing/gloss param")
	end

	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.format_borrowed(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"], "learned")
end

function export.semi_learned_borrowing(frame)
	if frame:getParent().args.gloss then
		track("semi_learned_borrowing/gloss param")
	end

	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.format_borrowed(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"], "semi-learned")
end

function export.orthographic_borrowing(frame)
	if frame:getParent().args.gloss then
		track("orthographic_borrowing/gloss param")
	end

	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.format_borrowed(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"], "orthographic")
end

function export.unadapted_borrowing(frame)
	if frame:getParent().args.gloss then
		track("unadapted_borrowing/gloss param")
	end

	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.format_borrowed(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"], "unadapted")
end

function export.calque(frame)
	local args = frame:getParent().args
	
	-- More informative error message.
	if args["etyl lang"] or args["etyl term"] or args["etyl t"] or args["etyl tr"] then
		error("{{[[Template:calque|calque]]}} no longer supports parameters beginning with etyl. " ..
			"The parameters supported are similar to those used by " ..
			"{{[[Template:der|der]]}}, {{[[Template:inh|inh]]}}, " ..
			"{{[[Template:bor|bor]]}}. See [[Template:calque/documentation]] for more.")
	end
	
	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.calque(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"])
end

function export.partial_calque(frame)
	if frame:getParent().args.gloss then
		track("partial_calque/gloss param")
	end

	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.partial_calque(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"])
end

function export.semantic_loan(frame)
	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.semantic_loan(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"])
end

function export.psm(frame)
	local args, lang, term = parse_2_lang_args(frame, "has text")
	return m_etymology.phono_semantic_matching(lang, term, args["sort"],
		args["nocap"], args["notext"], args["nocat"])
end

local function qualifier(content)
	if content then
		return table.concat{
			'<span class="ib-brac qualifier-brac">(</span>',
			'<span class="ib-content qualifier-content">',
			content,
			'</span>',
			'<span class="ib-brac qualifier-brac">)</span>'
		}
	end
end


local function add_tooltip(text, tooltip)
	return '<span class="desc-arr" title="' .. tooltip .. '">' .. text .. '</span>'
end


local function desc_or_desc_tree(frame, desc_tree)
	local params
	if desc_tree then
		params = {
			[1] = {required = true, default = "gem-pro"},
			[2] = {required = true, list = "term", allow_holes = true, default = "*fuhsaz"},
			["notext"] = { type = "boolean" },
			["noalts"] = { type = "boolean" },
			["noparent"] = { type = "boolean" },
		}
	else
		params = {
			[1] = { required = true, default = "en" },
			[2] = { list = "term", allow_holes = true, default = "word" },
			["alts"] = { type = "boolean" }
		}
	end

	for k, v in pairs({
		[3] = { alias_of = "alt" },
		["alt"] = { list = true, allow_holes = true },
		[4] = { alias_of = "t" },
		-- FIXME, currently all genders refer to first term, for historical reasons
		["g"] = { list = true, allow_holes = true },
		["gloss"] = { alias_of = "t", list = true, allow_holes = true },
		["id"] = { list = true, allow_holes = true },
		["lit"] = { list = true, allow_holes = true },
		["pos"] = { list = true, allow_holes = true },
		["t"] = { list = true, allow_holes = true },
		["tr"] = { list = true, allow_holes = true },
		["ts"] = { list = true, allow_holes = true },
		["sc"] = { list = true, allow_holes = true },
		["inh"] = { type = "boolean" },
		["partinh"] = { type = "boolean", list = "inh", allow_holes = true, require_index = true },
		["bor"] = { type = "boolean" },
		["partbor"] = { type = "boolean", list = "bor", allow_holes = true, require_index = true },
		["lbor"] = { type = "boolean" },
		["partlbor"] = { type = "boolean", list = "lbor", allow_holes = true, require_index = true },
		["slb"] = { type = "boolean" },
		["partslb"] = { type = "boolean", list = "slb", allow_holes = true, require_index = true },
		["der"] = { type = "boolean" },
		["partder"] = { type = "boolean", list = "der", allow_holes = true, require_index = true },
		["clq"] = { type = "boolean" },
		["partclq"] = { type = "boolean", list = "clq", allow_holes = true, require_index = true },
		["cal"] = { alias_of = "clq", type = "boolean" },
		["partcal"] = { alias_of = "partclq", type = "boolean", list = "cal", allow_holes = true, require_index = true },
		["calq"] = { alias_of = "clq", type = "boolean" },
		["partcalq"] = { alias_of = "partclq", type = "boolean", list = "calq", allow_holes = true, require_index = true },
		["calque"] = { alias_of = "clq", type = "boolean" },
		["partcalque"] = { alias_of = "partclq", type = "boolean", list = "calque", allow_holes = true, require_index = true },
		["pclq"] = { type = "boolean" },
		["partpclq"] = { type = "boolean", list = "pclq", allow_holes = true, require_index = true },
		["sml"] = { type = "boolean" },
		["partsml"] = { type = "boolean", list = "sml", allow_holes = true, require_index = true },
		["unc"] = { type = "boolean" },
		["partunc"] = { type = "boolean", list = "unc", allow_holes = true, require_index = true },
		["sclb"] = { type = "boolean" },
		["nolb"] = { type = "boolean" },
		["q"] = {},
		["partq"] = { list = "q", allow_holes = true, require_index = true },
		["sandbox"] = { type = "boolean" },
	}) do
		params[k] = v
	end

	local namespace = mw.title.getCurrentTitle().nsText

	local parent_args
	if frame.args[1] then
		parent_args = frame.args
	else
		parent_args = frame:getParent().args
	end

	if parent_args[3] then
		track("descendants/arg3")
	end
	if parent_args[4] then
		track("descendants/arg4")
	end

	local args = require("Module:parameters").process(parent_args, params)

	if args.sandbox then
		if namespace == "" or namespace == "Reconstruction" then
			error("The sandbox module, Module:descendants tree/sandbox, should not be used in entries.")
		end
	end

	local m_desctree
	if desc_tree or args["alts"] then
		if args.sandbox or require("Module:yesno")(frame.args.sandbox, false) then
			m_desctree = require("Module:descendants tree/sandbox")
		else
			m_desctree = require("Module:descendants tree")
		end
	end

	-- FIXME! Remove this after converting existing instances of multiple genders to a single gender param, comma-separated.
	if args["g"].maxindex > 0 then
		local genders = {}
		for i = 1, args["g"].maxindex do
			if args["g"][i] then
				table.insert(genders, args["g"][i])
			end
		end
		args["g"] = {table.concat(genders, ",")}
		args["g"].maxindex = 1
	end

	-- Check for any remaining places where g2= might be used for the first term's second gender.
	-- (Applies after the FIXME code just above is removed.)
	if args["g"].maxindex > args[2].maxindex then
		track("descendant/gender-no-term")
	end

	local lang = args[1]
	lang = m_languages.getByCode(lang, 1, "allow etym")
	local entryLang = m_languages.getNonEtymological(lang)
	
	if not desc_tree and entryLang:getType() == "family" then
		error("Cannot use language family code in [[Template:desc]].")
	end
	
	if lang:getCode() ~= entryLang:getCode() then
		-- [[Special:WhatLinksHere/Template:tracking/descendant/etymological]]
		track("descendant/etymological")
		track("descendant/etymological/" .. lang:getCode())
	end
	
	local languageName = lang:getCanonicalName()
	languageName = mw.ustring.gsub(languageName, "^Proto%-", "")

	local label
	
	-- Find the maximum index among any of the list parameters.
	local maxmaxindex = 0
	for k, v in pairs(args) do
		if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
			maxmaxindex = v.maxindex
		end
	end

	local function get_arrow(index)
		local function val(arg)
			if index == 0 then
				return args[arg]
			else
				return args["part" .. arg][index]
			end
		end

		local arrow

		if val("bor") then
			arrow = add_tooltip("→", "borrowed")
		elseif val("lbor") then
			arrow = add_tooltip("→", "learned borrowing")
		elseif val("slb") then
			arrow = add_tooltip("→", "semi-learned borrowing")
		elseif val("clq") then
			arrow = add_tooltip("→", "calque")
		elseif val("pclq") then
			arrow = add_tooltip("→", "partial calque")
		elseif val("sml") then
			arrow = add_tooltip("→", "semantic loan")
		elseif val("unc") and not val("der") then
			arrow = add_tooltip(">", "inherited")
		else
			arrow = ""
		end
		-- allow der=1 in conjunction with bor=1 to indicate e.g. English "pars recta"
		-- derived and borrowed from Latin "pars".
		if val("der") then
			arrow = arrow .. add_tooltip("⇒", "reshaped by analogy or addition of morphemes")
		end
		
		if val("unc")then
			arrow = arrow .. add_tooltip("?", "uncertain")
		end

		if arrow ~= "" then
			arrow = arrow .. " "
		end

		return arrow
	end

	local function get_post_qualifiers(index)
		local function val(arg)
			if index == 0 then
				return args[arg]
			else
				return args["part" .. arg][index]
			end
		end

		local postqs = {}
		if val("inh") then
			table.insert(postqs, qualifier("inherited"))
		end
		if val("lbor") then
			table.insert(postqs, qualifier("learned"))
		end
		if val("slb") then
			table.insert(postqs, qualifier("semi-learned"))
		end
		if val("clq") then
			table.insert(postqs, qualifier("calque"))
		end
		if val("pclq") then
			table.insert(postqs, qualifier("partial calque"))
		end
		if val("sml") then
			table.insert(postqs, qualifier("semantic loan"))
		end
		-- FIXME, should we use the qualifier support in full_link() (in which case the qualifier precedes the term)?
		if val("q") then
			table.insert(postqs, require("Module:qualifier").format_qualifier(val("q")))
		end
		if #postqs > 0 then
			return " " .. table.concat(postqs, " ")
		else
			return ""
		end
	end

	local parts = {}
	local descendants = {}
	local saw_descendants = false
	local terms = {}

	for i = 1, maxmaxindex do
		local term = args[2][i]
		local alt = args["alt"][i]
		local id = args["id"][i]
		local sc = args["sc"][i] and require("Module:scripts").getByCode(args["sc"][i], "sc" .. (i == 1 and "" or i))
		local tr = args["tr"][i]
		local ts = args["ts"][i]
		local gloss = args["t"][i]
		local pos = args["pos"][i]
		local lit = args["lit"][i]
		local g = args["g"][i] and rsplit(args["g"][i], "%s*,%s*") or {}

		if i == 1 then
			if args["sclb"] then
				if sc then
					label = sc:getCanonicalName()
				elseif not term then
					error("When sclb=1, must specify sc= or 2=")
				else
					label = require("Module:scripts").findBestScript(term, lang):getCanonicalName()
				end
			else
				label = languageName
			end
		end
	
		local link = ""
		
		if term ~= "-" then
			link = require("Module:links").full_link(
				{
					lang = entryLang,
					sc = sc,
					term = term,
					alt = alt,
					id = id,
					tr = tr,
					ts = ts,
					genders = g,
					gloss = gloss,
					pos = pos,
					lit = lit,
				},
				nil,
				true)
		elseif ts or gloss or #g > 0 then
			-- [[Special:WhatLinksHere/Template:tracking/descendant/no term]]
			track("descendant/no term")
			link = require("Module:links").full_link(
				{
					lang = entryLang,
					sc = sc,
					ts = ts,
					gloss = gloss,
					genders = g,
				},
				nil,
				true)
			link = link
				:gsub("<small>%[Term%?%]</small> ", "")
				:gsub("<small>%[Term%?%]</small>&nbsp;", "")
				:gsub("%[%[Category:[^%[%]]+ term requests%]%]", "")
		else -- display no link at all
			-- [[Special:WhatLinksHere/Template:tracking/descendant/no term or annotations]]
			track("descendant/no term or annotations")
		end

		local arrow = get_arrow(i)
		local postqs = get_post_qualifiers(i)
		local alts
		
		if desc_tree and term and term ~= "-" then
			table.insert(terms, term)
			descendants[i] = m_desctree.getDescendants(entryLang, term, id, maxmaxindex > 1)
			if descendants[i] then
				saw_descendants = true
			end
		end

		descendants[i] = descendants[i] or ""
		
		if desc_tree and not args["noalts"] or not desc_tree and args["alts"] then
			-- [[Special:WhatLinksHere/Template:tracking/desc/alts]]
			track("desc/alts")
			alts = m_desctree.getAlternativeForms(entryLang, term)
		else
			alts = ""
		end

		local linktext = table.concat{link, alts, postqs}
		if not args["notext"] then
			linktext = arrow .. linktext
		end
		if linktext ~= "" then
			table.insert(parts, linktext)
		end
	end

	if desc_tree and not saw_descendants then
		if #terms == 0 then
			error("{{desctree}} invoked but no terms to retrieve descendants from")
		elseif #terms == 1 then
			error("No Descendants section was found in the entry [[" .. terms[1] ..
				"]] under the header for " .. entryLang:getCanonicalName() .. ".")
		else
			for i, term in ipairs(terms) do
				terms[i] = "[[" .. term .. "]]"
			end
			error("No Descendants section was found in any of the entries " ..
				table.concat(terms, ", ") .. " under the header for " .. entryLang:getCanonicalName() .. ".")
		end
	end

	descendants = table.concat(descendants)
	if args["noparent"] then
		return descendants
	end

	local initial_arrow = get_arrow(0)
	local final_postqs = get_post_qualifiers(0)

	local all_linktext = table.concat(parts, ", ") .. final_postqs .. descendants

	if args["notext"] then
		return all_linktext
	elseif args["nolb"] then
		return initial_arrow .. all_linktext
	else
		return table.concat{initial_arrow, label, ":", linktext ~= "" and " " or "", all_linktext}
	end
end
	
function export.descendant(frame)
	return desc_or_desc_tree(frame, false) .. require("Module:TemplateStyles")("Module:etymology/style.css")
end

function export.descendants_tree(frame)
	return desc_or_desc_tree(frame, true)
end

-- Implementation of miscellaneous templates such as {{back-formation}}, {{clipping}},
-- {{ellipsis}}, {{rebracketing}}, and {{reduplication}} that have a single
-- associated term.
function export.misc_variant(frame)
	local params = {
		[1] = {required = true, default = "und"},
		[2] = {},
		[3] = {alias_of = "alt"},
		[4] = {alias_of = "t"},
		
		["alt"] = {},
		["gloss"] = {alias_of = "t"},
		["g"] = {list = true},
		["id"] = {},
		["lit"] = {},
		["pos"] = {},
		["t"] = {},
		["tr"] = {},
		["ts"] = {},
		["sc"] = {},
		
		["nocap"] = {type = "boolean"}, -- should be processed in the template itself
		["notext"] = {type = "boolean"},
		["nocat"] = {type = "boolean"},
		["sort"] = {},
	}
	
	-- |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 = mw.text.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] = {}
		end
	end
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	
	local lang = m_languages.getByCode(args[1], 1)
	local sc = fetch_script(args["sc"])

	local parts = {}
	if not args["notext"] then
		table.insert(parts, frame.args["text"])
	end
	if args[2] or args["alt"] then
		if not args["notext"] then
			table.insert(parts, " ")
			table.insert(parts, frame.args["oftext"] or "of")
			table.insert(parts, " ")
		end
		table.insert(parts, require("Module:links").full_link(
			{
				lang = lang,
				sc = sc,
				term = args[2],
				alt = args["alt"],
				id = args["id"],
				tr = args["tr"],
				ts = args["ts"],
				genders = args["g"],
				gloss = args["t"],
				pos = args["pos"],
				lit = args["lit"],
			},
			"term",
			true))
	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 = mw.text.trim(cat)
			if cat ~= "" then
				table.insert(categories, lang:getCanonicalName() .. " " .. cat)
			end
			cat_number = (cat_number or 1) + 1
		end
	end
	if #categories > 0 then
		table.insert(
			parts,
			require("Module:utilities").format_categories(categories, lang, args["sort"]))
	end

	return table.concat(parts)
end


local function get_parsed_part(template, lang, args, terms, i)
	local term = terms[i]
	local alt = args["alt"][i]
	local id = args["id"][i]
	local sc = fetch_script(args["sc"][i])

	local tr = args["tr"][i]
	local ts = args["ts"][i]
	local gloss = args["t"][i]
	local pos = args["pos"][i]
	local lit = args["lit"][i]
	local g = args["g"][i]

	if not (term or alt or tr or ts) then
		track(template .. "/no term or alt or tr")
		return nil
	else
		return require("Module:links").full_link(
			{ term = term, alt = alt, id = id, lang = lang, sc = sc, tr = tr,
			ts = ts, gloss = gloss, pos = pos, lit = lit,
			genders = g and rsplit(g, ",") or {}
		}, "term", true)
	end
end


local function get_parsed_parts(template, lang, args, terms)
	local parts = {}

	-- Find the maximum index among any of the list parameters.
	local maxmaxindex = 0
	for k, v in pairs(args) do
		if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
			maxmaxindex = v.maxindex
		end
	end

	for index = 1, maxmaxindex do
		table.insert(parts, get_parsed_part(template, lang, args, terms, index))
	end
	
	return parts
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:compound/templates]].
function export.misc_variant_multiple_terms(frame)
	local params = {
		[1] = {required = true, default = "und"},
		[2] = {list = true, allow_holes = true},

		["t"] = {list = true, allow_holes = true, require_index = true},
		["gloss"] = {list = true, allow_holes = true, require_index = true, alias_of = "t"},
		["tr"] = {list = true, allow_holes = true, require_index = true},
		["ts"] = {list = true, allow_holes = true, require_index = true},
		["g"] = {list = true, allow_holes = true, require_index = true},
		["id"] = {list = true, allow_holes = true, require_index = true},
		["alt"] = {list = true, allow_holes = true, require_index = true},
		["lit"] = {list = true, allow_holes = true, require_index = true},
		["pos"] = {list = true, allow_holes = true, require_index = true},
		["sc"] = {list = true, allow_holes = true, require_index = true},

		["nocap"] = {type = "boolean"}, -- should be processed in the template itself
		["notext"] = {type = "boolean"},
		["nocat"] = {type = "boolean"},
		["sort"] = {},
	}

	local args = require("Module:parameters").process(frame:getParent().args, params)
	
	local lang = m_languages.getByCode(args[1], 1)

	local parts = {}
	if not args["notext"] then
		table.insert(parts, frame.args["text"])
	end
	if #args[2] > 0 or #args["alt"] > 0 then
		if not args["notext"] then
			table.insert(parts, " ")
			table.insert(parts, frame.args["oftext"] or "of")
			table.insert(parts, " ")
		end
		local formatted_terms = get_parsed_parts(mw.ustring.lower(
			-- Remove link and convert uppercase to lowercase to get an
			-- approximation of the original template name.
			rsub(rsub(frame.args["text"], "^%[%[.*|", ""), "%]%]$", "")),
			lang, args, args[2])
		table.insert(parts, require("Module:table").serialCommaJoin(formatted_terms))
	end
	if not args["nocat"] and frame.args["cat"] then
		local categories = {}
		table.insert(categories, lang:getCanonicalName() .. " " .. frame.args["cat"])
		table.insert(parts, require("Module:utilities").format_categories(categories, lang, args["sort"]))
	end

	return table.concat(parts)
end


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

		["title"] = {},
		["nocap"] = {type = "boolean"}, -- should be processed in the template itself
		["notext"] = {type = "boolean"},
		["nocat"] = {type = "boolean"},
		["sort"] = {},
	}

	if frame.args["title2_alias"] then
		params[2] = {alias_of = "title"}
	end
	
	local args = require("Module:parameters").process(frame:getParent().args, params)
	
	local lang = m_languages.getByCode(args[1], 1)

	local parts = {}
	if not args["notext"] then
		table.insert(parts, args["title"] or frame.args["text"])
	end
	if not args["nocat"] and frame.args["cat"] then
		local categories = {}
		table.insert(categories, lang:getCanonicalName() .. " " .. frame.args["cat"])
		table.insert(parts, require("Module:utilities").format_categories(categories, lang, args["sort"]))
	end

	return table.concat(parts)
end

return export