跳转到内容

模組:Documentation

維基詞典,自由的多語言詞典

驅動Template:Documentation,渲染包括模組頁面、模板頁面的說明文檔。


local export = {}

local debug_track_module = "Module:debug/track"
local frame_module = "Module:frame"
local fun_is_callable_module = "Module:fun/isCallable"
local languages_module = "Module:languages"
local links_module = "Module:links"
local load_module = "Module:load"
local module_categorization_module = "Module:module categorization"
local number_list_show_module = "Module:number list/show"
local chemical_element_list_show_module = "Module:chemical element list/show"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local scripts_module = "Module:scripts"
local string_endswith_module = "Module:string/endswith"
local string_gline_module = "Module:string/gline"
local string_startswith_module = "Module:string/startswith"
local string_utilities_module = "Module:string utilities"
local template_parser_module = "Module:template parser"
local title_exists_module = "Module:title/exists"
local title_new_title_module = "Module:title/newTitle"

local concat = table.concat
local error = error
local full_url = mw.uri.fullUrl
local get_current_title = mw.title.getCurrentTitle
local insert = table.insert
local ipairs = ipairs
local list_to_text = mw.text.listToText
local new_message = mw.message.new
local pcall = pcall
local require = require
local tonumber = tonumber
local tostring = tostring
local type = type
local unpack = unpack or table.unpack -- Lua 5.2 compatibility

local function categorize_module(...)
	categorize_module = require(module_categorization_module).categorize_module
	return categorize_module(...)
end

local function debug_track(...)
	debug_track = require(debug_track_module)
	return debug_track(...)
end

local function endswith(...)
	endswith = require(string_endswith_module)
	return endswith(...)
end

local function expand_template(...)
	expand_template = require(frame_module).expandTemplate
	return expand_template(...)
end

local function find_templates(...)
	find_templates = require(template_parser_module).find_templates
	return find_templates(...)
end

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

local function get_lang(...)
	get_lang = require(languages_module).getByCode
	return get_lang(...)
end

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

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

local function gline(...)
	gline = require(string_gline_module)
	return gline(...)
end

local function is_callable(...)
	is_callable = require(fun_is_callable_module)
	return is_callable(...)
end

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

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

local function new_title(...)
	new_title = require(title_new_title_module)
	return new_title(...)
end

local function number_list_show_table(...)
	number_list_show_table = require(number_list_show_module).table
	return number_list_show_table(...)
end

local function chemical_element_list_show_table(...)
	chemical_element_list_show_table = require(chemical_element_list_show_module).table
	return chemical_element_list_show_table(...)
end

local function preprocess(...)
	preprocess = require(frame_module).preprocess
	return preprocess(...)
end

local function process_params(...)
	process_params = require(parameters_module).process
	return process_params(...)
end

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

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

local function startswith(...)
	startswith = require(string_startswith_module)
	return startswith(...)
end

local function title_exists(...)
	title_exists = require(title_exists_module)
	return title_exists(...)
end

local function ugsub(...)
	ugsub = require(string_utilities_module).gsub
	return ugsub(...)
end

local function umatch(...)
	umatch = require(string_utilities_module).match
	return umatch(...)
end

local skins = {
	["common"] = "",
	["vector"] = "Vector",
	["monobook"] = "Monobook",
	["cologneblue"] = "Cologne Blue",
	["modern"] = "Modern",
}

local function track(page)
	debug_track("documentation/" .. page)
	return true
end

local function compare_pages(page1, page2, text)
	return "[" .. tostring(
			full_url("Special:ComparePages", { page1 = page1, page2 = page2 }))
		.. " " .. text .. "]"
end

-- l10n: zhwt runs with $wgCapitalLinks, which capitalizes the initial letter of every page
-- name, while the patterns below are written for enwt's mostly-lowercase module names.
-- Lowercasing that letter on both the title and the pattern lets one set of patterns match
-- under either setting.
local function lcfirst_module_name(name)
	return (name:gsub("^(%^?Module:)(%a)", function(prefix, letter)
		return prefix .. letter:lower()
	end))
end

-- Avoid transcluding [[Module:languages/cache]] everywhere.
local lang_cache = setmetatable({}, {
	__index = function(self, k)
		return require("Module:languages/cache")[k]
	end
})

local function zh_link(word)
	return full_link {
		lang = lang_cache.zh,
		term = word
	}
end

local function make_languages_data_documentation(_title, cats, division)
	local doc_template, module_cat
	if endswith(division, "/extra") then
		division = division:sub(1, -7)
		doc_template = "language extradata documentation"
		module_cat = "語言額外資料模塊"
	else
		doc_template = "language data documentation"
		module_cat = "語言資料模塊"
	end
	local sort_key
	if division == "exceptional" then
		sort_key = "x"
	else
		sort_key = division:gsub("/", "")
	end
	insert(cats, module_cat .. "|" .. sort_key)
	return {
		title = doc_template
	}
end

local function make_Unicode_data_documentation(title, _cats)
	local subpage, first_three_of_code_point
	= title.fullText:match("^Module:Unicode data/([^/]+)/(%x%x%x)$")
	if subpage == "names" or subpage == "images" or subpage == "emoji images" then
		local low, high =
			tonumber(first_three_of_code_point .. "000", 16),
			tonumber(first_three_of_code_point .. "FFF", 16)
		local text, text_type
		if subpage == "names" then
			text_type = "名稱"
		elseif subpage == "images" then
			text_type = "圖像標題"
		elseif subpage == "emoji images" then
			text_type = "繪文字風格圖像"
		end
		text = string.format(
			"本-{zh-hans:数据模块;zh-hant:資料模組}-包含U+%04X到U+%04X範圍內的" ..
			"[[Appendix:Unicode|Unicode]]碼位的字符所對應的%s。",
			low, high, text_type)
		if subpage == "images" and safe_load_data("Module:Unicode data/emoji images/" .. first_three_of_code_point) then
			text = text ..
				" 此列表包含繪文字的文字變體。關於這些字符的繪文字變體列表,請參見[[Module:Unicode data/emoji images/" ..
				first_three_of_code_point .. "]]。"
		elseif subpage == "emoji images" then
			text = text ..
				" 關於文字風格的圖像,請參見[[Module:Unicode data/images/" .. first_three_of_code_point .. "]]。"
		end
		return text
	end
end

local function insert_lang_data_module_cats(cats, langcode, overall_data_module_cat)
	local lang = lang_cache[langcode]
	if lang then
		local langname
		if lang._fullCode then
			langname = lang_cache[lang._fullCode]:getCanonicalName()
		else
			langname = lang:getCanonicalName()
		end
		insert(cats, overall_data_module_cat .. "|" .. langname)
		insert(cats, langname .. "模塊")
		insert(cats, langname .. "資料模塊")
		return lang, langname
	end
end

--[=[
This provides categories and documentation for various data modules, so that [[Category:Uncategorized modules]] isn't
unnecessarily cluttered. It is a list of tables, each of which have the following possible fields:

`regex` (required): A Lua pattern to match the module's title. If it matches, the data in this entry will be used.
	Any captures in the pattern can by referenced in the `cat` field using %1 for the first capture, %2 for the
	second, etc. (often used for creating the sortkey for the category). In addition, the captures are passed to the
	`process` function as the third and subsequent parameters.

`process` (optional): This may be a function or a string. If it is a function, it is called as follows:
	   `process(TITLE, CATS, CAPTURE1, CAPTURE2, ...)`
	where:
	   * TITLE is a title object describing the module's title; see
		 [https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Title_objects].
	   * CATS is an list of categories that the module will be added to.
	   * CAPTURE1, CAPTURE2, ... contain any captures in the `regex` field.
	The return value of `process` should either be a string (which will be used as the module's documentation), or a
	table specifying the name of a template to expand to get the documentation, along with the arguments to that
	template. In the latter format, the template name (bare, without the "Template:" prefix) should be in the `title`
	field, and any arguments should be in `args; in this case, the template name will be listed above the generated
	documentation as the source of the documentation, along with an edit button to edit the template's contents.
	If, however, the return value of the `process` function is a string, any template invocations will be expanded
	using frame:preprocess(), and [[Module:documentation]] will be listed as the source of the documentation.

	If `process` itself is a string rather than a function, it should name a submodule under
	[[Module:documentation/functions/]] which returns a function, of the same type as described above. This submodule
	will be specified as the source of the documentation (unless it returns a table naming a template to expand to get
	the documentation, as described above).

	If `process` is omitted entirely, the module will have no documentation.

`cat` (optional): A string naming the category into which the module should be placed, or a list of such strings.
	Captures specified in `regex` may be referenced in this string using %1 for the first capture, %2 for the second,
	etc. It is also possible to add categories in the `process` function by inserting them into the passed-in CATS
	list (the second parameter).
]=]

local module_regex = {
	{
		regex = "^Module:languages/data/(3/%l/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(3/%l)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(2/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(2)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(exceptional/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(exceptional)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/.+$",
		cat = "處理語言和文字的模塊",
	},
	{
		regex = "^Module:scripts/.+$",
		cat = "處理語言和文字的模塊",
	},
	{
		regex = "^Module:data tables/data..?.?.?$",
		cat = "參考資料模塊共用資料表",
	},
	{
		regex = "^Module:zh/data/dial%-pron/.+$",
		cat = "漢語方言讀音資料模塊",
		process = "zh dial or syn",
	},
	{
		regex = "^Module:zh/data/dial%-syn/.+$",
		cat = "漢語方言同義詞資料模塊",
		process = "zh dial or syn",
	},
	{
		regex = "^Module:zh/data/glyph%-data/.+$",
		cat = "古代漢字字形資料模塊",
		process = function(title, _cats)
			local character = lcfirst_module_name(title.fullText):match("^Module:zh/data/glyph%-data/(.+)")
			if character then
				return ("此-{zh-hans:模块;zh-hant:模組}-包含漢字%s的歷史字形資料。")
					:format(zh_link(character))
			end
		end,
	},
	{
		regex = "^Module:zh/data/ltc%-pron/(.+)$",
		cat = "中古漢語讀音資料模塊|%1",
		process = "zh data",
	},
	{
		regex = "^Module:zh/data/och%-pron%-BS/(.+)$",
		cat = "上古漢語(白一平-沙加爾)讀音資料模塊|%1",
		process = "zh data",
	},
	{
		regex = "^Module:zh/data/och%-pron%-ZS/(.+)$",
		cat = "上古漢語(鄭張尚芳)讀音資料模塊|%1",
		process = "zh data",
	},
	{
		-- capture rest of zh/data submodules
		regex = "^Module:zh/data/(.+)$",
		cat = "漢語資料模塊|%1",
	},
	{
		regex = "^Module:mul/guoxue%-data/cjk%-?(.*)$",
		process = "guoxue-data",
	},
	{
		regex = "^Module:Unicode data/(.+)$",
		cat = "Unicode資料模塊|%1",
		process = make_Unicode_data_documentation,
	},
	{
		regex = "^Module:number list/data/(.+)$",
		process = function(_title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "數字資料模塊")
			if lang then
				return ("本-{zh-hans:模块;zh-hant:模組}-包含%s的各種數字資料。\n%s")
					:format(lang:makeCategoryLink(), number_list_show_table() or "")
			end
		end,
	},
	{
		regex = "^Module:chemical element list/data/(.+)$",
		process = function(_title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "化學元素資料模塊")
			if lang then
				return ("本-{zh-hans:模块;zh-hant:模組}-包含%s的化學元素資料。\n%s")
					:format(lang:makeCategoryLink(), chemical_element_list_show_table() or "")
			end
		end,
	},
	{
		regex = "^Module:accel/(.+)$",
		process = function(title, cats)
			local lang_code = title.subpageText
			local lang = lang_cache[lang_code]
			if lang then
				insert(cats, lang:getCanonicalName() .. "模塊|accel")
				insert(cats, ("Accel子模組|%s"):format(lang:getCanonicalName()))
				return ("本-{zh-hans:模块;zh-hant:模組}-包含%s的新條目建立規則;有關概述,請參見[[:en:WT:ACCEL]],有關創建新規則的資訊,請參見[[Module:accel]]。")
					:format(lang:makeCategoryLink())
			end
		end,
	},
	{
		regex = "^Module:inc%-ash/dial/data/(.+)$",
		cat = "阿輸迦普拉克里特語模塊|%1",
		process = function(title, _cats)
			local word = lcfirst_module_name(title.fullText):match("^Module:inc%-ash/dial/data/(.+)$")
			if word then
				local lang = lang_cache["inc-ash"]
				return ("本-{zh-hans:模块;zh-hant:模組}-包含%s在%s方言中的讀音資料。")
					:format(full_link({ term = word, lang = lang }, "term"),
						lang:makeCategoryLink())
			end
		end,
	},
	{
		regex = "^.+%-translit$",
		process = function(title, _cats)
			return require("Module:documentation/translit-like").documentation {
				operation = "translit",
				title_without_namespace = title.text,
			}
		end,
	},
	{
		regex = "^.+%-sortkey$",
		process = function(title, _cats)
			return require("Module:documentation/translit-like").documentation {
				operation = "sortkey",
				title_without_namespace = title.text,
			}
		end,
	},
	{
		regex = "^.+%-stripdiacritics$",
		process = function(title, _cats)
			return require("Module:documentation/translit-like").documentation {
				operation = "strip diacritics",
				title_without_namespace = title.text,
			}
		end,
	},
	{
		regex = "^Module:form of/lang%-data/(.+)$",
		process = function(_title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "特定語言之形式模塊")
			if lang then
				-- FIXME, display more info.
				return "此-{zh-hans:模块;zh-hant:模組}-包含" .. langname .. "的之形式資料(標籤、捷徑、基本詞元參數等)。"
			end
		end
	},
	{
		regex = "^Module:labels/data/lang/(.+)$",
		process = function(_title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "特定語言標籤資料模塊")
			if lang then
				return {
					title = "label language-specific data documentation",
					args = { [1] = lang_code },
				}
			end
		end
	},
	{
		regex = "^Module:category tree/lang/(.+)$",
		process = function(_title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Category tree資料模塊/lang")
			if lang then
				return "此-{zh-hans:模块;zh-hant:模組}-負責生成格式為「" .. langname .. " LABEL」的" .. langname .. "分類頁面的描述和分類"
					.. "(其中 LABEL 可以是任何文字)。例如"
					.. "[[:Category:Bulgarian conjugation 2.1 verbs]] 和 [[:Category:Russian velar-stem neuter-form nouns]]。\n"
					.. "該-{zh-hans:模块;zh-hant:模組}-是 category tree 系統的一部分,該系統是用於生成"
					.. "分類頁面的描述和分類的通用框架。\n\n"
					.. "有關更多資訊,請參閱[[Module:category tree/lang/doc]]。\n\n"
					.. "'''注意:'''如果您新增了特定語言的-{zh-hans:模块;zh-hant:模組}-,"
					.. "請將語言代碼新增至 [[Module:category tree/lang]] 頂部的清單中,"
					.. "使程式碼能識別該-{zh-hans:模块;zh-hant:模組}-。"
			end
		end
	},
	{
		regex = "^Module:category tree/topic/(.+)$",
		process = function(_title, cats, _submodule)
			insert(cats, "Category tree資料模塊/topic| ")
			return {
				title = "topic cat data submodule documentation"
			}
		end
	},
	{
		regex = "^Module:category tree/(.+)$",
		process = function(_title, cats, _submodule)
			insert(cats, "Category tree資料模塊| ")
			return {
				title = "category tree data submodule documentation"
			}
		end
	},
	{
		regex = "^Module:ja/data/(.+)$",
		cat = "日語資料模塊|%1",
	},
	{
		regex = "^Module:fi%-dialects/data/feature/Kettunen1940 ([0-9]+)$",
		cat = "芬蘭語方言地圖集資料模塊|%1",
		process = function(_title, _cats, shard)
			return "本-{zh-hans:模块;zh-hant:模組}-包含 Lauri Kettunen 1940 年著作" ..
				"《Suomen murteet III A. Murrekartasto》(芬蘭語方言 III A:方言地圖集)線上版本的第 " .. shard .. " 分片。" ..
				"它是由 ''Kotimaisten kielten keskus'' 根據 CC BY 4.0 許可證發布的 urn:nbn:fi:csc-kata20151130145346403821 導入和轉換而來。"
		end
	},
	{
		regex = "^Module:fi%-dialects/data/feature/(.+)",
		cat = "芬蘭語方言資料模塊|%1",
	},
	{
		regex = "^Module:fi%-dialects/data/word/(.+)",
		cat = "芬蘭語方言資料模塊|%1",
	},
	{
		regex = "^Module:Swadesh/data/([%l-]+)$",
		process = function(_title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "斯氏核心詞模塊")
			if lang then
				return "本-{zh-hans:模块;zh-hant:模組}-包含" .. langname .. "中的[[w:斯瓦迪士核心詞列表|斯瓦迪士核心詞列表]]。"
			end
		end
	},
	{
		regex = "^Module:Swadesh/data/([%l-]+)/([^/]*)$",
		process = function(_title, cats, lang_code, variety)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "斯氏核心詞模塊")
			if lang then
				local prefix = "本-{zh-hans:模块;zh-hant:模組}-包含"
				local etym_lang = get_lang(variety, nil, "allow etym")
				if etym_lang then
					return ("%s%s變體(屬於%s)的[[w:斯瓦迪士核心詞列表|斯瓦迪士核心詞列表]]。"):format(prefix, etym_lang:getCanonicalName(), langname)
				end
				local script = get_script(variety)
				if script then
					return ("%s%s(%s)的[[w:斯瓦迪士核心詞列表|斯瓦迪士核心詞列表]]。"):format(prefix, langname, script:getCanonicalName(lang))
				end
				return ("%s%s變體(屬於%s)的[[w:斯瓦迪士核心詞列表|斯瓦迪士核心詞列表]]。"):format(prefix, variety, langname)
			end
		end
	},
	{
		regex = "^Module:typing%-aids",
		process = function(title, cats)
			local data_suffix = lcfirst_module_name(title.fullText):match("^Module:typing%-aids/data/(.+)$")
			local sortkey
			if data_suffix then
				if data_suffix:find "^[%l-]+$" then
					local lang = get_lang(data_suffix)
					if lang then
						sortkey = lang:getCanonicalName()
						insert(cats, sortkey .. "資料模塊")
					end
				elseif data_suffix:find "^%u%l%l%l$" then
					local script = get_script(data_suffix)
					if script then
						-- FIXME: no lang to pass here
						sortkey = script:getCanonicalName()
						insert(cats, script:getCategoryName())
					end
				end
				insert(cats, "字符插入資料模塊|" .. (sortkey or data_suffix))
			end
		end,
	},
	{
		regex = "^Module:R:([%l-]+):(.+)$",
		process = function(_title, cats, lang_code, refname)
			local lang = lang_cache[lang_code]
			if lang then
				insert(cats, lang:getCanonicalName() .. "模塊|" .. refname)
				insert(cats, ("參考模塊|%s"):format(lang:getCanonicalName()))
				return "此-{zh-hans:模块;zh-hant:模組}-實現了參考模板 {{temp|R:" .. lang_code .. ":" .. refname .. "}}。"
			end
		end,
	},
	{
		regex = "^Module:Quotations/([%l-]+)/?(.*)",
		process = "Quotation",
	},
	{
		regex = "^Module:affix/lang%-data/([%l-]+)",
		process = "affix lang-data",
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)$",
		process = function(_title, cats, lang_code)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				insert(cats, "方言同義詞資料模塊|" .. langname)
				insert(cats, langname .. "方言同義詞資料模塊| ")
				return "本-{zh-hans:模块;zh-hant:模組}-包含" .. langname .. "特定變體的資料,供 " ..
					"{{tl|dialect synonyms}} 使用。實際的同義詞本身包含在子模組中。\n\n" ..
					"==== 語言資料模組結構 ====\n" ..
					"* <code>export.title</code> — 選填;表格標題模板(例如「%s的地區同義詞」)。\n" ..
					"* <code>export.columns</code> — 選填;地點層級的欄位標題列表(例如 {\"方言群\", \"方言\", \"地點\"})。\n" ..
					"* <code>export.notes</code> — 選填;註解鍵對應文字的表。\n" ..
					"* <code>export.sources</code> — 選填;來源鍵對應文字的表。\n" ..
					"* <code>export.note_aliases</code> — 選填;註解的別名對照表。\n" ..
					"* <code>export.varieties</code> — 必填;變體節點的巢狀表。每個節點都必須有 <code>name</code>;列表部分存放子節點。節點的鍵可包含 <code>text_display</code>、<code>color</code>、<code>code</code>、<code>wikidata</code>、<code>lat</code>、<code>long</code>,以及特定語言的鍵(例如 <code>persian</code>、<code>armenian</code>、<code>chinese</code>)。\n\n" ..
					expand_template({ title = 'dial syn', args = { lang_code, ["demo mode"] = "y" } })
			end
		end,
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)/([^/]+)$",
		process = function(_title, cats, lang_code, term)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				insert(cats, "方言同義詞資料模塊|" .. langname)
				insert(cats, langname .. "方言同義詞資料模塊|" .. term)
				return ("%s\n\n%s"):format(
					"==== 詞條/詞義模組結構 ====\n" ..
					"* <code>export.title</code> — 選填;自訂表格標題(例如「母音之間「強 R」的實現」)。會覆寫語言層級的預設值。\n" ..
					"* <code>export.meaning</code> — 選填;意義/釋義(<code>gloss</code> 的替代)。\n" ..
					"* <code>export.gloss</code> — 選填;表格用的簡短意義。\n" ..
					"* <code>export.note</code> — 選填;單一註解鍵或字串,或註解鍵的列表。\n" ..
					"* <code>export.notes</code> — 選填;註解鍵的列表。\n" ..
					"* <code>export.source</code> / <code>export.sources</code> — 選填;來源鍵。\n" ..
					"* <code>export.last_column</code> — 選填;資料欄的標籤(預設為「Words」;例如「Realization」)。\n" ..
					"* <code>export.syns</code> — 必填;將變體/地點名稱(語言資料模組中的鍵)對應到詞條列表的表。每個項目可以是字串或表(例如 <code>{ ipa = \"[ɽ]\" }</code> 或 <code>{ term = \"word\" }</code>)。\n\n" ..
					"範例(自訂標題與資料欄,IPA 實現):\n" ..
					"<pre>\nlocal export = {}\n\nexport.title = \"Realization of 'strong R' between vowels\"\n" ..
					"export.meaning = \"\"\nexport.note = \"realization of 'strong R' between vowels\"\n" ..
					"export.last_column = \"Realization\"\n\nexport.syns = {\n\t[\"ALERS-158\"] = { { ipa = \"[ɽ]\" } },\n\t[\"ALERS-175\"] = { { ipa = \"[x]\" } },\n}\n\nreturn export\n</pre>\n\n",
					expand_template({ title = 'dial syn', args = { lang_code, term } }))
			end
		end,
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)/([^/]+)/([^/]+)$",
		process = function(_title, cats, lang_code, term, id)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				insert(cats, "方言同義詞資料模塊|" .. langname)
				insert(cats, langname .. "方言同義詞資料模塊|" .. term)
				return ("%s\n\n%s"):format(
					"==== 詞條/詞義模組結構 ====\n" ..
					"* <code>export.title</code> — 選填;自訂表格標題(例如「母音之間「強 R」的實現」)。會覆寫語言層級的預設值。\n" ..
					"* <code>export.meaning</code> — 選填;意義/釋義(<code>gloss</code> 的替代)。\n" ..
					"* <code>export.gloss</code> — 選填;表格用的簡短意義。\n" ..
					"* <code>export.note</code> — 選填;單一註解鍵或字串,或註解鍵的列表。\n" ..
					"* <code>export.notes</code> — 選填;註解鍵的列表。\n" ..
					"* <code>export.source</code> / <code>export.sources</code> — 選填;來源鍵。\n" ..
					"* <code>export.last_column</code> — 選填;資料欄的標籤(預設為「Words」;例如「Realization」)。\n" ..
					"* <code>export.syns</code> — 必填;將變體/地點名稱(語言資料模組中的鍵)對應到詞條列表的表。每個項目可以是字串或表(例如 <code>{ ipa = \"[ɽ]\" }</code> 或 <code>{ term = \"word\" }</code>)。\n\n" ..
					"範例(自訂標題與資料欄,IPA 實現):\n" ..
					"<pre>\nlocal export = {}\n\nexport.title = \"Realization of 'strong R' between vowels\"\n" ..
					"export.meaning = \"\"\nexport.note = \"realization of 'strong R' between vowels\"\n" ..
					"export.last_column = \"Realization\"\n\nexport.syns = {\n\t[\"ALERS-158\"] = { { ipa = \"[ɽ]\" } },\n\t[\"ALERS-175\"] = { { ipa = \"[x]\" } },\n}\n\nreturn export\n</pre>\n\n",
					expand_template({ title = 'dial syn', args = { lang_code, term, id = id } }))
			end
		end,
	},
	{
		regex = "^Module:bibliography/data/([%l-]+)$",
		process = function(title, cats, lang_code)
			if lang_code == "preload" then
				return '點選「create new language submodule」按鈕時,作為其他語言的基礎範本。'
			end
			local page = require(title.fullText).bib_page
			if not page then
				page = lang_cache[lang_code]:getCanonicalName()
				if page then
					insert(cats, page .. "模塊")
				end
			end
			insert(cats, "參考模塊")
			return "本-{zh-hans:模块;zh-hant:模組}-包含 " ..
				page .. " 的書目資料。關於格式化後的書目,請參見 '''[[Appendix:Bibliography/" .. page .. "]]'''。"
		end,
	},
}

function export.show(frame)
	local boolean_default_false = { type = "boolean", default = false }
	local args = process_params(frame.args, {
		["hr"] = true,
		["for"] = true,
		["from"] = true,
		["allowondoc"] = boolean_default_false, -- Don't throw an error if used on a documentation subpage.
		["notsubpage"] = boolean_default_false,
		["nodoc"] = boolean_default_false,
		["nolinks"] = boolean_default_false, -- suppress all "Useful links"
		["nosandbox"] = boolean_default_false, -- supress sandbox
	})

	local output = {'\n<div class="documentation" style="display:block; clear:both">\n'}
	local function ins(txt)
		insert(output, txt)
	end
	local cats = {}
	local function inscat(cat)
		insert(cats, cat)
	end

	local nodoc = args.nodoc

	if (not args.hr) or (args.hr == "above") then
		ins("----\n")
	end

	local title = args["for"] and new_title(args["for"]) or get_current_title()
	-- l10n: zhwt keeps template and module documentation at /doc, not /documentation.
	local doc_title = args.from ~= "-" and new_title(args.from or title.fullText .. '/doc') or nil
	local contentModel = title.contentModel
	local pagetype, is_script_or_stylesheet = get_pagetype(title)
	local preload, fallback_docs, doc_content, old_doc_title, user_name, skin_name, needs_doc
	local doc_content_source = "Module:documentation"
	local auto_generated_cat_source
	local cats_auto_generated = false

	if not args.allowondoc and is_documentation(title) then
		-- TODO: merge with {{documentation subpage}}, and choose behaviour based on the page type.
		error("This template should not be used on a documentation page. Please use [[Template:documentation subpage]].")
	elseif is_sandbox(title) then
		local sandbox_ns = title.nsText
		preload = ("Template:documentation/preload%s%sSandbox"):format(
			sandbox_ns == "Module" and sandbox_ns or "Template",
			title.rootText:match("^[Uu]ser:(.+)") and "User" or ""
		)
	elseif pagetype:match("%f[%w]gadget%f[%W]") then
		preload = "Template:documentation/preloadGadget"
	elseif pagetype:match("%f[%w]script%f[%W]") then -- .js
		if title.nsText == "MediaWiki" then
			preload = "Template:documentation/preloadMediaWikiJavaScript"
		else
			preload = "Template:documentation/preloadTemplate" -- XXX
			if title.nsText == "User" then
				user_name = title.rootText
			end
		end
		is_script_or_stylesheet = true
	elseif pagetype:match("%f[%w]stylesheet%f[%W]") then -- .css
		preload = "Template:documentation/preloadTemplate" -- XXX
		if title.nsText == "User" then
			user_name = title.rootText
		end
		is_script_or_stylesheet = true
	elseif contentModel == "Scribunto" then -- Exclude pages in Module: which aren't Scribunto.
		preload = "Template:documentation/preloadModule"
	elseif pagetype:match("%f[%w]template%f[%W]") or pagetype:match("%f[%w]project%f[%W]") then
		preload = "Template:documentation/preloadTemplate"
	end

	-- l10n: translate the page type for display. This happens after the checks above, which
	-- match against the English page type returned by [[Module:pages]].
	local pagetype_names = {
		["Module"] = "-{zh-hans:模块;zh-hant:模組}-",
		["Template"] = "模板",
		["User"] = "-{zh-hans:用户页; zh-hant:使用者頁面}-",
		["MediaWiki"] = "介面頁面",
	}
	if is_script_or_stylesheet then
		if pagetype:match("script") then
			pagetype = "腳本"
		elseif pagetype:match("stylesheet") then
			pagetype = "樣式表"
		end
	elseif pagetype_names[title.nsText] then
		pagetype = pagetype_names[title.nsText]
	end

	if doc_title and doc_title.isRedirect then
		old_doc_title = doc_title
		doc_title = doc_title.redirectTarget
	end

	ins("<dl class=\"plainlinks\" style=\"font-size: smaller;\">")

	local function get_module_doc_and_cats(categories_only)
		cats_auto_generated = true
		local automatic_cats = nil
		if user_name then
			fallback_docs = "documentation/fallback/user module"
			automatic_cats = { "使用者沙盒模塊" }
		else
			local module_name = lcfirst_module_name(title.fullText)
			for _, data in ipairs(module_regex) do
				local captures = { umatch(module_name, lcfirst_module_name(data.regex)) }
				if #captures > 0 then
					local cat, process_function
					if is_callable(data.process) then
						process_function = data.process
					elseif type(data.process) == "string" then
						doc_content_source = "Module:documentation/functions/" .. data.process
						process_function = require(doc_content_source)
					end

					if process_function then
						doc_content = process_function(title, cats, unpack(captures))
					end
					if type(doc_content) == "table" then
						doc_content_source = doc_content.title and "Template:" .. doc_content.title or doc_content_source
						doc_content = expand_template(doc_content)
					elseif doc_content ~= nil then
						doc_content = preprocess(doc_content)
					end
					cat = data.cat

					if cat then
						if type(cat) == "string" then
							cat = { cat }
						end
						for _, c in ipairs(cat) do
							insert(cats, (ugsub(module_name, lcfirst_module_name(data.regex), c)))
						end
					end
					break
				end
			end
		end

		if title.subpageText == "templates" then
			inscat("模板介面模塊")
		end

		if automatic_cats then
			for _, c in ipairs(automatic_cats) do
				inscat(c)
			end
		end

		if #cats == 0 then
			local auto_cats = categorize_module {
				return_raw = true,
				noerror = true,
			}
			if #auto_cats > 0 then
				auto_generated_cat_source = "Module:module categorization"
			end
			for _, category in ipairs(auto_cats) do
				inscat(category)
			end
		end

		-- meaning module is not in user’s sandbox or one of many datamodule boring series
		needs_doc = not categories_only and not (automatic_cats or doc_content or fallback_docs)
	end

	-- Override automatic documentation, if present.
	if doc_title and doc_title.exists then
		local cats_auto_generated_text = ""
		if contentModel == "Scribunto" then
			local doc_page_content = doc_title.content
			-- Track then do nothing if there are uses of includeonly. The
			-- pattern is slightly too permissive, but any false-positives are
			-- obvious typos that should be corrected.
			if doc_page_content:lower():match("</?includeonly%f[%s/>][^>]*>") then
				track("module-includeonly")
			else
				-- Check for uses of {{module cat}}. find_templates treats the
				-- input as transcluded by default (i.e. it parses the wikitext
				-- which will be transcluded through to the module page).
				local module_cat
				for template in find_templates(doc_page_content) do
					if template:get_name() == "module cat" then
						module_cat = true
						break
					end
				end
				if not module_cat then
					get_module_doc_and_cats("categories only")
					auto_generated_cat_source = auto_generated_cat_source or doc_content_source
					cats_auto_generated_text = " 分類由 [[" ..
						auto_generated_cat_source .. "]] 自動生成。<sup>[[" ..
						new_title(auto_generated_cat_source):fullUrl { action = "edit" } .. " 編輯]]</sup>"
				end
			end
		end

		ins(
			"<dd><i style=\"font-size: larger;\">下列" ..
			"[[Help:為模板和模組撰寫說明文檔|說明文檔]]位於[[" ..
			doc_title.fullText .. "]]。" .. "<sup>[[" .. doc_title:fullUrl { action = "edit" } .. " 編輯]]</sup>" ..
			cats_auto_generated_text .. "</i></dd>")
	else
		if contentModel == "Scribunto" then
			get_module_doc_and_cats(false)
		elseif title.nsText == "Template" then
			--inscat("Uncategorized templates")
			needs_doc = not (fallback_docs or nodoc)
		elseif user_name and is_script_or_stylesheet then
			skin_name = skins[title.text:sub(#title.rootText + 1):match("^/(%l+)%.[jc]ss?$")]
			if skin_name then
				fallback_docs = "documentation/fallback/user " .. contentModel
			end
		end

		if doc_content then
			ins(
				"<dd><i style=\"font-size: larger;\">下列" ..
				"[[Help:為模板和模組撰寫說明文檔|說明文檔]]由" ..
				"[[" .. doc_content_source .. "]]生成。<sup>[[" ..
				new_title(doc_content_source):fullUrl { action = "edit" } ..
				" 編輯]]</sup> </i></dd>")
		elseif not nodoc then
			if doc_title then
				ins(
					"<dd><i style=\"font-size: larger;\">此" .. pagetype ..
					"缺少[[Help:為模板和模組撰寫說明文檔|說明文檔子頁面]]。" ..
					(fallback_docs and "您可以" or "請") ..
					"[" .. doc_title:fullUrl { action = "edit", preload = preload }
					.. " 協助創建]。</i></dd>\n")
			else
				ins(
					"<dd><i style=\"font-size: larger; color: var(--wikt-palette-red-9,#FF0000);\">無法為此" ..
					pagetype .. "自動生成說明文檔。</i></dd>\n")
			end
		end
	end

	if startswith(title.fullText, "MediaWiki:Gadget-") then
		local is_gadget = false
		for line in gline(new_title("MediaWiki:Gadgets-definition").content) do
			local gadget, items = line:match("^%*%s*(%a[%w_-]*)%[.-%]|(.+)$")
			if not gadget then
				gadget, items = line:match("^%*%s*(%a[%w_-]*)|(.+)$")
			end
			if gadget then
				items = split(items, "|")
				for i, item in ipairs(items) do
					if title.fullText == ("MediaWiki:Gadget-" .. item) then
						is_gadget = true

						ins("<dd> 該腳本是小工具<code>")
						ins(gadget)
						ins("</code>的一部分([")
						ins(tostring(full_url("MediaWiki:Gadgets-definition", { action = "edit" })))
						ins(" 編輯定義])<dl>")

						ins("<dd> 描述([")
						ins(tostring(full_url("MediaWiki:Gadget-" .. gadget, { action = "edit" })))
						ins(" 編輯]):")

						ins(preprocess(new_message('Gadget-' .. gadget):plain()))
						ins(" </dd>")

						table.remove(items, i)
						if #items > 0 then
							for j, item in ipairs(items) do
								items[j] = '[[MediaWiki:Gadget-' .. item .. '|' .. item .. ']]'
							end
							ins("<dd> 其他部分:")
							ins(list_to_text(items))
							ins("</dd>")
						end

						ins("</dl></dd>")

						break
					end
				end
			end
		end

		if not is_gadget then
			ins("<dd> 該腳本不屬於任何 [")
			ins(tostring(full_url("Special:Gadgets", { uselang = "zh" })))
			ins(' 小工具]([')
			ins(tostring(full_url("MediaWiki:Gadgets-definition", { action = "edit" })))
			ins(' 編輯定義])。</dd>')
			-- else
			-- inscat("Wiktionary gadgets")
		end
	end

	if old_doc_title then
		ins("<dd> 重定向自 [")
		ins(old_doc_title:fullUrl { redirect = "no" })
		ins(" ")
		ins(old_doc_title.fullText)
		ins("] ([")
		ins(old_doc_title:fullUrl { action = "edit" })
		ins(" 編輯])。</dd>\n")
	end

	if not args.nolinks then
		local links = {}
		local function inslinks(txt)
			insert(links, txt)
		end

		if title.isSubpage and not args.notsubpage then
			inslinks("[[:" .. title.nsText .. ":" .. title.rootText .. "|根頁面]]")
			inslinks("[[Special:PrefixIndex/" .. title.nsText .. ":" .. title.rootText .. "/|根頁面的子頁面]]")
			-- l10n: extra link, not present on enwt
			inslinks("[[Special:PrefixIndex/" .. title.fullText .. "/|本頁面的子頁面]]")
		else
			inslinks("[[Special:PrefixIndex/" .. title.fullText .. "/|子頁面列表]]")
		end

		inslinks(
			"[" ..
			tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidetrans = true, hideredirs = true })) ..
			" -{zh-hans:链入; zh-hant:連入}-]")

		if contentModel ~= "Scribunto" then
			inslinks(
				"[" ..
				tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidelinks = true, hidetrans = true })) ..
				" -{zh-hans:重定向; zh-hant:重新導向}-]")
		end

		if is_script_or_stylesheet then
			if user_name then
				inslinks("[[Special:MyPage" .. title.text:sub(#title.rootText + 1) .. "|您的對應頁面]]")
			end
		else
			inslinks(
				"[" ..
				tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidelinks = true, hideredirs = true })) ..
				" 嵌入包含]")
		end

		if contentModel == "Scribunto" then
			local is_testcases = title.isSubpage and title.subpageText == "testcases"
			local without_subpage = title.nsText .. ":" .. title.baseText
			if is_testcases then
				inslinks("[[:" .. without_subpage .. "|測試-{zh-hans:模块;zh-hant:模組}-]]")
			else
				inslinks("[[" .. title.fullText .. "/testcases|測試用例]]")
			end

			if user_name then
				inslinks("[[User:" .. user_name .. "|-{zh-hans:用户页; zh-hant:使用者頁面}-]]")
				inslinks("[[User talk:" .. user_name .. "|-{zh-hans:用户; zh-hant:使用者}-討論頁]]")
				inslinks("[[Special:PrefixIndex/User:" .. user_name .. "/|-{zh-hans:用户; zh-hant:使用者}-命名空間]]")
				-- If sandbox module, add a link to the module that this is a sandbox of.
				-- Exclude user sandbox modules like [[User:Dine2016/sandbox]].
			elseif title.text:find("^sandbox%d*/") or title.text:find("/sandbox%d*%f[/%z]") then
				inscat("沙盒模塊")

				-- Sandbox modules don’t really need documentation.
				needs_doc = false

				-- Don't track user sandbox modules.
				local text_title = new_title(title.text)
				if not (text_title and text_title.nsText == "User") then
					local diff
					local sandbox_of = title.text:match("^(.*)/sandbox%d*%f[/%z]")
					if sandbox_of then
						track("sandbox to be moved")
					else
						sandbox_of = title.text:match("^sandbox%d*/(.*)$")
					end
					if not sandbox_of then
						error(("Internal error: Something wrong, couldn't extract sandbox-of module from title '%s'")
							:format(title.text))
					end
					sandbox_of = title.nsText .. ":" .. sandbox_of
					if title_exists(sandbox_of) then
						diff = " (" .. compare_pages(title.fullText, sandbox_of, "差異") .. ")"
					else
						track("no sandbox of")
					end

					inslinks("[[:" .. sandbox_of .. "|沙盒本體]]" .. (diff or ""))
				end
				-- If not a sandbox module, add link to sandbox module.
				-- Sometimes there are multiple sandboxes for a single module:
				-- [[Module:sandbox/sa-pronunc]],  [[Module:sandbox2/sa-pronunc]].
			else
				local sandbox_title
				local user_prefix, user_rest = title.text:match("^(User:.-/)(.*)$")
				if not user_prefix then
					user_prefix = ""
					user_rest = title.text
				end
				sandbox_title = title.nsText .. ":" .. user_prefix .. "sandbox/" .. user_rest
				local sandbox_link = "[[:" .. sandbox_title .. "|沙盒]]"

				local diff
				if title_exists(sandbox_title) then
					diff = " (" .. compare_pages(title.fullText, sandbox_title, "差異") .. ")"
				end

				inslinks(sandbox_link .. (diff or ""))
			end
		end

		if title.nsText == "Template" then
			-- Error search: all(any namespace), hastemplate (show pages using the template), insource (show source code), incategory (any/specific error) -- [[mw:Help:CirrusSearch]], [[w:Help:Searching/Regex]]
			-- apparently same with/without: &profile=advanced&fulltext=1
			local errorq = 'searchengineselect=mediawiki&search=all: hastemplate:\"' ..
				title.rootText .. '\" insource:\"' .. title.rootText .. '\" incategory:'
			local eincategory =
			"Pages_with_module_errors|ParserFunction_errors|DisplayTitle_errors|Pages_with_ISBN_errors|Pages_with_ISSN_errors|Pages_with_reference_errors|Pages_with_syntax_highlighting_errors|Pages_with_TemplateStyles_errors"

			inslinks(
				'[' .. tostring(full_url('Special:Search', errorq .. eincategory)) .. ' 錯誤]'
				.. ' (' ..
				'[' .. tostring(full_url('Special:Search', errorq .. 'ParserFunction_errors')) .. ' 解析器]'
				.. '/' ..
				'[' .. tostring(full_url('Special:Search', errorq .. 'Pages_with_module_errors')) .. ' 模塊]'
				.. ')'
			)

			if title.isSubpage and title.text:find("/sandbox%d*%f[/%z]") then -- This is a sandbox template.
				-- At the moment there are no user sandbox templates with subpage
				-- “/sandbox”.
				inscat("沙盒模板")

				-- Sandbox templates don’t really need documentation.
				needs_doc = false

				-- Will behave badly if “/sandbox” occurs twice in title!
				local sandbox_of = title.fullText:gsub("/sandbox%d*%f[/%z]", "")

				local diff
				if title_exists(sandbox_of) then
					diff = " (" .. compare_pages(title.fullText, sandbox_of, "差異") .. ")"
				else
					track("no sandbox of")
				end

				inslinks("[[:" .. sandbox_of .. "|沙盒本體]]" .. (diff or ""))
				-- This is a template that can have a sandbox.
			elseif not args.nosandbox then -- unless we tell it not to
				local sandbox_title = title.fullText .. "/sandbox"

				local diff
				if title_exists(sandbox_title) then
					diff = " (" .. compare_pages(title.fullText, sandbox_title, "差異") .. ")"
				end

				inslinks("[[:" .. sandbox_title .. "|沙盒]]" .. (diff or ""))
			end
		end

		if #links > 0 then
			ins("<dd> 相關-{zh:連結;zh-hans:链接;zh-hant:連結;}-:" .. concat(links, " • ") .. "</dd>")
		end
	end

	ins("</dl>\n")

	-- Show error from [[Module:category tree/topic cat/data]] on its submodules'
	-- documentation to, for instance, warn about duplicate labels.
	if startswith(lcfirst_module_name(title.fullText), "Module:category tree/topic/") then
		local ok, err = pcall(require, "Module:category tree/topic/data")
		if not ok then
			ins('<span class="error">' .. err .. '</span>\n\n')
		end
	end

	if doc_title and doc_title.exists then
		-- Override automatic documentation, if present.
		doc_content = expand_template { title = doc_title.fullText }
	elseif not doc_content and fallback_docs then
		doc_content = expand_template {
			title = fallback_docs,
			args = {
				['user'] = user_name,
				['page'] = title.fullText,
				['skin name'] = skin_name,
			},
		}
	end

	if doc_content then
		ins(doc_content)
	end

	ins(('\n<%s style="clear: both;" />'):format(args.hr == "below" and "hr" or "br"))

	if cats_auto_generated and not cats[1] and (not doc_content or not doc_content:find("%[%[Category:")) then
		if contentModel == "Scribunto" then
			inscat("未分類模塊")
			-- elseif title.nsText == "Template" then
			-- inscat("Uncategorized templates")
		end
	end

	if needs_doc then
		inscat("需要說明文檔的模板和模組")
	end

	for _, cat in ipairs(cats) do
		ins("[[Category:" .. cat .. "]]")
	end

	ins("</div>\n")

	return concat(output)
end

function export.module_auto_doc_table()
	local parts = {}
	local function ins(text)
		insert(parts, text)
	end
	ins('{|class="wikitable"')
	ins("! 正則表達式 !! 分類 !! 處理模組")
	for _, spec in ipairs(module_regex) do
		local cat_text
		local cats = spec.cat
		if cats then
			local cat_parts = {}
			if type(cats) == "string" then
				cats = { cats }
			end
			for _, cat in ipairs(cats) do
				insert(cat_parts, ("<code>%s</code>"):format((cat:gsub("|", "&#124;"))))
			end
			cat_text = concat(cat_parts, ", ")
		else
			cat_text = "(未指定)"
		end
		ins("|-")
		ins(("| <code>%s</code> || %s || %s"):format(spec.regex, cat_text,
			is_callable(spec.process) and "(內部處理)" or
			type(spec.process) == "string" and ("[[Module:documentation/functions/%s]]"):format(spec.process) or
			"(無說明文檔生成器)"))
	end
	ins("|}")
	return concat(parts, "\n")
end

return export