Module:names: Difference between revisions

From Linguifex
Jump to navigation Jump to search
No edit summary
(this and last edit) support diminutives of classes of names (described using addl=) rather than specific names
Line 1: Line 1:
local export = {}
local m_languages = require("Module:languages")
local m_languages = require("Module:languages")
local m_links = require("Module:links")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_utilities = require("Module:utilities")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local m_table = require("Module:table")
local en_utilities_module = "Module:en-utilities"
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local pron_qualifier_module = "Module:pron qualifier"


local export = {}
local enlang = m_languages.getByCode("en")
 
local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local u = m_str_utils.char


local enlang = m_languages.getByCode("en")
local function rsub(str, from, to)
    return (rsubn(str, from, to))
end


local rfind = mw.ustring.find
local TEMP_LESS_THAN = u(0xFFF2)
local rmatch = mw.ustring.match
local rsubn = mw.ustring.gsub
local rsplit = mw.text.split


local force_cat = false -- for testing
local force_cat = false -- for testing


-- version of rsubn() that discards all but the first return value
--[=[
local function rsub(term, foo, bar)
 
local retval = rsubn(term, foo, bar)
FIXME:
return retval
 
end
1. from=the Bible (DONE)
2. origin=18th century [DONE]
3. popular= (DONE)
4. varoftype= (DONE)
5. eqtype= [DONE]
6. dimoftype= [DONE]
7. from=de:Elisabeth (same language) (DONE)
8. blendof=, blendof2= [DONE]
9. varform, dimform [DONE]
10. from=English < Latin [DONE]
11. usage=rare -> categorize as rare?
12. dimeq= (also vareq=?) [DONE]
13. fromtype= [DONE]
14. <tr:...> and similar params [DONE]
]=]


-- Used in category code
-- Used in category code; name types which are full-word end-matching substrings of longer name types (e.g. "surnames"
-- of "male surnames", but not "male surnames" of "female surnames" because "male" only matches a part of the word
-- "female") should follow the longer name.
export.personal_name_types = {
export.personal_name_types = {
"surnames", "patronymics", "given names",
"male surnames", "female surnames", "common-gender surnames", "surnames",
"male given names", "female given names", "unisex given names",
"patronymics", "matronymics",
"diminutives of male given names", "diminutives of female given names",
"diminutives of unisex given names",
"augmentatives of male given names", "augmentatives of female given names",
"augmentatives of unisex given names"
}
}


export.personal_name_type_set = m_table.listToSet(export.personal_name_types)
export.given_name_genders = {
male = {type = "human"},
female = {type = "human"},
unisex = {type = "human", cat = {"male given names", "female given names", "unisex given names"}, article = "a"},
["unknown-gender"] = {type = "human", cat = {}, track = true},
animal = {type = "animal", track = true},
cat = {type = "animal"},
cow = {type = "animal"},
dog = {type = "animal"},
horse = {type = "animal"},
pig = {type = "animal"},
}
local function get_given_name_cats(gender, props)
local cats = props.cat
if not cats then
if props.type == "animal" then
cats = {gender .. " names"}
else
cats = {gender .. " given names"}
end
end
return cats
end
do
local function do_cat(cat)
if not export.personal_name_type_set[cat] then
export.personal_name_type_set[cat] = true
table.insert(export.personal_name_types, cat)
end
end
for gender, props in pairs(export.given_name_genders) do
local cats = get_given_name_cats(gender, props)
for _, cat in ipairs(cats) do
do_cat("diminutives of " .. cat)
do_cat("augmentatives of " .. cat)
do_cat(cat)
end
end
do_cat("given names")
end


local translit_name_type_list = {
local translit_name_type_list = {
Line 36: Line 105:
"patronymic"
"patronymic"
}
}
local translit_name_types = m_table.listToSet(translit_name_type_list)
local function track(page)
require("Module:debug").track("names/" .. page)
end


local param_mods = {"t", "alt", "tr", "ts", "pos", "lit", "id", "sc", "g", "q", "eq"}
local param_mod_set = m_table.listToSet(param_mods)


-- Get raw text, for use in computing the indefinite article. Use get_plaintext() in [[Module:utilities]] and also
-- remove parens that may surround qualifier or label text preceding a term.
local function get_rawtext(text)
text = m_utilities.get_plaintext(text)
text = text:gsub("[()%[%]]", "")
return text
end




Line 47: Line 123:
'Kunigunde<q:medieval, now rare>' or 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' where the modifying properties
'Kunigunde<q:medieval, now rare>' or 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' where the modifying properties
are contained in <...> specifications after the term. `term` is the full parameter value including any angle brackets
are contained in <...> specifications after the term. `term` is the full parameter value including any angle brackets
and colons; `pname` is the name of the parameter that this value comes from, for error purposes; `deflang` is a
and colons; `paramname` is the name of the parameter that this value comes from, for error purposes; `deflang` is a
language object used in the return value when the language isn't specified (e.g. in the examples 'Karlheinz' and
language object used in the return value when the language isn't specified (e.g. in the examples 'Karlheinz' and
'Kunigunde<q:medieval, now rare>' above); `allow_explicit_lang` indicates whether the language can be explicitly given
'Kunigunde<q:medieval, now rare>' above); `allow_explicit_lang` indicates whether the language can be explicitly given
(e.g. in the examples 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' above).
(e.g. in the examples 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' above).


Normally the return value is an object with properties '.term' (a terminfo object that can be passed to full_link() in
Normally the return value is a terminfo object that can be passed to full_link() in [[Module:links]]), additionally
[[Module:links]]) and '.q' (a qualifier). However, if `allow_multiple_terms` is given, multiple comma-separated names
with optional fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as the returned
can be given in `term`, and the return value is a list of objects of the form described just above.
terminfo object. However, if `allow_multiple_terms` is given, multiple comma-separated names can be given in `term`,
and the return value is a list of objects of the form described just above.
]=]
]=]
local function parse_term_with_annotations(term, pname, deflang, allow_explicit_lang, allow_multiple_terms)
local function parse_term_with_annotations(term, paramname, deflang, allow_explicit_lang, allow_multiple_terms)
local function parse_single_run_with_annotations(run)
local param_mods = require(parameter_utilities_module).construct_param_mods {
local function parse_err(msg)
{group = {"link", "l", "q", "ref"}},
error(msg .. ": " .. pname .. "= " .. table.concat(run))
{param = "eq", convert = function(eqval, parse_err)
return parse_term_with_annotations(eqval, paramname .. ".eq", enlang, false, "allow multiple terms")
end},
}
local function generate_obj(term, parse_err)
local termlang
if allow_explicit_lang then
local actual_term
actual_term, termlang = require(parse_interface_module).parse_term_with_lang {
term = term,
parse_err = parse_err,
paramname = paramname,
}
term = actual_term or term
end
end
if #run == 1 and run[1] == "" then
return {
error("Blank form for param '" .. pname .. "' not allowed")
term = term,
end
lang = termlang or deflang,
local termobj = {term = {}}
}
local lang, form = run[1]:match("^(.-):(.*)$")
if lang then
if not allow_explicit_lang then
parse_err("Explicit language '" .. lang .. "' not allowed for this parameter")
end
termobj.term.lang = m_languages.getByCode(lang, pname, "allow etym lang")
termobj.term.term = form
else
termobj.term.lang = deflang
termobj.term.term = run[1]
end
 
for i = 2, #run - 1, 2 do
if run[i + 1] ~= "" then
parse_err("Extraneous text '" .. run[i + 1] .. "' after modifier")
end
local modtext = run[i]:match("^<(.*)>$")
if not modtext then
parse_err("Internal error: Modifier '" .. modtext .. "' isn't surrounded by angle brackets")
end
local prefix, arg = modtext:match("^([a-z]+):(.*)$")
if not prefix then
parse_err("Modifier " .. run[i] .. " lacks a prefix, should begin with one of '" ..
table.concat(param_mods, ":', '") .. ":'")
end
if param_mod_set[prefix] then
local obj_to_set
if prefix == "q" or prefix == "eq" then
obj_to_set = termobj
else
obj_to_set = termobj.term
end
if obj_to_set[prefix] then
parse_err("Modifier '" .. prefix .. "' occurs twice, second occurrence " .. run[i])
end
if prefix == "t" then
termobj.term.gloss = arg
elseif prefix == "g" then
termobj.term.genders = rsplit(arg, ",")
elseif prefix == "sc" then
termobj.term.sc = require("Module:scripts").getByCode(arg, pname)
elseif prefix == "eq" then
termobj.eq = parse_term_with_annotations(arg, pname .. ".eq", enlang, false, "allow multiple terms")
else
obj_to_set[prefix] = arg
end
else
parse_err("Unrecognized prefix '" .. prefix .. "' in modifier " .. run[i])
end
end
return termobj
end
 
local iut = require("Module:inflection utilities")
local run = iut.parse_balanced_segment_run(term, "<", ">")
if allow_multiple_terms then
local comma_separated_runs = iut.split_alternating_runs(run, "%s*,%s*")
local termobjs = {}
for _, comma_separated_run in ipairs(comma_separated_runs) do
table.insert(termobjs, parse_single_run_with_annotations(comma_separated_run))
end
return termobjs
else
return parse_single_run_with_annotations(run)
end
end
return require(parse_interface_module).parse_inline_modifiers(term, {
param_mods = param_mods,
paramname = paramname,
generate_obj = generate_obj,
splitchar = allow_multiple_terms and "," or nil,
})
end
end


Line 135: Line 167:
--[=[
--[=[
Link a single term. If `do_language_link` is given and a given term's language is English, the link will be constructed
Link a single term. If `do_language_link` is given and a given term's language is English, the link will be constructed
using language_link() in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned
using language_link() in [[Module:links]]; otherwise, with full_link(). `termobj` is an object as returned by
by parse_term_with_annotations(), i.e. it contains fields '.term' (a terminfo structure suitable for passing to
parse_term_with_annotations(), i.e. it is suitable for passing to [[Module:links]] and additionally contains optional
full_link() or language_link()), optional '.q' (a qualifier) and optional '.eq' (a list of objects of the same form as
fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as `termobj`).
`termobj`).
]=]
]=]
local function link_one_term(termobj, do_language_link)
local function link_one_term(termobj, do_language_link)
termobj.term.lang = m_languages.getNonEtymological(termobj.term.lang)
local link
local link
if do_language_link and termobj.term.lang:getCode() == "en" then
if do_language_link and termobj.lang:getCode() == "en" then
link = m_links.language_link(termobj.term, nil, true)
link = m_links.language_link(termobj)
else
else
link = m_links.full_link(termobj.term, nil, true)
link = m_links.full_link(termobj)
end
end
if termobj.q then
if termobj.q and termobj.q[1] or termobj.qq and termobj.qq[1] or
link = require("Module:qualifier").format_qualifier(termobj.q) .. " " .. link
termobj.l and termobj.l[1] or termobj.ll and termobj.ll[1] or termobj.refs and termobj.refs[1] then
link = require(pron_qualifier_module).format_qualifiers {
lang = termobj.lang,
text = link,
q = termobj.q,
qq = termobj.qq,
l = termobj.l,
ll = termobj.ll,
refs = termobj.refs,
}
end
end
if termobj.eq then
if termobj.eq then
Line 170: Line 209:
`do_language_link` is given and a given term's language is English, the link will be constructed using language_link()
`do_language_link` is given and a given term's language is English, the link will be constructed using language_link()
in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned by
in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned by
parse_term_with_annotations(), i.e. it contains fields '.term' (a terminfo structure suitable for passing to full_link()
parse_term_with_annotations().
or language_link()), optional '.q' (a qualifier) and optional '.eq' (a list of objects of the same form as in `terms`).
]=]
]=]
local function join_terms(terms, include_langname, do_language_link, conj)
local function join_terms(terms, include_langname, do_language_link, conj)
Line 178: Line 216:
for _, termobj in ipairs(terms) do
for _, termobj in ipairs(terms) do
if include_langname and not langnametext then
if include_langname and not langnametext then
langnametext = termobj.term.lang:getCanonicalName() .. " "
langnametext = termobj.lang:getCanonicalName() .. " "
end
end
table.insert(links, link_one_term(termobj, do_language_link))
table.insert(links, link_one_term(termobj, do_language_link))
Line 211: Line 249:
end
end


for i, term in ipairs(args[pname]) do
local function process_one_term(term, i)
table.insert(termobjs, parse_term_with_annotations(term, pname .. (i == 1 and "" or i), lang, allow_explicit_lang))
for _, termobj in ipairs(parse_term_with_annotations(term, pname .. (i == 1 and "" or i), lang,
allow_explicit_lang, "allow multiple terms")) do
table.insert(termobjs, termobj)
end
end
 
if not args[pname] then
return "", 0
elseif type(args[pname]) == "table" then
for i, term in ipairs(args[pname]) do
process_one_term(term, i)
end
else
process_one_term(args[pname], 1)
end
end
return join_terms(termobjs, nil, do_language_link, conj), #termobjs
return join_terms(termobjs, nil, do_language_link, conj), #termobjs
Line 222: Line 273:
local lastlang = nil
local lastlang = nil
local last_eqseg = {}
local last_eqseg = {}
for i, term in ipairs(args.eq) do
local function process_one_term(term, i)
local termobj = parse_term_with_annotations(term, "eq" .. (i == 1 and "" or i), enlang, "allow explicit lang")
for _, termobj in ipairs(parse_term_with_annotations(term, "eq" .. (i == 1 and "" or i), enlang,
local termlang = termobj.term.lang:getCode()
"allow explicit lang", "allow multiple terms")) do
if lastlang and lastlang ~= termlang then
local termlang = termobj.lang:getCode()
if #last_eqseg > 0 then
if lastlang and lastlang ~= termlang then
table.insert(eqsegs, last_eqseg)
if #last_eqseg > 0 then
table.insert(eqsegs, last_eqseg)
end
last_eqseg = {}
end
end
last_eqseg = {}
lastlang = termlang
table.insert(last_eqseg, termobj)
end
end
if type(args.eq) == "table" then
for i, term in ipairs(args.eq) do
process_one_term(term, i)
end
end
lastlang = termlang
elseif type(args.eq) == "string" then
table.insert(last_eqseg, termobj)
process_one_term(args.eq, 1)
end
end
if #last_eqseg > 0 then
if #last_eqseg > 0 then
Line 253: Line 313:
local unrecognized = false
local unrecognized = false
local prefix, suffix
local prefix, suffix
if from == "surnames" then
if from == "surnames" or from == "given names" or from == "nicknames" or from == "place names" or from == "common nouns" or from == "month names" then
prefix = "transferred from the "
prefix = "transferred from the "
suffix = "surname"
suffix = from:gsub("s$", "")
table.insert(catparts, from)
table.insert(catparts, from)
elseif from == "place names" then
elseif from == "patronymics" or from == "matronymics" or from == "coinages" then
prefix = "transferred from the "
prefix = "originating "
suffix = "place name"
suffix = "as a " .. from:gsub("s$", "")
table.insert(catparts, from)
table.insert(catparts, from)
elseif from == "coinages" then
elseif from == "occupations" or from == "ethnonyms" then
prefix = "originating "
prefix = "originating "
suffix = "as a coinage"
suffix = "as an " .. from:gsub("s$", "")
table.insert(catparts, from)
table.insert(catparts, from)
elseif from == "the Bible" then
elseif from == "the Bible" then
Line 272: Line 332:
prefix = "from "
prefix = "from "
if from:find(":") then
if from:find(":") then
local termobj = parse_term_with_annotations(from, "from" .. (i == 1 and "" or i), lang, "allow explicit lang")
local termobj = parse_term_with_annotations(from, "from" .. (i == 1 and "" or i), lang,
"allow explicit lang")
local fromlangname = ""
local fromlangname = ""
if termobj.term.lang:getCode() ~= lang:getCode() then
if termobj.lang:getCode() ~= lang:getCode() then
-- If name is derived from another name in the same language, don't include lang name after text "from "
-- If name is derived from another name in the same language, don't include lang name after text
-- or create a category like "German male given names derived from German".
-- "from " or create a category like "German male given names derived from German".
local canonical_name = termobj.term.lang:getCanonicalName()
local canonical_name = termobj.lang:getCanonicalName()
fromlangname = canonical_name .. " "
fromlangname = canonical_name .. " "
table.insert(catparts, canonical_name)
table.insert(catparts, canonical_name)
end
end
termobj.term.lang = m_languages.getNonEtymological(termobj.term.lang)
suffix = fromlangname .. link_one_term(termobj)
suffix = fromlangname .. link_one_term(termobj)
elseif from:find(" languages$") then
local family = from:match("^(.*) languages$")
if require("Module:families").getByCanonicalName(family) then
table.insert(catparts, from)
else
unrecognized = true
end
suffix = "the " .. from
else
else
if m_languages.getByCanonicalName(from, nil, "allow etym") then
local family = from:match("^(.+) languages$") or
table.insert(catparts, from)
from:match("^.+ Languages$") or
from:match("^.+ [Ll]ects$")
if family then
if require("Module:families").getByCanonicalName(family) then
table.insert(catparts, from)
else
unrecognized = true
end
suffix = "the " .. from
else
else
unrecognized = true
if m_languages.getByCanonicalName(from, nil, "allow etym") then
table.insert(catparts, from)
else
unrecognized = true
end
suffix = from
end
end
suffix = from
end
end
end
if unrecognized then
track("unrecognized from")
track("unrecognized from/" .. from)
end
end
return prefix, suffix
return prefix, suffix
Line 304: Line 372:


local last_fromseg = nil
local last_fromseg = nil
while args.from[i] do
local put = require(parse_utilities_module)
local rawfrom = args.from[i]
local from_args = args.from or {}
local froms = rsplit(rawfrom, "%s+<%s+")
if type(from_args) == "string" then
if #froms == 1 then
from_args = {from_args}
local prefix, suffix = parse_from(froms[1])
end
if last_fromseg and (last_fromseg.has_multiple_froms or last_fromseg.prefix ~= prefix) then
while from_args[i] do
table.insert(fromsegs, last_fromseg)
-- We may have multiple comma-separated items, each of which may have multiple items separated by a
last_fromseg = nil
-- space-delimited < sign, each of which may have inline modifiers with embedded commas in them. To handle
end
-- this correctly, first replace space-delimited < signs with a special character, then split on balanced
if not last_fromseg then
-- <...> and [...] signs, then split on comma, then rejoin the stuff between commas. We will then split on
last_fromseg = {prefix = prefix, suffixes = {}}
-- TEMP_LESS_THAN (the replacement for space-delimited < signs) and reparse.
end
local rawfroms = rsub(from_args[i], "%s+<%s+", TEMP_LESS_THAN)
table.insert(last_fromseg.suffixes, suffix)
        local segments = put.parse_multi_delimiter_balanced_segment_run(rawfroms, {{"<", ">"}, {"[", "]"}})
else
        local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
if last_fromseg then
        for j, comma_separated_group in ipairs(comma_separated_groups) do
table.insert(fromsegs, last_fromseg)
        comma_separated_groups[j] = table.concat(comma_separated_group)
last_fromseg = nil
        end
end
for _, rawfrom in ipairs(comma_separated_groups) do
local first_suffixpart = ""
local froms = rsplit(rawfrom, TEMP_LESS_THAN)
local rest_suffixparts = {}
if #froms == 1 then
for j, from in ipairs(froms) do
local prefix, suffix = parse_from(froms[1])
local prefix, suffix = parse_from(from)
if last_fromseg and (last_fromseg.has_multiple_froms or last_fromseg.prefix ~= prefix) then
if j == 1 then
table.insert(fromsegs, last_fromseg)
first_suffixpart = prefix .. suffix
last_fromseg = nil
else
end
table.insert(rest_suffixparts, prefix .. suffix)
if not last_fromseg then
last_fromseg = {prefix = prefix, suffixes = {}}
end
table.insert(last_fromseg.suffixes, suffix)
else
if last_fromseg then
table.insert(fromsegs, last_fromseg)
last_fromseg = nil
end
local first_suffixpart = ""
local rest_suffixparts = {}
for j, from in ipairs(froms) do
local prefix, suffix = parse_from(from)
if j == 1 then
first_suffixpart = prefix .. suffix
else
table.insert(rest_suffixparts, prefix .. suffix)
end
end
end
local full_suffix = first_suffixpart .. " [in turn " .. table.concat(rest_suffixparts, ", in turn ") .. "]"
last_fromseg = {prefix = "", has_multiple_froms = true, suffixes = {full_suffix}}
end
end
local full_suffix = first_suffixpart .. " [in turn " .. table.concat(rest_suffixparts, ", in turn ") .. "]"
last_fromseg = {prefix = "", has_multiple_froms = true, suffixes = {full_suffix}}
end
end
i = i + 1
i = i + 1
Line 340: Line 425:
local fromtextsegs = {}
local fromtextsegs = {}
for _, fromseg in ipairs(fromsegs) do
for _, fromseg in ipairs(fromsegs) do
table.insert(fromtextsegs, fromseg.prefix .. m_table.serialCommaJoin(fromseg.suffixes, {conj = "or"}))
table.insert(fromtextsegs, fromseg.prefix .. m_table.serialCommaJoin(fromseg.suffixes, {conj = "or"}))
end
end
return m_table.serialCommaJoin(fromtextsegs, {conj = "or"}), catparts
return m_table.serialCommaJoin(fromtextsegs, {conj = "or"}), catparts
end
local function parse_given_name_genders(genderspec)
if export.given_name_genders[genderspec] then -- optimization
return {{
type = genderspec,
props = export.given_name_genders[genderspec],
}}, export.given_name_genders[genderspec].type == "animal"
end
local genders = {}
local is_animal = nil
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"l", "q", "ref"}},
{param = {"text", "article"}},
}
local function generate_obj(term, parse_err)
if not export.given_name_genders[term] then
local valid_genders = {}
for k, _ in pairs(export.given_name_genders) do
table.insert(valid_genders, k)
end
table.sort(valid_genders)
parse_err(("Unrecognized gender '%s': valid genders are %s"):format(
term, table.concat(valid_genders, ", ")))
end
return {
type = term,
props = export.given_name_genders[term],
}
end
local retval = require(parse_interface_module).parse_inline_modifiers(genderspec, {
param_mods = param_mods,
paramname = "2",
generate_obj = generate_obj,
splitchar = ",",
})
for _, spec in ipairs(retval) do
local this_is_animal = spec.props.type == "animal"
if is_animal == nil then
is_animal = this_is_animal
elseif is_animal ~= this_is_animal then
error("Can't mix animal and human genders")
end
end
return retval, is_animal
end
local function generate_given_name_genders(lang, genders)
local parts = {}
for _, spec in ipairs(genders) do
local text
if spec.text then
-- NOTE: This assumes no % sign in the gender type, which seems safe.
text = spec.text:gsub("%+", spec.type)
else
if spec.props.type == "animal" then
text = "[[" .. spec.type .. "]]"
else
text = spec.type
end
end
if spec.q and spec.q[1] or spec.qq and spec.qq[1] or spec.l and spec.l[1] or spec.ll and spec.ll[1] or
spec.refs and spec.refs[1] then
text = require(pron_qualifier_module).format_qualifiers {
lang = lang,
text = text,
q = spec.q,
qq = spec.qq,
l = spec.l,
ll = spec.ll,
refs = spec.refs,
raw = true,
}
end
table.insert(parts, text)
end
local retval = m_table.serialCommaJoin(parts, {conj = "or"})
local article = genders[1].article
if not article and not genders[1].text and not genders[1].q and not genders[1].l then
article = genders[1].props.article
end
if not article then
article = require(en_utilities_module).get_indefinite_article(get_rawtext(retval))
end
return retval, article
end
end


Line 352: Line 524:
local offset = compat and 0 or 1
local offset = compat and 0 or 1


local params = {
local lang_index = compat and "lang" or 1
[compat and "lang" or 1] = { required = true, default = "und" },
 
["gender"] = { default = "unknown-gender" },
local list = {list = true}
[1 + offset] = { alias_of = "gender", default = "unknown-gender" },
local args = require("Module:parameters").process(parent_args, {
-- second gender
[lang_index] = {required = true, type = "language", default = "und"},
["or"] = {},
["gender"] = {default = "unknown-gender"},
["usage"] = {},
[1 + offset] = {alias_of = "gender"},
["origin"] = {},
["usage"] = true,
["popular"] = {},
["origin"] = true,
["populartype"] = {},
["popular"] = true,
["meaning"] = { list = true },
["populartype"] = true,
["meaningtype"] = {},
["meaning"] = list,
["q"] = {},
["meaningtype"] = true,
["addl"] = true,
-- initial article: A or An
-- initial article: A or An
["A"] = {},
["A"] = true,
["sort"] = {},
["sort"] = true,
["from"] = { list = true },
["from"] = true,
[2 + offset] = { alias_of = "from", list = true },
[2 + offset] = {alias_of = "from"},
["fromtype"] = {},
["fromtype"] = true,
["xlit"] = { list = true },
["xlit"] = true,
["eq"] = { list = true },
["eq"] = true,
["eqtype"] = {},
["eqtype"] = true,
["varof"] = { list = true },
["varof"] = true,
["varoftype"] = {},
["varoftype"] = true,
["var"] = { alias_of = "varof", list = true },
["var"] = {alias_of = "varof"},
["vartype"] = { alias_of = "varoftype" },
["vartype"] = {alias_of = "varoftype"},
["varform"] = { list = true },
["varform"] = true,
["dimof"] = { list = true },
["varformtype"] = true,
["dimoftype"] = {},
["dimof"] = true,
["dim"] = { alias_of = "dimof", list = true },
["dimoftype"] = true,
["dimtype"] = { alias_of = "dimoftype" },
["dim"] = {alias_of = "dimof"},
["diminutive"] = { alias_of = "dimof", list = true },
["dimtype"] = {alias_of = "dimoftype"},
["diminutivetype"] = { alias_of = "dimoftype" },
["dimform"] = true,
["dimform"] = { list = true },
["dimformtype"] = true,
["blend"] = { list = true },
["augof"] = true,
["blendtype"] = {},
["augoftype"] = true,
["m"] = { list = true },
["aug"] = {alias_of = "augof"},
["mtype"] = {},
["augtype"] = {alias_of = "augoftype"},
["f"] = { list = true },
["augform"] = true,
["ftype"] = {},
["augformtype"] = true,
}
["clipof"] = true,
["clipoftype"] = true,
["blend"] = true,
["blendtype"] = true,
["m"] = true,
["mtype"] = true,
["f"] = true,
["ftype"] = true,
})


local args = require("Module:parameters").process(parent_args, params)
local textsegs = {}
local textsegs = {}
local lang = m_languages.getByCode(args[compat and "lang" or 1], compat and "lang" or 1)
local lang = args[lang_index]
local langcode = lang:getCode()


local function fetch_typetext(param)
local function fetch_typetext(param)
Line 403: Line 583:
end
end


local dimoftext, numdims = join_names(lang, args, "dimof")
local genders, is_animal = parse_given_name_genders(args.gender)
 
local dimoftext, numdimofs = join_names(lang, args, "dimof")
local augoftext, numaugofs = join_names(lang, args, "augof")
local xlittext = join_names(nil, args, "xlit")
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend", "and")
local blendtext = join_names(lang, args, "blend", "and")
local varoftext = join_names(lang, args, "varof")
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local ftext = join_names(lang, args, "f")
local varformtext, numvarforms = join_names(lang, args, "varform", ", ")
local varformtext, numvarforms = join_names(lang, args, "varform", ", ")
local dimformtext, numdimforms = join_names(lang, args, "dimform", ", ")
local dimformtext, numdimforms = join_names(lang, args, "dimform", ", ")
local augformtext, numaugforms = join_names(lang, args, "augform", ", ")
local meaningsegs = {}
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '"' .. meaning .. '"')
table.insert(meaningsegs, '' .. meaning .. '')
end
end
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "or"})
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "or"})
local eqtext = get_eqtext(args)
local eqtext = get_eqtext(args)


table.insert(textsegs, "<span class='use-with-mention'>")
local function ins(txt)
local dimtype = args.dimtype
table.insert(textsegs, txt)
local article = args.A or
end
dimtype and rfind(dimtype, "^[aeiouAEIOU]") and "An" or
local dimoftype = args.dimoftype
args.gender == "unknown-gender" and "An" or
local augoftype = args.augoftype
"A"
added_text = nil
if numdimofs > 0 then
added_text = (dimoftype and dimoftype .. " " or "") .. "[[diminutive]]" ..
(xlittext ~= "" and ", " .. xlittext .. "," or "") .. " of "
elseif numaugofs > 0 then
added_text = (augoftype and augoftype .. " " or "") .. "[[augmentative]]" ..
(xlittext ~= "" and ", " .. xlittext .. "," or "") .. " of "
end
force_plural = false
if added_text ~= nil then
if args.dimof == "-" then
dimoftext = ""
force_plural = true
else
added_text = added_text .. "the "
end
ins(added_text)
end
local article = args.A
if not article and textsegs[1] then
article = require(en_utilities_module).get_indefinite_article(textsegs[1])
end
if not is_animal then
local gendertext, gender_article = generate_given_name_genders(lang, genders)
article = article or gender_article
ins(gendertext)
ins(" ")
end
ins((numdimofs > 1 or numaugofs > 1 or force_plural) and "[[given name|given names]]" or "[[given name]]")
article = article or "a" -- if no article set yet, it's "a" based on "given name"
if langcode == "en" then
article = mw.getContentLanguage():ucfirst(article)
end


table.insert(textsegs, article .. " ")
if numdims > 0 then
table.insert(textsegs,
(dimtype and dimtype .. " " or "") ..
"[[wikt:diminutive|diminutive]]" ..
(xlittext ~= "" and ", " .. xlittext .. "," or "") ..
" of the ")
end
local genders = {}
table.insert(genders, args.gender)
table.insert(genders, args["or"])
table.insert(textsegs, table.concat(genders, " or ") .. " ")
table.insert(textsegs, numdims > 1 and "[[wikt:given name|given names]]" or
"[[wikt:given name|given name]]")
local need_comma = false
local need_comma = false
if numdims > 0 then
if numdimofs > 0 then
table.insert(textsegs, " " .. dimoftext)
ins(" " .. dimoftext)
need_comma = not is_animal
elseif numaugofs > 0 then
ins(" " .. augoftext)
need_comma = not is_animal
elseif xlittext ~= "" then
ins(", " .. xlittext)
need_comma = true
need_comma = true
elseif xlittext ~= "" then
end
table.insert(textsegs, ", " .. xlittext)
 
if is_animal then
if need_comma then
ins(",")
end
need_comma = true
need_comma = true
ins(" for ")
local gendertext, gender_article = generate_given_name_genders(lang, genders)
ins(gender_article)
ins(" ")
ins(gendertext)
end
end
local from_catparts = {}
local from_catparts = {}
if #args.from > 0 then
if args.from then
if need_comma then
if need_comma then
table.insert(textsegs, ",")
ins(",")
end
end
need_comma = true
need_comma = true
table.insert(textsegs, " " .. fetch_typetext("fromtype"))
ins(" " .. fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
m_table.insertIfNot(from_catparts, catpart)
end
end
table.insert(textsegs, textseg)
ins(textseg)
end
end
if meaningtext ~= "" then
if meaningtext ~= "" then
if need_comma then
if need_comma then
table.insert(textsegs, ",")
ins(",")
end
end
need_comma = true
need_comma = true
table.insert(textsegs, " " .. fetch_typetext("meaningtype") .. "meaning " .. meaningtext)
ins(" " .. fetch_typetext("meaningtype") .. "meaning " .. meaningtext)
end
end
if args.origin then
if args.origin then
if need_comma then
if need_comma then
table.insert(textsegs, ",")
ins(",")
end
end
need_comma = true
need_comma = true
table.insert(textsegs, " of " .. args.origin .. " origin")
ins(" of " .. args.origin .. " origin")
end
end
if args.usage then
if args.usage then
if need_comma then
if need_comma then
table.insert(textsegs, ",")
ins(",")
end
end
need_comma = true
need_comma = true
table.insert(textsegs, " of " .. args.usage .. " usage")
ins(" of " .. args.usage .. " usage")
end
end
if varoftext ~= "" then
if varoftext ~= "" then
table.insert(textsegs, ", " ..fetch_typetext("varoftype") .. "variant of " .. varoftext)
ins(", " ..fetch_typetext("varoftype") .. "variant of " .. varoftext)
end
if clipoftext ~= "" then
ins(", " .. fetch_typetext("clipoftype") .. "clipping of " .. clipoftext)
end
end
if blendtext ~= "" then
if blendtext ~= "" then
table.insert(textsegs, ", " .. fetch_typetext("blendtype") .. "blend of " .. blendtext)
ins(", " .. fetch_typetext("blendtype") .. "blend of " .. blendtext)
end
end
if args.popular then
if args.popular then
table.insert(textsegs, ", " .. fetch_typetext("populartype") .. "popular " .. args.popular)
ins(", " .. fetch_typetext("populartype") .. "popular " .. args.popular)
end
end
if mtext ~= "" then
if mtext ~= "" then
table.insert(textsegs, ", " .. fetch_typetext("mtype") .. "masculine equivalent " .. mtext)
ins(", " .. fetch_typetext("mtype") .. "masculine equivalent " .. mtext)
end
end
if ftext ~= "" then
if ftext ~= "" then
table.insert(textsegs, ", " .. fetch_typetext("ftype") .. "feminine equivalent " .. ftext)
ins(", " .. fetch_typetext("ftype") .. "feminine equivalent " .. ftext)
end
end
if eqtext ~= "" then
if eqtext ~= "" then
table.insert(textsegs, ", " .. fetch_typetext("eqtype") .. "equivalent to " .. eqtext)
ins(", " .. fetch_typetext("eqtype") .. "equivalent to " .. eqtext)
end
end
if args.q then
if args.addl then
table.insert(textsegs, ", " .. args.q)
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(" " .. args.addl:sub(2))
else
ins(", " .. args.addl)
end
end
end
if varformtext ~= "" then
if varformtext ~= "" then
table.insert(textsegs, "; variant form" .. (numvarforms > 1 and "s" or "") .. " " .. varformtext)
ins("; " .. fetch_typetext("varformtype") .. "variant form" .. (numvarforms > 1 and "s" or "") .. " " ..
varformtext)
end
end
if dimformtext ~= "" then
if dimformtext ~= "" then
table.insert(textsegs, "; diminutive form" .. (numdimforms > 1 and "s" or "") .. " " .. dimformtext)
ins("; " .. fetch_typetext("dimformtype") .. "diminutive form" .. (numdimforms > 1 and "s" or "") .. " " ..
dimformtext)
end
if augformtext ~= "" then
ins("; " .. fetch_typetext("augformtype") .. "augmentative form" .. (numaugforms > 1 and "s" or "") .. " " ..
augformtext)
end
end
table.insert(textsegs, "</span>")
textsegs = "<span class='use-with-mention'>" .. article .. " " .. table.concat(textsegs) .. "</span>"


local categories = {}
local categories = {}
local langname = lang:getCanonicalName() .. " "
local langname = lang:getCanonicalName() .. " "
local function insert_cats(isdim)
local function insert_cats(dimaugof)
if isdim == "" then
if dimaugof == "" and genders[1].props.type == "human" then
-- No category such as "English diminutives of given names"
-- No category such as "English diminutives of given names"
table.insert(categories, langname .. isdim .. "given names")
table.insert(categories, langname .. "given names")
end
end
local function insert_cats_gender(g)
local function insert_cat(cat)
if g == "unknown-gender" then
table.insert(categories, langname .. dimaugof .. cat)
return
for _, catpart in ipairs(from_catparts) do
table.insert(categories, langname .. dimaugof .. cat .. " from " .. catpart)
end
end
if g ~= "male" and g ~= "female" and g ~= "unisex" then
end
error("Unrecognized gender: " .. g)
for _, spec in ipairs(genders) do
local typ = spec.type
if spec.props.track then
track(typ)
end
end
if g == "unisex" then
local cats = get_given_name_cats(spec.type, spec.props)
insert_cats_gender("male")
for _, cat in ipairs(cats) do
insert_cats_gender("female")
insert_cat(cat)
end
table.insert(categories, langname .. isdim .. g .. " given names")
for _, catpart in ipairs(from_catparts) do
table.insert(categories, langname .. isdim .. g .. " given names from " .. catpart)
end
end
end
insert_cats_gender(args.gender)
if args["or"] then
insert_cats_gender(args["or"])
end
end
end
end
insert_cats("")
insert_cats("")
if numdims > 0 then
if numdimofs > 0 then
insert_cats("diminutives of ")
insert_cats("diminutives of ")
elseif numaugofs > 0 then
insert_cats("augmentatives of ")
end
end


return table.concat(textsegs, "") ..
return textsegs .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
end


-- The entry point for {{name translit}} and {{name respelling}}.
-- The entry point for {{surname}}, {{patronymic}} and {{matronymic}}.
function export.name_translit(frame)
function export.surname(frame)
    local iparams = {
local iargs = require("Module:parameters").process(frame.args, {
        ["desctext"] = {required = true},
["type"] = {required = true, set = {"surname", "patronymic", "matronymic"}},
    }
})
    local iargs = require("Module:parameters").process(frame.args, iparams)


local parent_args = frame:getParent().args
local parent_args = frame:getParent().args
local compat = parent_args.lang
local offset = compat and 0 or 1


local params = {
if parent_args.dot or parent_args.nodot then
[1] = { required = true, default = "en" },
error("dot= and nodot= are no longer supported in [[Template:" .. iargs.type .. "]] because a trailing " ..
[2] = { required = true, default = "ru" },
"period is no longer added by default; if you want it, add it explicitly after the template")
[3] = { list = true },
end
["type"] = { required = true, list = true, default = "patronymic" },
 
["alt"] = { list = true, allow_holes = true },
local lang_index = compat and "lang" or 1
["t"] = { list = true, allow_holes = true },
["gloss"] = { list = true, alias_of = "t", allow_holes = true },
local list = {list = true}
["tr"] = { list = true, allow_holes = true },
local gender_arg = iargs.type == "surname" and "g" or 1 + offset
["ts"] = { list = true, allow_holes = true },
local adj_arg = iargs.type == "surname" and 1 + offset or 2 + offset
["id"] = { list = true, allow_holes = true },
local args = require("Module:parameters").process(parent_args, {
["sc"] = { list = true, allow_holes = true },
[lang_index] = {required = true, type = "language", template_default = "und"},
["g"] = { list = true, allow_holes = true },
[gender_arg] = iargs.type == "surname" and true or {required = true, template_default = "unknown"}, -- gender(s)
["q"] = { list = true, allow_holes = true },
[adj_arg] = true, -- adjective/qualifier
["xlit"] = { list = true, allow_holes = true },
["usage"] = true,
["eq"] = { list = true, allow_holes = true },
["origin"] = true,
["dim"] = { type = "boolean" },
["popular"] = true,
["aug"] = { type = "boolean" },
["populartype"] = true,
["nocap"] = { type = "boolean" },
["meaning"] = list,
["sort"] = {},
["meaningtype"] = true,
}
["parent"] = true,
["addl"] = true,
-- initial article: by default A or An (English), a or an (otherwise)
["A"] = true,
["sort"] = true,
["from"] = true,
["fromtype"] = true,
["xlit"] = true,
["eq"] = true,
["eqtype"] = true,
["varof"] = true,
["varoftype"] = true,
["var"] = {alias_of = "varof"},
["vartype"] = {alias_of = "varoftype"},
["varform"] = true,
["varformtype"] = true,
["clipof"] = true,
["clipoftype"] = true,
["blend"] = true,
["blendtype"] = true,
["m"] = true,
["mtype"] = true,
["f"] = true,
["ftype"] = true,
["nocat"] = {type = "boolean"},
})
local args = require("Module:parameters").process(parent_args, params)
local textsegs = {}
local lang = m_languages.getByCode(args[1], 1)
local lang = args[lang_index]
local sources = {}
local langcode = lang:getCode()
local source_non_etym_langs = {}
 
for _, source in ipairs(rsplit(args[2], "%s*,%s*")) do
local function fetch_typetext(param)
local sourcelang = m_languages.getByCode(source, 2, "allow etym")
return args[param] and args[param] .. " " or ""
table.insert(sources, sourcelang)
table.insert(source_non_etym_langs, m_languages.getNonEtymological(sourcelang))
end
end


local nametypes = {}
local saw_male = false
for _, typearg in ipairs(args["type"]) do
local saw_female = false
for _, ty in ipairs(rsplit(typearg, "%s*,%s*")) do
local genders = {}
if not translit_name_types[ty] then
if args[gender_arg] then
local quoted_types = {}
for _, g in ipairs(require(parse_interface_module).split_on_comma(args[gender_arg])) do
for _, nametype in ipairs(translit_name_type_list) do
if g == "unknown" or g == "unknown gender" or g == "unknown-gender" or g == "?" then
table.insert(quoted_types, "'" .. nametype .. "'")
g = "unknown-gender"
end
track("unknown gender")
error("Unrecognized type '" .. ty .. "': It should be one of " ..
elseif g == "unisex" or g == "common gender" or g == "common-gender" or g == "c" then
m_table.serialCommaJoin(quoted_types, {conj = "or"}))
g = "common-gender"
saw_male = true
saw_female = true
elseif g == "m" or g == "male" then
g = "male"
saw_male = true
elseif g == "f" or g == "female" then
g = "female"
saw_female = true
else
error("Unrecognized gender: " .. g)
end
end
table.insert(nametypes, ty)
table.insert(genders, g)
end
end
 
local adj = args[adj_arg]
local xlittext = join_names(nil, args, "xlit")
local blendtext = join_names(lang, args, "blend", "and")
local varoftext = join_names(lang, args, "varof")
local clipoftext = join_names(lang, args, "clipof")
local mtext = join_names(lang, args, "m")
local ftext = join_names(lang, args, "f")
local parenttext = join_names(lang, args, "parent", nil, "allow explicit lang")
local varformtext, numvarforms = join_names(lang, args, "varform", ", ")
local meaningsegs = {}
for _, meaning in ipairs(args.meaning) do
table.insert(meaningsegs, '“' .. meaning .. '”')
end
if parenttext ~= "" then
local child = saw_male and not saw_female and "son" or saw_female and not saw_male and "daughter" or
"son/daughter"
table.insert(meaningsegs, ("“%s of %s”"):format(child, parenttext))
end
 
local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "or"})
local eqtext = get_eqtext(args)
 
local function ins(txt)
table.insert(textsegs, txt)
end
 
ins("<span class='use-with-mention'>")
 
-- If gender is supplied, it goes before the specified adjective in adj=. The only value of gender that uses "an" is
-- "unknown-gender" (note that "unisex" wouldn't use it but in any case we map "unisex" to "common-gender"). If gender
-- isn't supplied, look at the first letter of the value of adj= if supplied; otherwise, the article is always "a"
-- because the word "surname", "patronymic" or "matronymic" follows. Capitalize "A"/"An" if English.
local article
if args.A then
article = args.A
else
article = #genders > 0 and genders[1] == "unknown-gender" and "an" or
#genders == 0 and adj and require(en_utilities_module).get_indefinite_article(adj) or
"a"
if langcode == "en" then
article = mw.getContentLanguage():ucfirst(article)
end
end
ins(article .. " ")
 
if #genders > 0 then
ins(table.concat(genders, " or ") .. " ")
end
if adj then
ins(adj .. " ")
end
ins("[[" .. iargs.type .. "]]")
local need_comma = false
if xlittext ~= "" then
ins(", " .. xlittext)
need_comma = true
end
local from_catparts = {}
if args.from then
if need_comma then
ins(",")
end
need_comma = true
ins(" " .. fetch_typetext("fromtype"))
local textseg, this_catparts = get_fromtext(lang, args)
for _, catpart in ipairs(this_catparts) do
m_table.insertIfNot(from_catparts, catpart)
end
ins(textseg)
end
if meaningtext ~= "" then
if need_comma then
ins(",")
end
need_comma = true
ins(" " .. fetch_typetext("meaningtype") .. "meaning " .. meaningtext)
end
if args.origin then
if need_comma then
ins(",")
end
need_comma = true
ins(" of " .. args.origin .. " origin")
end
if args.usage then
if need_comma then
ins(",")
end
end
need_comma = true
ins(" of " .. args.usage .. " usage")
end
if varoftext ~= "" then
ins(", " ..fetch_typetext("varoftype") .. "variant of " .. varoftext)
end
if clipoftext ~= "" then
ins(", " .. fetch_typetext("clipoftype") .. "clipping of " .. clipoftext)
end
if blendtext ~= "" then
ins(", " .. fetch_typetext("blendtype") .. "blend of " .. blendtext)
end
if args.popular then
ins(", " .. fetch_typetext("populartype") .. "popular " .. args.popular)
end
if mtext ~= "" then
ins(", " .. fetch_typetext("mtype") .. "masculine equivalent " .. mtext)
end
if ftext ~= "" then
ins(", " .. fetch_typetext("ftype") .. "feminine equivalent " .. ftext)
end
if eqtext ~= "" then
ins(", " .. fetch_typetext("eqtype") .. "equivalent to " .. eqtext)
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(" " .. args.addl:sub(2))
else
ins(", " .. args.addl)
end
end
if varformtext ~= "" then
ins("; " .. fetch_typetext("varformtype") .. "variant form" ..
(numvarforms > 1 and "s" or "") .. " " .. varformtext)
end
ins("</span>")
local text = table.concat(textsegs, "")
if args.nocat then
return text
end
end


-- Find the maximum index among any of the list parameters, to determine how many names are given.
local categories = {}
local maxmaxindex = #args[3]
local langname = lang:getCanonicalName() .. " "
for k, v in pairs(args) do
local function insert_cats(g)
if type(v) == "table" and v.maxindex and v.maxindex > maxmaxindex then
g = g and g .. " " or ""
maxmaxindex = v.maxindex
table.insert(categories, langname .. g .. iargs.type .. "s")
for _, catpart in ipairs(from_catparts) do
table.insert(categories, langname .. g .. iargs.type .. "s from " .. catpart)
end
end
insert_cats(nil)
local function insert_cats_gender(g)
if g == "unknown-gender" then
return
end
if g == "common-gender" then
insert_cats_gender("male")
insert_cats_gender("female")
end
end
insert_cats(g)
end
for _, g in ipairs(genders) do
insert_cats_gender(g)
end
end


return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
-- The entry point for {{name translit}}, {{name respelling}}, {{name obor}} and {{foreign name}}.
function export.name_translit(frame)
local boolean = {type = "boolean"}
local iargs = require("Module:parameters").process(frame.args, {
["desctext"] = {required = true},
["obor"] = boolean,
["foreign_name"] = boolean,
})
local parent_args = frame:getParent().args
local params = {
[1] = {required = true, type = "language", template_default = "en"},
[2] = {required = true, type = "language", sublist = true, template_default = "ru"},
[3] = {list = true, allow_holes = true},
["type"] = {required = true, set = translit_name_type_list, sublist = true, default = "patronymic"},
["dim"] = boolean,
["aug"] = boolean,
["nocap"] = boolean,
["addl"] = true,
["sort"] = true,
["pagename"] = true,
}
local m_param_utils = require(parameter_utilities_module)
local param_mods = m_param_utils.construct_param_mods {
{group = {"link", "q", "l", "ref"}},
{param = {"xlit", "eq"}},
}
local names, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
params = params,
param_mods = param_mods,
raw_args = parent_args,
termarg = 3,
track_module = "names/name translit",
disallow_custom_separators = true,
-- Use the first source language as the language of the specified names.
lang = function(args) return args[2][1] end,
sc = "sc.default",
}
local lang = args[1]
local langcode = lang:getCode()
local sources = args[2]
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local textsegs = {}
local textsegs = {}
table.insert(textsegs, "<span class='use-with-mention'>")
local function ins(txt)
table.insert(textsegs, txt)
end
ins("<span class='use-with-mention'>")
local desctext = iargs.desctext
local desctext = iargs.desctext
if not args.nocap then
if langcode == "en" and not args.nocap then
desctext = mw.getContentLanguage():ucfirst(desctext)
desctext = mw.getContentLanguage():ucfirst(desctext)
end
end
table.insert(textsegs, desctext)
ins(desctext .. " ")
table.insert(textsegs, " of ")
if not iargs.foreign_name then
ins("of ")
end
local langsegs = {}
local langsegs = {}
for i, source in ipairs(sources) do
for i, source in ipairs(sources) do
local sourcename = source:getCanonicalName()
local sourcename = source:getCanonicalName()
local function get_source_link()
local function get_source_link()
if args[3][1] then
local term_to_link = names[1] and names[1].term or pagename
return m_links.language_link {
-- We link the language name to either the first specified name or the pagename, in the following
lang = source_non_etym_langs[i], term = args[3][1], alt = sourcename, tr = "-"
-- circumstances:
-- (1) More than one language was given along with at least one name; or
-- (2) We're handling {{foreign name}} or {{name obor}}, and no name was given.
-- The reason for (1) is that if more than one language was given, we want a link to the name
-- in each language, as the name that's displayed is linked only to the first specified language.
-- However, if only one language was given, linking the language to the name is redundant.
-- The reason for (2) is that {{foreign name}} is often used when the name in the destination language
-- is spelled the same as the name in the source language (e.g. [[Clinton]] or [[Obama]] in Italian),
-- and in that case no name will be explicitly specified but we still want a link to the name in the
-- source language. The reason we restrict this to {{foreign name}} or {{name obor}}, not to
-- {{name translit}} or {{name respelling}}, is that {{name translit}} and {{name respelling}} ought to be
-- used for names spelled differently in the destination language (either transliterated or respelled), so
-- assuming the pagename is the name in the source language is wrong.
if names[1] and #sources > 1 or (iargs.foreign_name or iargs.obor) and not names[1] then
return m_links.language_link{
lang = sources[i], term = term_to_link, alt = sourcename, tr = "-"
}
}
else
else
Line 633: Line 1,118:
end
end
if i == 1 then
if i == 1 and not iargs.foreign_name then
-- If at least one name is given, we say "A transliteration of the LANG surname FOO", linking LANG to FOO.
-- If at least one name is given, we say "A transliteration of the LANG surname FOO", linking LANG to FOO.
-- Otherwise we say "A transliteration of a LANG surname".
-- Otherwise we say "A transliteration of a LANG surname".
if maxmaxindex > 0 then
if names[1] then
table.insert(langsegs, "the " .. get_source_link())
table.insert(langsegs, "the " .. get_source_link())
else
else
table.insert(langsegs, require("Module:string utilities").add_indefinite_article(sourcename))
table.insert(langsegs, require(en_utilities_module).add_indefinite_article(sourcename))
end
end
else
else
Line 645: Line 1,130:
end
end
end
end
table.insert(textsegs, m_table.serialCommaJoin(langsegs, {conj = "or"}))
local langseg_text = m_table.serialCommaJoin(langsegs, {conj = "or"})
table.insert(textsegs, " " .. m_table.serialCommaJoin(nametypes))
local augdim_text
if args.dim then
if args.dim then
table.insert(textsegs, " [[wikt:diminutive|diminutive]]")
augdim_text = " [[diminutive]]"
elseif args.aug then
elseif args.aug then
table.insert(textsegs, " [[wikt:augmentative|augmentative]]")
augdim_text = " [[augmentative]]"
else
augdim_text = ""
end
local nametype_linked = {}
for _, nametype in ipairs(args["type"]) do
if nametype == "surname" or nametype == "patronymic" then
table.insert(nametype_linked, "[[" .. nametype .. "]]")
elseif nametype == "male given name" then
table.insert(nametype_linked, "male [[given name]]")
elseif nametype == "female given name" then
table.insert(nametype_linked, "female [[given name]]")
elseif nametype == "unisex given name" then
table.insert(nametype_linked, "unisex [[given name]]")
else
table.insert(nametype_linked, nametype)
end
end
local nametype_text = m_table.serialCommaJoin(nametype_linked) .. augdim_text
 
if not iargs.foreign_name then
ins(langseg_text .. " ")
ins(nametype_text)
if names[1] then
ins(" ")
end
else
ins(nametype_text)
ins(" in " .. langseg_text)
if names[1] then
ins(", ")
end
end
end
table.insert(textsegs, " ")
local names = {}


local linked_names = {}
local embedded_comma = false
local embedded_comma = false


for i = 1, maxmaxindex do
for _, name in ipairs(names) do
local sc = require("Module:scripts").getByCode(args["sc"][i], true)
local linked_name = m_links.full_link(name, "term")
if name.q and name.q[1] or name.qq and name.qq[1] or name.l and name.l[1] or name.ll and name.ll[1] or
local terminfo = {
name.refs and name.refs[1] then
lang = source_non_etym_langs[1], term = args[3][i], alt = args["alt"][i], id = args["id"][i], sc = sc,
linked_name = require(pron_qualifier_module).format_qualifiers {
tr = args["tr"][i], ts = args["ts"][i], gloss = args["t"][i],
lang = name.lang,
genders = args["g"][i] and rsplit(args["g"][i], ",") or {}
text = linked_name,
}
q = name.q,
local linked_term = m_links.full_link(terminfo, "term")
qq = name.qq,
if  args["q"][i] then
l = name.l,
linked_term = require("Module:qualifier").format_qualifier(args["q"][i]) .. " " .. linked_term
ll = name.ll,
refs = name.refs,
raw = true,
}
end
end
if args["xlit"][i] then
if name.xlit then
embedded_comma = true
embedded_comma = true
linked_term = linked_term .. ", " .. m_links.language_link({ lang = m_languages.getByCode("en"), term = args["xlit"][i] })
linked_name = linked_name .. ", " .. m_links.language_link { lang = enlang, term = name.xlit }
end
end
if args["eq"][i] then
if name.eq then
embedded_comma = true
embedded_comma = true
linked_term = linked_term .. ", equivalent to " .. m_links.language_link({ lang = m_languages.getByCode("en"), term = args["eq"][i] })
linked_name = linked_name .. ", equivalent to " .. m_links.language_link { lang = enlang, term = name.eq }
end
end
table.insert(names, linked_term)
table.insert(linked_names, linked_name)
end
end


if embedded_comma then
if embedded_comma then
table.insert(textsegs, table.concat(names, "; or of "))
ins(table.concat(linked_names, "; or of "))
else
else
table.insert(textsegs, m_table.serialCommaJoin(names, {conj = "or"}))
ins(m_table.serialCommaJoin(linked_names, {conj = "or"}))
end
if args.addl then
if args.addl:find("^;") then
ins(args.addl)
elseif args.addl:find("^_") then
ins(" " .. args.addl:sub(2))
else
ins(", " .. args.addl)
end
end
end
table.insert(textsegs, "</span>")
ins("</span>")


local categories = {}
local categories = {}
for _, nametype in ipairs(nametypes) do
local function inscat(cat)
local function insert_cats(isdim)
table.insert(categories, lang:getFullName() .. " " .. cat)
end
 
for _, nametype in ipairs(args.type) do
local function insert_cats(dimaugof)
local function insert_cats_type(ty)
local function insert_cats_type(ty)
if ty == "unisex given name" then
if ty == "unisex given name" then
Line 695: Line 1,226:
insert_cats_type("female given name")
insert_cats_type("female given name")
end
end
for i, source in ipairs(sources) do
for _, source in ipairs(sources) do
table.insert(categories, lang:getCode() .. ":" .. source:getCanonicalName() .. " " .. isdim .. ty .. "s")
inscat("renderings of " .. source:getCanonicalName() .. " " .. dimaugof .. ty .. "s")
local sourcelang = source_non_etym_langs[i]
inscat("terms derived from " .. source:getCanonicalName())
if source:getCode() ~= sourcelang:getCode() then
inscat("terms borrowed from " .. source:getCanonicalName())
if iargs.obor then
inscat("orthographic borrowings from " .. source:getCanonicalName())
end
if source:getCode() ~= source:getFullCode() then
-- etymology language
-- etymology language
table.insert(categories, lang:getCode() .. ":" .. sourcelang:getCanonicalName() .. " " .. isdim .. ty .. "s")
inscat("renderings of " .. source:getFullName() .. " " .. dimaugof .. ty .. "s")
end
end
end
end
Line 715: Line 1,250:
end
end


return table.concat(textsegs, "") ..
return table.concat(textsegs, "") .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end
end


return export
return export

Revision as of 03:43, 4 October 2025



local export = {}

local m_languages = require("Module:languages")
local m_links = require("Module:links")
local m_utilities = require("Module:utilities")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local en_utilities_module = "Module:en-utilities"
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
local parse_utilities_module = "Module:parse utilities"
local pron_qualifier_module = "Module:pron qualifier"

local enlang = m_languages.getByCode("en")

local rsubn = m_str_utils.gsub
local rsplit = m_str_utils.split
local u = m_str_utils.char

local function rsub(str, from, to)
    return (rsubn(str, from, to))
end

local TEMP_LESS_THAN = u(0xFFF2)

local force_cat = false -- for testing

--[=[

FIXME:

1. from=the Bible (DONE)
2. origin=18th century [DONE]
3. popular= (DONE)
4. varoftype= (DONE)
5. eqtype= [DONE]
6. dimoftype= [DONE]
7. from=de:Elisabeth (same language) (DONE)
8. blendof=, blendof2= [DONE]
9. varform, dimform [DONE]
10. from=English < Latin [DONE]
11. usage=rare -> categorize as rare?
12. dimeq= (also vareq=?) [DONE]
13. fromtype= [DONE]
14. <tr:...> and similar params [DONE]
]=]

-- Used in category code; name types which are full-word end-matching substrings of longer name types (e.g. "surnames"
-- of "male surnames", but not "male surnames" of "female surnames" because "male" only matches a part of the word
-- "female") should follow the longer name.
export.personal_name_types = {
	"male surnames", "female surnames", "common-gender surnames", "surnames",
	"patronymics", "matronymics",
}

export.personal_name_type_set = m_table.listToSet(export.personal_name_types)

export.given_name_genders = {
	male = {type = "human"},
	female = {type = "human"},
	unisex = {type = "human", cat = {"male given names", "female given names", "unisex given names"}, article = "a"},
	["unknown-gender"] = {type = "human", cat = {}, track = true},
	animal = {type = "animal", track = true},
	cat = {type = "animal"},
	cow = {type = "animal"},
	dog = {type = "animal"},
	horse = {type = "animal"},
	pig = {type = "animal"},
}

local function get_given_name_cats(gender, props)
	local cats = props.cat
	if not cats then
		if props.type == "animal" then
			cats = {gender .. " names"}
		else
			cats = {gender .. " given names"}
		end
	end
	return cats
end

do
	local function do_cat(cat)
		if not export.personal_name_type_set[cat] then
			export.personal_name_type_set[cat] = true
			table.insert(export.personal_name_types, cat)
		end
	end
	
	for gender, props in pairs(export.given_name_genders) do
		local cats = get_given_name_cats(gender, props)
		for _, cat in ipairs(cats) do
			do_cat("diminutives of " .. cat)
			do_cat("augmentatives of " .. cat)
			do_cat(cat)
		end
	end
	
	do_cat("given names")
end

local translit_name_type_list = {
	"surname", "male given name", "female given name", "unisex given name",
	"patronymic"
}
local function track(page)
	require("Module:debug").track("names/" .. page)
end


-- Get raw text, for use in computing the indefinite article. Use get_plaintext() in [[Module:utilities]] and also
-- remove parens that may surround qualifier or label text preceding a term.
local function get_rawtext(text)
	text = m_utilities.get_plaintext(text)
	text = text:gsub("[()%[%]]", "")
	return text
end


--[=[
Parse a term and associated properties. This works with parameters of the form 'Karlheinz' or
'Kunigunde<q:medieval, now rare>' or 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' where the modifying properties
are contained in <...> specifications after the term. `term` is the full parameter value including any angle brackets
and colons; `paramname` is the name of the parameter that this value comes from, for error purposes; `deflang` is a
language object used in the return value when the language isn't specified (e.g. in the examples 'Karlheinz' and
'Kunigunde<q:medieval, now rare>' above); `allow_explicit_lang` indicates whether the language can be explicitly given
(e.g. in the examples 'non:Óláfr' or 'ru:Фру́нзе<tr:Frúnzɛ><q:rare>' above).

Normally the return value is a terminfo object that can be passed to full_link() in [[Module:links]]), additionally
with optional fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as the returned
terminfo object. However, if `allow_multiple_terms` is given, multiple comma-separated names can be given in `term`,
and the return value is a list of objects of the form described just above.
]=]
local function parse_term_with_annotations(term, paramname, deflang, allow_explicit_lang, allow_multiple_terms)
	local param_mods = require(parameter_utilities_module).construct_param_mods {
		{group = {"link", "l", "q", "ref"}},
		{param = "eq", convert = function(eqval, parse_err)
			return parse_term_with_annotations(eqval, paramname .. ".eq", enlang, false, "allow multiple terms")
		end},
	}
	local function generate_obj(term, parse_err)
		local termlang
		if allow_explicit_lang then
			local actual_term
			actual_term, termlang = require(parse_interface_module).parse_term_with_lang {
				term = term,
				parse_err = parse_err,
				paramname = paramname,
			}
			term = actual_term or term
		end
		return {
			term = term,
			lang = termlang or deflang,
		}
	end
	return require(parse_interface_module).parse_inline_modifiers(term, {
		param_mods = param_mods,
		paramname = paramname,
		generate_obj = generate_obj,
		splitchar = allow_multiple_terms and "," or nil,
	})
end


--[=[
Link a single term. If `do_language_link` is given and a given term's language is English, the link will be constructed
using language_link() in [[Module:links]]; otherwise, with full_link(). `termobj` is an object as returned by
parse_term_with_annotations(), i.e. it is suitable for passing to [[Module:links]] and additionally contains optional
fields `.q`, `.qq`, `.l`, `.ll`, `.refs` and `.eq` (a list of objects of the same form as `termobj`).
]=]
local function link_one_term(termobj, do_language_link)
	local link
	if do_language_link and termobj.lang:getCode() == "en" then
		link = m_links.language_link(termobj)
	else
		link = m_links.full_link(termobj)
	end
	if termobj.q and termobj.q[1] or termobj.qq and termobj.qq[1] or
		termobj.l and termobj.l[1] or termobj.ll and termobj.ll[1] or termobj.refs and termobj.refs[1] then
		link = require(pron_qualifier_module).format_qualifiers {
			lang = termobj.lang,
			text = link,
			q = termobj.q,
			qq = termobj.qq,
			l = termobj.l,
			ll = termobj.ll,
			refs = termobj.refs,
		}
	end
	if termobj.eq then
		local eqtext = {}
		for _, eqobj in ipairs(termobj.eq) do
			table.insert(eqtext, link_one_term(eqobj, true))
		end
		link = link .. " [=" .. m_table.serialCommaJoin(eqtext, {conj = "or"}) .. "]"
	end
	return link
end


--[=[
Link the terms in `terms`, and join them using the conjunction in `conj` (defaulting to "or"). Joining is done using
serialCommaJoin() in [[Module:table]], so that e.g. two terms are joined as "TERM or TERM" while three terms are joined
as "TERM, TERM or TERM" with special CSS spans before the final "or" to allow an "Oxford comma" to appear if configured
appropriately. (However, if `conj` is the special value ", ", joining is done directly using that value.)
If `include_langname` is given, the language of the first term will be prepended to the joined terms. If
`do_language_link` is given and a given term's language is English, the link will be constructed using language_link()
in [[Module:links]]; otherwise, with full_link(). Each term in `terms` is an object as returned by
parse_term_with_annotations().
]=]
local function join_terms(terms, include_langname, do_language_link, conj)
	local links = {}
	local langnametext
	for _, termobj in ipairs(terms) do
		if include_langname and not langnametext then
			langnametext = termobj.lang:getCanonicalName() .. " "
		end
		table.insert(links, link_one_term(termobj, do_language_link))
	end
	local joined_terms
	if conj == ", " then
		joined_terms = table.concat(links, conj)
	else
		joined_terms = m_table.serialCommaJoin(links, {conj = conj or "or"})
	end
	return (langnametext or "") .. joined_terms
end


--[=[
Gather the parameters for multiple names and link each name using full_link() (for foreign names) or language_link()
(for English names), joining the names using serialCommaJoin() in [[Module:table]] with the conjunction `conj`
(defaulting to "or"). (However, if `conj` is the special value ", ", joining is done directly using that value.)
This can be used, for example, to fetch and join all the masculine equivalent names for a feminine given name. Each
name is specified using parameters beginning with `pname` in `args`, e.g. "m", "m2", "m3", etc. `lang` is a language
object specifying the language of the names (defaulting to English), for use in linking them. If `allow_explicit_lang`
is given, the language of the terms can be specified explicitly by prefixing a term with a language code, e.g.
'sv:Björn' or 'la:[[Nicolaus|Nīcolāī]]'. This function assumes that the parameters have already been parsed by
[[Module:parameters]] and gathered into lists, so that e.g. all "mN" parameters are in a list in args["m"].
]=]
local function join_names(lang, args, pname, conj, allow_explicit_lang)
	local termobjs = {}
	local do_language_link = false
	if not lang then
		lang = enlang
		do_language_link = true
	end

	local function process_one_term(term, i)
		for _, termobj in ipairs(parse_term_with_annotations(term, pname .. (i == 1 and "" or i), lang,
			allow_explicit_lang, "allow multiple terms")) do
			table.insert(termobjs, termobj)
		end
	end

	if not args[pname] then
		return "", 0
	elseif type(args[pname]) == "table" then
		for i, term in ipairs(args[pname]) do
			process_one_term(term, i)
		end
	else
		process_one_term(args[pname], 1)
	end
	return join_terms(termobjs, nil, do_language_link, conj), #termobjs
end


local function get_eqtext(args)
	local eqsegs = {}
	local lastlang = nil
	local last_eqseg = {}
	local function process_one_term(term, i)
		for _, termobj in ipairs(parse_term_with_annotations(term, "eq" .. (i == 1 and "" or i), enlang,
			"allow explicit lang", "allow multiple terms")) do
			local termlang = termobj.lang:getCode()
			if lastlang and lastlang ~= termlang then
				if #last_eqseg > 0 then
					table.insert(eqsegs, last_eqseg)
				end
				last_eqseg = {}
			end
			lastlang = termlang
			table.insert(last_eqseg, termobj)
		end
	end
	if type(args.eq) == "table" then
		for i, term in ipairs(args.eq) do
			process_one_term(term, i)
		end
	elseif type(args.eq) == "string" then
		process_one_term(args.eq, 1)
	end
	if #last_eqseg > 0 then
		table.insert(eqsegs, last_eqseg)
	end
	local eqtextsegs = {}
	for _, eqseg in ipairs(eqsegs) do
		table.insert(eqtextsegs, join_terms(eqseg, "include langname"))
	end
	return m_table.serialCommaJoin(eqtextsegs, {conj = "or"})
end


local function get_fromtext(lang, args)
	local catparts = {}
	local fromsegs = {}
	local i = 1

	local function parse_from(from)
		local unrecognized = false
		local prefix, suffix
		if from == "surnames" or from == "given names" or from == "nicknames" or from == "place names" or from == "common nouns" or from == "month names" then
			prefix = "transferred from the "
			suffix = from:gsub("s$", "")
			table.insert(catparts, from)
		elseif from == "patronymics" or from == "matronymics" or from == "coinages" then
			prefix = "originating "
			suffix = "as a " .. from:gsub("s$", "")
			table.insert(catparts, from)
		elseif from == "occupations" or from == "ethnonyms" then
			prefix = "originating "
			suffix = "as an " .. from:gsub("s$", "")
			table.insert(catparts, from)
		elseif from == "the Bible" then
			prefix = "originating "
			suffix = "from the Bible"
			table.insert(catparts, from)
		else
			prefix = "from "
			if from:find(":") then
				local termobj = parse_term_with_annotations(from, "from" .. (i == 1 and "" or i), lang,
					"allow explicit lang")
				local fromlangname = ""
				if termobj.lang:getCode() ~= lang:getCode() then
					-- If name is derived from another name in the same language, don't include lang name after text
					-- "from " or create a category like "German male given names derived from German".
					local canonical_name = termobj.lang:getCanonicalName()
					fromlangname = canonical_name .. " "
					table.insert(catparts, canonical_name)
				end
				suffix = fromlangname .. link_one_term(termobj)
			else
				local family = from:match("^(.+) languages$") or
					from:match("^.+ Languages$") or
					from:match("^.+ [Ll]ects$")
				if family then
					if require("Module:families").getByCanonicalName(family) then
						table.insert(catparts, from)
					else
						unrecognized = true
					end
					suffix = "the " .. from
				else
					if m_languages.getByCanonicalName(from, nil, "allow etym") then
						table.insert(catparts, from)
					else
						unrecognized = true
					end
					suffix = from
				end
			end
		end
		if unrecognized then
			track("unrecognized from")
			track("unrecognized from/" .. from)
		end
		return prefix, suffix
	end

	local last_fromseg = nil
	local put = require(parse_utilities_module)
	local from_args = args.from or {}
	if type(from_args) == "string" then
		from_args = {from_args}
	end
	while from_args[i] do
		-- We may have multiple comma-separated items, each of which may have multiple items separated by a
		-- space-delimited < sign, each of which may have inline modifiers with embedded commas in them. To handle
		-- this correctly, first replace space-delimited < signs with a special character, then split on balanced
		-- <...> and [...] signs, then split on comma, then rejoin the stuff between commas. We will then split on
		-- TEMP_LESS_THAN (the replacement for space-delimited < signs) and reparse.
		local rawfroms = rsub(from_args[i], "%s+<%s+", TEMP_LESS_THAN)
        local segments = put.parse_multi_delimiter_balanced_segment_run(rawfroms, {{"<", ">"}, {"[", "]"}})
        local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
        for j, comma_separated_group in ipairs(comma_separated_groups) do
        	comma_separated_groups[j] = table.concat(comma_separated_group)
        end
		for _, rawfrom in ipairs(comma_separated_groups) do
			local froms = rsplit(rawfrom, TEMP_LESS_THAN)
			if #froms == 1 then
				local prefix, suffix = parse_from(froms[1])
				if last_fromseg and (last_fromseg.has_multiple_froms or last_fromseg.prefix ~= prefix) then
					table.insert(fromsegs, last_fromseg)
					last_fromseg = nil
				end
				if not last_fromseg then
					last_fromseg = {prefix = prefix, suffixes = {}}
				end
				table.insert(last_fromseg.suffixes, suffix)
			else
				if last_fromseg then
					table.insert(fromsegs, last_fromseg)
					last_fromseg = nil
				end
				local first_suffixpart = ""
				local rest_suffixparts = {}
				for j, from in ipairs(froms) do
					local prefix, suffix = parse_from(from)
					if j == 1 then
						first_suffixpart = prefix .. suffix
					else
						table.insert(rest_suffixparts, prefix .. suffix)
					end
				end
				local full_suffix = first_suffixpart .. " [in turn " .. table.concat(rest_suffixparts, ", in turn ") .. "]"
				last_fromseg = {prefix = "", has_multiple_froms = true, suffixes = {full_suffix}}
			end
		end
		i = i + 1
	end
	table.insert(fromsegs, last_fromseg)
	local fromtextsegs = {}
	for _, fromseg in ipairs(fromsegs) do
		table.insert(fromtextsegs, fromseg.prefix .. m_table.serialCommaJoin(fromseg.suffixes, {conj = "or"}))
	end
	return m_table.serialCommaJoin(fromtextsegs, {conj = "or"}), catparts
end


local function parse_given_name_genders(genderspec)
	if export.given_name_genders[genderspec] then -- optimization
		return {{
			type = genderspec,
			props = export.given_name_genders[genderspec],
		}}, export.given_name_genders[genderspec].type == "animal"
	end
	local genders = {}
	local is_animal = nil
	local param_mods = require(parameter_utilities_module).construct_param_mods {
		{group = {"l", "q", "ref"}},
		{param = {"text", "article"}},
	}
	local function generate_obj(term, parse_err)
		if not export.given_name_genders[term] then
			local valid_genders = {}
			for k, _ in pairs(export.given_name_genders) do
				table.insert(valid_genders, k)
			end
			table.sort(valid_genders)
			parse_err(("Unrecognized gender '%s': valid genders are %s"):format(
				term, table.concat(valid_genders, ", ")))
		end
		return {
			type = term,
			props = export.given_name_genders[term],
		}
	end
	local retval = require(parse_interface_module).parse_inline_modifiers(genderspec, {
		param_mods = param_mods,
		paramname = "2",
		generate_obj = generate_obj,
		splitchar = ",",
	})
	for _, spec in ipairs(retval) do
		local this_is_animal = spec.props.type == "animal"
		if is_animal == nil then
			is_animal = this_is_animal
		elseif is_animal ~= this_is_animal then
			error("Can't mix animal and human genders")
		end
	end
	return retval, is_animal
end


local function generate_given_name_genders(lang, genders)
	local parts = {}
	for _, spec in ipairs(genders) do
		local text
		if spec.text then
			-- NOTE: This assumes no % sign in the gender type, which seems safe.
			text = spec.text:gsub("%+", spec.type)
		else
			if spec.props.type == "animal" then
				text = "[[" .. spec.type .. "]]"
			else
				text = spec.type
			end
		end
		if spec.q and spec.q[1] or spec.qq and spec.qq[1] or spec.l and spec.l[1] or spec.ll and spec.ll[1] or
			spec.refs and spec.refs[1] then
			text = require(pron_qualifier_module).format_qualifiers {
				lang = lang,
				text = text,
				q = spec.q,
				qq = spec.qq,
				l = spec.l,
				ll = spec.ll,
				refs = spec.refs,
				raw = true,
			}
		end
		table.insert(parts, text)
	end
	local retval = m_table.serialCommaJoin(parts, {conj = "or"})
	local article = genders[1].article
	if not article and not genders[1].text and not genders[1].q and not genders[1].l then
		article = genders[1].props.article
	end
	if not article then
		article = require(en_utilities_module).get_indefinite_article(get_rawtext(retval))
	end
	return retval, article
end


-- The entry point for {{given name}}.
function export.given_name(frame)
	local parent_args = frame:getParent().args
	local compat = parent_args.lang
	local offset = compat and 0 or 1

	local lang_index = compat and "lang" or 1

	local list = {list = true}
	local args = require("Module:parameters").process(parent_args, {
		[lang_index] = {required = true, type = "language", default = "und"},
		["gender"] = {default = "unknown-gender"},
		[1 + offset] = {alias_of = "gender"},
		["usage"] = true,
		["origin"] = true,
		["popular"] = true,
		["populartype"] = true,
		["meaning"] = list,
		["meaningtype"] = true,
		["addl"] = true,
		-- initial article: A or An
		["A"] = true,
		["sort"] = true,
		["from"] = true,
		[2 + offset] = {alias_of = "from"},
		["fromtype"] = true,
		["xlit"] = true,
		["eq"] = true,
		["eqtype"] = true,
		["varof"] = true,
		["varoftype"] = true,
		["var"] = {alias_of = "varof"},
		["vartype"] = {alias_of = "varoftype"},
		["varform"] = true,
		["varformtype"] = true,
		["dimof"] = true,
		["dimoftype"] = true,
		["dim"] = {alias_of = "dimof"},
		["dimtype"] = {alias_of = "dimoftype"},
		["dimform"] = true,
		["dimformtype"] = true,
		["augof"] = true,
		["augoftype"] = true,
		["aug"] = {alias_of = "augof"},
		["augtype"] = {alias_of = "augoftype"},
		["augform"] = true,
		["augformtype"] = true,
		["clipof"] = true,
		["clipoftype"] = true,
		["blend"] = true,
		["blendtype"] = true,
		["m"] = true,
		["mtype"] = true,
		["f"] = true,
		["ftype"] = true,
	})

	local textsegs = {}
	local lang = args[lang_index]
	local langcode = lang:getCode()

	local function fetch_typetext(param)
		return args[param] and args[param] .. " " or ""
	end

	local genders, is_animal = parse_given_name_genders(args.gender)

	local dimoftext, numdimofs = join_names(lang, args, "dimof")
	local augoftext, numaugofs = join_names(lang, args, "augof")
	local xlittext = join_names(nil, args, "xlit")
	local blendtext = join_names(lang, args, "blend", "and")
	local varoftext = join_names(lang, args, "varof")
	local clipoftext = join_names(lang, args, "clipof")
	local mtext = join_names(lang, args, "m")
	local ftext = join_names(lang, args, "f")
	local varformtext, numvarforms = join_names(lang, args, "varform", ", ")
	local dimformtext, numdimforms = join_names(lang, args, "dimform", ", ")
	local augformtext, numaugforms = join_names(lang, args, "augform", ", ")
	local meaningsegs = {}
	for _, meaning in ipairs(args.meaning) do
		table.insert(meaningsegs, '“' .. meaning .. '”')
	end
	local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "or"})
	local eqtext = get_eqtext(args)

	local function ins(txt)
		table.insert(textsegs, txt)
	end
	local dimoftype = args.dimoftype
	local augoftype = args.augoftype
	added_text = nil
	if numdimofs > 0 then
		added_text = (dimoftype and dimoftype .. " " or "") .. "[[diminutive]]" ..
					 (xlittext ~= "" and ", " .. xlittext .. "," or "") .. " of "
	elseif numaugofs > 0 then
		added_text = (augoftype and augoftype .. " " or "") .. "[[augmentative]]" ..
					 (xlittext ~= "" and ", " .. xlittext .. "," or "") .. " of "
	end
	force_plural = false
	if added_text ~= nil then
		if args.dimof == "-" then
			dimoftext = ""
			force_plural = true
		else
			added_text = added_text .. "the "
		end
		ins(added_text)
	end
	local article = args.A
	if not article and textsegs[1] then
		article = require(en_utilities_module).get_indefinite_article(textsegs[1])
	end
	if not is_animal then
		local gendertext, gender_article = generate_given_name_genders(lang, genders)
		article = article or gender_article
		ins(gendertext)
		ins(" ")
	end
	ins((numdimofs > 1 or numaugofs > 1 or force_plural) and "[[given name|given names]]" or "[[given name]]")
	article = article or "a" -- if no article set yet, it's "a" based on "given name"
	if langcode == "en" then
		article = mw.getContentLanguage():ucfirst(article)
	end

	local need_comma = false
	if numdimofs > 0 then
		ins(" " .. dimoftext)
		need_comma = not is_animal
	elseif numaugofs > 0 then
		ins(" " .. augoftext)
		need_comma = not is_animal
	elseif xlittext ~= "" then
		ins(", " .. xlittext)
		need_comma = true
	end

	if is_animal then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" for ")
		local gendertext, gender_article = generate_given_name_genders(lang, genders)
		ins(gender_article)
		ins(" ")
		ins(gendertext)
	end

	local from_catparts = {}
	if args.from then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" " .. fetch_typetext("fromtype"))
		local textseg, this_catparts = get_fromtext(lang, args)
		for _, catpart in ipairs(this_catparts) do
			m_table.insertIfNot(from_catparts, catpart)
		end
		ins(textseg)
	end
	
	if meaningtext ~= "" then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" " .. fetch_typetext("meaningtype") .. "meaning " .. meaningtext)
	end
	if args.origin then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" of " .. args.origin .. " origin")
	end
	if args.usage then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" of " .. args.usage .. " usage")
	end
	if varoftext ~= "" then
		ins(", " ..fetch_typetext("varoftype") .. "variant of " .. varoftext)
	end
	if clipoftext ~= "" then
		ins(", " .. fetch_typetext("clipoftype") .. "clipping of " .. clipoftext)
	end
	if blendtext ~= "" then
		ins(", " .. fetch_typetext("blendtype") .. "blend of " .. blendtext)
	end
	if args.popular then
		ins(", " .. fetch_typetext("populartype") .. "popular " .. args.popular)
	end
	if mtext ~= "" then
		ins(", " .. fetch_typetext("mtype") .. "masculine equivalent " .. mtext)
	end
	if ftext ~= "" then
		ins(", " .. fetch_typetext("ftype") .. "feminine equivalent " .. ftext)
	end
	if eqtext ~= "" then
		ins(", " .. fetch_typetext("eqtype") .. "equivalent to " .. eqtext)
	end
	if args.addl then
		if args.addl:find("^;") then
			ins(args.addl)
		elseif args.addl:find("^_") then
			ins(" " .. args.addl:sub(2))
		else
			ins(", " .. args.addl)
		end
	end
	if varformtext ~= "" then
		ins("; " .. fetch_typetext("varformtype") .. "variant form" .. (numvarforms > 1 and "s" or "") .. " " ..
			varformtext)
	end
	if dimformtext ~= "" then
		ins("; " .. fetch_typetext("dimformtype") .. "diminutive form" .. (numdimforms > 1 and "s" or "") .. " " ..
			dimformtext)
	end
	if augformtext ~= "" then
		ins("; " .. fetch_typetext("augformtype") .. "augmentative form" .. (numaugforms > 1 and "s" or "") .. " " ..
			augformtext)
	end
	textsegs = "<span class='use-with-mention'>" .. article .. " " .. table.concat(textsegs) .. "</span>"

	local categories = {}
	local langname = lang:getCanonicalName() .. " "
	local function insert_cats(dimaugof)
		if dimaugof == "" and genders[1].props.type == "human" then
			-- No category such as "English diminutives of given names"
			table.insert(categories, langname .. "given names")
		end
		local function insert_cat(cat)
			table.insert(categories, langname .. dimaugof .. cat)
			for _, catpart in ipairs(from_catparts) do
				table.insert(categories, langname .. dimaugof .. cat .. "  from " .. catpart)
			end
		end
		for _, spec in ipairs(genders) do
			local typ = spec.type
			if spec.props.track then
				track(typ)
			end
			local cats = get_given_name_cats(spec.type, spec.props)
			for _, cat in ipairs(cats) do
				insert_cat(cat)
			end
		end
	end
	insert_cats("")
	if numdimofs > 0 then
		insert_cats("diminutives of ")
	elseif numaugofs > 0 then
		insert_cats("augmentatives of ")
	end

	return textsegs .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end

-- The entry point for {{surname}}, {{patronymic}} and {{matronymic}}.
function export.surname(frame)
	local iargs = require("Module:parameters").process(frame.args, {
		["type"] = {required = true, set = {"surname", "patronymic", "matronymic"}},
	})

	local parent_args = frame:getParent().args
	local compat = parent_args.lang
	local offset = compat and 0 or 1

	if parent_args.dot or parent_args.nodot then
		error("dot= and nodot= are no longer supported in [[Template:" .. iargs.type .. "]] because a trailing " ..
			"period is no longer added by default; if you want it, add it explicitly after the template")
	end

	local lang_index = compat and "lang" or 1
	
	local list = {list = true}
	local gender_arg = iargs.type == "surname" and "g" or 1 + offset
	local adj_arg = iargs.type == "surname" and 1 + offset or 2 + offset
	local args = require("Module:parameters").process(parent_args, {
		[lang_index] = {required = true, type = "language", template_default = "und"},
		[gender_arg] = iargs.type == "surname" and true or {required = true, template_default = "unknown"}, -- gender(s)
		[adj_arg] = true, -- adjective/qualifier
		["usage"] = true,
		["origin"] = true,
		["popular"] = true,
		["populartype"] = true,
		["meaning"] = list,
		["meaningtype"] = true,
		["parent"] = true,
		["addl"] = true,
		-- initial article: by default A or An (English), a or an (otherwise)
		["A"] = true,
		["sort"] = true,
		["from"] = true,
		["fromtype"] = true,
		["xlit"] = true,
		["eq"] = true,
		["eqtype"] = true,
		["varof"] = true,
		["varoftype"] = true,
		["var"] = {alias_of = "varof"},
		["vartype"] = {alias_of = "varoftype"},
		["varform"] = true,
		["varformtype"] = true,
		["clipof"] = true,
		["clipoftype"] = true,
		["blend"] = true,
		["blendtype"] = true,
		["m"] = true,
		["mtype"] = true,
		["f"] = true,
		["ftype"] = true,
		["nocat"] = {type = "boolean"},
	})
	
	local textsegs = {}
	local lang = args[lang_index]
	local langcode = lang:getCode()

	local function fetch_typetext(param)
		return args[param] and args[param] .. " " or ""
	end

	local saw_male = false
	local saw_female = false
	local genders = {}
	if args[gender_arg] then
		for _, g in ipairs(require(parse_interface_module).split_on_comma(args[gender_arg])) do
			if g == "unknown" or g == "unknown gender" or g == "unknown-gender" or g == "?" then
				g = "unknown-gender"
				track("unknown gender")
			elseif g == "unisex" or g == "common gender" or g == "common-gender" or g == "c" then
				g = "common-gender"
				saw_male = true
				saw_female = true
			elseif g == "m" or g == "male" then
				g = "male"
				saw_male = true
			elseif g == "f" or g == "female" then
				g = "female"
				saw_female = true
			else
				error("Unrecognized gender: " .. g)
			end
			table.insert(genders, g)
		end
	end

	local adj = args[adj_arg]
	local xlittext = join_names(nil, args, "xlit")
	local blendtext = join_names(lang, args, "blend", "and")
	local varoftext = join_names(lang, args, "varof")
	local clipoftext = join_names(lang, args, "clipof")
	local mtext = join_names(lang, args, "m")
	local ftext = join_names(lang, args, "f")
	local parenttext = join_names(lang, args, "parent", nil, "allow explicit lang")
	local varformtext, numvarforms = join_names(lang, args, "varform", ", ")
	local meaningsegs = {}
	for _, meaning in ipairs(args.meaning) do
		table.insert(meaningsegs, '“' .. meaning .. '”')
	end
	if parenttext ~= "" then
		local child = saw_male and not saw_female and "son" or saw_female and not saw_male and "daughter" or
			"son/daughter"
		table.insert(meaningsegs, ("“%s of %s”"):format(child, parenttext))
	end

	local meaningtext = m_table.serialCommaJoin(meaningsegs, {conj = "or"})
	local eqtext = get_eqtext(args)

	local function ins(txt)
		table.insert(textsegs, txt)
	end

	ins("<span class='use-with-mention'>")

	-- If gender is supplied, it goes before the specified adjective in adj=. The only value of gender that uses "an" is
	-- "unknown-gender" (note that "unisex" wouldn't use it but in any case we map "unisex" to "common-gender"). If gender
	-- isn't supplied, look at the first letter of the value of adj= if supplied; otherwise, the article is always "a"
	-- because the word "surname", "patronymic" or "matronymic" follows. Capitalize "A"/"An" if English.
	local article
	if args.A then
		article = args.A
	else
		article = #genders > 0 and genders[1] == "unknown-gender" and "an" or
			#genders == 0 and adj and require(en_utilities_module).get_indefinite_article(adj) or
			"a"
		if langcode == "en" then
			article = mw.getContentLanguage():ucfirst(article)
		end
	end
	ins(article .. " ")

	if #genders > 0 then
		ins(table.concat(genders, " or ") .. " ")
	end
	if adj then
		ins(adj .. " ")
	end
	ins("[[" .. iargs.type .. "]]")
	local need_comma = false
	if xlittext ~= "" then
		ins(", " .. xlittext)
		need_comma = true
	end
	local from_catparts = {}
	if args.from then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" " .. fetch_typetext("fromtype"))
		local textseg, this_catparts = get_fromtext(lang, args)
		for _, catpart in ipairs(this_catparts) do
			m_table.insertIfNot(from_catparts, catpart)
		end
		ins(textseg)
	end
	
	if meaningtext ~= "" then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" " .. fetch_typetext("meaningtype") .. "meaning " .. meaningtext)
	end
	if args.origin then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" of " .. args.origin .. " origin")
	end
	if args.usage then
		if need_comma then
			ins(",")
		end
		need_comma = true
		ins(" of " .. args.usage .. " usage")
	end
	if varoftext ~= "" then
		ins(", " ..fetch_typetext("varoftype") .. "variant of " .. varoftext)
	end
	if clipoftext ~= "" then
		ins(", " .. fetch_typetext("clipoftype") .. "clipping of " .. clipoftext)
	end
	if blendtext ~= "" then
		ins(", " .. fetch_typetext("blendtype") .. "blend of " .. blendtext)
	end
	if args.popular then
		ins(", " .. fetch_typetext("populartype") .. "popular " .. args.popular)
	end
	if mtext ~= "" then
		ins(", " .. fetch_typetext("mtype") .. "masculine equivalent " .. mtext)
	end
	if ftext ~= "" then
		ins(", " .. fetch_typetext("ftype") .. "feminine equivalent " .. ftext)
	end
	if eqtext ~= "" then
		ins(", " .. fetch_typetext("eqtype") .. "equivalent to " .. eqtext)
	end
	if args.addl then
		if args.addl:find("^;") then
			ins(args.addl)
		elseif args.addl:find("^_") then
			ins(" " .. args.addl:sub(2))
		else
			ins(", " .. args.addl)
		end
	end
	if varformtext ~= "" then
		ins("; " .. fetch_typetext("varformtype") .. "variant form" ..
			(numvarforms > 1 and "s" or "") .. " " .. varformtext)
	end
	ins("</span>")

	local text = table.concat(textsegs, "")
	if args.nocat then
		return text
	end

	local categories = {}
	local langname = lang:getCanonicalName() .. " "
	local function insert_cats(g)
		g = g and g .. " " or ""
		table.insert(categories, langname .. g .. iargs.type .. "s")
		for _, catpart in ipairs(from_catparts) do
			table.insert(categories, langname .. g .. iargs.type .. "s from " .. catpart)
		end
	end
	insert_cats(nil)
	local function insert_cats_gender(g)
		if g == "unknown-gender" then
			return
		end
		if g == "common-gender" then
			insert_cats_gender("male")
			insert_cats_gender("female")
		end
		insert_cats(g)
	end
	for _, g in ipairs(genders) do
		insert_cats_gender(g)
	end

	return text .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end

-- The entry point for {{name translit}}, {{name respelling}}, {{name obor}} and {{foreign name}}.
function export.name_translit(frame)
	local boolean = {type = "boolean"}

	local iargs = require("Module:parameters").process(frame.args, {
		["desctext"] = {required = true},
		["obor"] = boolean,
		["foreign_name"] = boolean,
	})

	local parent_args = frame:getParent().args
	local params = {
		[1] = {required = true, type = "language", template_default = "en"},
		[2] = {required = true, type = "language", sublist = true, template_default = "ru"},
		[3] = {list = true, allow_holes = true},
		["type"] = {required = true, set = translit_name_type_list, sublist = true, default = "patronymic"},
		["dim"] = boolean,
		["aug"] = boolean,
		["nocap"] = boolean,
		["addl"] = true,
		["sort"] = true,
		["pagename"] = true,
	}

	local m_param_utils = require(parameter_utilities_module)

	local param_mods = m_param_utils.construct_param_mods {
		{group = {"link", "q", "l", "ref"}},
		{param = {"xlit", "eq"}},
	}

	local names, args = m_param_utils.parse_list_with_inline_modifiers_and_separate_params {
		params = params,
		param_mods = param_mods,
		raw_args = parent_args,
		termarg = 3,
		track_module = "names/name translit",
		disallow_custom_separators = true,
		-- Use the first source language as the language of the specified names.
		lang = function(args) return args[2][1] end,
		sc = "sc.default",
	}

	local lang = args[1]
	local langcode = lang:getCode()
	local sources = args[2]
	local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
	
	local textsegs = {}
	local function ins(txt)
		table.insert(textsegs, txt)
	end
	ins("<span class='use-with-mention'>")
	local desctext = iargs.desctext
	if langcode == "en" and not args.nocap then
		desctext = mw.getContentLanguage():ucfirst(desctext)
	end
	ins(desctext .. " ")
	if not iargs.foreign_name then
		ins("of ")
	end
	local langsegs = {}
	for i, source in ipairs(sources) do
		local sourcename = source:getCanonicalName()
		local function get_source_link()
			local term_to_link = names[1] and names[1].term or pagename
			-- We link the language name to either the first specified name or the pagename, in the following
			-- circumstances:
			-- (1) More than one language was given along with at least one name; or
			-- (2) We're handling {{foreign name}} or {{name obor}}, and no name was given.
			-- The reason for (1) is that if more than one language was given, we want a link to the name
			-- in each language, as the name that's displayed is linked only to the first specified language.
			-- However, if only one language was given, linking the language to the name is redundant.
			-- The reason for (2) is that {{foreign name}} is often used when the name in the destination language
			-- is spelled the same as the name in the source language (e.g. [[Clinton]] or [[Obama]] in Italian),
			-- and in that case no name will be explicitly specified but we still want a link to the name in the
			-- source language. The reason we restrict this to {{foreign name}} or {{name obor}}, not to
			-- {{name translit}} or {{name respelling}}, is that {{name translit}} and {{name respelling}} ought to be
			-- used for names spelled differently in the destination language (either transliterated or respelled), so
			-- assuming the pagename is the name in the source language is wrong.
			if names[1] and #sources > 1 or (iargs.foreign_name or iargs.obor) and not names[1] then
				return m_links.language_link{
					lang = sources[i], term = term_to_link, alt = sourcename, tr = "-"
				}
			else
				return sourcename
			end
		end
		
		if i == 1 and not iargs.foreign_name then
			-- If at least one name is given, we say "A transliteration of the LANG surname FOO", linking LANG to FOO.
			-- Otherwise we say "A transliteration of a LANG surname".
			if names[1] then
				table.insert(langsegs, "the " .. get_source_link())
			else
				table.insert(langsegs, require(en_utilities_module).add_indefinite_article(sourcename))
			end
		else
			table.insert(langsegs, get_source_link())
		end
	end
	local langseg_text = m_table.serialCommaJoin(langsegs, {conj = "or"})
	local augdim_text
	if args.dim then
		augdim_text = " [[diminutive]]"
	elseif args.aug then
		augdim_text = " [[augmentative]]"
	else
		augdim_text = ""
	end
	local nametype_linked = {}
	for _, nametype in ipairs(args["type"]) do
		if nametype == "surname" or nametype == "patronymic" then
			table.insert(nametype_linked, "[[" .. nametype .. "]]")
		elseif nametype == "male given name" then
			table.insert(nametype_linked, "male [[given name]]")
		elseif nametype == "female given name" then
			table.insert(nametype_linked, "female [[given name]]")
		elseif nametype == "unisex given name" then
			table.insert(nametype_linked, "unisex [[given name]]")
		else
			table.insert(nametype_linked, nametype)
		end
	end
	local nametype_text = m_table.serialCommaJoin(nametype_linked) .. augdim_text

	if not iargs.foreign_name then
		ins(langseg_text .. " ")
		ins(nametype_text)
		if names[1] then
			ins(" ")
		end
	else
		ins(nametype_text)
		ins(" in " .. langseg_text)
		if names[1] then
			ins(", ")
		end
	end

	local linked_names = {}
	local embedded_comma = false

	for _, name in ipairs(names) do
		local linked_name = m_links.full_link(name, "term")
		if name.q and name.q[1] or name.qq and name.qq[1] or name.l and name.l[1] or name.ll and name.ll[1] or
			name.refs and name.refs[1] then
			linked_name = require(pron_qualifier_module).format_qualifiers {
				lang = name.lang,
				text = linked_name,
				q = name.q,
				qq = name.qq,
				l = name.l,
				ll = name.ll,
				refs = name.refs,
				raw = true,
			}
		end
		if name.xlit then
			embedded_comma = true
			linked_name = linked_name .. ", " .. m_links.language_link { lang = enlang, term = name.xlit }
		end
		if name.eq then
			embedded_comma = true
			linked_name = linked_name .. ", equivalent to " .. m_links.language_link { lang = enlang, term = name.eq }
		end
		table.insert(linked_names, linked_name)
	end

	if embedded_comma then
		ins(table.concat(linked_names, "; or of "))
	else
		ins(m_table.serialCommaJoin(linked_names, {conj = "or"}))
	end
	if args.addl then
		if args.addl:find("^;") then
			ins(args.addl)
		elseif args.addl:find("^_") then
			ins(" " .. args.addl:sub(2))
		else
			ins(", " .. args.addl)
		end
	end
	ins("</span>")

	local categories = {}
	local function inscat(cat)
		table.insert(categories, lang:getFullName() .. " " .. cat)
	end

	for _, nametype in ipairs(args.type) do
		local function insert_cats(dimaugof)
			local function insert_cats_type(ty)
				if ty == "unisex given name" then
					insert_cats_type("male given name")
					insert_cats_type("female given name")
				end
				for _, source in ipairs(sources) do
					inscat("renderings of " .. source:getCanonicalName() .. " " .. dimaugof .. ty .. "s")
					inscat("terms derived from " .. source:getCanonicalName())
					inscat("terms borrowed from " .. source:getCanonicalName())
					if iargs.obor then
						inscat("orthographic borrowings from " .. source:getCanonicalName())
					end
					if source:getCode() ~= source:getFullCode() then
						-- etymology language
						inscat("renderings of " .. source:getFullName() .. " " .. dimaugof .. ty .. "s")
					end
				end
			end
			insert_cats_type(nametype)
		end
		insert_cats("")
		if args.dim then
			insert_cats("diminutives of ")
		end
		if args.aug then
			insert_cats("augmentatives of ")
		end
	end

	return table.concat(textsegs, "") .. m_utilities.format_categories(categories, lang, args.sort, nil, force_cat)
end

return export