본문으로 이동

모듈:headword

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

함수

[편집]

export.head_is_multiword

[편집]

function export.head_is_multiword(head)

주어진 표제어가 다중 단어인지 여부를 반환합니다.

[편집]

function export.add_multiword_links(head, default)

이 함수에는 설명문서가 존재하지 않습니다. 함수에 대한 사용법과 입출력, 유사한 함수와의 차이점을 다른 사용자들이 참고할 수 있도록 추가하거나, 함수 목록에서 제거하기 위해 지역 함수로 변경하세요.

export.pluralize_pos

[편집]

function export.pluralize_pos(pos)

이 함수에는 설명문서가 존재하지 않습니다. 함수에 대한 사용법과 입출력, 유사한 함수와의 차이점을 다른 사용자들이 참고할 수 있도록 추가하거나, 함수 목록에서 제거하기 위해 지역 함수로 변경하세요.

export.pos_lemma_or_nonlemma

[편집]

function export.pos_lemma_or_nonlemma(plpos, best_guess)

이 함수에는 설명문서가 존재하지 않습니다. 함수에 대한 사용법과 입출력, 유사한 함수와의 차이점을 다른 사용자들이 참고할 수 있도록 추가하거나, 함수 목록에서 제거하기 위해 지역 함수로 변경하세요.

export.canonicalize_pos

[편집]

function export.canonicalize_pos(pos)

이 함수에는 설명문서가 존재하지 않습니다. 함수에 대한 사용법과 입출력, 유사한 함수와의 차이점을 다른 사용자들이 참고할 수 있도록 추가하거나, 함수 목록에서 제거하기 위해 지역 함수로 변경하세요.

export.maintenance_cats

[편집]

function export.maintenance_cats(page, lang, lang_cats, page_cats)

이 함수에는 설명문서가 존재하지 않습니다. 함수에 대한 사용법과 입출력, 유사한 함수와의 차이점을 다른 사용자들이 참고할 수 있도록 추가하거나, 함수 목록에서 제거하기 위해 지역 함수로 변경하세요.

export.full_headword

[편집]

function export.full_headword(data)

이것이 주요 외부 진입점입니다. full_headword(data) Lua 오류: Parameters 1 and 2 are required.. 및 다양한 언어별 표제어 틀에서 전체 표제어 줄을 표시하는 데 사용됩니다.

하위 모듈

[편집]

local export = {}

-- Named constants for all modules used, to make it easier to swap out sandbox versions.
local debug_track_module = "Module:debug/track"
local en_utilities_module = "Module:en-utilities"
local gender_and_number_module = "Module:gender and number"
local headword_data_module = "Module:headword/data"
local headword_page_module = "Module:headword/page"
local links_module = "Module:also/link"
local load_module = "Module:load"
local pages_module = "Module:pages"
local palindromes_module = "Module:palindromes"
local pron_qualifier_module = "Module:pron qualifier"
local scripts_module = "Module:scripts"
local scripts_data_module = "Module:scripts/data"
local script_utilities_module = "Module:script utilities"
local script_utilities_data_module = "Module:script utilities/data"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"

local concat = table.concat
local insert = table.insert
local ipairs = ipairs
local max = math.max
local new_title = mw.title.new
local pairs = pairs
local require = require
local toNFC = mw.ustring.toNFC
local toNFD = mw.ustring.toNFD
local type = type
local ufind = mw.ustring.find
local ugmatch = mw.ustring.gmatch
local ugsub = mw.ustring.gsub
local umatch = mw.ustring.match


--[==[
다른 모듈의 함수 로더. 처음 호출될 때 자신을 대상 함수로 덮어씁니다.
이를 통해 모듈이 필요할 때만 로드되도록 보장하며, 미리 로드된 로컬 함수 선언의 속도와 편의성을 유지하고,
첫 호출 이후에는 오버헤드가 없습니다.
]==]
	local function debug_track(...)
		debug_track = require(debug_track_module)
		return debug_track(...)
	end

	local function encode_entities(...)
		encode_entities = require(string_utilities_module).encode_entities
		return encode_entities(...)
	end

	local function extend(...)
		extend = require(table_module).extend
		return extend(...)
	end

	local function find_best_script_without_lang(...)
		find_best_script_without_lang = require(scripts_module).findBestScriptWithoutLang
		return find_best_script_without_lang(...)
	end

	local function format_categories(...)
		format_categories = require(utilities_module).format_categories
		return format_categories(...)
	end

	local function format_genders(...)
		format_genders = require(gender_and_number_module).format_genders
		return format_genders(...)
	end

	local function format_pron_qualifiers(...)
		format_pron_qualifiers = require(pron_qualifier_module).format_qualifiers
		return format_pron_qualifiers(...)
	end

	local function full_link(...)
		full_link = require(links_module).full_link
		return full_link(...)
	end

	local function get_current_L2(...)
		get_current_L2 = require(pages_module).get_current_L2
		return get_current_L2(...)
	end

	local function get_link_page(...)
		get_link_page = require(links_module).get_link_page
		return get_link_page(...)
	end

	local function get_script(...)
		get_script = require(scripts_module).getByCode
		return get_script(...)
	end

	local function is_palindrome(...)
		is_palindrome = require(palindromes_module).is_palindrome
		return is_palindrome(...)
	end

	local function language_link(...)
		language_link = require(links_module).language_link
		return language_link(...)
	end

	local function load_data(...)
		load_data = require(load_module).load_data
		return load_data(...)
	end

	local function pattern_escape(...)
		pattern_escape = require(string_utilities_module).pattern_escape
		return pattern_escape(...)
	end

	local function pluralize(...)
		pluralize = require(string_utilities_module).pluralize
		return pluralize(...)
	end

	local function process_page(...)
		process_page = require(headword_page_module).process_page
		return process_page(...)
	end

	local function remove_links(...)
		remove_links = require(links_module).remove_links
		return remove_links(...)
	end

	local function shallow_copy(...)
		shallow_copy = require(table_module).shallowCopy
		return shallow_copy(...)
	end

	local function tag_text(...)
		tag_text = require(script_utilities_module).tag_text
		return tag_text(...)
	end

	local function tag_transcription(...)
		tag_transcription = require(script_utilities_module).tag_transcription
		return tag_transcription(...)
	end

	local function tag_translit(...)
		tag_translit = require(script_utilities_module).tag_translit
		return tag_translit(...)
	end

	local function trim(...)
		trim = require(string_utilities_module).trim
		return trim(...)
	end

	local function ulen(...)
		ulen = require(string_utilities_module).len
		return ulen(...)
	end
	
--[==[
객체 로더. 데이터를 변수에 로드하며, "foo or get_foo()" 형태로 접근할 수 있습니다.
get_foo 함수는 객체를 "foo"로 설정한 후 반환합니다.
]==]
	local m_data
	local function get_data()
		m_data = load_data(headword_data_module)
		return m_data
	end

	local script_data
	local function get_script_data()
		script_data = load_data(scripts_data_module)
		return script_data
	end

	local script_utilities_data
	local function get_script_utilities_data()
		script_utilities_data = load_data(script_utilities_data_module)
		return script_utilities_data
	end

-- true로 설정하면 일반 문서 공간이 아니더라도 항상 분류가 표시됩니다.
local test_force_categories = false

-- 특정 속성을 가진 항목을 추적하기 위해 추적 분류를 추가하는 함수입니다.
local function track(track_id, lang)
	local tracking_page = "headword/" .. track_id
	if lang and lang:hasType("etymology-only") then
		debug_track{tracking_page, tracking_page .. "/" .. lang:getCode(),
			tracking_page .. "/" .. lang:getFullCode()}
	elseif lang then
		debug_track{tracking_page, tracking_page .. "/" .. lang:getCode()}
	else
		debug_track(tracking_page)
	end
	return true
end

local function text_in_script(text, script_code)
	local sc = get_script(script_code)
	if not sc then
		error("내부 오류: 잘못된 스크립트 코드 " .. script_code)
	end
	local characters = sc.characters

	local out
	if characters then
		text = ugsub(text, "%W", "")
		out = ufind(text, "[" .. characters .. "]")
	end

	return out and true or false
end


local spacingPunctuation = "[%s%p]+"
-- 단어 내부에 나타나는 구두점 또는 공백 문자 목록
local wordPunc = "-#%%&@־׳״'.·*’་•:᠊"
local notWordPunc = "[^" .. wordPunc .. "]+"


-- 한정사, 라벨, 참조, 구분자와 함께 용어를 형식화하는 함수
local function format_term_with_qualifiers_and_refs(lang, part, formatted, j)
	local function part_non_empty(field)
		local list = part[field]
		if not list then
			return nil
		end
		if type(list) ~= "table" then
			error(('내부 오류: `part.%s`의 타입이 "table"이어야 하나 "%s"입니다: %s'):format(field, type(list), mw.dumpObject(list)))
		end
		return list[1]
	end

	if part_non_empty("q") or part_non_empty("qq") or part_non_empty("l") or
		part_non_empty("ll") or part_non_empty("refs") then
		formatted = format_pron_qualifiers {
			lang = lang,
			text = formatted,
			q = part.q,
			qq = part.qq,
			l = part.l,
			ll = part.ll,
			refs = part.refs,
		}
	end

	local separator = part.separator or j > 1 and " <i>또는</i> " -- ""를 사용하여 구분자 없음을 요청

	if separator then
		formatted = separator .. formatted
	end

	return formatted
end


--[==[주어진 표제어가 다중 단어인지 여부를 반환합니다.]==]
function export.head_is_multiword(head)
	for possibleWordBreak in ugmatch(head, spacingPunctuation) do
		if umatch(possibleWordBreak, notWordPunc) then
			return true
		end
	end
	return false
end

do
	local function workaround_to_exclude_chars(s)
		return (ugsub(s, notWordPunc, "\2%1\1"))
	end

	-- 다중 단어 표제어에 링크를 추가합니다.
	function export.add_multiword_links(head, default)
		head = "\1" .. ugsub(head, spacingPunctuation, workaround_to_exclude_chars) .. "\2"
		if default then
			head = head
				:gsub("(\1[^\2]*)\\([:#][^\2]*\2)", "%1\\\\%2")
				:gsub("(\1[^\2]*)([:#][^\2]*\2)", "%1\\%2")
		end

		-- 링크를 깨뜨릴 수 있는 대괄호를 이스케이프 처리합니다.
		head = encode_entities(head, "[]", true, true)

		return (head
			:gsub("\1\2", "")
			:gsub("[\1\2]", {["\1"] = "[[", ["\2"] = "]]"}))
	end
end

local function non_categorizable(full_raw_pagename)
	return full_raw_pagename:find("^부록:Gestures/") or
		(full_raw_pagename:find("^Unsupported titles/") and not full_raw_pagename:find("`"))
end

local function tag_text_and_add_quals_and_refs(data, head, formatted, j)
	-- 언어 및 스크립트 래퍼를 추가합니다.
	formatted = tag_text(formatted, data.lang, head.sc, "head", nil, j == 1 and data.id or nil)
	-- 한정사, 레이블, 참조 및 구분자를 추가합니다.
	return format_term_with_qualifiers_and_refs(data.lang, head, formatted, j)
end

-- 음역과 함께 표제어를 형식화합니다.
local function format_headword(data)
	local has_translits = false
	local has_manual_translits = false

	------ 표제어 형식화 ------
	local head_parts = {}
	local unique_head_parts = {}
	local has_multiple_heads = not not data.heads[2]

	for j, head in ipairs(data.heads) do
		if head.tr or head.ts then
			has_translits = true
		end
		if head.tr and head.tr_manual or head.ts then
			has_manual_translits = true
		end

		local formatted
		if head.term:find("[[", nil, true) and head.sc:getCode() ~= "Image" then
			formatted = language_link{term = head.term, lang = data.lang}
		else
			formatted = data.lang:makeDisplayText(head.term, head.sc, true)
		end

		local head_part = tag_text_and_add_quals_and_refs(data, head, formatted, j)
		insert(head_parts, head_part)

		if has_multiple_heads then
			local unique_head_part = (j == 1) and head_part or tag_text_and_add_quals_and_refs(data, head, formatted, 1)
			unique_head_parts[unique_head_part] = true
		end
	end

	local set_size = 0
	if has_multiple_heads then
		for _ in pairs(unique_head_parts) do
			set_size = set_size + 1
		end
	end
	if set_size == 1 then
		head_parts = head_parts[1]
	else
		head_parts = concat(head_parts)
	end

	if has_manual_translits then
		track("manual-tr", data.lang)
	end

	------ 음역 및 전사 형식화 ------
	local translits_formatted
	if has_translits then
		local translit_parts = {}
		for _, head in ipairs(data.heads) do
			if head.tr or head.ts then
				local this_parts = {}
				if head.tr then
					insert(this_parts, tag_translit(head.tr, data.lang:getCode(), "head", nil, head.tr_manual))
					if head.ts then
						insert(this_parts, " ")
					end
				end
				if head.ts then
					insert(this_parts, "/" .. tag_transcription(head.ts, data.lang:getCode(), "head") .. "/")
				end
				insert(translit_parts, concat(this_parts))
			end
		end

		translits_formatted = " (" .. concat(translit_parts, " 또는 ") .. ")"

		local langname = data.lang:getCanonicalName()
		local transliteration_page = new_title(langname .. " 로마자 표기법", "위키낱말사전")
		local saw_translit_page = false

		if transliteration_page and transliteration_page.exists then
			translits_formatted = " [[위키낱말사전:" .. langname .. " 로마자 표기법|•]]" .. translits_formatted
			saw_translit_page = true
		end

		if not saw_translit_page and data.lang:hasType("etymology-only") then
			langname = data.lang:getFullName()
			transliteration_page = new_title(langname .. " 로마자 표기법", "위키낱말사전")

			if transliteration_page and transliteration_page.exists then
				translits_formatted = " [[위키낱말사전:" .. langname .. " 로마자 표기법|•]]" .. translits_formatted
			end
		end
	else
		translits_formatted = ""
	end

	------ 표제어와 음역/전사 결합 ------
	local lemma_gloss = data.gloss and ' <span class="ib-content qualifier-content">' .. data.gloss .. '</span>' or ""
	return head_parts .. translits_formatted .. lemma_gloss
end

local function format_headword_genders(data)
	local retval = ""
	if data.genders and data.genders[1] then
		if data.gloss then
			retval = ","
		end
		local pos_for_cat
		if not data.nogendercat then
			local no_gender_cat = (m_data or get_data()).no_gender_cat
			if not (no_gender_cat[data.lang:getCode()] or no_gender_cat[data.lang:getFullCode()]) then
				pos_for_cat = (m_data or get_data()).pos_for_gender_number_cat[data.pos_category:gsub("^reconstructed ", "")]
			end
		end
		local text, cats = format_genders(data.genders, data.lang, pos_for_cat)
		if cats then
			extend(data.categories, cats)
		end
		retval = retval .. "&nbsp;" .. text
	end
	return retval
end

local function format_inflection_parts(data, parts)
	local any_part_translit = false

	for j, part in ipairs(parts) do
		if type(part) ~= "table" then
			part = {term = part}
		end

		local partaccel = part.accel
		local face = part.face or "bold"
		if face ~= "bold" and face ~= "plain" and face ~= "hypothetical" then
			error(("글자체 `%s`는(은) %s"):format(face,
				((script_utilities_data or get_script_utilities_data()).faces[face] and
				"표제어가 아닌 용어에는 사용할 수 없습니다." or "유효하지 않습니다.")))
		end

		local nolinkinfl = part.face == "hypothetical" or (part.nolink and track("nolink") or part.nolinkinfl) or (
			data.nolink and track("nolink") or data.nolinkinfl)

		local formatted
		if part.label then
			formatted = part.label
		else
			local tr = part.translit or (not (parts.enable_auto_translit or data.inflections.enable_auto_translit) and "-" or nil)
			if tr ~= "-" then
				any_part_translit = true
			end
			formatted = full_link(
				{
					term = not nolinkinfl and part.term or nil,
					alt = part.alt or (nolinkinfl and part.term or nil),
					lang = part.lang or data.lang,
					sc = part.sc or parts.sc or nil,
					gloss = part.gloss,
					pos = part.pos,
					lit = part.lit,
					id = part.id,
					genders = part.genders,
					tr = tr,
					ts = part.transcription,
					accel = partaccel or parts.accel,
				},
				face
				)
		end

		parts[j] = format_term_with_qualifiers_and_refs(part.lang or data.lang, part, formatted, j)
	end

	local parts_output
	if parts[1] then
		parts_output = (parts.label and " " or "") .. concat(parts)
	elseif parts.request then
		parts_output = " <small>[제공 필요]</small>"
		insert(data.categories, "곡용/활용 요청이 있는 " .. data.lang:getFullName() .. " 낱말")
	else
		parts_output = ""
	end

	local parts_label = parts.label or ""
	return format_term_with_qualifiers_and_refs(data.lang, parts, parts_label .. parts_output, 1), any_part_translit
end

-- 표제어 뒤에 오는 활용형들을 형식화합니다.
local function format_inflections(data)
	if data.inflections and data.inflections[1] then
		for key, infl in ipairs(data.inflections) do
			data.inflections[key] = format_inflection_parts(data, infl)
		end
		local concat_result = concat(data.inflections, ", ")
		return " (" .. concat_result .. ")"
	else
		return ""
	end
end

-- 품사(pos)의 복수형을 반환합니다.
function export.pluralize_pos(pos)
	local data = m_data or get_data()
	return data.irregular_plurals[pos] or
		pos:sub(-1) == "s" and pos or
		pluralize(pos)
end

-- 주어진 품사가 기본형인지 파생형인지 등을 반환합니다.
function export.pos_lemma_or_nonlemma(plpos, best_guess)
	local isLemma = (m_data or get_data()).lemmas
	if isLemma[plpos] or isLemma[plpos:gsub("^reconstructed ", "")] then
		return "기본형"
	end

	local isNonLemma = (m_data or get_data()).nonlemmas
	if isNonLemma[plpos] or isNonLemma[plpos:gsub("^reconstructed ", "")] or isNonLemma[plpos:gsub("^mutated ", "")] then
		return "파생형"
	end

	if best_guess then
		return plpos:find(" forms$") and "파생형" or "기본형"
	else
		return nil
	end
end

function export.canonicalize_pos(pos)
	-- 모호한 약어 사용 시 오류 발생
	if pos == "pre" then
		error("전치사를 뜻하는 품사 'pre'는 더 이상 사용할 수 없습니다. 'prep'을 사용해주세요.")
	end
	if pos == "pro" or pos == "prof" then
		error("대명사를 뜻하는 품사 'pro'는 더 이상 사용할 수 없습니다. 'pron'을 사용해주세요.")
	end
	local data = m_data or get_data()
	if data.pos_aliases[pos] then
		pos = data.pos_aliases[pos]
	elseif pos:sub(-1) == "f" then
		pos = pos:sub(1, -2)
		pos = (data.pos_aliases[pos] or pos) .. " forms"
	end
	return export.pluralize_pos(pos)
end

-- 배열에서 최대 인덱스를 찾아 반환하고, 지정되지 않은 경우 초기화합니다.
local function init_and_find_maximum_index(data, element, allow_blank_string)
	local maxind = 0
	if not data[element] then
		data[element] = {}
	end
	local typ = type(data[element])
	if typ ~= "table" then
		error(("full_headword()에서, `data.%s`는 배열이어야 하지만 %s입니다."):format(element, typ))
	end
	for k, v in pairs(data[element]) do
		if k ~= "maxindex" then
			if type(k) ~= "number" then
				error(("`data.%s`에서 인식할 수 없는 숫자가 아닌 키 '%s'"):format(element, k))
			end
			if k > maxind then
				maxind = k
			end
			if v then
				if type(v) ~= "string" then
					error(("`data.%s`의 키 '%s'에 대해, 값은 문자열이어야 하지만 %s입니다."):format(element, k, type(v)))
				end
				if not allow_blank_string and v == "" then
					error(("`data.%s`의 키 '%s'에 대해 빈 문자열은 허용되지 않습니다. 기본값으로 'false'를 사용하세요."):format(element, k))
				end
			end
		end
	end
	return maxind
end

do
	local function handle_raw_sortkeys(tbl, sortkey, page, lang, lang_cats)
		sortkey = sortkey or lang:makeSortKey(page.pagename)
		if tbl == true then
			if page.raw_defaultsort ~= sortkey then
				insert(lang_cats, "중복되지 않는 수동 정렬 키가 있는 " .. lang:getFullName() .. " 낱말")
			end
			return
		end
		local redundant, different
		for k in pairs(tbl) do
			if k == sortkey then
				redundant = true
			else
				different = true
			end
		end
		if redundant then
			insert(lang_cats, "중복된 정렬 키가 있는 " .. lang:getFullName() .. " 낱말")
		end
		if different then
			insert(lang_cats, "중복되지 않는 수동 정렬 키가 있는 " .. lang:getFullName() .. " 낱말")
		end
		return sortkey
	end

	function export.maintenance_cats(page, lang, lang_cats, page_cats)
		extend(page_cats, page.cats)
		lang = lang:getFull()
		local canonical = lang:getCanonicalName()
		local tbl, sortkey = page.wikitext_topic_cat[lang:getCode()]
		if tbl then
			sortkey = handle_raw_sortkeys(tbl, sortkey, page, lang, lang_cats)
			insert(lang_cats, "원시 마크업을 사용한 주제 분류가 있는 " .. canonical .. " 항목")
		end
		tbl = page.wikitext_langname_cat[canonical]
		if tbl then
			handle_raw_sortkeys(tbl, sortkey, page, lang, lang_cats)
			insert(lang_cats, "원시 마크업을 사용한 언어 이름 분류가 있는 " .. canonical .. " 항목")
		end
		-- Parsoid 오류 문제로 주석 처리함
		-- if toNFC(get_current_L2() or "") ~= toNFC(canonical) then
		-- 	insert(lang_cats, "잘못된 언어 헤더가 있는 " .. canonical .. " 항목")
		-- 	track("incorrect language header", lang)
		-- end
	end
end


--[==[이것이 주요 외부 진입점입니다.
{{lua|full_headword(data)}}
{{head}} 및 다양한 언어별 표제어 틀에서 전체 표제어 줄을 표시하는 데 사용됩니다.
]==]
function export.full_headword(data)
	local data = shallow_copy(data)

	------------ 1. 기본 검사 ------------
	if data.getCanonicalName then
		error("full_headword()에서, 첫 번째 인수 `data`는 언어 객체가 아닌 속성의 Lua 객체(테이블)여야 합니다.")
	end

	if not data.lang or type(data.lang) ~= "table" or not data.lang.getCode then
		error("full_headword()에서, 첫 번째 인수 `data`는 Lua 객체(테이블)여야 하며 `data.lang`은 언어 객체여야 합니다.")
	end

	if data.id and type(data.id) ~= "string" then
		error("data 테이블의 id는 문자열이어야 합니다.")
	end

	------------ 2. 페이지 이름 등 초기화 ------------
	local langcode = data.lang:getCode()
	local full_langcode = data.lang:getFullCode()
	local langname = data.lang:getCanonicalName()
	local full_langname = data.lang:getFullName()

	local raw_pagename, page = data.pagename
	if raw_pagename and raw_pagename ~= (m_data or get_data()).pagename then
		page = process_page(raw_pagename)
	else
		page = (m_data or get_data()).page
	end

	local namespace = page.namespace
	if namespace == "" then
		if data.lang:hasType("reconstructed") then
			error(langname .. " 항목은 Reconstruction: 이름공간에 있어야 합니다.")
		elseif data.lang:hasType("appendix-constructed") then
			error(langname .. " 항목은 부록: 이름공간에 있어야 합니다.")
		end
	elseif namespace == "Citations" or namespace == "Thesaurus" then
		error("표제어 틀은 " .. namespace .. ": 이름공간에서 사용하면 안 됩니다.")
	end

	------------ 3. `data.heads` 테이블 초기화; 구식인 경우 새 방식으로 변환 ------------
	if type(data.heads) == "table" and type(data.heads[1]) == "table" then
		if data.translits or data.transcriptions then
			error("full_headword()에서, `data.heads`가 새 스타일(헤드 객체 배열)인 경우 `data.translits`와 `data.transcriptions`를 지정할 수 없습니다.")
		end
	else
		local maxind = max(
			init_and_find_maximum_index(data, "heads"),
			init_and_find_maximum_index(data, "translits", true),
			init_and_find_maximum_index(data, "transcriptions", true)
		)
		for i = 1, maxind do
			data.heads[i] = {
				term = data.heads[i],
				tr = data.translits[i],
				ts = data.transcriptions[i],
			}
		end
	end

	if not data.heads[1] then
		data.heads[1] = {}
	end

	------------ 4. 분류 초기화 및 기본 분류 추가 ------------
	if data.altform then
		data.noposcat = true
	end

	init_and_find_maximum_index(data, "categories")
	init_and_find_maximum_index(data, "whole_page_categories")
	local pos_category_already_present = false
	if data.categories[1] then
		local escaped_langname = pattern_escape(full_langname)
		local matches_lang_pattern = "^" .. escaped_langname .. " "
		for _, cat in ipairs(data.categories) do
			if not cat:find(matches_lang_pattern) then
				track("no lang category", data.lang)
			end
		end

		if not data.pos_category and data.categories[1]:find(matches_lang_pattern) then
			data.pos_category = data.categories[1]:gsub(matches_lang_pattern, "")
			pos_category_already_present = true
		end
	end

	if not data.pos_category then
		error("`data.pos_category`가 지정되지 않았거나 `data.categories`에 지정된 분류에서 추론할 수 없습니다. " ..
			"`data.pos_category`에 복수형 품사를 지정하거나(예: \"proper nouns\"), `data.categories`의 첫 번째 분류가 " ..
			"언어의 정식 이름과 복수형 품사로 구성되도록 하십시오(예: \"Norwegian Bokmål proper nouns\").")
	end

	if not pos_category_already_present and not data.noposcat then
		local pos_category = full_langname .. " " .. data.pos_category
		if pos_category ~= "범언어" then
			insert(data.categories, 1, pos_category)
		end
	end

	local postype = export.pos_lemma_or_nonlemma(data.pos_category)
	if not postype then
		track("unrecognized pos", data.lang)
		track("unrecognized pos/pos/" .. data.pos_category, data.lang)
	elseif not data.noposcat then
		-- 한국어에서는 복수형 's'를 붙이지 않음
		insert(data.categories, 1, full_langname .. " " .. postype)
	end

	if data.altform then
		insert(data.categories, 1, full_langname .. " 대체형")
	end

	------------ 5. 기본 표제어 생성 및 다중 단어 링크 추가 ------------
	local is_reconstructed = namespace == "Reconstruction" or data.lang:hasType("reconstructed")
	local default_head = page.pagename

	if not (is_reconstructed or data.nolinkhead) then
		local no_links = (m_data or get_data()).no_multiword_links
		if not (no_links[langcode] or no_links[full_langcode]) and export.head_is_multiword(default_head) then
			default_head = export.add_multiword_links(default_head, true)
		end
	end

	if is_reconstructed then
		default_head = "*" .. default_head
	end

	------------ 6. `data.heads`의 누락된 값 채우기 ------------
	local any_script_has_spaces = false
	local has_redundant_head_param = false

	for _, head in ipairs(data.heads) do
		------ 6a. 누락된 표제어 채우기
		if not head.term then
			head.term = default_head
		elseif head.term == default_head then
			has_redundant_head_param = true
		elseif head.term:find("^[!?]$") then
			head.term = default_head .. head.term
		end

		if is_reconstructed then
			local head_term = remove_links(head.term)
			if head_term:sub(1, 1) ~= "*" then
				error("재구된 표제어 '" .. head_term .. "'는 재구되었음을 나타내기 위해 '*'로 시작해야 합니다.")
			end
		end

		------ 6b. 스크립트 감지
		local auto_sc = data.lang:findBestScript(head.term)
		if auto_sc:getCode() == "None" and find_best_script_without_lang(head.term):getCode() ~= "None" then
			insert(data.categories, "비표준 문자가 포함된 " .. full_langname .. " 낱말")
		end

		if not (head.sc or data.sc) then
			head.sc = auto_sc
		else
			if not head.sc then
				head.sc = data.sc
			end
			if head.sc:getCode() == auto_sc:getCode() then
				insert(data.categories, "중복된 문자 체계 코드가 있는 " .. full_langname .. " 낱말")
			else
				insert(data.categories, "수동으로 지정된 비중복 문자 체계 코드가 있는 " .. full_langname .. " 낱말")
			end
		end

		if head.sc:hasNormalizationFixes() then
			local composed_head = toNFC(head.term)
			if head.sc:fixDiscouragedSequences(composed_head) ~= composed_head then
				insert(data.whole_page_categories, "권장되지 않는 문자 시퀀스를 사용하는 페이지")
			end
		end

		any_script_has_spaces = any_script_has_spaces or head.sc:hasSpaces()

		any_script_has_spaces = any_script_has_spaces or head.sc:hasSpaces()

		------ 6c. 자동 음역 생성
		head.tr_manual = nil
		if head.tr == "-" then
			head.tr = nil
		else
			local notranslit = (m_data or get_data()).notranslit
			if not (notranslit[langcode] or notranslit[full_langcode]) and head.sc:isTransliterated() then
				head.tr_manual = not not head.tr

				local text = not data.lang:link_tr(head.sc) and remove_links(head.term) or head.term
				local automated_tr, tr_categories = data.lang:transliterate(text, head.sc)

				if automated_tr then
					if head.tr then
						if remove_links(head.tr) == remove_links(automated_tr) then
							insert(data.categories, "중복된 로마자 표기가 있는 " .. full_langname .. " 낱말")
						else
							insert(data.categories, "수동으로 지정된 비중복 로마자 표기가 있는 " .. full_langname .. " 낱말")
						end
					else
						head.tr = automated_tr
						if type(tr_categories) == "table" then
							extend(data.categories, tr_categories)
						end
					end
				end

				if not head.tr then
					head.tr = "<small>로마자 표기 필요</small>"
					insert(data.categories, "로마자 표기가 요청된 " .. full_langname .. " 낱말")
				else
					head.tr = trim(head.tr)
				end
			end
		end

		if head.tr and data.lang:link_tr(head.sc) then
			head.tr = full_link{
				term = head.tr,
				lang = data.lang,
				sc = get_script("Latn"),
				tr = "-"
			}
		end
	end

	------------ 7. DISPLAYTITLE로 제목 태그 지정 ------------
	local display_title
	local dt_script = data.heads[1].sc
	local dt_script_code = dt_script:getCode()
	local page_non_ascii = namespace == "" and not page.pagename:find("^[%z\1-\127]+$")
	local unsupported_pagename, unsupported = page.full_raw_pagename:gsub("^Unsupported titles/", "")
	if unsupported == 1 and page.unsupported_titles[unsupported_pagename] then
		display_title = 'Unsupported titles/<span class="' .. dt_script_code .. '">' .. page.unsupported_titles[unsupported_pagename] .. '</span>'
	elseif page_non_ascii and (m_data or get_data()).toBeTagged[dt_script_code]
		or (dt_script_code == "Jpan" and (text_in_script(page.pagename, "Hira") or text_in_script(page.pagename, "Kana")))
		or (dt_script_code == "Kore" and text_in_script(page.pagename, "Hang")) then
		display_title = '<span class="' .. dt_script_code .. '">' .. page.full_raw_pagename .. '</span>'
	elseif page_non_ascii and (dt_script_code == "Hant" or dt_script_code == "Hans") then
		display_title = '<span class="Hani">' .. page.full_raw_pagename .. '</span>'
	elseif namespace == "Reconstruction" then
		local matched
		display_title, matched = ugsub(
			page.full_raw_pagename,
			"^(Reconstruction:[^/]+/)(.+)$",
			function(before, term)
				return before .. tag_text(term, data.lang, dt_script)
			end
		)
		if matched == 0 then
			display_title = nil
		end
	end

	if (dt_script_code == "ur-Arab" or dt_script_code == "ku-Arab" or dt_script_code == "pa-Arab") and page.L2_list.n > 1 then
		display_title = nil
	end

	if display_title then
		mw.getCurrentFrame():callParserFunction("DISPLAYTITLE", display_title)
	end

	------------ 8. Insert additional categories. ------------

	if data.force_cat_output then
		track("force cat output")
	end

	if has_redundant_head_param and not data.no_redundant_head_cat then
		insert(data.categories, "중복된 head 매개변수가 있는 " .. full_langname .. " 낱말")
	end

	if not data.nomultiwordcat and any_script_has_spaces and postype == "기본형" then
		local no_multiword_cat = (m_data or get_data()).no_multiword_cat
		if not (no_multiword_cat[langcode] or no_multiword_cat[full_langcode]) then
			local no_hyphen = (m_data or get_data()).hyphen_not_multiword_sep
			local checkpattern = (no_hyphen[langcode] or no_hyphen[full_langcode]) and ".[%s፡]." or ".[%s%-፡]."
			if umatch(page.pagename, checkpattern) and not non_categorizable(page.full_raw_pagename) then
				insert(data.categories, full_langname .. " 다어 낱말")
			end
		end
	end

	if data.sccat then
		for _, head in ipairs(data.heads) do
			insert(data.categories, head.sc:getDisplayForm() .. "의 " .. full_langname .. " " ..
				data.pos_category)
		end
	end

	-- Reconstructed terms often use weird combinations of scripts and realistically aren't spelled so much as notated.
	if namespace ~= "Reconstruction" then
		local characters_to_ignore = {
			["aaq"] = "α", -- Penobscot
			["acy"] = "δθ", -- Cypriot Arabic
			["anc"] = "γ", -- Ngas
			["aou"] = "χ", -- A'ou
			["awg"] = "β", -- Anguthimri
			["bhp"] = "β", -- Bima
			["byk"] = "θ", -- Biao
			["cdy"] = "θ", -- Chadong
			["clm"] = "χ", -- Klallam
			["col"] = "χ", -- Colombia-Wenatchi
			["coo"] = "χ", -- Comox; FIXME: others? E.g. Greek theta (θ)?
			["ets"] = "θ", -- Yekhee
			["gmw-gts"] = "χ", -- Gottscheerish
			["hur"] = "θ", -- Halkomelem
			["izh"] = "ь", -- Ingrian
			["kic"] = "θ", -- Kickapoo
			["lil"] = "χ", -- Lillooet
			["mhz"] = "β", -- Mor (Austronesian)
			["neg"]=  "ӡ", -- Negidal (normally in Cyrillic)
			["oui"] = "γβ", -- Old Uyghur: FIXME: others? E.g. Greek delta (δ)?
			["pox"] = "χ", -- Polabian
			["rom"] = "Θθ", -- Romani: International Standard; two different thetas???
			["sah"] = "ь", -- Yakut (1929 - 1939 Latin spelling)
			["sjw"] = "θ", -- Shawnee
			["squ"] = "χ", -- Squamish
			["str"] = "χθ", -- Saanich; uses two Greek letters
			["twa"] = "χ", -- Twana
			["yha"] = "θ", -- Baha
			["za"] = "зч", -- Zhuang; 1957-1982 alphabet used two Cyrillic letters (as well as some others like
						   -- ƃ, ƅ, ƨ, ɯ and ɵ that look like Cyrillic or Greek but are actually Latin)
			["zlw-slv"] = "χђћ", -- Slovincian; FIXME: χ is Greek, the other two are Cyrillic, but I'm not sure
								 -- the currect characters are being chosen in the entry names
			["zng"] = "θ", -- Mang
		}
		-- Determine how many real scripts are found in the pagename, where we exclude symbols and such. We exclude
		-- scripts whose `character_category` is false as well as Zmth (mathematical notation symbols), which has a
		-- category of "Mathematical notation symbols". When counting scripts, we need to elide language-specific
		-- variants because e.g. Beng and as-Beng have slightly different characters but we don't want to consider them
		-- two different scripts (e.g. [[এৰ]] has two characters which are detected respectively as Beng and as-Beng).
		local seen_scripts = {}
		local num_seen_scripts = 0
		local canon_pagename = page.pagename
		local ch_to_ignore = characters_to_ignore[full_langcode]
		if ch_to_ignore then
			canon_pagename = ugsub(canon_pagename, "[" .. ch_to_ignore .. "]", "")
		end
		local num_loops = 0
		while canon_pagename ~= "" and num_seen_scripts < 2 and num_loops < 10 do
			num_loops = num_loops + 1
			local pagename_script = find_best_script_without_lang(canon_pagename, "None only as last resort")
			local script_chars = pagename_script.characters
			if not script_chars then break end
			local script_code = pagename_script:getCode()
			local replaced
			canon_pagename, replaced = ugsub(canon_pagename, "[" .. script_chars .. "]", "")
			if replaced and script_code ~= "Zmth" and (script_data or get_script_data())[script_code] and
				script_data[script_code].character_category ~= false then
				script_code = script_code:gsub("^.-%-", "")
				if not seen_scripts[script_code] then
					seen_scripts[script_code] = true
					num_seen_scripts = num_seen_scripts + 1
				end
			end
		end

		if num_seen_scripts > 1 then
			insert(data.categories, "여러 문자 체계로 쓰인 " .. full_langname .. " 낱말")
		end
	end
	
	-- Categorise for unusual characters. Takes into account combining characters, so that we can categorise for characters with diacritics that aren't encoded as atomic characters (e.g. U̠). These can be in two formats: single combining characters (i.e. character + diacritic(s)) or double combining characters (i.e. character + diacritic(s) + character). Each can have any number of diacritics.
	local standard = data.lang:getStandardCharacters()
	if standard and not non_categorizable(page.full_raw_pagename) then
		local function char_category(char)
			local specials = {
				["#"] = "해시 기호",
				["("] = "소괄호",
				[")"] = "소괄호",
				["<"] = "부등호",
				[">"] = "부등호",
				["["] = "대괄호",
				["]"] = "대괄호",
				["_"] = "밑줄",
				["{"] = "중괄호",
				["|"] = "수직선",
				["}"] = "중괄호",
				["ß"] = "ẞ",
				["\205\133"] = "", -- this is UTF-8 for U+0345 ( ͅ)
				["\239\191\189"] = "대체 문자",
			}
			char = toNFD(char)
				:gsub(".[\128-\191]*", function(m)
					local new_m = specials[m]
					new_m = new_m or m:uupper()
					return new_m
				end)
			return toNFC(char)
		end
		if full_langcode ~= "hi" and full_langcode ~= "lo" then
			local standard_chars_scripts = {}
			for _, head in ipairs(data.heads) do
				standard_chars_scripts[head.sc:getCode()] = true
			end
			-- Iterate over the scripts, in case there is more than one (as they can have different sets of standard characters).
			for code in pairs(standard_chars_scripts) do
				local sc_standard = data.lang:getStandardCharacters(code)
				if sc_standard then
					if page.pagename_len > 1 then
						local explode_standard = {}
						local function explode(char)
							explode_standard[char] = true
							return ""
						end
						local sc_standard = ugsub(sc_standard, page.comb_chars.combined_double, explode)
						sc_standard = ugsub(sc_standard,page.comb_chars.combined_single, explode)
							:gsub(".[\128-\191]*", explode)
						local num_cat_inserted
						for char in pairs(page.explode_pagename) do
							if not explode_standard[char] then
								if char:find("[0-9]") then
									if not num_cat_inserted then
										insert(data.categories, "숫자가 포함된 " .. full_langname .. " 낱말")
										num_cat_inserted = true
									end
								elseif ufind(char, page.emoji_pattern) then
									insert(data.categories, "이모지가 포함된 " .. full_langname .. " 낱말")
								else
									local upper = char_category(char)
									if not explode_standard[upper] then
										char = upper
									end
									insert(data.categories, char .. "가 포함된 " .. full_langname .. " 낱말")
								end
							end
						end
					end
					-- If a diacritic doesn't appear in any of the standard characters, also categorise for it generally.
					sc_standard = toNFD(sc_standard)
					for diacritic in ugmatch(page.decompose_pagename, page.comb_chars.diacritics_single) do
						if not umatch(sc_standard, diacritic) then
							insert(data.categories, "◌" .. diacritic .. " 발음 구별 기호가 포함된 " .. full_langname .. " 낱말")
						end
					end
					for diacritic in ugmatch(page.decompose_pagename, page.comb_chars.diacritics_double) do
						if not umatch(sc_standard, diacritic) then
							insert(data.categories, "◌" .. diacritic .. "◌ 발음 구별 기호가 포함된 " .. full_langname .. " 낱말")
						end
					end
				end
			end
		-- Ancient Greek, Hindi and Lao handled the old way for now, as their standard chars still need to be converted to the new format (because there are a lot of them).
		elseif ulen(page.pagename) ~= 1 then
			for character in ugmatch(page.pagename, "([^" .. standard .. "])") do
				local upper = char_category(character)
				if not umatch(upper, "[" .. standard .. "]") then
					character = upper
				end
				insert(data.categories, character .. "가 포함된 " .. full_langname .. " 낱말")
			end
		end
	end
	
	if data.heads[1].sc:isSystem("alphabet") then
		local pagename, i = page.pagename:ulower(), 2
		while umatch(pagename, "(%a)" .. ("%1"):rep(i)) do
			i = i + 1
			insert(data.categories, "동일한 글자가 " .. i .. "번 연속된 " .. full_langname .. " 낱말")
		end
	end
	
	if not data.nopalindromecat and namespace ~= "Reconstruction" and ulen(page.pagename) > 2
		and is_palindrome(page.pagename, data.lang, data.heads[1].sc) then
		insert(data.categories, full_langname .. " 회문")
	end

	if namespace == "" and not data.lang:hasType("reconstructed") then
		for _, head in ipairs(data.heads) do
			if page.full_raw_pagename ~= get_link_page(remove_links(head.term), data.lang, head.sc) then
				track("pagename spelling mismatch", data.lang)
				break
			end
		end
	end

	export.maintenance_cats(page, data.lang, data.categories, data.whole_page_categories)

	------------ 9. 최종 결과 형식화 및 반환 ------------
	local text = '<span class="headword-line">' ..
		format_headword(data) ..
		format_headword_genders(data) ..
		format_inflections(data) .. '</span>'

	local cats = format_categories(
		data.categories, data.lang, data.sort_key, page.encoded_pagename,
		data.force_cat_output or test_force_categories, data.heads[1].sc
	)
	local whole_page_cats = format_categories(
		data.whole_page_categories, nil, "-"
	)
	return text .. cats .. whole_page_cats
end

return export