본문으로 이동

모듈:names

위키낱말사전, 말과 글의 누리

이 모듈에 대한 설명문서는 모듈:names/설명문서에서 만들 수 있습니다

local export = {}

local m_languages = require("Module:languages")
local m_links = require("Module:also/link")
local m_utilities = require("Module:utilities")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
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 kolang = m_languages.getByCode("ko")

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 = {
	"남자 성씨", "여자 성씨", "남녀 공용 성씨", "성씨",
	"부칭", "모칭",
}

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

export.given_name_genders = {
	["남자"] = {type = "human"},
	["여자"] = {type = "human"},
	["남녀 공용"] = {type = "human", cat = {"남자 이름", "여자 이름", "남녀 공용 이름"}},
	["알 수 없는 성별"] = {type = "human", cat = {}, track = true},
	["동물"] = {type = "animal", track = true},
	["고양이"] = {type = "animal"},
	["소"] = {type = "animal"},
	["개"] = {type = "animal"},
	["말"] = {type = "animal"},
	["돼지"] = {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 .. " 이름"}
		else
			cats = {gender .. " 이름"}
		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(cat .. "의 지소사")
			do_cat(cat .. "의 확대사")
			do_cat(cat)
		end
	end
	
	do_cat("이름")
end

local translit_name_type_list = {
	"성씨", "남자 이름", "여자 이름", "남녀 공용 이름",
	"부칭"
}
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


--[=[
낱말과 관련 속성을 파싱합니다. '카를하인츠', '쿠니군데<q:중세, 현재는 희귀>' 또는 'non:Óláfr',
'ru:Фру́нзе<tr:Frúnzɛ><q:희귀>'와 같은 형식의 매개변수를 처리합니다.
수정 속성은 낱말 뒤의 <...> 명세 안에 포함됩니다.
`term`은 꺾쇠괄호와 콜론을 포함한 전체 매개변수 값입니다.
`paramname`은 오류 메시지를 위한 매개변수 이름입니다.
`deflang`는 언어가 명시되지 않았을 때(위 예시의 '카를하인츠'와 '쿠니군데<...>') 반환값에 사용되는 언어 객체입니다.
`allow_explicit_lang`는 언어를 명시적으로 지정할 수 있는지(위 예시의 'non:Óláfr'와 'ru:Фру́нзе<...>')를 나타냅니다.

일반적으로 반환값은 [[Module:links]]의 full_link()에 전달할 수 있는 terminfo 객체이며,
선택적으로 `.q`, `.qq`, `.l`, `.ll`, `.refs` 및 `.eq` 필드를 포함합니다.
만약 `allow_multiple_terms`가 주어지면, 쉼표로 구분된 여러 이름을 `term`에 지정할 수 있으며,
반환값은 위에서 설명한 형식의 객체 목록이 됩니다.
]=]
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", kolang, 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


--[=[
단일 낱말을 링크합니다. `do_language_link`가 주어지고 용어의 언어가 한국어인 경우,
[[Module:links]]의 language_link()를 사용하여 링크를 구성하고, 그렇지 않으면 full_link()를 사용합니다.
`termobj`는 parse_term_with_annotations()가 반환하는 객체입니다.
]=]
local function link_one_term(termobj, do_language_link)
	local link
	if do_language_link and termobj.lang:getCode() == "ko" 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 = "또는"}) .. "]"
	end
	return link
end


--[=[
`terms`의 용어들을 링크하고, `conj`에 지정된 접속사(기본값: '또는')로 연결합니다.
연결은 [[Module:table]]의 serialCommaJoin()을 사용하여 수행됩니다.
`include_langname`이 주어지면 첫 번째 용어의 언어 이름이 연결된 용어 앞에 추가됩니다.
`do_language_link`가 주어지고 용어의 언어가 한국어인 경우, 링크는 language_link()로 구성됩니다.
`terms`의 각 용어는 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 "또는"})
	end
	return (langnametext or "") .. joined_terms
end


--[=[
여러 이름에 대한 매개변수를 모아 각 이름을 full_link()(외국어 이름) 또는 language_link()(한국어 이름)를 사용하여
링크하고, [[Module:table]]의 serialCommaJoin()을 사용하여 접속사 `conj`(기본값 '또는')로 연결합니다.
예를 들어, 여성 이름에 대한 남성 등가 이름을 가져와 연결하는 데 사용할 수 있습니다.
각 이름은 `args`에서 `pname`으로 시작하는 매개변수(예: "m", "m2", "m3" 등)를 사용하여 지정됩니다.
`lang`은 이름을 연결하는 데 사용되는 언어 객체(기본값: 한국어)입니다.
`allow_explicit_lang`가 주어지면 'sv:Björn'과 같이 언어 코드를 접두사로 붙여 용어의 언어를 명시적으로 지정할 수 있습니다.
이 함수는 매개변수가 이미 [[Module:parameters]]에 의해 파싱되어 목록으로 수집되었다고 가정합니다.
]=]
local function join_names(lang, args, pname, conj, allow_explicit_lang)
	local termobjs = {}
	local do_language_link = false
	if not lang then
		lang = kolang
		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), kolang,
			"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 = "또는"})
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 == "성씨" or from == "이름" or from == "별명" or from == "지명" or from == "보통 명사" or from == "월 이름" then
			prefix = from .. "에서 유래한 "
			suffix = ""
			table.insert(catparts, from)
		elseif from == "부칭" or from == "모칭" or from == "신조어" then
			prefix = from .. "으로 유래한 "
			suffix = ""
			table.insert(catparts, from)
		elseif from == "직업" or from == "민족명" then
			prefix = from .. "으로 유래한 "
			suffix = ""
			table.insert(catparts, from)
		elseif from == "성경" then
			prefix = "성경에서 유래한 "
			suffix = ""
			table.insert(catparts, from)
		else
			prefix = ""
			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
					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("^(.+)어족$") or
					from:match("^(.+)어파$") or
					from:match("^(.+) 방언$")
				if family then
					if require("Module:families").getByCanonicalName(family, "allow redirects") then
						table.insert(catparts, from)
					else
						unrecognized = true
					end
					suffix = 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 .. " [차례로 " .. table.concat(rest_suffixparts, ", 차례로 ") .. "]"
				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 = "또는"}))
	end
	return m_table.serialCommaJoin(fromtextsegs, {conj = "또는"}), catparts
end


local function parse_given_name_genders(genderspec)
	if export.given_name_genders[genderspec] then -- 최적화
		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(("'%s'은(는) 인식할 수 없는 성별입니다. 유효한 성별: %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("동물과 사람의 성별을 섞을 수 없습니다")
		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
			-- 참고: 성별 유형에 % 기호가 없다고 가정함
			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 = "또는"})
	
	return retval
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 = "알 수 없는 성별"},
		[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"] = {list = 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", "과")
	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 = "또는"})
	local eqtext = get_eqtext(args)

	local function ins(txt)
		table.insert(textsegs, txt)
	end
	
	local dimoftype = args.dimoftype
	local augoftype = args.augoftype
	if numdimofs > 0 then
		ins((dimoftype and dimoftype .. " " or ""))
	elseif numaugofs > 0 then
		ins((augoftype and augoftype .. " " or ""))
	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
	
	if numdimofs > 0 then
		ins(dimoftext .. "의 ")
		ins((xlittext ~= "" and xlittext .. "에 해당하는 " or ""))
		ins((dimoftype and dimoftype .. " " or "") .. "[[지소사]]")
	elseif numaugofs > 0 then
		ins(augoftext .. "의 ")
		ins((xlittext ~= "" and xlittext .. "에 해당하는 " or ""))
		ins((augoftype and augoftype .. " " or "") .. "[[확대사]]")
	else
		ins("[[이름]]")
		if xlittext ~= "" then
			ins(" " .. xlittext)
		end
	end

	local need_comma = true

	if is_animal then
		ins(", ")
		local gendertext = generate_given_name_genders(lang, genders)
		ins(gendertext)
		ins("의 이름")
	end

	local from_catparts = {}
	if args.from and #args.from > 0 then
		ins(", ")
		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
		ins(", ")
		ins(fetch_typetext("meaningtype") .. meaningtext .. "을 의미")
	end
	if args.origin then
		ins(", ")
		ins(args.origin .. " 유래")
	end
	if args.usage then
		ins(", ")
		ins(args.usage .. " 용법")
	end
	if varoftext ~= "" then
		ins(", " .. fetch_typetext("varoftype") .. varoftext .. "의 변이형")
	end
	if clipoftext ~= "" then
		ins(", " .. fetch_typetext("clipoftype") .. clipoftext .. "의 단축형")
	end
	if blendtext ~= "" then
		ins(", " .. fetch_typetext("blendtype") .. blendtext .. (numblends > 1 and "의" or "") .. " 혼성어")
	end
	if args.popular then
		ins(", ")
		ins(fetch_typetext("populartype") .. "널리 쓰이는 " .. args.popular)
	end
	if mtext ~= "" then
		ins(", ")
		ins(fetch_typetext("mtype") .. "남성형은 " .. mtext)
	end
	if ftext ~= "" then
		ins(", ")
		ins(fetch_typetext("ftype") .. "여성형은 " .. ftext)
	end
	if eqtext ~= "" then
		ins(", ")
		ins(fetch_typetext("eqtype") .. 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") .. "변이형: " .. varformtext)
	end
	if dimformtext ~= "" then
		ins("; " .. fetch_typetext("dimformtype") .. "지소형: " .. dimformtext)
	end
	if augformtext ~= "" then
		ins("; " .. fetch_typetext("augformtype") .. "확대형: " .. augformtext)
	end
	textsegs = "<span class='use-with-mention'>" .. table.concat(textsegs) .. "</span>"

	local categories = {}
	local langname = lang:getCanonicalName()
	local function insert_cats(dimaug_suffix)
		dimaug_suffix = dimaug_suffix or ""
		if dimaug_suffix == "" and genders[1].props.type == "human" then
			table.insert(categories, langname .. " 이름")
		end
		local function insert_cat(cat)
			table.insert(categories, langname .. " " .. cat .. dimaug_suffix)
			for _, catpart in ipairs(from_catparts) do
				table.insert(categories, langname .. " " .. catpart .. "에서 유래한 " .. cat .. dimaug_suffix)
			end
		end
		for _, spec in ipairs(genders) do
			if spec.props.track then
				track(spec.type)
			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(" 지소사")
	elseif numaugofs > 0 then
		insert_cats(" 확대사")
	end

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

-- {{성씨}}, {{부칭}}, {{모칭}} 틀의 진입점.
function export.surname(frame)
	local iargs = require("Module:parameters").process(frame.args, {
		["type"] = {required = true, set = {"성씨", "부칭", "모칭"}},
	})

	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= 및 nodot=는 더 이상 [[틀:" .. iargs.type .. "]]에서 지원되지 않습니다. " ..
			"마침표는 더 이상 기본적으로 추가되지 않으므로, 원하시면 틀 뒤에 직접 추가하십시오.")
	end

	local lang_index = compat and "lang" or 1
	
	local list = {list = true}
	local gender_arg = iargs.type == "성씨" and "g" or 1 + offset
	local adj_arg = iargs.type == "성씨" 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 == "성씨" and true or {required = true, template_default = "알 수 없음"}, -- 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 == "알 수 없음" or g == "알 수 없는 성별" or g == "?" then
				g = "알 수 없는 성별"
				track("unknown gender")
			elseif g == "남녀 공용" or g == "c" then
				g = "남녀 공용"
				saw_male = true
				saw_female = true
			elseif g == "m" or g == "남자" then
				g = "남자"
				saw_male = true
			elseif g == "f" or g == "여자" then
				g = "여자"
				saw_female = true
			else
				error("인식할 수 없는 성별: " .. 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", "과")
	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 "아들" or saw_female and not saw_male and "딸" or
			"자녀"
		table.insert(meaningsegs, ("“%s의 %s”"):format(parenttext, child))
	end

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

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

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

	if #genders > 0 then
		ins(table.concat(genders, " 또는 ") .. " ")
	end
	if adj then
		ins(adj .. " ")
	end
	
	local from_catparts = {}
	if args.from and #args.from > 0 then
		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
	ins("[[" .. iargs.type .. "]]")
	
	if xlittext ~= "" then
		ins(" " .. xlittext)
	end
	
	
	if meaningtext ~= "" then
		ins(", ")
		ins(fetch_typetext("meaningtype") .. meaningtext .. "을 의미")
	end
	if args.origin then
		ins(", ")
		ins(args.origin .. " 유래")
	end
	if args.usage then
		ins(", ")
		ins(args.usage .. " 용법")
	end
	if varoftext ~= "" then
		ins(", " ..fetch_typetext("varoftype") .. varoftext .. "의 변이형")
	end
	if clipoftext ~= "" then
		ins(", " .. fetch_typetext("clipoftype") .. clipoftext .. "의 단축형")
	end
	if blendtext ~= "" then
		ins(", " .. fetch_typetext("blendtype") .. blendtext .. (numblends > 1 and "의" or "") .. " 혼성어")
	end
	if args.popular then
		ins(", ")
		ins(fetch_typetext("populartype") .. "널리 쓰이는 " .. args.popular)
	end
	if mtext ~= "" then
		ins(", ")
		ins(fetch_typetext("mtype") .. "남성형은 " .. mtext)
	end
	if ftext ~= "" then
		ins(", ")
		ins(fetch_typetext("ftype") .. "여성형은 " .. ftext)
	end
	if eqtext ~= "" then
		ins(", ")
		ins(fetch_typetext("eqtype") .. 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") .. "변이형: " .. 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 ""
		local full_type = langname .. " " .. g .. iargs.type
		table.insert(categories, full_type)
		for _, catpart in ipairs(from_catparts) do
			table.insert(categories, catpart .. "에서 유래한 " .. full_type)
		end
	end
	
	insert_cats(nil)
	local function insert_cats_gender(g)
		if g == "알 수 없는 성별" then
			return
		end
		if g == "남녀 공용" then
			insert_cats_gender("남자")
			insert_cats_gender("여자")
		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 = "ko"},
		[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 = "부칭"},
		["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,
		-- 지정된 이름의 언어로 첫 번째 출처 언어를 사용
		lang = function(args) return args[2][1] end,
		sc = "sc.default",
	}

	local lang = args[1]
	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 not args.nocap then
		desctext = mw.getContentLanguage():ucfirst(desctext)
	end
	if not iargs.foreign_name then
		ins("")
	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
			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, get_source_link())
			else
				table.insert(langsegs, sourcename)
			end
		else
			table.insert(langsegs, get_source_link())
		end
	end
	local langseg_text = m_table.serialCommaJoin(langsegs, {conj = "또는"})
	local augdim_text
	if args.dim then
		augdim_text = " [[지소사]]"
	elseif args.aug then
		augdim_text = " [[확대사]]"
	else
		augdim_text = ""
	end
	local nametype_linked = {}
	for _, nametype in ipairs(args["type"]) do
		if nametype == "성씨" or nametype == "부칭" then
			table.insert(nametype_linked, "[[" .. nametype .. "]]")
		elseif nametype == "남자 이름" then
			table.insert(nametype_linked, "남자 [[이름]]")
		elseif nametype == "여자 이름" then
			table.insert(nametype_linked, "여자 [[이름]]")
		elseif nametype == "남녀 공용 이름" then
			table.insert(nametype_linked, "남녀 공용 [[이름]]")
		else
			table.insert(nametype_linked, nametype)
		end
	end
	local nametype_text = m_table.serialCommaJoin(nametype_linked) .. augdim_text
	
	local linked_names_text = ""
	if names[1] then
		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 = kolang, term = name.xlit }
			end
			if name.eq then
				embedded_comma = true
				linked_name = linked_name .. ", " .. m_links.language_link { lang = kolang, term = name.eq } .. "에 해당"
			end
			table.insert(linked_names, linked_name)
		end
		
		if embedded_comma then
			linked_names_text = table.concat(linked_names, "; 또는 ")
		else
			linked_names_text = m_table.serialCommaJoin(linked_names, {conj = "또는"})
		end
	end

	if not iargs.foreign_name then
		ins(langseg_text .. " " .. nametype_text)
		if linked_names_text ~= "" then
			ins(" " .. linked_names_text)
		end
		ins("의 " .. desctext)
	else
		-- foreign_name의 경우도 어순을 맞춥니다.
		ins(nametype_text)
		if linked_names_text ~= "" then
			ins(" " .. linked_names_text)
		end
		ins("는(은), " .. langseg_text .. "에서 쓰이는 " .. desctext)
	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 target_lang_name = lang:getFullName()
	
	local function inscat(cat)
		table.insert(categories, lang:getFullName() .. " " .. cat)
	end

	for _, nametype in ipairs(args.type) do
		local function insert_cats(dimaug_prefix)
			local function insert_cats_type(ty)
				if ty == "남녀 공용 이름" then
					insert_cats_type("남자 이름")
					insert_cats_type("여자 이름")
				end
				for _, source in ipairs(sources) do
					local source_lang_name = source:getCanonicalName()
					table.insert(categories, source_lang_name .. " " .. dimaug_prefix .. ty .. "의 " .. target_lang_name .. " 표현")

					-- 파생 및 차용어 분류는 대상 언어(lang)를 기준으로 생성
					table.insert(categories, source_lang_name .. "에서 파생된 " .. target_lang_name .. " 낱말")
					table.insert(categories, source_lang_name .. "에서 차용한 " .. target_lang_name .. " 낱말")
					if iargs.obor then
						table.insert(categories, source_lang_name .. "에서 철자 차용한 " .. target_lang_name .. " 낱말")
					end
					
					if source:getCode() ~= source:getFullCode() then
						table.insert(categories, source:getFullName() .. " " .. dimaug_prefix .. ty .. "의 " .. target_lang_name .. " 표현")
					end
				end
			end
			insert_cats_type(nametype)
		end
		insert_cats("")
		if args.dim then
			insert_cats("지소사 ")
		end
		if args.aug then
			insert_cats("확대사 ")
		end
	end

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

return export