본문으로 이동

모듈:category tree/wiktionary users

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

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

local raw_categories = {}
local raw_handlers = {}

local concat = table.concat
local insert = table.insert
local unpack = unpack or table.unpack -- Lua 5.2 호환성

local string_utilities_module = "Module:string utilities"


-----------------------------------------------------------------------------
--                                                                         --
--                              원시(RAW) 분류                             --
--                                                                         --
-----------------------------------------------------------------------------


raw_categories["위키낱말사전"] = {
	description = "위키낱말사전과 그 운영에 관한 자료를 위한 상위 분류.",
	parents = "기본",
}

raw_categories["위키낱말사전 통계"] = {
	description = "위키낱말사전 사용 방식에 대한 통계를 담은 분류 및 문서.",
	parents = {"위키낱말사전", sort = "통계"},
}

raw_categories["위키낱말사전 사용자"] = {
	description = "사용자 권한에 따라 위키인을 나열하는 문서와, 언어 및 코딩 능력에 따라 위키인을 나열하는 분류.",
	breadcrumb = "사용자",
	additional = "자동으로 생성된 모든 사용자 목록은 [[특수:사용자목록]]을 참고하십시오.",
	parents = {"위키낱말사전", sort = "사용자"},
}

raw_categories["WMF가 차단한 위키인"] = {
	description = "[[m:WMF Global Ban Policy|WMF 전역 차단 정책]]에 따라 [[m:Wikimedia Foundation|위키미디어 재단]]으로부터 [[m:Global bans|전역 차단]]을 받은 사용자.",
	breadcrumb = "WMF에 의해 차단됨",
	parents = "위키낱말사전 사용자",
}

raw_categories["사용자 언어"] = {
	description = "언어 능력에 따라 위키인을 나열하는 분류.",
	parents = {
		"위키낱말사전 사용자",
		"분류:위키낱말사전 다중언어 문제",
	},
}

raw_categories["잘못된 코드의 사용자 언어"] = {
	description = "언어 능력에 따라 위키인을 나열하지만, 위키낱말사전에서 유효하지 않은 언어 코드를 사용하는 분류.",
	additional = "이 코드 대부분은 유효한 ISO 639-3 코드이지만, 언어의 분리 및 병합에 대한 다른 선택 등 다양한 이유로 위키낱말사전에서는 유효하지 않습니다.",
	parents = {name = "사용자 언어", sort = " "},
}

raw_categories["사용자 문자 체계"] = {
	description = "주어진 문자 체계를 읽는 능력에 따라 위키인을 나열하는 분류.",
	parents = {
		"위키낱말사전 사용자",
		"분류:위키낱말사전 다중언어 문제",
	},
}

raw_categories["사용자 코더"] = {
	description = "코딩 능력에 따라 위키인을 나열하는 분류.",
	parents = "위키낱말사전 사용자",
}

raw_categories["사용자 어족"] = {
	description = "주어진 어족에 대한 지식에 따라 위키인을 나열하는 분류.",
	parents = "위키낱말사전 사용자"
}

raw_categories["항목이 있는 문서"] = {
	description = "언어 항목을 포함하는 문서.",
	additional = "이 분류 내의 하위 분류들은 위키낱말사전의 총 항목 수를 결정하는 데 사용됩니다.",
	parents = "위키낱말사전",
	can_be_empty = true,
	hidden = true,
}

raw_categories["Redirects connected to a Wikidata item"] = {
	description = "Redirect pages which are connected to a [[d:|Wikidata]] item.",
	additional = "These are rarely needed, but are occasionally useful following a page merger, where other wikis may still separate the two.",
	parents = "Wiktionary statistics",
	can_be_empty = true,
	hidden = true,
}

raw_categories["Unsupported titles"] = {
	description = "Pages with titles that are not supported by the MediaWiki software.",
	additional = "For an explanation of the reasons why certain titles are not supported, see [[Appendix:Unsupported titles]].",
	parents = "Wiktionary",
	can_be_empty = true,
	hidden = true,
}

-- Tracked according to [[phab:T347324]].
for ext, data in pairs {
	["DynamicPageList"] = {"DynamicPageList (Wikimedia)", "T287380"},
	["EasyTimeline"] = {"EasyTimeline", "T137291"},
	["Graph"] = {"Graph", "T334940"},
	["JsonConfig"] = {"JsonConfig"},
	["Kartographer"] = {"Kartographer"},
	["Phonos"] = {"Phonos"},
	["Score"] = {"Score"},
	["WikiHiero"] = {"WikiHiero", "T344534"},
} do
	local link, phab = unpack(data)
	raw_categories["Pages using the " .. ext .. " extension"] = {
		description = ("Pages which make use of the [[mw:Extension:%s|%s]] extension."):format(link, ext),
		additional = phab and ("See [[phab:%s|%s]] on Phabricator for background information on why this extension is tracked."):format(phab, phab) or nil,
		breadcrumb = ("Using the %s extension"):format(ext),
		parents = "Wiktionary statistics",
		can_be_empty = true,
		hidden = true,
	}
end

-----------------------------------------------------------------------------
--                                                                         --
--                              원시(RAW) 핸들러                           --
--                                                                         --
-----------------------------------------------------------------------------


local function get_level_params(data)
	local speak_verb = "구사합니다"
	if data.typ == "lang" then
		local is_sign_language = data.obj and data.obj:getFamilyCode() and data.obj:getFamilyCode():find("^sgn") or data.langfam:find("수화$")
		speak_verb = data.args.verb or is_sign_language and "사용합니다" or "구사합니다"
	end
	return {
		["-"] = {
			cssclass = "babel-3",
			lang = "이 사용자는 !LANGFAM!을(를) " .. speak_verb .. ".",
			script = "이 사용자는 !SCRIPT!을(를) 읽을 수 있습니다.",
			coder = "이 사용자는 !LANGFAM!으로 코딩하는 법을 압니다.",
			family = "이 사용자는 !LANGFAM!을(를) 압니다.",
		},
		["1"] = {
			cssclass = "babel-1",
			lang = "이 사용자는 !LANGFAM!을(를) '''기초적인''' 수준으로 " .. speak_verb .. ".",
			script = "이 사용자는 !SCRIPT!을(를) '''기초적인''' 수준으로 읽을 수 있습니다.",
			coder = "이 사용자는 !LANGFAM! 코드 작성의 '''기초'''를 알고 간단한 수정을 할 수 있습니다.",
			family = "이 사용자는 !LANGFAM!에 기여하는 것의 '''기초'''를 압니다.",
		},
		["2"] = {
			cssclass = "babel-2",
			lang = "이 사용자는 !LANGFAM!을(를) '''중급''' 수준으로 " .. speak_verb .. ".",
			script = "이 사용자는 !SCRIPT!을(를) '''중급''' 수준으로 읽을 수 있습니다.",
			coder = "이 사용자는 !LANGFAM!을(를) '''상당히 다룰 줄 알며''', 다른 사람이 작성한 일부 스크립트를 이해할 수 있습니다.",
			family = "이 사용자는 !LANGFAM!에 '''상당히 익숙합니다'''.",
		},
		["3"] = {
			cssclass = "babel-3",
			lang = "이 사용자는 !LANGFAM!을(를) '''고급''' 수준으로 " .. speak_verb .. ".",
			script = "이 사용자는 !SCRIPT!을(를) '''고급''' 수준으로 읽을 수 있습니다.",
			coder = "이 사용자는 '''더 복잡한''' !LANGFAM! 코드를 작성할 수 있으며, 다른 사람이 작성한 대부분의 스크립트를 이해하고 수정할 수 있습니다.",
			family = "이 사용자는 !LANGFAM!에 '''정기적으로''' 기여합니다.",
		},
		["4"] = {
			cssclass = "babel-4",
			lang = "이 사용자는 !LANGFAM!을(를) '''준원어민''' 수준으로 " .. speak_verb .. ".",
			script = "이 사용자는 !SCRIPT!을(를) '''준원어민''' 수준으로 읽을 수 있습니다.",
			coder = "이 사용자는 '''매우 복잡한''' !LANGFAM! 코드를 작성하고 이해할 수 있습니다.",
		},
		["5"] = {
			cssclass = "babel-5",
			lang = "이 사용자는 !LANGFAM!을(를) '''전문가''' 수준으로 " .. speak_verb .. ".",
			script = "이 사용자는 !SCRIPT!을(를) '''전문가''' 수준으로 읽을 수 있습니다.",
			coder = "이 사용자는 !LANGFAM! 코드를 '''전문가''' 수준으로 작성하고 이해할 수 있습니다.",
		},
		["N"] = {
			cssclass = "babel-N",
			lang = "이 사용자는 !LANGFAM!의 '''원어민'''입니다.",
			script = "이 사용자의 '''모어 문자'''는 !SCRIPT!입니다.",
		},
	}
end


local coder_links = {
	Asm = "w:ko:어셈블리어",
	Bash = "w:ko:Bash",
	C = "w:ko:C (프로그래밍 언어)",
	["C++"] = "w:ko:C++",
	["C Sharp"] = {link = "w:ko:C 샤프", lang = "C#"},
	CSS = "w:ko:CSS",
	Go = "w:ko:Go (프로그래밍 언어)",
	Haskell = "w:ko:하스켈 (프로그래밍 언어)",
	HTML = "w:ko:HTML",
	Java = "w:ko:자바 (프로그래밍 언어)",
	JavaScript = "w:ko:자바스크립트",
	Lua = "위키낱말사전:스크립토",
	Perl = "w:ko:Perl",
	PHP = "w:ko:PHP",
	Python = "w:ko:파이썬",
	regex = {link = "w:ko:정규 표현식", name = "정규 표현식"},
	Ruby = "w:ko:루비 (프로그래밍 언어)",
	Rust = "w:ko:러스트 (프로그래밍 언어)",
	SQL = "w:ko:SQL",
	template = {link = "위키낱말사전:틀", name = "위키 틀"},
	TypeScript = "w:ko:타입스크립트",
}

local custom_script_links = {
	IPA = "w:ko:국제 음성 기호",
	UPA = "w:ko:우랄 음성 기호",
}

-- Generic implementation of competency handler for (natural) languages, scripts, families and "coders" (= programming languages).
local function competency_handler(data)
	local category = data.category
	local langtext = data.langtext
	local typ = data.typ
	local args = data.args
	local code = data.code
	local langfam = data.langfam
	local langfamcat = data.langfamcat
	local script = data.script
	local scriptcat = data.scriptcat
	local level = data.level
	local parents = data.parents
	local addl_parents = data.addl_parents
	local topright = data.topright
	local data_addl = data.additional
	local inactive = data.inactive

	local parts = {}
	local function ins(txt)
		insert(parts, txt)
	end
	local level_params = get_level_params(data)

	local params = level_params[level or "-"]
	if not params then
		error(("Internal error: No params for for code '%s', level %s"):format(code, level or "-"))
	end
	local function insert_text()
		if langtext then
			ins(langtext)
			ins("<hr />")
		end
		if not params[typ] then
			error(("No English text for code '%s', type '%s', level %s"):format(code, typ, level or "-"))
		end
		local pattern, repl
		if typ == "script" then
			pattern = "!SCRIPT!"
			repl = ("'''" .. scriptcat .. "'''"):format(script)
		else
			pattern = "!LANGFAM!"
			repl = ("'''" .. langfamcat .. "'''"):format(langfam)
			if script then
				repl = repl .. (" written in '''" .. scriptcat .. "'''"):format(script)
			end
		end
		ins(params[typ]:gsub(pattern, repl))
	end

	local additional = {}
	if level then
		insert(additional, ("이 목록에 포함되려면 사용자 문서에 {{틀|바벨|%s}}을(를) 추가하십시오. 전체 설명은 [[위키낱말사전:바벨]]에서 확인할 수 있습니다.")
			:format(level == "N" and code or ("%s-%s"):format(code, level)))
	else
		insert(additional, "이 목록에 포함되려면 사용자 문서에 {{틀|바벨}}을(를) 사용하십시오. 전체 설명은 [[위키낱말사전:바벨]]에서 확인할 수 있습니다.")
	end

	if inactive then
		insert(additional, "'''참고:''' 이 분류의 사용자는 적어도 2년 동안 한국어 위키낱말사전에서 활동하지 않았으며, [[위키낱말사전:투표/pl-2017-04/Removing inactive editors from user-proficiency categories]]에 따라 '비활동' 상태로 이동되었습니다.")
	end
	
	if addl_parents then
		for _, addl_parent in ipairs(addl_parents) do
			insert(parents, addl_parent)
		end
	end
	
	if data_addl then
		insert(additional, data_addl)
	end

	local babel_templatestyles = require("Module:TemplateStyles")("Template:Babel/style.css")
	if level then
		ins(('<div class="babel-box %s">'):format(params.cssclass))
		ins('<table class="babel-content" style="width:238px"><tr>')
		ins('<td class="babel-code" style="font-size:14pt">')
		ins(("'''%s-%s'''</td>"):format(code, level))
		ins('<td class="babel-text">')
		insert_text()
		ins('</td></tr></table></div><br clear="left">')

		return {
			description = concat(parts) .. babel_templatestyles,
			additional = concat(additional, "\n\n"),
			breadcrumb = inactive and "Inactive" or "레벨 " .. level,
			parents = parents,
		}, not not args
	else
		ins(('<div class="babel-box %s">\n'):format(params.cssclass))
		ins('{| class="babel-content" style="width:260px;"\n')
		ins('| class="babel-code" style="font-size:14pt;" | ')
		ins(("'''%s'''\n"):format(code))
		ins('| class="babel-text" style="text-align:center;" | ')
		insert_text()
		ins('\n|}</div><br clear="left">')

		return {
			topright = topright,
			description = concat(parts) .. babel_templatestyles,
			additional = concat(additional, "\n\n"),
			breadcrumb = inactive and "Inactive" or lang,
			parents = parents,
		}, not not args
	end
end

-- Guts of implementation of competency handlers for natural languages (full or etymology-only), possibly with a
-- script attached (e.g. [[Category:User ko-Kore]]).
local function handle_user_lang_maybe_script(data, category, inactive, code, sccode, level, args)
	local lang = require("Module:languages").getByCode(code, nil, "allow etym")
	local langname = args.langname
	local sc, scriptname
	if sccode then
		sc = require("Module:scripts").getByCode(sccode)
		scriptname = args.scriptname
	end
	local code_with_script = code .. (sccode and "-" .. sccode or "")

	if not lang or sccode and not sc then
		-- If unrecognized language and called from inside, we're handling the parents and breadcrumb for a
		-- higher-level category, so at least return something.
		if not level and data.called_from_inside then
			return {
				-- FIXME, scrape langname= category?
				breadcrumb = {name = code_with_script, nocap = true},
				parents = {name = lang and "User languages with invalid script code" or
					"User languages with invalid code", sort = code_with_script}
			}, true
		end
		
		if not langname then
			-- Check if the code matches a Wikimedia language (e.g. "ku" for Kurdish). If it does, treat
			-- its canonical name as though it had been given as langname=.
			local wm_lang = require("Module:wikimedia languages").getByCode(code)
			if not wm_lang then
				mw.log(("Skipping category '%s' because lang code '%s' is unrecognized and langname= not given"):
					format(data.category, code))
				return
			end
			langname = wm_lang:getCanonicalName()
		end
		if sccode and not sc and not scriptname then
			mw.log(("Skipping category '%s' because script code '%s' is unrecognized and scriptname= not given"):
				format(data.category, sccode))
			return
		end
	end

	if not langname then
		if not lang then
			error("Internal error: Something went wrong, undefined lang= should have been caught above")
		end
		langname = lang:getCanonicalName()
	end
	if not scriptname and sccode then
		if not sc then
			error("Internal error: Something went wrong, undefined sc= should have been caught above")
		end
		-- Use `getCategoryName` not `getCanonicalName` to display 'Foo script' than just 'Foo', as so many scripts
		-- are the same as language names, and otherwise we get nonsensical output like "These users are native speakers
		-- of Korean written in Korean".
		scriptname = sc:getCategoryName()
	end

	-- Insert text, appropriately script-tagged, unless already script-tagged (we check for '<span'), in which case we
	-- insert it directly. Also handle <<...>> and <<<...>>> in text and convert to bolded link to parent category.
	local function wrap(txt)
		if not txt then
			return
		end
		if sccode then
			-- Substitute <<<...>>> (where ... is supposed to be the native rendering of the script) with a link to the
			-- top-level 'User SCRIPT' category (e.g. [[:Category:User Kore]] if we're in a sublevel category, or to the
			-- top-level script category (e.g. [[:Category:Korean script]]) if we're in a top-level 'User CODE-SCRIPT'
			-- category.
			txt = txt:gsub("<<<(.-)>>>", function(inside)
				if level then
					return ("'''[[:분류:사용자 %s|%s]]'''"):format(sccode, inside)
				elseif sc then
					return ("'''[[:분류:%s|%s]]'''"):format(sc:getCategoryName(), inside)
				else
					return ("'''%s'''"):format(inside)
				end
			end)
		end
		-- Substitute <<...>> (where ... is supposed to be the native rendering of the language) with a link to the
		-- top-level 'User CODE' category (e.g. [[:Category:User fr]] or [[:Category:User fr-CA]]) if we're in a
		-- sublevel category, or to the top-level language category (e.g. [[:Category:French language]] or
		-- [[:Category:Canadian English]]) if we're in a top-level 'User CODE' category.
		txt = txt:gsub("<<(.-)>>", function(inside)
			if level then
				return ("'''[[:분류:사용자 %s|%s]]'''"):format(code, inside)
			elseif lang then
				return ("'''[[:분류:%s|%s]]'''"):format(lang:getCategoryName(), inside)
			else
				return ("'''%s'''"):format(inside)
			end
		end)
		if txt:find("<span") or not lang then
			return txt
		else
			return require("Module:script utilities").tag_text(txt, lang, sc)
		end
	end

	local function get_request_cats()
		if args.text or code == "ko" or code:find("^ko%-") then
			return
		end
		local num_pages = mw.site.stats.pagesInCategory(data.category, "pages")
		local count_cat, count_sort
		if num_pages == 0 then
			count_cat = "사용자가 없는 사용자 능력 분류 번역 요청"
			count_sort = "*" .. code_with_script
		elseif num_pages == 1 then
			count_cat = "사용자가 1명인 사용자 능력 분류 번역 요청"
			count_sort = "*" .. code_with_script
		else
			local lowernum, uppernum
			lowernum = 2
			while true do
				uppernum = lowernum * 2 - 1
				if num_pages <= uppernum then
					break
				end
				lowernum = lowernum * 2
			end
			count_cat = ("사용자가 %s-%s명인 사용자 능력 분류 번역 요청"):format(
				lowernum, uppernum)
			count_sort = "*" .. ("%0" .. #(tostring(uppernum)) .. "d"):format(num_pages)
		end

		local addl_parents = {}
		insert(addl_parents, {
			name = "언어별 사용자 능력 분류 번역 요청",
			sort = code_with_script,
		})
		insert(addl_parents, {
			name = count_cat,
			sort = count_sort,
		})
		return addl_parents
	end

	local invalid_lang_warning
	if not lang then
		invalid_lang_warning = "'''경고''': 지정된 언어 코드는 위키낱말사전에서 유효하지 않습니다. 모든 역량 등급을 가장 가까운 유효한 코드로 이전하십시오."
	end

	local parents
	if level then
		parents = {("사용자 %s"):format(code_with_script), sort = level}
	elseif sccode then
		parents = {}
		insert(parents, {name = ("사용자 %s"):format(code), sort = sccode})
		insert(parents, {name = ("사용자 %s"):format(sccode), sort = code})
	elseif lang then
		parents = {}
		if lang:hasType("etymology-only") then
			local full_code = lang:getFullCode()
			local sort_key = code:gsub(("^%s%%-"):format(require(string_utilities_module).pattern_escape(full_code)),
				"")
			insert(parents,	{name = ("사용자 %s"):format(full_code), sort = sort_key})
		else
			insert(parents, {name = "사용자 언어", sort = code})
		end
		insert(parents, {name = lang:getCategoryName(), sort = "사용자"})
	else
		parents = {"User languages with invalid code", sort = code}
	end
	local addl_parents = get_request_cats()

	local topright
	if args.commonscat then
		local commonscat = require("Module:yesno")(args.commonscat, "+")
		if commonscat == "+" or commonscat == true then
			commonscat = data.category
		end
		if commonscat then
			topright = ("{{commonscat|%s}}"):format(commonscat)
		end
	end

	local langcat
	if level then
		langcat = ("[[:분류:사용자 %s|%%s]]"):format(code)
	elseif lang then
		langcat = ("[[:분류:%s|%%s]]"):format(lang:getCategoryName())
	else
		langcat = "[[%s]]"
	end

	local scriptcat
	if sccode then
		if level then
			scriptcat = ("[[:분류:사용자 %s|%%s]]"):format(sccode)
		elseif sc then
			scriptcat = ("[[:분류:%s|%%s]]"):format(sc:getCategoryName())
		else
			scriptcat = "[[%s]]"
		end
	end

	return competency_handler {
		category = category,
		inactive = inactive,
		langtext = wrap(args.text),
		typ = "lang",
		args = args,
		obj = lang,
		code = code_with_script,
		langfam = langname,
		langfamcat = langcat,
		script = scriptname,
		scriptcat = scriptcat,
		level = level,
		parents = parents,
		addl_parents = addl_parents,
		topright = topright,
		additional = invalid_lang_warning,
	}
end

-- Hander for categories named [[Category:User LANG-SCRIPT]] or [[Category:User LANG-SCRIPT-#]] where # is a
-- competency level (0 through 5 or N), e.g. [[Category:zh-Hans]] or [[Category:yue-Hant-N]]. It's a bit tricky because
-- of the multitude of language formats, e.g. ko-KP is a language code (etym variety) but ko-Kore is a combination
-- lang + script code. We depend on the fact that all script codes are currently of the form Xxxx or Xxxxx, and check
-- for that first. We also need to run prior to the lang-only handler (next handler) so it doesn't try to interpret
-- the script code as an etym variant code.
--
-- Note that there are current categories named things like 'zh-Hant-TW' and 'zh-Hant-HK-3', which we don't support.
-- They should be renamed to some supported code, e.g. 'cmn-TW-Hant' and 'yue-HK-Hant-3'.
insert(raw_handlers, function(data)
	local category, inactive = data.category:match("^(.*) (%(inactive%))$")
	category = category or data.category
	local code, sccode, level = category:match("^사용자 ([a-z][a-z][a-z]?)%-([A-Z][a-z][a-z][a-z][a-z]?)%-([0-5N])$")
	if not code then
		code, sccode, level =
			category:match("^사용자 ([a-z][a-z][a-z]?%-[a-zA-Z-]+)%-([A-Z][a-z][a-z][a-z][a-z]?)%-([0-5N])$")
	end
	if not code then
		code, sccode = category:match("^사용자 ([a-z][a-z][a-z]?)%-([A-Z][a-z][a-z][a-z][a-z]?)$")
	end
	if not code then
		code, sccode = category:match("^사용자 ([a-z][a-z][a-z]?%-[a-zA-Z-]+)%-([A-Z][a-z][a-z][a-z][a-z]?)$")
	end
	if not code then
		return
	end

	local args = require("Module:parameters").process(data.args, {
		text = true,
		verb = true,
		langname = true,
		scriptname = true,
		scname = {alias_of = "scriptname"},
		commonscat = true,
	})
	
	return handle_user_lang_maybe_script(data, category, inactive, code, sccode, level, args)
end)

-- Hander for categories named [[Category:User LANG]] e.g. [[Category:User en]], [[Category:User en-US]],
-- [[Category:User ine-pro]] or [[Category:User LANG-#]] where # is a competency level (0 through 5 or N) e.g.
-- [[Category:User en-N]] or [[Category:User ndl-nl-1]].
insert(raw_handlers, function(data)
	local category, inactive = data.category:match("^(.*) (%(inactive%))$")
	category = category or data.category
	local code, level = category:match("^사용자 ([a-z][a-z][a-z]?)%-([0-5N])$")
	if not code then
		code, level = category:match("^사용자 ([a-z][a-z][a-z]?%-[a-zA-Z-]+)%-([0-5N])$")
	end
	if not code then
		code = category:match("^사용자 ([a-z][a-z][a-z]?)$")
	end
	if not code then
		code = category:match("^사용자 ([a-z][a-z][a-z]?%-[a-zA-Z-]+)$")
	end
	if not code then
		return
	end

	local args = require("Module:parameters").process(data.args, {
		text = true,
		verb = true,
		langname = true,
		commonscat = true,
	})

	return handle_user_lang_maybe_script(data, category, inactive, code, nil, level, args)
end)

-- Handler for scripts.
insert(raw_handlers, function(data)
	local category, inactive = data.category:match("^(.*) (%(inactive%))$")
	category = category or data.category
	local code, level = category:match("^사용자 ([A-Z][a-z][a-z][a-z][a-z]?)%-([0-5N])$")
	if not code then
		code = category:match("^사용자 ([A-Z][a-z][a-z][a-z][a-z]?)$")
	end
	if not code then
		code, level = category:match("^사용자 ([a-z][a-z][a-z]?%-[A-Z][a-z][a-z][a-z][a-z]?)%-([0-5N])$")
	end
	if not code then
		code = category:match("^사용자 ([a-z][a-z][a-z]?%-[A-Z][a-z][a-z][a-z][a-z]?)$")
	end
	if not code then
		return
	end
	local sc = require("Module:scripts").getByCode(code)
	if not sc then
		return
	end

	local parents
	if level then
		parents = {("사용자 %s"):format(code), sort = level}
	else
		parents = {
			{name = "User scripts", sort = code},
			{name = sc:getCategoryName(), sort = "사용자"},
		}
	end

	local scriptcat
	-- Better to display 'Foo script' than just 'Foo', as so many scripts are the same as language names.
	if level then
		scriptcat = ("[[:분류:사용자 %s|%s]]"):format(code, sc:getCategoryName())
	else
		scriptcat = ("[[:분류:%s|%s]]"):format(sc:getCategoryName(), sc:getCategoryName())
	end

	return competency_handler {
		category = category,
		inactive = inactive,
		typ = "script",
		obj = sc,
		code = code,
		script = sc:getCanonicalName(),
		scriptcat = scriptcat,
		level = level,
		parents = parents,
	}
end)

-- Handler for "custom" scripts (e.g. IPA).
insert(raw_handlers, function(data)
	local category, inactive = data.category:match("^(.*) (%(inactive%))$")
	category = category or data.category
	local code, level
	if not code then
		code, level = category:match("^사용자 ([A-Za-z-]+)%-([0-5N])$")
	end
	if not code then
		code = category:match("^사용자 ([A-Za-z-]+)$")
	end
	if not code or not custom_script_links[code] then
		return
	end

	local parents
	if level then
		parents = {("사용자 %s"):format(code), sort = level}
	else
		parents = {"User scripts", sort = code}
	end

	local scriptdata = custom_script_links[code]
	if type(scriptdata) == "string" then
		scriptdata = {link = scriptdata}
	end

	local scriptcat = ("[[%s|%%s]]"):format(scriptdata.link)

	return competency_handler {
		category = category,
		inactive = inactive,
		typ = "script",
		code = code,
		script = scriptdata.script or code,
		scriptcat = scriptcat,
		level = level,
		parents = parents,
	}
end)

-- Handler for programming languages.
insert(raw_handlers, function(data)
	local category, inactive = data.category:match("^(.*) (%(inactive%))$")
	category = category or data.category
	local code, level
	if not code then
		code, level = category:match("^사용자 ([A-Za-z+-]+) coder%-([0-5N])$")
	end
	if not code then
		code = category:match("^사용자 ([A-Za-z+-]+) coder$")
	end
	if not code or not coder_links[code] then
		return
	end

	local parents
	if level then
		parents = {("사용자 %s coder"):format(code), sort = level}
	else
		parents = {"User coders", sort = code}
	end

	local langdata = coder_links[code]
	if type(langdata) == "string" then
		langdata = {link = langdata}
	end

	local langcat = ("[[%s|%%s]]"):format(langdata.link)

	return competency_handler {
		category = category,
		inactive = inactive,
		typ = "coder",
		code = code,
		langfam = langdata.lang or code,
		langfamcat = langcat,
		level = level,
		parents = parents,
	}
end)

-- Handler for language families.
insert(raw_handlers, function(data)
	local category, inactive = data.category:match("^(.*) (%(inactive%))$")
	category = category or data.category
	local code, level = category:match("^사용자 ([a-z][a-z][a-z]?)%-([0-5N])$")
	if not code then
		code, level = category:match("^사용자 ([a-z][a-z][a-z]?%-[a-zA-Z-]+)%-([0-5N])$")
	end
	if not code then
		code = category:match("^사용자 ([a-z][a-z][a-z]?)$")
	end
	if not code then
		code = category:match("^사용자 ([a-z][a-z][a-z]?%-[a-zA-Z-]+)$")
	end
	if not code then
		return
	end
	local fam = require("Module:families").getByCode(code)
	if not fam then
		return
	end

	local parents
	if level then
		parents = {("사용자 %s"):format(code), sort = level}
	else
		parents = {
			{name = "User families", sort = code},
			{name = fam:getCategoryName(), sort = "사용자"},
		}
	end

	local famcat
	if level then
		famcat = ("[[:분류:사용자  %s|%s]]"):format(code, fam:getCategoryName())
	else
		famcat = ("[[:분류:%s|%s]]"):format(fam:getCategoryName(), fam:getCategoryName())
	end

	return competency_handler {
		category = category,
		inactive = inactive,
		typ = "family",
		obj = fam,
		code = code,
		langfam = fam:getCanonicalName(),
		langfamcat = famcat,
		level = level,
		parents = parents,
	}
end)

insert(raw_handlers, function(data)
	-- "N개의 항목이 있는 문서" 패턴을 찾습니다.
	local n = data.category:match("^(%d+)개의 항목이 있는 문서$")
	
	-- 숫자 맨 앞에 0이 오지 않는지 확인합니다.
	if not (n and not n:match("^0%d")) then
		return
	end
	
	return {
		breadcrumb = ("%d개 항목"):format(n),
		description = ("%s개의 언어 항목을 포함하는 문서."):format(n),
		additional = "이 분류와 유사한 다른 분류들은 위키낱말사전의 총 항목 수를 결정하는 데 사용됩니다.",
		hidden = true,
		can_be_empty = true,
		parents = {
			{name = "항목이 있는 문서", sort = require("Module:category tree").numeral_sortkey(n)},
			n == "0" and "위키낱말사전 관리" or nil, -- "0개의 항목이 있는 문서"는 문제가 있는 문서만 포함합니다.
		},
	}
end)


return {RAW_CATEGORIES = raw_categories, RAW_HANDLERS = raw_handlers}