模組:Category tree/topic/Places
外观
-- Chinese Wiktionary port of enwikt [[Module:category tree/topic/Places]].
-- Generates descriptions and parent-category specs for place-related category pages.
--
-- Architecture mirrors enwikt: two tables exported as LABELS and HANDLERS.
-- LABELS — static label → spec mappings (top-level grouping categories)
-- HANDLERS — ordered list of functions; first non-nil return wins
--
-- Chinese-specific adaptations vs. enwikt:
-- * Pattern matching on 的 instead of English "in"/"of"
-- * Handler 3 bare-location parent: CONTAINER的TYPE (order reversed vs. enwikt)
-- Special case: TYPE=="地點" → CONTAINERTYPE (no 的)
-- * Handler 6 uses forward zh_name lookup (div.type → zh_name) instead of
-- a reverse map, because many English placetypes share the same Chinese name
-- (e.g. state/oblast/canton all → "州"; province/department/voivodeship → "省")
-- * Handler 7 (new): compound capital label + location ("美國的州首府")
-- Compound = zh_name(holonym_type) .. zh_cap_label(holonym_type)
-- Built at load time; skips country/constituent country (no 國首都 category)
-- * No article handling (Chinese has no definite article)
-- * Serial enumeration: 、(non-final) 和(final)
-- * Description format: {{{langname}}}中LOCATION_DESC[[PLACETYPE]]的名稱。
local labels = {}
local handlers = {}
local m_table = require("Module:table")
local m_locations = require("Module:place/locations")
local m_placetypes = require("Module:place/placetypes")
local m_zh_data = require("Module:place/data")
local internal_error = m_locations.internal_error
local zh_strings = m_placetypes.zh_strings
local zh_cap_by_holonym = m_placetypes.zh_capital_label_by_holonym_type
local format_zh_name = m_placetypes.format_zh_name
local insert = table.insert
local concat = table.concat
local function is_callable(val) return type(val) == "function" end
-- Returns 的 for 1-char zh_type strings; "" for 2+ chars.
-- Mirrors export.zh_cat_de in place-placetypes.lua but takes an already-formatted string.
local function zh_de(zh_type)
if not zh_type then return "" end
if mw.ustring.len(zh_type) == 1 then
return zh_strings.de_particle
end
if mw.ustring.match(zh_type, "^.和") then
return zh_strings.de_particle
end
return ""
end
-- Append candidate splits for 2+ char zh_names used as bare suffixes (no 的 form).
-- name_map: any table whose keys are zh_name strings (zh_name_to_pt_keys, etc.).
-- Skips suffixes whose preceding text ends in 的 (already captured by split_at_de).
local function add_no_de_suffix_splits(splits, label, name_map)
local de_bytes = #zh_strings.de_particle
for zh_type in pairs(name_map) do
if mw.ustring.len(zh_type) >= 2 then
local bytes = #zh_type
if #label > bytes and label:sub(-bytes) == zh_type then
local place_str = label:sub(1, #label - bytes)
if place_str:sub(-de_bytes) ~= zh_strings.de_particle then
insert(splits, {place = place_str, placetype = zh_type})
end
end
end
end
end
-- Literal-string gsub (no regex metacharacters interpreted in `from` or `to`).
local function gsub_literally(str, from, to)
return (str:gsub(from:gsub("[%(%)%.%%%+%-%*%?%[%^%$]", "%%%1"),
to:gsub("%%", "%%%%")))
end
-- Get the first (primary) placetype from spec.
local function fetch_primary_placetype(key, spec)
local placetype = spec.placetype
if type(placetype) == "table" then placetype = placetype[1] end
return placetype
end
-- True for continent/supercontinent/planet specs (used to omit 的 in 非洲國家).
local function spec_is_broad(spec)
local pt = spec and spec.placetype
if not pt then return false end
local pts = type(pt) == "table" and pt or {pt}
for _, p in ipairs(pts) do
if p == "continent" or p == "supercontinent" or p == "planet"
or p == "continental region" then return true end
end
return false
end
-- ---------------------------------------------------------------------------
-- Chinese serial joiner: {"A","B","C"} → "A、B和C"
-- ---------------------------------------------------------------------------
local function zh_join(items)
local n = #items
if n == 0 then return "" end
if n == 1 then return items[1] end
local parts = {}
for i = 1, n - 1 do
insert(parts, items[i])
if i < n - 1 then insert(parts, zh_strings.enum_sep) end
end
insert(parts, zh_strings.and_conj)
insert(parts, items[n])
return concat(parts)
end
-- ---------------------------------------------------------------------------
-- Placetype class → Chinese bare parent category label
-- ---------------------------------------------------------------------------
local class_to_bare_category_parent = {
["polity"] = "政治實體",
["subpolity"] = "政治區劃",
["settlement"] = "聚居地",
["non-admin settlement"] = "聚居地",
["capital"] = "首都",
["natural feature"] = "自然",
["man-made structure"] = "人造構築物",
["geographic region"] = "地理文化區域",
}
local class_is_political_division = {
["polity"] = true,
["subpolity"] = true,
["settlement"] = true,
["non-admin settlement"] = false,
["capital"] = true,
["natural feature"] = false,
["man-made structure"] = false,
["geographic region"] = false,
["generic place"] = false,
}
-- Enwikt bare_category_parent / addl_bare_category_parents → zhwikt label.
-- false = no equivalent, skip.
local en_category_to_zh = {
["islands"] = "島嶼",
["cities"] = "城市",
["political divisions"] = "政治區劃",
["former polities"] = "前政治實體",
["former places"] = "地點",
["places"] = "地點",
["former settlements"] = "歷史聚居地",
["country-like entities"]= "類國家實體",
["names"] = "名稱",
["man-made structures"] = "人造構築物",
["countries"] = "國家",
["bodies of water"] = "水體",
["water"] = "水",
["landforms"] = "地形",
["ecosystems"] = "生態系統",
["forestry"] = "林學",
["mountains"] = "山",
["seas"] = "海",
["bridges"] = "橋",
["buildings"] = "建築",
["roads"] = "道路",
}
-- Translate enwikt category parent string to zhwikt equivalent.
-- Returns zh string, or nil if should be skipped.
-- Non-English strings (assumed already Chinese) pass through unchanged.
local function translate_cat_parent(val)
if type(val) ~= "string" then return nil end
local mapped = en_category_to_zh[val]
if mapped == false then return nil end
if mapped then return mapped end
return val
end
-- ---------------------------------------------------------------------------
-- Load-time reverse maps
-- ---------------------------------------------------------------------------
-- Chinese capital label → holonym type (e.g. "首都" → "country")
local zh_cap_label_to_holonym = {}
for holonym_type, zh_label in pairs(zh_cap_by_holonym) do
if not zh_cap_label_to_holonym[zh_label] then
zh_cap_label_to_holonym[zh_label] = holonym_type
end
end
-- Chinese placetype name → list of English placetype keys (one-to-many).
-- Used by handler 2 (bare placetype) and handler 4 (generic X的Y).
local zh_name_to_pt_keys = {}
do
local function add(name, en_key)
zh_name_to_pt_keys[name] = zh_name_to_pt_keys[name] or {}
insert(zh_name_to_pt_keys[name], en_key)
end
for en_key, entry in pairs(m_zh_data.zh_placetype_data) do
if entry.zh_name then
local names = type(entry.zh_name) == "table" and entry.zh_name or {entry.zh_name}
for _, name in ipairs(names) do add(name, en_key) end
end
if entry.zh_name_by_holonym_type then
for _, zh_name_val in pairs(entry.zh_name_by_holonym_type) do
local names = type(zh_name_val) == "table" and zh_name_val or {zh_name_val}
for _, name in ipairs(names) do add(name, en_key) end
end
end
end
end
-- Compound capital label → list of holonym placetype keys.
-- Compound = zh_name(holonym_type) .. zh_cap_label(holonym_type).
-- E.g. "州首府" → {"state"}, "地區首府" → {"territory", "region"}.
-- Skip country/constituent country (no 國首都 category per design).
-- Skip entries where compound == cap_label (e.g. "省省會"/"縣縣治" excluded to
-- avoid ambiguous matches — these bare labels are already handled by handler 5).
local zh_compound_cap_to_placetypes = {}
do
local function add_compound(compound, pt)
zh_compound_cap_to_placetypes[compound] = zh_compound_cap_to_placetypes[compound] or {}
insert(zh_compound_cap_to_placetypes[compound], pt)
end
for holonym_type, cap_label in pairs(zh_cap_by_holonym) do
if holonym_type ~= "country" and holonym_type ~= "constituent country" then
local zh_name_raw = m_placetypes.get_zh_placetype_name(holonym_type, nil)
if zh_name_raw then
local zh_name_str = format_zh_name(zh_name_raw)
local compound = zh_name_str .. cap_label
if compound ~= cap_label then
add_compound(compound, holonym_type)
end
end
end
end
end
-- ---------------------------------------------------------------------------
-- Location-linking helpers
-- ---------------------------------------------------------------------------
local function construct_linked_location(group, key, spec)
local full_placename = m_locations.key_to_placename(group, key)
return m_locations.construct_linked_placename(spec, full_placename)
end
-- Returns a location description string for embedding in category descriptions.
-- Shows one level of container for sub-national entries:
-- country → [[法國]]
-- state → [[加州]]([[美國]]的[[州]])
-- city → [[舊金山]]([[加州]]的[[城市]])
local function construct_location_desc(group, key, spec)
local linked = construct_linked_location(group, key, spec)
if spec.no_include_container_in_desc then return linked end
local container_iterator = m_locations.iterate_containers(group, key, spec)
local containers = container_iterator()
if not containers then return linked end
local location_type = spec.placetype
if type(location_type) == "table" then location_type = location_type[1] end
local zh_name_raw = location_type and m_placetypes.get_zh_placetype_name(location_type, nil)
local zh_type = zh_name_raw and format_zh_name(zh_name_raw)
local cont_links = {}
for _, c in ipairs(containers) do
insert(cont_links, construct_linked_location(c.group, c.key, c.spec))
end
local cont_str = zh_join(cont_links)
if zh_type then
return linked .. "(" .. cont_str .. "的" .. zh_type .. ")"
else
return linked .. "(位於" .. cont_str .. ")"
end
end
-- Fetch or construct the location description, substituting "+++" with the
-- auto-constructed description if present in spec.keydesc.
local function fetch_or_construct_location_desc(group, key, spec)
local val = spec.keydesc
if is_callable(val) then
val = val(group, key, spec)
spec.keydesc = val
end
if val then
if val:find("%+%+%+", 1, true) then
local base = construct_location_desc(group, key, spec)
val = gsub_literally(val, "+++", base)
end
return val
end
return construct_location_desc(group, key, spec)
end
-- ---------------------------------------------------------------------------
-- Split label at every occurrence of 的
-- Returns list of {place, placetype} tables (all possible splits left→right).
-- ---------------------------------------------------------------------------
local function split_at_de(label)
local results = {}
local de = "的"
local start = 1
while true do
local s, e = label:find(de, start, true)
if not s then break end
local place_part = label:sub(1, s - 1)
local type_part = label:sub(e + 1)
if #place_part > 0 and #type_part > 0 then
insert(results, {place = place_part, placetype = type_part})
end
start = e + 1
end
return results
end
-- Locate group + key + spec for a Chinese location string.
local function find_place(place_str)
local group, spec = m_locations.find_canonical_key(place_str)
if group then return group, place_str, spec end
return nil
end
-- No articles in Chinese; just return the key.
local function get_prefixed_key(key, spec)
if m_placetypes.get_prefixed_key then
return m_placetypes.get_prefixed_key(key, spec)
end
return key
end
-- Check whether zh_name_val (string or array) contains type_zh.
local function zh_name_has(zh_name_val, type_zh)
if type(zh_name_val) == "table" then
for _, n in ipairs(zh_name_val) do
if n == type_zh then return true end
end
return false
end
return zh_name_val == type_zh
end
-- Search divs list for a div whose Chinese name matches type_zh.
-- Singularizes div.type before zh_name lookup (divs use plural English keys
-- like "states", while zh_placetype_data uses singular keys like "state").
-- Uses forward lookup (English key → zh_name) to handle many-to-one collisions.
-- Returns: div_parent (English key or false), prep, matched_en_type.
-- div_parent == nil means no match; div_parent == false means match but no parent cat.
-- holonym_pt: the primary placetype of the container spec (e.g. "empire" for Roman Empire).
-- Passed to get_zh_placetype_name so context-sensitive zh_name_by_holonym_type overrides apply
-- (e.g. "province" + holonym_pt "empire" → "行省" instead of "省").
local function find_div_by_zh_name(divs, type_zh, holonym_pt)
if not divs then return nil, nil, nil end
if type(divs) ~= "table" then divs = {divs} end
for _, div in ipairs(divs) do
if type(div) == "string" then div = {type = div} end
local cat_as = div.cat_as or div.type
if type(cat_as) ~= "table" or cat_as.type then cat_as = {cat_as} end
for _, pt_cat_as in ipairs(cat_as) do
if type(pt_cat_as) == "string" then pt_cat_as = {type = pt_cat_as} end
-- Try direct key first (handles combined keys like "provinces and autonomous regions"),
-- then singularize as fallback (handles standard plural keys like "provinces").
local lookup_type = m_placetypes.maybe_singularize_placetype(pt_cat_as.type)
or pt_cat_as.type
local div_zh_raw = m_placetypes.get_zh_placetype_name(pt_cat_as.type, holonym_pt)
or m_placetypes.get_zh_placetype_name(lookup_type, holonym_pt)
if div_zh_raw and zh_name_has(div_zh_raw, type_zh) then
local div_parent = pt_cat_as.container_parent_type
if div_parent == nil then div_parent = div.container_parent_type end
if div_parent == nil then div_parent = pt_cat_as.type end
return div_parent, pt_cat_as.prep or div.prep or "of", lookup_type
end
end
end
return nil, nil, nil
end
-- Look up Chinese category name from a container's div cat_as, mirroring enwikt's
-- PL_PLACETYPE resolution. Used by handler 3 so that provinces + autonomous regions
-- of China both resolve to "省和自治區" rather than their individual zh_names.
-- Returns zh_name string (e.g. "省和自治區"), or nil to fall back to parent_zh_type.
local function find_cat_zh_from_container_divs(loc_type, container_spec)
if not loc_type or not container_spec then return nil end
local pl_type = m_placetypes.pluralize_placetype(loc_type)
if not pl_type then return nil end
for _, div_list in ipairs({container_spec.divs, container_spec.addl_divs}) do
if div_list then
local divs = type(div_list) == "table" and div_list or {div_list}
for _, div in ipairs(divs) do
if type(div) == "string" then div = {type = div} end
if div.type == pl_type then
local cat_as = div.cat_as
if cat_as and type(cat_as) == "string" then
-- Try cat_as directly first (handles combined keys like
-- "provinces and autonomous regions" which singularize incorrectly).
local zh_raw = m_placetypes.get_zh_placetype_name(cat_as, nil)
if not zh_raw then
local sg = m_placetypes.maybe_singularize_placetype(cat_as)
if sg then zh_raw = m_placetypes.get_zh_placetype_name(sg, nil) end
end
if zh_raw then return format_zh_name(zh_raw) end
end
return nil -- found div but no usable cat_as
end
end
end
end
return nil
end
-- ===========================================================================
-- Handler 1 — Bare capital category labels
-- e.g. "首都", "省會", "首府", "縣治"
-- ===========================================================================
insert(handlers, function(label)
local holonym_type = zh_cap_label_to_holonym[label]
if not holonym_type then return nil end
local zh_raw = m_placetypes.get_zh_placetype_name(holonym_type, nil)
local zh_holonym = zh_raw and format_zh_name(zh_raw) or holonym_type
return {
type = "name",
topic = label,
description = "{{{langname}}}中" .. zh_holonym .. "[[" .. label .. "]]的名稱。",
parents = {"首都"},
}
end)
-- ===========================================================================
-- Handler 2 — Bare placetype categories
-- e.g. "城市", "河流", "山脈", "國家"
-- ===========================================================================
insert(handlers, function(label)
local en_keys = zh_name_to_pt_keys[label]
if not en_keys then return nil end
for _, en_key in ipairs(en_keys) do
local canon_pt, ptdata = m_placetypes.get_placetype_data(en_key, "from category")
if canon_pt then
local from_cat = {from_category = true, no_split_qualifiers = true}
local bare_parent = m_placetypes.get_equiv_placetype_prop(canon_pt, function(pt)
local bcp = m_placetypes.get_placetype_prop(pt, "bare_category_parent")
if bcp then
local zh = translate_cat_parent(bcp)
if zh then return zh end
end
local cls = m_placetypes.get_placetype_prop(pt, "class")
if cls then return class_to_bare_category_parent[cls] end
end, from_cat) or "地點"
local addl_parents_raw = m_placetypes.get_equiv_placetype_prop(canon_pt, function(pt)
return m_placetypes.get_placetype_prop(pt, "addl_bare_category_parents")
end, from_cat)
local addl_parents
if addl_parents_raw then
addl_parents = {}
for _, v in ipairs(addl_parents_raw) do
local zh = translate_cat_parent(v)
if zh then m_table.insertIfNot(addl_parents, zh) end
end
if #addl_parents == 0 then addl_parents = nil end
end
local breadcrumb = m_placetypes.get_equiv_placetype_prop(canon_pt, function(pt)
return m_placetypes.get_placetype_prop(pt, "bare_category_breadcrumb")
end, from_cat)
if type(bare_parent) == "string" and breadcrumb then
bare_parent = {name = bare_parent, sort = breadcrumb}
end
local parents = {bare_parent}
if addl_parents then m_table.extend(parents, addl_parents) end
return {
type = "name",
topic = label,
description = "{{{langname}}}中[[" .. label .. "]]的名稱。",
breadcrumb = breadcrumb,
parents = parents,
}
end
end
end)
-- ===========================================================================
-- Handler 3 — Bare placename categories (known locations)
-- e.g. "美國", "法國", "北京", "加利福尼亞州"
-- Supports: overriding_bare_label_parents, bare_category_parent_type,
-- "+++" expansion, addl_parents, wp/wpcat/commonscat, fulldesc.
-- "+++" expands to: CONTAINER的TYPE (Chinese order) or CONTAINERTYPE for "地點".
-- ===========================================================================
insert(handlers, function(label)
local group, spec = m_locations.find_canonical_key(label)
if not group then return nil end
local wp = spec.wp
if wp == nil then wp = true end
local wpcat = spec.wpcat
if wpcat == nil then wpcat = wp end
local commonscat = spec.commonscat
if commonscat == nil then commonscat = wpcat end
local full_placename = m_locations.key_to_placename(group, label)
local full_container_placename
local container_iterator = m_locations.iterate_containers(group, label, spec)
local containers = container_iterator()
if containers then
full_container_placename = m_locations.key_to_placename(
containers[1].group, containers[1].key)
end
-- Substitute %l (full placename), %e (same, no elliptical form in Chinese),
-- %c (container placename) in wp/wpcat/commonscat values.
local function format_boxval(val)
if val == true then val = "%l" end
if type(val) == "string" then
val = gsub_literally(val, "%l", full_placename)
val = gsub_literally(val, "%e", full_placename)
if val:find("%%c", 1, true) and full_container_placename then
val = gsub_literally(val, "%c", full_container_placename)
end
end
return val
end
-- Determine Chinese type label for parent category construction.
-- bare_category_parent_type overrides the location's own placetype.
local parent_zh_type
do
local override = spec.bare_category_parent_type
if override then
local t = type(override) == "table"
and (override.type or override[1]) or override
local raw = type(t) == "string"
and m_placetypes.get_zh_placetype_name(t, nil)
parent_zh_type = raw and format_zh_name(raw)
end
if not parent_zh_type then
local loc_type = fetch_primary_placetype(label, spec)
local raw = loc_type and m_placetypes.get_zh_placetype_name(loc_type, nil)
parent_zh_type = raw and format_zh_name(raw)
end
end
local parents = {}
local bare_label_parents = spec.overriding_bare_label_parents
if not bare_label_parents then
bare_label_parents = {"+++"}
end
local inserted_containers = false
local loc_type = fetch_primary_placetype(label, spec)
for _, parent in ipairs(bare_label_parents) do
if parent == "+++" then
-- Expand to Chinese parent: CONTAINER的TYPE or CONTAINERTYPE for "地點";
-- omit 的 when TYPE=="國家" and container is broad (大洲/大陸地區 → 亞洲國家/加勒比地區國家);
-- use bare container when TYPE=="國家" but container is not broad (新西蘭 not 新西蘭的國家).
-- First, try container's div cat_as (mirrors enwikt PL_PLACETYPE resolution),
-- so that e.g. provinces and ARs of China both resolve to "省和自治區".
if containers then
for _, container in ipairs(containers) do
local ck = get_prefixed_key(container.key, container.spec)
local effective_zh_type = (loc_type
and find_cat_zh_from_container_divs(loc_type, container.spec))
or parent_zh_type
local parent_cat
if effective_zh_type == "地點" then
parent_cat = ck .. "地點"
elseif effective_zh_type == "國家" then
if spec_is_broad(container.spec) then
parent_cat = ck .. "國家"
else
parent_cat = ck
end
elseif effective_zh_type then
parent_cat = ck .. zh_de(effective_zh_type) .. effective_zh_type
else
parent_cat = ck
end
m_table.insertIfNot(parents, parent_cat)
end
inserted_containers = true
end
else
m_table.insertIfNot(parents, parent)
end
end
-- If "+++" wasn't present in bare_label_parents and containers exist, insert
-- bare container keys as fallback (mirrors enwikt behaviour).
if not inserted_containers and containers then
for _, container in ipairs(containers) do
m_table.insertIfNot(parents, container.key)
end
end
if spec.addl_parents then
for _, p in ipairs(spec.addl_parents) do
m_table.insertIfNot(parents, p)
end
end
if #parents == 0 then insert(parents, "地點") end
local description = spec.fulldesc or (
"{{{langname}}}中與" ..
fetch_or_construct_location_desc(group, label, spec) ..
"相關的詞語。")
return {
type = "topic",
description = description,
breadcrumb = full_placename,
parents = parents,
wp = format_boxval(wp),
wpcat = format_boxval(wpcat),
commonscat = format_boxval(commonscat),
}
end)
-- ===========================================================================
-- Handler 4 — Generic placetype + location
-- e.g. "法國的城市", "德國的河流", "亞洲的國家"
-- Matches placetypes that carry generic_before_non_cities or generic_before_cities.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_name_to_pt_keys)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
local en_keys = zh_name_to_pt_keys[type_zh]
if en_keys then
for _, en_key in ipairs(en_keys) do
local canon_pt, ptdata = m_placetypes.get_placetype_data(en_key, "from category")
if canon_pt and ptdata and
(ptdata.generic_before_non_cities or ptdata.generic_before_cities) then
local group, key, spec = find_place(place_str)
if group then
local allow = true
if spec.is_former_place and en_key ~= "place" then allow = false end
if allow then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = key})
local spt = spec.placetype
if spt == "country" or (type(spt) == "table" and m_table.contains(spt, "country")) then
insert(parents, "特定國家的政治區劃")
end
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(type_zh) .. type_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end
end
end
end)
-- ===========================================================================
-- Handler 4b — Continent/broad location + 國家 (no 的)
-- e.g. "非洲國家", "亞洲國家"
-- Handles the no-的 form generated for country categories under continents.
-- ===========================================================================
insert(handlers, function(label)
local zh_type = "國家"
local zh_type_len = #zh_type -- byte length (6 for 2 Chinese chars in UTF-8)
if #label <= zh_type_len or label:sub(-zh_type_len) ~= zh_type then return nil end
local place_str = label:sub(1, #label - zh_type_len)
local group, key, spec = find_place(place_str)
if not group or not spec_is_broad(spec) then return nil end
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. zh_type .. "]]的名稱。"
local parents = {key, {name = zh_type, sort = " " .. key}}
return {
type = "name",
topic = label,
description = desc,
breadcrumb = zh_type,
parents = parents,
}
end)
-- ===========================================================================
-- Handler 4c — Generic "X地點" categories (no 的)
-- e.g. "臺灣地點", "美國地點", "加州地點"
-- generic_place_cat_handler in place-placetypes.lua emits this no-的 form.
-- Handler 4 (split_at_de) cannot match it because there is no 的 separator.
-- ===========================================================================
insert(handlers, function(label)
local zh_type = "地點"
local zh_type_len = #zh_type -- 6 bytes (2 UTF-8 chars)
if #label <= zh_type_len or label:sub(-zh_type_len) ~= zh_type then return nil end
local place_str = label:sub(1, #label - zh_type_len)
local group, key, spec = find_place(place_str)
if not group then return nil end
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. zh_type .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = zh_type, sort = " " .. key})
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_type,
sort = key
})
end
else
insert(parents, {name = zh_type, sort = " " .. key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = zh_type,
parents = parents,
}
end)
-- ===========================================================================
-- Handler 5 — Capital + location (bare capital label)
-- e.g. "美國的首都", "四川省的省會", "德克薩斯州的首府"
-- Handles bare capital labels from zh_cap_label_to_holonym.
-- Must precede handler 6 (compound capital) and handler 7 (division) to avoid
-- mis-matching capital labels that also appear in a location's divs via cat_as.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_cap_label_to_holonym)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
if zh_cap_label_to_holonym[type_zh] then
local group, key, spec = find_place(place_str)
if group then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = key})
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(type_zh) .. type_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end)
-- ===========================================================================
-- Handler 6 — Compound capital label + location
-- e.g. "美國的州首府", "法國的行政區首府", "澳大利亞的地區首府"
-- Compound label = zh_name(holonym_type) .. zh_cap_label(holonym_type).
-- Built at load time in zh_compound_cap_to_placetypes.
-- Must precede handler 7 (division) to avoid mis-matching.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_compound_cap_to_placetypes)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
local target_pts = zh_compound_cap_to_placetypes[type_zh]
if target_pts then
local group, key, spec = find_place(place_str)
if group then
-- Check if any target placetype appears in this location's divs.
-- Div types are plural English strings ("states"); singularize before comparing.
local found = false
for _, divlist in ipairs({spec.divs, spec.addl_divs,
spec.addl_divs_for_categorization}) do
if divlist and not found then
if type(divlist) ~= "table" then divlist = {divlist} end
for _, div in ipairs(divlist) do
if type(div) == "string" then div = {type = div} end
local function check_div_type(pt_type)
local sg = m_placetypes.maybe_singularize_placetype(pt_type)
or pt_type
for _, target_pt in ipairs(target_pts) do
if sg == target_pt or pt_type == target_pt then
found = true
break
end
end
end
check_div_type(div.type)
if not found and div.cat_as then
local ca = div.cat_as
if type(ca) ~= "table" or ca.type then ca = {ca} end
for _, pt_ca in ipairs(ca) do
if type(pt_ca) == "string" then
pt_ca = {type = pt_ca}
end
check_div_type(pt_ca.type)
if found then break end
end
end
if found then break end
end
end
if found then break end
end
if found then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc
.. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = key})
local spt = spec.placetype
if spt == "country" or
(type(spt) == "table" and m_table.contains(spt, "country")) then
insert(parents, "特定國家的政治區劃")
end
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(type_zh) .. type_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = key})
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end
end)
-- ===========================================================================
-- Handler 7 — Administrative division + location
-- e.g. "美國的州", "法國的省", "英國的構成國", "日本的縣"
-- Uses forward zh_name lookup (English div type → zh_name) to avoid collision
-- between English types that share the same Chinese name (state/oblast/canton → "州").
-- Singularizes plural div types ("states" → "state") before zh_name lookup.
-- ===========================================================================
insert(handlers, function(label)
local splits = split_at_de(label)
add_no_de_suffix_splits(splits, label, zh_name_to_pt_keys)
for _, split in ipairs(splits) do
local place_str = split.place
local type_zh = split.placetype
local group, key, spec = find_place(place_str)
if group then
local primary_pt = fetch_primary_placetype(key, spec)
local div_parent, div_prep, matched_type = find_div_by_zh_name(spec.divs, type_zh, primary_pt)
if div_parent == nil then
div_parent, div_prep, matched_type = find_div_by_zh_name(spec.addl_divs, type_zh, primary_pt)
end
if div_parent == nil then
div_parent, div_prep, matched_type =
find_div_by_zh_name(spec.addl_divs_for_categorization, type_zh, primary_pt)
end
if div_parent ~= nil then
local loc_desc = fetch_or_construct_location_desc(group, key, spec)
local desc = "{{{langname}}}中" .. loc_desc .. "[[" .. type_zh .. "]]的名稱。"
local parents = {key}
if div_parent then -- div_parent == false means suppress parent cat
if spec.no_container_parent then
insert(parents, {name = type_zh, sort = " " .. key})
local spt = spec.placetype
if spt == "country" or (type(spt) == "table" and m_table.contains(spt, "country")) then
insert(parents, "特定國家的政治區劃")
end
else
local cont_iter = m_locations.iterate_containers(group, key, spec)
local next_conts = cont_iter()
if next_conts then
for _, c in ipairs(next_conts) do
-- Map English div_parent key back to Chinese for parent category name.
local dp_zh
if type(div_parent) == "string" then
local dp_raw = m_placetypes.get_zh_placetype_name(div_parent, nil)
if not dp_raw then
local sg_dp = m_placetypes.maybe_singularize_placetype(div_parent)
if sg_dp then dp_raw = m_placetypes.get_zh_placetype_name(sg_dp, nil) end
end
dp_zh = dp_raw and format_zh_name(dp_raw) or type_zh
else
dp_zh = type_zh
end
insert(parents, {
name = get_prefixed_key(c.key, c.spec) .. zh_de(dp_zh) .. dp_zh,
sort = key
})
end
else
insert(parents, {name = type_zh, sort = " " .. key})
end
end
end
return {
type = "name",
topic = label,
description = desc,
breadcrumb = type_zh,
parents = parents,
}
end
end
end
end)
-- ===========================================================================
-- Static labels — top-level grouping categories
-- ===========================================================================
labels["地點"] = {
type = "grouping",
description = "{{{langname}}}中地點的分類。",
parents = {"名稱"},
}
labels["政治實體"] = {
type = "grouping",
description = "{{{langname}}}中政治實體的名稱。",
parents = {"地點"},
}
labels["類國家實體"] = {
type = "grouping",
description = "{{{langname}}}中類國家實體(海外屬地、未被普遍承認之國家等)的名稱。",
parents = {"政治實體"},
}
labels["聚居地"] = {
type = "grouping",
description = "{{{langname}}}中聚落地名。",
parents = {"地點"},
}
labels["首都"] = {
type = "grouping",
description = "{{{langname}}}中首都及各類首府的名稱。",
parents = {"聚居地"},
}
labels["地形"] = {
type = "grouping",
description = "{{{langname}}}中地形的名稱。",
parents = {"自然"},
}
labels["人造構築物"] = {
type = "grouping",
description = "{{{langname}}}中人造構築物的名稱。",
parents = {"地點"},
}
labels["地理文化區域"] = {
type = "grouping",
description = "{{{langname}}}中地理及文化區域的名稱。",
parents = {"地點"},
}
labels["政治區劃"] = {
type = "grouping",
description = "{{{langname}}}中政治區劃(行政區及其他非主權行政分區)的名稱。",
parents = {"地點"},
}
labels["前政治實體"] = {
type = "grouping",
description = "{{{langname}}}中已消亡的政治實體(國家、王國、帝國等)的名稱。",
parents = {"政治實體"},
}
labels["特定國家的政治區劃"] = {
type = "grouping",
description = "{{{langname}}}中特定國家政治區劃的分類。",
parents = {"地點"},
}
-- ---------------------------------------------------------------------------
-- Misc. place-related labels
-- ---------------------------------------------------------------------------
labels["外名"] = {
type = "name",
description = "{{{langname}}}[[外名]]。",
parents = {"地點"},
}
labels["古埃及行政區"] = {
type = "name",
description = "{{{langname}}}中[[古埃及]][[行政區]]的名稱。",
breadcrumb = "行政區",
parents = {"古埃及"},
}
-- ---------------------------------------------------------------------------
-- Sui generis place categories (cross-jurisdictional / transcontinental regions)
-- ---------------------------------------------------------------------------
labels["大西洋"] = {
type = "related-to",
description = "{{{langname}}}中與[[大西洋]]相關的詞語。",
parents = {"地球"},
}
labels["不列顛群島"] = {
type = "related-to",
description = "{{{langname}}}中與[[大不列顛島]]、[[愛爾蘭島]]及鄰近島嶼相關的詞語。",
parents = {"歐洲", "島嶼"},
}
labels["歐盟"] = {
type = "related-to",
description = "{{{langname}}}中與[[歐盟]]相關的詞語。",
parents = {"歐洲"},
}
labels["加斯科涅"] = {
type = "related-to",
description = "{{{langname}}}中與[[加斯科涅]]相關的詞語。",
parents = {"法國"},
}
labels["印度次大陸"] = {
type = "related-to",
description = "{{{langname}}}中與[[印度次大陸]]相關的詞語。",
parents = {"南亞"},
}
labels["孟加拉"] = {
type = "related-to",
description = "{{{langname}}}中與[[孟加拉]]地區相關的詞語。",
parents = {"印度次大陸"},
}
labels["克什米爾"] = {
type = "related-to",
description = "{{{langname}}}中與[[克什米爾]]相關的詞語。",
parents = {"印度次大陸"},
}
labels["克什米爾(印度)"] = {
type = "related-to",
description = "{{{langname}}}中[[印度]]管轄的[[克什米爾]]地名。",
parents = {"印度", "克什米爾"},
}
labels["朝鮮半島"] = {
type = "related-to",
description = "{{{langname}}}中與[[朝鮮半島]]相關的詞語。",
parents = {"亞洲"},
}
labels["朗格多克"] = {
type = "related-to",
description = "{{{langname}}}中與[[朗格多克]]相關的詞語。",
parents = {"法國"},
}
labels["拉普蘭"] = {
type = "related-to",
description = "{{{langname}}}中與[[拉普蘭]]相關的詞語。",
parents = {"歐洲", "芬蘭", "挪威", "俄羅斯", "瑞典"},
}
labels["中東"] = {
type = "related-to",
description = "{{{langname}}}中與[[中東]]相關的詞語。",
parents = {"非洲", "亞洲"},
}
labels["荷屬安的列斯"] = {
type = "related-to",
description = "{{{langname}}}中與[[荷屬安的列斯]]相關的詞語。",
parents = {"荷蘭", "北美洲"},
}
labels["海外法國"] = {
type = "related-to",
description = "{{{langname}}}中與[[海外法國]]相關的詞語。",
parents = {"法國"},
}
labels["普羅旺斯"] = {
type = "related-to",
description = "{{{langname}}}中與[[普羅旺斯]]相關的詞語。",
parents = {"法國"},
}
labels["波蘭人民共和國"] = {
type = "related-to",
description = "{{{langname}}}中與[[波蘭人民共和國]]相關的詞語。",
parents = {"波蘭"},
}
labels["南亞"] = {
type = "related-to",
description = "{{{langname}}}中與[[南亞]]相關的詞語。",
parents = {"歐亞大陸", "亞洲"},
}
return {LABELS = labels, HANDLERS = handlers}