Wiktionary
miwiktionary
https://mi.wiktionary.org/wiki/Hau_K%C4%81inga
MediaWiki 1.47.0-wmf.11
case-sensitive
Media
Special
Talk
User
User talk
Wiktionary
Wiktionary talk
File
File talk
MediaWiki
MediaWiki talk
Template
Template talk
Help
Help talk
Category
Category talk
TimedText
TimedText talk
Module
Module talk
Event
Event talk
Module:category tree
828
5877
17463
17324
2026-07-15T16:04:56Z
Hiyuune
6766
17463
Scribunto
text/plain
-- Prevent substitution.
if mw.isSubsting() then
return require("Module:unsubst")
end
local export = {}
local category_tree_submodule_prefix = "Module:category tree/"
local category_tree_styles_css = "Module:category tree/styles.css"
local m_str_utils = require("Module:string utilities")
local m_template_parser = require("Module:template parser")
local m_utilities = require("Module:utilities")
local ceil = math.ceil
local class_else_type = m_template_parser.class_else_type
local concat = table.concat
local deep_copy = require("Module:table").deepCopy
local full_url = mw.uri.fullUrl
local insert = table.insert
local is_callable = require("Module:fun").is_callable
local log10 = math.log10 or require("Module:math").log10
local new_title = mw.title.new
local pages_in_category = mw.site.stats.pagesInCategory
local parse = m_template_parser.parse
local remove_comments = require("Module:string/removeComments")
local sort = table.sort
local split = m_str_utils.split
local string_compare = require("Module:string/compare")
local trim = m_str_utils.trim
local uupper = m_str_utils.upper
local yesno = require("Module:yesno")
local current_frame = mw.getCurrentFrame()
local current_title = mw.title.getCurrentTitle()
local namespace = current_title.namespace
local poscatboiler_subsystem = "poscatboiler"
local extra_args_error = "Extra arguments to {{((}}auto cat{{))}} are not allowed for this category."
-- Generates a sortkey for a numeral `n`, adding leading zeroes to avoid the "1, 10, 2, 3" sorting problem. `max_n` is the greatest expected value of `n`, and is used to determine how many leading zeroes are needed. If not supplied, it defaults to the number of languages.
function export.numeral_sortkey(n, max_n)
max_n = max_n or require("Module:list of languages").count()
return ("#%%0%dd"):format(ceil(log10(max_n + 1))):format(n)
end
function export.split_lang_label(title_text)
local getByCanonicalName = require("Module:languages").getByCanonicalName
-- Progressively remove a word from the potential canonical name until it
-- matches an actual canonical name.
local words = split(title_text, " ", true)
for i = #words - 1, 1, -1 do
local lang = getByCanonicalName(concat(words, " ", 1, i))
if lang then
return lang, concat(words, " ", i + 1)
end
end
return nil, title_text
end
local function show_error(text)
return require("Module:message box").maintenance(
"red",
"[[File:Ambox warning pn.svg|50px]]",
"This category is not defined in Wiktionary's category tree.",
text
)
end
-- Show the text that goes at the very top right of the page.
local function show_topright(current)
return current.getTopright and current:getTopright() or nil
end
local function link_box(content)
return ("<div class=\"noprint plainlinks\" style=\"float: right; clear: both; margin: 0 0 .5em 1em; border: 1px var(--border-color-base, #aaaaaa) solid; margin-top: -1px; padding: 5px; font-weight: bold;\">%s</div>"):format(content)
end
local function show_editlink(current)
return link_box(("[%s Edit category data]"):format(tostring(full_url(current:getDataModule(), "action=edit"))))
end
function show_related_changes()
local title = current_title.fullText
return link_box(("[%s <span title=\"Recent edits and other changes to pages in %s\">Recent changes</span>]"):format(
tostring(full_url("Special:RecentChangesLinked", {
target = title,
showlinkedto = 0,
})),
title
))
end
local function show_pagelist(current)
local namespace = "namespace="
local info = current:getInfo()
local lang_code = info.code
if info.label == "citations" or info.label == "citations of undefined terms" then
namespace = namespace .. "Citations"
elseif lang_code then
local lang = require("Module:languages").getByCode(lang_code, true)
if lang then
-- Proto-Norse (gmq-pro) is the probably language with a code ending in -pro
-- that's intended to have mostly non-reconstructed entries.
if (lang_code:find("%-pro$") and lang_code ~= "gmq-pro") or lang:hasType("reconstructed") then
namespace = namespace .. "Reconstruction"
elseif lang:hasType("appendix-constructed") then
namespace = namespace .. "Appendix"
end
end
elseif info.label:match("templates") then
namespace = namespace .. "Template"
elseif info.label:match("modules") then
namespace = namespace .. "Module"
elseif info.label:match("^Wiktionary") or info.label:match("^Pages") then
namespace = ""
end
end
-- Show navigational "breadcrumbs" at the top of the page.
local function show_breadcrumbs(current)
local steps = {}
-- Start at the current label and move our way up the "chain" from child to parent, until we can't go further.
while current do
local category, display_name, nocap
if type(current) == "string" then
category = current
display_name = current:gsub("^Category:", "")
else
if not current.getCategoryName then
error("Internal error: Bad format in breadcrumb chain structure, probably a misformatted value for `parents`: " ..
mw.dumpObject(current))
end
category = "Category:" .. current:getCategoryName()
display_name, nocap = current:getBreadcrumbName()
end
if not nocap then
display_name = mw.getContentLanguage():ucfirst(display_name)
end
insert(steps, 1, ("[[:%s|%s]]"):format(category, display_name))
-- Move up the "chain" by one level.
if type(current) == "string" then
current = nil
else
current = current:getParents()
end
if current then
current = current[1].name
end
end
local templateStyles = require("Module:TemplateStyles")(category_tree_styles_css)
local ol = mw.html.create("ol")
for i, step in ipairs(steps) do
local li = mw.html.create("li")
if i ~= 1 then
local span = mw.html.create("span")
:attr("aria-hidden", "true")
:addClass("ts-categoryBreadcrumbs-separator")
:wikitext(" » ")
li:node(span)
end
li:wikitext(step)
ol:node(li)
end
return templateStyles .. tostring(mw.html.create("div")
:attr("role", "navigation")
:attr("aria-label", "Breadcrumb")
:addClass("ts-categoryBreadcrumbs")
:node(ol))
end
local function show_also(current)
local also = current._info.also
if also and #also > 0 then
return ('<div style="margin-top:-1em;margin-bottom:1.5em">%s</div>'):format(require("Module:also").main(also))
end
return nil
end
-- Show a short description text for the category.
local function show_description(current)
return current.getDescription and current:getDescription() or nil
end
local function show_appendix(current)
local appendix = current.getAppendix and current:getAppendix()
return appendix and ("For more information, see [[%s]]."):format(appendix) or nil
end
local function sort_children(child1, child2)
return string_compare(uupper(child1.sort), uupper(child2.sort))
end
-- Show a list of child categories.
local function show_children(current)
local children = current.getChildren and current:getChildren() or nil
if not children then
return nil
end
sort(children, sort_children)
local children_list = {}
for _, child in ipairs(children) do
local child_name, child_pagetitle = child.name
if type(child_name) == "string" then
child_pagetitle = child_name
else
child_pagetitle = "Category:" .. child_name:getCategoryName()
end
if new_title(child_pagetitle).exists then
insert(children_list, ("* [[:%s]]: %s"):format(
child_pagetitle,
child.description or
type(child_name) == "string" and child_name:gsub("^Category:", "") .. "." or
child_name:getDescription("child")
))
end
end
return concat(children_list, "\n")
end
-- Show a table of contents with links to each letter in the language's script.
local function show_TOC(current)
local titleText = current_title.text
local inCategoryPages = pages_in_category(titleText, "pages")
local inCategorySubcats = pages_in_category(titleText, "subcats")
local TOC_type
-- Compute type of table of contents required.
if inCategoryPages > 2500 or inCategorySubcats > 2500 then
TOC_type = "full"
elseif inCategoryPages > 200 or inCategorySubcats > 200 then
TOC_type = "normal"
else
-- No (usual) need for a TOC if all pages or subcategories can fit on one page;
-- but allow this to be overridden by a custom TOC handler.
TOC_type = "none"
end
if current.getTOC then
local TOC_text = current:getTOC(TOC_type)
if TOC_text ~= true then
return TOC_text or nil
end
end
if TOC_type ~= "none" then
local templatename = current:getTOCTemplateName()
local TOC_template
if TOC_type == "full" then
-- This category is very large, see if there is a "full" version of the TOC.
local TOC_template_full = new_title(templatename .. "/full")
if TOC_template_full.exists then
TOC_template = TOC_template_full
end
end
if not TOC_template then
local TOC_template_normal = new_title(templatename)
if TOC_template_normal.exists then
TOC_template = TOC_template_normal
end
end
if TOC_template then
return current_frame:expandTemplate{title = TOC_template.text, args = {}}
end
end
return nil
end
-- Show the "catfix" that adds language attributes and script classes to the page.
local function show_catfix(current)
local lang, sc = current:getCatfixInfo()
return lang and m_utilities.catfix(lang, sc) or nil
end
-- Show the parent categories that the current category should be placed in.
local function show_categories(current, categories)
local parents = current.getParents and current:getParents() or nil
if not parents then
return nil
end
for _, parent in ipairs(parents) do
local parent_name = parent.name
local sortkey = type(parent.sort) == "table" and parent.sort:makeSortKey() or parent.sort
if type(parent_name) == "string" then
insert(categories, ("[[%s|%s]]"):format(parent_name, sortkey))
else
insert(categories, ("[[Category:%s|%s]]"):format(parent_name:getCategoryName(), sortkey))
end
end
-- Also put the category in its corresponding "umbrella" or "by language" category.
local umbrella = current:getUmbrella()
if umbrella then
-- FIXME: use a language-neutral sorting function like the Unicode Collation Algorithm.
local sortkey = current._lang and current._lang:getCanonicalName() or current:getCategoryName()
sortkey = require("Module:languages").getByCode("mi", true):makeSortKey(sortkey)
if type(umbrella) == "string" then
insert(categories, ("[[%s|%s]]"):format(umbrella, sortkey))
else
insert(categories, ("[[Category:%s|%s]]"):format(umbrella:getCategoryName(), sortkey))
end
end
-- Check for various unwanted parser functions, which should be integrated into the category tree data instead.
-- Note: HTML comments shouldn't be removed from `content` until after this step, as they can affect the result.
local content = current_title:getContent()
if not content then
-- This happens when using [[Special:ExpandTemplates]] to call {{auto cat}} on a nonexistent category page,
-- which is needed by Benwing's create_wanted_categories.py script.
return
end
local defaultsort, displaytitle, page_has_param
for node in parse(content):iterate_nodes() do
local node_class = class_else_type(node)
if node_class == "template" then
local name = node:get_name()
if name == "DEFAULTSORT:" and not defaultsort then
insert(categories, "[[Category:Pages with DEFAULTSORT conflicts]]")
defaultsort = true
elseif name == "DISPLAYTITLE:" and not displaytitle then
insert(categories,"[[Category:Pages with DISPLAYTITLE conflicts]]")
displaytitle = true
end
elseif node_class == "parameter" and not page_has_param then
insert(categories,"[[Category:Pages with raw triple-brace template parameters]]")
page_has_param = true
end
end
-- Check for raw category markup, which should also be integrated into the category tree data.
content = remove_comments(content, "BOTH")
local head = content:find("[[", 1, true)
while head do
local close = content:find("]]", head + 2, true)
if not close then
break
end
-- Make sure there are no intervening "[[" between head and close.
local open = content:find("[[", head + 2, true)
while open and open < close do
head = open
open = content:find("[[", head + 2, true)
end
local cat = content:sub(head + 2, close - 1)
local colon = cat:match("^[ _\128-\244]*[Cc][Aa][Tt][EeGgOoRrYy _\128-\244]*():")
if colon then
local pipe = cat:find("|", colon + 1, true)
if pipe ~= #cat then
local title = new_title(pipe and cat:sub(1, pipe - 1) or cat)
if title and title.namespace == 14 then
insert(categories,"[[Category:Categories with categories using raw markup]]")
break
end
end
end
head = open
end
end
local function generate_output(current)
if current then
for _, functionName in pairs{
"getBreadcrumbName",
"getDataModule",
"canBeEmpty",
"getDescription",
"getParents",
"getChildren",
"getUmbrella",
"getAppendix",
"getTOCTemplateName",
} do
if not is_callable(current[functionName]) then
require("Module:debug").track{"category tree/missing function", "category tree/missing function/" .. functionName}
end
end
end
local boxes, display, categories = {}, {}, {}
-- Categories should never show files as a gallery.
insert(categories, "__NOGALLERY__")
if current_frame:getParent():getTitle() == "Template:auto cat" then
insert(categories, "[[Category:Ngā kāwai karanga Template:auto cat]]")
end
-- Check if the category is empty
local totalPages = pages_in_category(current_title.text, "all")
local hugeCategory = totalPages > 1000000 -- 1 million
-- Categorize huge categories, as they cause DynamicPageList to time out and make the category inaccessible.
if hugeCategory then
insert(categories, "[[Category:Huge categories]]")
end
-- Are the parameters valid?
if not current then
insert(categories, "[[Category:Categories that are not defined in the category tree]]")
insert(categories, totalPages == 0 and "[[Category:Empty categories]]" or nil)
insert(display, show_error(
"Double-check the category name for typos. <br>" ..
"[[Special:Search/Category: " .. current_title.text:gsub("^.+:", ""):gsub(" ", "~2 ") .. '~2|Search existing categories]] to check if this category should be created under a different name (for example, "Fruits" instead of "Fruit"). <br>' ..
"To add a new category to Wiktionary's category tree, please consult " .. current_frame:expandTemplate{title = "section link", args = {
"Help:Category#How_to_create_a_category",
}} .. "."))
-- Exit here, as all code beyond here relies on current not being nil
return concat(categories, "") .. concat(display, "\n\n"), true
end
-- Does the category have the correct name?
local currentName = current:getCategoryName()
local correctName = current_title.text == currentName
if not correctName then
insert(categories, "[[Category:Categories with incorrect names]]")
insert(display, show_error(("Based on the data in the category tree, this category should be called '''[[:Category:%s]]'''."):format(currentName)))
end
-- Add cleanup category for empty categories.
local canBeEmpty = current:canBeEmpty()
if canBeEmpty and correctName then
insert(categories, " __EXPECTUNUSEDCATEGORY__")
elseif totalPages == 0 then
insert(categories, "[[Category:Empty categories]]")
end
if current:isHidden() then
insert(categories, "__HIDDENCAT__")
end
-- Put all the float-right stuff into a <div> that does not clear, so that float-left stuff like the breadcrumbs and
-- description can go opposite the float-right stuff without vertical space.
insert(boxes, "<div style=\"float: right;\">")
insert(boxes, show_topright(current))
insert(boxes, show_editlink(current))
insert(boxes, show_related_changes())
insert(boxes, "</div>")
-- Generate the displayed information
insert(display, show_breadcrumbs(current))
insert(display, show_also(current))
insert(display, show_description(current))
insert(display, show_appendix(current))
insert(display, show_children(current))
insert(display, show_TOC(current))
insert(display, show_catfix(current))
insert(display, '<br class="clear-both-in-vector-2022-only">')
show_categories(current, categories)
return concat(boxes, "\n") .. "\n" .. concat(display, "\n\n") .. concat(categories, "")
end
--[==[
List of handler functions that try to match the page name. A handler should return the name of a submodule to
[[Module:category tree]] and an info table which is passed as an argument to the submodule. If a handler does not
recognize the page name, it should return nil. Note that the order of handlers matters!
]==]
local handlers = {}
-- Thesaurus per-language category
insert(handlers, function(title)
local code, label = title:match("^Thesaurus:(%l[%a-]*%a):(.+)")
if code then
return poscatboiler_subsystem, {label = title, raw = true}
end
end)
-- Topic per-language category
insert(handlers, function(title)
local code, label = title:match("^(%l[%a-]*%a):(.+)")
if code then
return poscatboiler_subsystem, {label = title, raw = true}
end
end)
-- Lect category e.g. for [[:Category:New Zealand English]] or [[:Category:Issime Walser]]
insert(handlers, function(title, args)
local lect = args.lect or args.dialect
if lect ~= "" and yesno(lect, true) then -- Same as boolean in [[Module:parameters]].
return poscatboiler_subsystem, {label = title, args = args, raw = true}
end
end)
-- poscatboiler per-language label, e.g. [[Category:English non-lemma forms]]
insert(handlers, function(title, args)
local lang, label = export.split_lang_label(title)
if not lang then
return
end
local baseLabel, script = label:match("(.+) in (.-) script$")
if script and baseLabel ~= "terms" then
local scriptObj = require("Module:scripts").getByCanonicalName(script)
if scriptObj then
return poscatboiler_subsystem, {label = baseLabel, code = lang:getCode(), sc = scriptObj:getCode(), args = args}
end
end
return poscatboiler_subsystem, {label = label, code = lang:getCode(), args = args}
end)
-- poscatboiler label umbrella category
insert(handlers, function(title, args)
local label = title:match("(.+) by language$")
if label then
-- The poscatboiler code will appropriately lowercase if needed.
return poscatboiler_subsystem, {label = label, args = args}
end
end)
-- poscatboiler raw handlers
insert(handlers, function(title, args)
return poscatboiler_subsystem, {label = title, args = args, raw = true}
end)
-- poscatboiler umbrella handlers without 'by language'
insert(handlers, function(title, args)
return poscatboiler_subsystem, {label = title, args = args}
end)
function export.show(frame)
local args, other_args = require("Module:parameters").process(frame:getParent().args, {
["also"] = {type = "title", sublist = "comma without whitespace", namespace = 14}
}, true)
if args.also then
for k, arg in next, args.also do
args.also[k] = arg.prefixedText
end
end
for k, arg in next, other_args do
other_args[k] = trim(arg)
end
if namespace == 10 then -- Template
return "(This template should be used on pages in the [[Help:Namespaces#Category|Category:]] namespace.)"
elseif namespace ~= 14 then -- Category
error("This template/module can only be used on pages in the [[mw:Help:Namespaces#Category|Category:]] namespace.")
end
local first_fail_args_handled, first_fail_cattext
-- Go through each handler in turn. If a handler doesn't recognize the format of the category, it will return nil,
-- and we will consider the next handler. Otherwise, it returns a template name and arguments to call it with, but
-- even then, that template might return an error, and we need to consider the next handler. This happens, for
-- example, with the category "CAT:Mato Grosso, Brazil", where "Mato" is the name of a language, so the poscatboiler
-- per-language label handler fires and tries to find a label "Grosso, Brazil". This throws an error, and
-- previously, this blocked fruther handler consideration, but now we check for the error and continue checking
-- handlers; eventually, the topic umbrella handler will fire and correctly handle the category.
for _, handler in ipairs(handlers) do
-- Use a new title object and args table for each handler, to keep them isolated.
local submodule, info = handler(current_title.text, deep_copy(other_args))
if submodule then
info.also = deep_copy(args.also)
require("Module:debug").track("auto cat/" .. submodule)
-- `failed` is true if no match was found.
submodule = require(category_tree_submodule_prefix .. submodule)
local cattext, failed = generate_output(submodule.main(info))
if failed then
if not first_fail_cattext then
first_fail_cattext = cattext
first_fail_args_handled = info.args and true or false
end
elseif not info.args and next(other_args) then
error(extra_args_error)
else
return cattext
end
end
end
-- If there were no matches, throw an error if any arguments were given, or otherwise return the cattext
-- from the first fail encountered. The final handlers call the boilers unconditionally, so there should
-- always be something to return.
if not first_fail_args_handled and next(other_args) then
error(extra_args_error)
end
return first_fail_cattext
end
-- TODO: new test entrypoint.
return export
sfht4f3gojwawhkkorvy8ruhyfz7wah
Module:category tree/poscatboiler
828
5901
17462
17268
2026-07-15T16:04:04Z
Hiyuune
6766
17462
Scribunto
text/plain
local lang_independent_data = require("Module:category tree/data")
local lang_specific_module = "Module:category tree/lang"
local lang_specific_module_prefix = lang_specific_module .. "/"
local labels_utilities_module = "Module:labels/utilities"
local template_parser_module = "Module:template parser"
local concat = table.concat
local dump = mw.dumpObject
local expand_template = require("Module:frame").expandTemplate
local insert = table.insert
local is_callable = require("Module:fun").is_callable
local lcfirst = require("Module:string utilities").lcfirst
local list_to_set = require("Module:table").listToSet
local make_title = mw.title.makeTitle
local new_title = mw.title.new
local parse = require(template_parser_module).parse
local sparse_concat = require("Module:table").sparseConcat
local tostring = tostring
local type = type
local ucfirst = require("Module:string utilities").ucfirst
local uupper = require("Module:string utilities").upper
local function get_lang(...)
local _get_lang = require("Module:languages").getByCode
function get_lang(...)
return _get_lang(...) or require("Module:languages/errorGetBy").code(...)
end
return get_lang(...)
end
local function get_script(...)
local _get_script = require("Module:scripts").getByCode
function get_script(code)
return _get_script(code) or require("Module:languages/error")(code, true, "script code")
end
return get_script(...)
end
-- Category object
local Category = {}
Category.__index = Category
function Category:get_originating_info()
local originating_info = ""
if self._info.originating_label then
originating_info = " (originating from label \"" .. self._info.originating_label .. "\" in module [[" .. self._info.originating_module .. "]])"
end
return originating_info
end
local valid_keys = list_to_set{"code", "label", "sc", "raw", "args", "also", "called_from_inside", "originating_label", "originating_module"}
function Category.new(info)
for key in pairs(info) do
if not valid_keys[key] then
error("The parameter \"" .. key .. "\" was not recognized.")
end
end
local self = setmetatable({}, Category)
self._info = info
if not self._info.label then
error("No label was specified.")
end
self:initCommon()
if not self._data then
error("The " .. (self._info.raw and "raw " or "") .. "label \"" .. self._info.label .. "\" does not exist" .. self:get_originating_info() .. ".")
end
return self
end
function Category:initCommon()
local args_handled = false
if self._info.raw then
-- Check if the category exists
local raw_categories = lang_independent_data["RAW_CATEGORIES"]
self._data = raw_categories[self._info.label]
if self._data then
if self._data.lang then
self._lang = get_lang(self._data.lang)
self._info.code = self._lang:getCode()
end
if self._data.sc then
self._sc = get_script(self._data.sc)
self._info.sc = self._sc:getCode()
end
else
-- Go through raw handlers
local data = {
category = self._info.label,
args = self._info.args or {},
called_from_inside = self._info.called_from_inside,
}
for _, handler in ipairs(lang_independent_data["RAW_HANDLERS"]) do
self._data, args_handled = handler.handler(data)
if self._data then
self._data.module = self._data.module or handler.module
break
end
end
if self._data then
-- Update the label if the handler specified a canonical name for it.
if self._data.canonical_name then
self._info.canonical_name = self._data.canonical_name
end
if self._data.lang then
if type(self._data.lang) ~= "string" then
error("Received non-string value " .. dump(self._data.lang) .. " for self._data.lang, label \"" .. self._info.label .. "\"" .. self:get_originating_info() .. ".")
end
self._lang = get_lang(self._data.lang)
self._info.code = self._lang:getCode()
end
if self._data.sc then
if type(self._data.sc) ~= "string" then
error("Received non-string value " .. dump(self._data.sc) .. " for self._data.sc, label \"" .. self._info.label .. "\"" .. self:get_originating_info() .. ".")
end
self._sc = get_script(self._data.sc)
self._info.sc = self._sc:getCode()
end
end
end
else
-- Already parsed into language + label
if self._info.code then
self._lang = get_lang(self._info.code)
else
self._lang = nil
end
if self._info.sc then
self._sc = get_script(self._info.sc)
else
self._sc = nil
end
self._info.orig_label = self._info.label
if not self._lang then
-- Umbrella categories without a preceding language always begin with a capital letter, but the actual label may be
-- lowercase (cf. [[:Category:Nouns by language]] with label 'nouns' with per-language [[:Category:English nouns]];
-- but [[:Category:Reddit slang by language]] with label 'Reddit slang' with per-language
-- [[:Category:English Reddit slang]]). Since the label is almost always lowercase, we lowercase it for umbrella
-- categories, storing the original into `orig_label`, and correct it later if needed.
self._info.label = lcfirst(self._info.label)
end
-- First, check lang-specific labels and handlers if this is not an umbrella category.
if self._lang then
local langs_with_modules = require(lang_specific_module)
local obj, seen = self._lang, {}
repeat
if langs_with_modules[obj:getCode()] then
local module = lang_specific_module_prefix .. obj:getCode()
local labels_and_handlers = require(module)
if labels_and_handlers.LABELS then
self._data = labels_and_handlers.LABELS[self._info.label]
if self._data then
if self._data.umbrella == nil and self._data.umbrella_parents == nil then
self._data.umbrella = false
end
self._data.module = self._data.module or module
end
end
if not self._data and labels_and_handlers.HANDLERS then
for _, handler in ipairs(labels_and_handlers.HANDLERS) do
local data = {
label = self._info.label,
lang = self._lang,
sc = self._sc,
args = self._info.args or {},
called_from_inside = self._info.called_from_inside,
}
self._data, args_handled = handler(data)
if self._data then
if self._data.umbrella == nil and self._data.umbrella_parents == nil then
self._data.umbrella = false
end
self._data.module = self._data.module or module
break
end
end
end
if self._data then
break
end
end
seen[obj:getCode()] = true
obj = obj:getFamily()
until not obj or seen[obj:getCode()]
end
-- Then check lang-independent labels.
if not self._data then
local labels = lang_independent_data["LABELS"]
self._data = labels[self._info.label]
-- See comment above about uppercase- vs. lowercase-initial labels, which are indistinguishable
-- in umbrella categories.
if not self._data then
self._data = labels[self._info.orig_label]
if self._data then
self._info.label = self._info.orig_label
end
end
end
-- Then check lang-independent handlers.
if not self._data then
local data = {
label = self._info.label,
lang = self._lang,
sc = self._sc,
args = self._info.args or {},
called_from_inside = self._info.called_from_inside,
}
for _, handler in ipairs(lang_independent_data["HANDLERS"]) do
self._data, args_handled = handler.handler(data)
if self._data then
self._data.module = self._data.module or handler.module
break
end
end
end
end
if not args_handled and self._data and self._info.args and next(self._info.args) then
local module_text = " (handled in [[" .. (self._data.module or "UNKNOWN").. "]])"
local args_text = {}
for k, v in pairs(self._info.args) do
insert(args_text, k .. "=" .. ((type(v) == "string" or type(v) == "number") and v or dump(v)))
end
error("poscatboiler label '" .. self._info.label .. "' " .. module_text .. " doesn't accept extra args " ..
concat(args_text, ", "))
end
if self._sc and not self._lang then
error("Umbrella categories cannot have a script specified.")
end
end
function Category:convert_spec_to_string(desc)
if not desc then
return desc
end
local desc_type = type(desc)
if desc_type == "string" then
return desc
elseif desc_type == "number" then
return tostring(desc)
elseif not is_callable(desc) then
error("Internal error: `desc` must be a string, number, function, callable table or nil; received a " .. desc_type)
end
desc = desc{
lang = self._lang,
sc = self._sc,
label = self._info.label,
raw = self._info.raw,
}
if not desc then
return desc
end
desc_type = type(desc)
if desc_type == "string" then
return desc
end
error("Internal error: the value returned by `desc` must be a string or nil; received a " .. desc_type)
end
local function add_obj_args(args, obj, obj_type)
if obj then
args[obj_type .. "code"] = obj:getCode()
args[obj_type .. "name"] = obj:getCanonicalName()
args[obj_type .. "disp"] = obj:getDisplayForm()
args[obj_type .. "cat"] = obj:getCategoryName()
args[obj_type .. "link"] = obj:makeCategoryLink()
end
end
-- Expands `desc` like a template, passing values for specs like {{{langname}}}.
function Category:substitute_template_specs(desc)
-- This may end up happening twice but that's OK as the function is (usually) idempotent.
-- FIXME: Not idempotent if a preprocessed template returns wikicode.
desc = self:convert_spec_to_string(desc)
if not desc then
return nil
end
-- Populate the substitution arguments.
local args = {}
args.umbrella_msg = "This is an umbrella category. It contains no dictionary entries, but only other, language-specific categories, which in turn contain relevant terms in a given language."
args.umbrella_meta_msg = "This is an umbrella metacategory, covering a general area such as \"lemmas\", \"names\" or \"terms by etymology\". It contains no dictionary entries, but holds only umbrella (\"by language\") categories covering specific subtopics, which in turn contain language-specific categories holding terms in a given language for that same topic."
add_obj_args(args, self._lang, "lang")
add_obj_args(args, self._sc, "sc")
return parse(desc, true):expand(args)
end
function Category:substitute_template_specs_in_args(args)
if not args then
return args
end
local pinfo = {}
for k, v in pairs(args) do
pinfo[self:substitute_template_specs(k)] = self:substitute_template_specs(v)
end
return pinfo
end
function Category:make_new(info)
info.originating_label = self._info.label
info.originating_module = self._data.module
info.called_from_inside = true
return Category.new(info)
end
function Category:getBreadcrumbName()
local ret
if self._lang or self._info.raw then
ret = self._data.breadcrumb
else
ret = self._data.umbrella and self._data.umbrella.breadcrumb
end
if not ret then
ret = self._info.label
end
if type(ret) ~= "table" then
ret = {name = ret}
end
local name = self:substitute_template_specs(ret.name)
local nocap = ret.nocap
if self._sc then
name = name .. " in " .. self._sc:getDisplayForm()
end
return name, nocap
end
local function expand_toc_template_if(template)
local template_obj = new_title(template, 10)
if template_obj.exists then
return expand_template{title = template_obj.text}
end
return nil
end
-- Return the textual expansion of the first existing template among the given templates, first performing
-- substitutions on the template name such as replacing {{{langcode}}} with the current language's code (if any).
-- If no templates exist after expansion, or if nil is passed in, return nil. If a single string is passed in,
-- treat it like a one-element list consisting of that string.
function Category:get_template_text(templates)
if templates == nil then
return nil
elseif type(templates) ~= "table" then
templates = {templates}
end
for _, template in ipairs(templates) do
if template == false then
return false
end
template = self:substitute_template_specs(template)
return expand_toc_template_if(template)
end
return nil
end
function Category:getTOC(toc_type)
-- Type "none" means everything fits on a single page; in that case, display nothing.
if toc_type == "none" then
return nil
end
local templates, fallback_templates
-- If TOC type is "full" (more than 2500 entries), do the following, in order:
-- 1. look up and expand the `toc_template_full` templates (normal or umbrella, depending on whether there is
-- a current language);
-- 2. look up and expand the `toc_template` templates (normal or umbrella, as above);
-- 3. do the default behavior, which is as follows:
-- 3a. look up a language-specific "full" template according to the current language (using English if there
-- is no current language);
-- 3b. look up a script-specific "full" template according to the first script of current language (using English
-- if there is no current language);
-- 3c. look up a language-specific "normal" template according to the current language (using English if there
-- is no current language);
-- 3d. look up a script-specific "normal" template according to the first script of the current language (using
-- English if there is no current language);
-- 3e. display nothing.
--
-- If TOC type is "normal" (between 200 and 2500 entries), do the following, in order:
-- 1. look up and expand the `toc_template` templates (normal or umbrella, depending on whether there is
-- a current language);
-- 2. do the default behavior, which is as follows:
-- 2a. look up a language-specific "normal" template according to the current language (using English if there
-- is no current language);
-- 2b. look up a script-specific "normal" template according to the first script of the current language (using
-- English if there is no current language);
-- 2c. display nothing.
local data_source
if self._lang or self._info.raw then
data_source = self._data
else
data_source = self._data.umbrella
end
if data_source then
if toc_type == "full" then
templates = data_source.toc_template_full
fallback_templates = data_source.toc_template
else
templates = data_source.toc_template
end
end
local text = self:get_template_text(templates)
if text then
return text
elseif text == false then
return nil
end
text = self:get_template_text(fallback_templates)
if text then
return text
elseif text == false then
return nil
end
local default_toc_templates_to_check = {}
local lang, sc = self:getCatfixInfo()
local langcode = lang and lang:getCode() or "mi"
local sccode = sc and sc:getCode() or lang and lang:getScriptCodes()[1] or "Latn"
-- FIXME: What is toctemplateprefix used for?
local tocname = (self._data.toctemplateprefix or "") .. "categoryTOC"
if toc_type == "full" then
insert(default_toc_templates_to_check, ("%s-%s/full"):format(langcode, tocname))
insert(default_toc_templates_to_check, ("%s-%s/full"):format(sccode, tocname))
end
insert(default_toc_templates_to_check, ("%s-%s"):format(langcode, tocname))
insert(default_toc_templates_to_check, ("%s-%s"):format(sccode, tocname))
for _, toc_template in ipairs(default_toc_templates_to_check) do
local toc_template_text = expand_toc_template_if(toc_template)
if toc_template_text then
return toc_template_text
end
end
return nil
end
function Category:getInfo()
return self._info
end
function Category:getDataModule()
return self._data.module
end
function Category:canBeEmpty()
if self._lang or self._info.raw then
return self._data.can_be_empty
end
return self._data.umbrella and self._data.umbrella.can_be_empty
end
function Category:isHidden()
if self._lang or self._info.raw then
return self._data.hidden
end
return self._data.umbrella and self._data.umbrella.hidden
end
function Category:getCategoryName()
if self._info.raw then
return self._info.canonical_name or self._info.label
elseif self._lang then
local ret = self._lang:getCanonicalName() .. " " .. self._info.label
if self._sc then
ret = ret .. " in " .. self._sc:getDisplayForm()
end
return ucfirst(ret)
end
local ret = ucfirst(self._info.label)
if not (self._data.no_by_language or self._data.umbrella and self._data.umbrella.no_by_language) then
ret = ret .. " by language"
end
return ret
end
function Category:getTopright()
if self._lang or self._info.raw then
return self:substitute_template_specs(self._data.topright)
end
return self._data.umbrella and self:substitute_template_specs(self._data.umbrella.topright)
end
function Category:display_title(displaytitle, lang)
if type(displaytitle) == "string" then
displaytitle = self:substitute_template_specs(displaytitle)
else
displaytitle = displaytitle(self:getCategoryName(), lang)
end
mw.getCurrentFrame():callParserFunction("DISPLAYTITLE", "Category:" .. displaytitle)
end
function Category:get_labels_categorizing()
local m_labels_utilities = require(labels_utilities_module)
local pos_cat_labels, sense_cat_labels, use_tlb
pos_cat_labels = m_labels_utilities.find_labels_for_category(self._info.label, "pos", self._lang)
local sense_label = self._info.label:match("^(.*) terms$")
if sense_label then
use_tlb = true
else
sense_label = self._info.label:match("^terms with (.*) senses$")
end
if not sense_label then
return nil
end
sense_cat_labels = m_labels_utilities.find_labels_for_category(sense_label, "sense", self._lang)
if use_tlb then
return m_labels_utilities.format_labels_categorizing(pos_cat_labels, sense_cat_labels, self._lang)
end
local all_labels = pos_cat_labels
for k, v in pairs(sense_cat_labels) do
all_labels[k] = v
end
return m_labels_utilities.format_labels_categorizing(all_labels, nil, self._lang)
end
-- FIXME: this is clunky.
local function remove_lang_params(desc)
-- Simply remove a language name/code/category from the beginning of the string, but replace the language name
-- in the middle of the string with either "specific languages" or "specific-language" depending on whether the
-- language name appears to be an attributive qualifier of another noun or to stand by itself. This may be wrong,
-- in which case the category in question should supply its own umbrella description.
desc = desc:gsub("^{{{langname}}} ", "")
:gsub("{{{langname}}} %(", "specific languages (")
:gsub("{{{langname}}}([.,])", "specific languages%1")
:gsub("{{{langname}}} ", "specific-language ")
:gsub("{{{langdisp}}}", "specific languages")
:gsub("{{{langlink}}}", "specific languages")
return desc
end
function Category:getDescription(isChild)
-- Allows different text in the list of a category's children
local isChild = isChild == "child"
if self._lang or self._info.raw then
if not isChild and self._data.displaytitle then
self:display_title(self._data.displaytitle, self._lang)
end
if self._sc then
return self:getCategoryName() .. "."
end
local desc = self:substitute_template_specs(self._data.description)
if not desc then
return nil
elseif isChild then
return desc
end
return sparse_concat({
self:substitute_template_specs(self._data.preceding),
desc,
self:substitute_template_specs(self._data.additional),
self:substitute_template_specs(self:get_labels_categorizing()),
}, "\n\n")
end
local umbrella = self._data.umbrella
if not isChild and umbrella and umbrella.displaytitle then
self:display_title(umbrella.displaytitle)
end
local desc = self:substitute_template_specs(umbrella and umbrella.description)
local has_umbrella_desc = not not desc
if not desc then
desc = self:convert_spec_to_string(self._data.description)
if desc then
desc = remove_lang_params(desc)
desc = lcfirst(desc)
desc = desc:gsub("%.$", "")
desc = "Categories with " .. desc .. "."
else
desc = "Categories with " .. self._info.label .. " in various specific languages."
end
desc = self:substitute_template_specs(desc)
end
if isChild then
return desc
end
return sparse_concat({
self:substitute_template_specs(umbrella and umbrella.preceding or not has_umbrella_desc and self._data.preceding),
desc,
self:substitute_template_specs(umbrella and umbrella.additional or not has_umbrella_desc and self._data.additional),
self:substitute_template_specs("{{{umbrella_msg}}}"),
self:substitute_template_specs(self:get_labels_categorizing()),
}, "\n\n")
end
function Category:new_sortkey(sortkey)
local sortkey_type = type(sortkey)
if sortkey_type == "string" then
sortkey = uupper(sortkey)
elseif sortkey_type == "table" then
function sortkey:makeSortKey()
local sort_func = self.sort_func
if sort_func ~= nil then
return sort_func(self.sort_base)
end
local lang = self.lang
if lang == nil then
return self.sort_base
end
lang = get_lang(lang, nil, true)
if lang == nil then
return self.sort_base
end
local sc = self.sc
if sc ~= nil then
sc = get_script(sc)
end
return lang:makeSortKey(self.sort_base, sc)
end
end
return sortkey
end
function Category:inherit_spec(spec, parent_spec)
if spec == false then
return nil
end
return self:substitute_template_specs(spec or parent_spec)
end
function Category:canonicalize_parents_children(cats, is_children)
if not cats then
return nil
elseif type(cats) == "table" then
if cats.name or cats.module then
cats = {cats}
elseif #cats == 0 then
return nil
end
else
cats = {cats}
end
local ret = {}
for _, cat in ipairs(cats) do
if type(cat) ~= "table" or not cat.name and not cat.module then
cat = {name = cat}
end
insert(ret, cat)
end
local is_umbrella = not self._lang and not self._info.raw
local table_type = is_children and "extra_children" or "parents"
for i, cat in ipairs(ret) do
local raw
if self._info.raw or is_umbrella then
raw = not cat.is_label
else
raw = cat.raw
end
local lang = self:inherit_spec(cat.lang, not raw and self._info.code or nil)
local sc = self:inherit_spec(cat.sc, not raw and self._info.sc or nil)
-- Get the sortkey.
local sortkey = cat.sort
if type(sortkey) == "table" then
sortkey.sort_base = self:substitute_template_specs(sortkey.sort_base) or
error("Missing .sort_base in '" .. table_type .. "' .sort table for '" ..
self._info.label .. "' category entry in module '" .. (self._data.module or "unknown") .. "'")
if sortkey.sort_func then
-- Not allowed to give a lang and/or script if sort_func is given.
local bad_spec = sortkey.lang and "lang" or sortkey.sc and "sc" or nil
if bad_spec then
error("Cannot specify both ." .. bad_spec .. " and .sort_func in '" .. table_type ..
"' .sort table for '" .. self._info.label .. "' category entry in module '" ..
(self._data.module or "unknown") .. "'")
end
else
sortkey.lang = self:inherit_spec(sortkey.lang, lang)
sortkey.sc = self:inherit_spec(sortkey.sc, sc)
end
else
sortkey = self:substitute_template_specs(sortkey)
end
local name
if cat.module then
-- A reference to a category using another category tree module.
if not cat.args then
error("Missing .args in '" .. table_type .. "' table with module=\"" .. cat.module .. "\" for '" ..
self._info.label .. "' category entry in module '" .. (self._data.module or "unknown") .. "'")
end
name = require("Module:category tree/" .. cat.module).new(self:substitute_template_specs_in_args(cat.args))
else
name = cat.name
if not name then
error("Missing .name in " .. (is_umbrella and "umbrella " or "") .. "'" .. table_type .. "' table for '" ..
self._info.label .. "' category entry in module '" .. (self._data.module or "unknown") .. "'")
elseif type(name) == "string" then -- otherwise, assume it's a category object and use it directly
name = self:substitute_template_specs(name)
if name:find("^Category:") then
-- It's a non-poscatboiler category name.
sortkey = sortkey or is_children and name:gsub("^Category:", "") or self:getCategoryName()
else
-- It's a label.
sortkey = sortkey or is_children and name or self._info.label
name = self:make_new{
label = name, code = lang, sc = sc,
raw = raw, args = self:substitute_template_specs_in_args(cat.args)
}
end
end
end
sortkey = sortkey or is_children and " " or self._info.label
ret[i] = {
name = name,
description = is_children and self:substitute_template_specs(cat.description) or nil,
sort = self:new_sortkey(sortkey)
}
end
return ret
end
function Category:getParents()
local is_umbrella, ret = not self._lang and not self._info.raw
if self._sc then
local parent1 = self:make_new{code = self._info.code, label = "terms in " .. self._sc:getCanonicalName() .. " script"}
local parent2 = self:make_new{code = self._info.code, label = self._info.label, raw = self._info.raw, args = self._info.args}
ret = {
{name = parent1, sort = self._sc:getCanonicalName()},
{name = parent2, sort = self._sc:getCanonicalName()},
}
else
local parents
if is_umbrella then
parents = self._data.umbrella and self._data.umbrella.parents or self._data.umbrella_parents
else
parents = self._data.parents
end
ret = self:canonicalize_parents_children(parents)
if not ret then
return nil
end
end
local self_cat = self:getCategoryName()
for _, parent in ipairs(ret) do
local parent_cat = parent.name.getCategoryName and parent.name:getCategoryName()
if self_cat == parent_cat then
error(("Internal error: Infinite loop would occur, as parent category '%s' is the same as the child category"):format(self_cat))
end
end
return ret
end
function Category:getChildren()
local is_umbrella = not self._lang and not self._info.raw
local children = self._data.children
local ret = {}
if not is_umbrella and children then
for _, child in ipairs(children) do
child = mw.clone(child)
if type(child) ~= "table" then
child = {name = child}
end
if not child.sort then
child.sort = child.name
end
-- FIXME, is preserving the script correct?
child.name = self:make_new{code = self._info.code, label = child.name, raw = child.raw, sc = self._info.sc}
insert(ret, child)
end
end
local extra_children
if is_umbrella then
extra_children = self._data.umbrella and self._data.umbrella.extra_children
else
extra_children = self._data.extra_children
end
extra_children = self:canonicalize_parents_children(extra_children, "children")
if extra_children then
for _, child in ipairs(extra_children) do
insert(ret, child)
end
end
return #ret > 0 and ret or nil
end
function Category:getUmbrella()
local umbrella = self._data.umbrella
if umbrella == false or self._info.raw or not self._lang or self._sc then
return nil
end
-- If `umbrella` is a string, use that; otherwise, use the label.
return self:make_new({label = type(umbrella) == "string" and umbrella or self._info.label})
end
function Category:getAppendix()
-- FIXME, this should be customizable.
local lang, label = self._lang, self._info.label
if self._info.raw or not (lang and label) then
return nil
end
local appendix = make_title(100, lang:getCanonicalName() .. " " .. label)
return appendix.exists and appendix.fullText or nil
end
function Category:getCatfixInfo()
if self._lang or self._sc or self._info.raw then
local langcode, sccode, lang, sc = self._data.catfix, self._data.catfix_sc
if langcode then
langcode = self:substitute_template_specs(langcode)
lang = get_lang(langcode)
elseif langcode == nil then -- not false
lang = self._lang
end
if sccode then
sccode = self:substitute_template_specs(sccode)
sc = get_script(sccode)
elseif sccode == nil then -- not false
sc = self._sc
end
return lang, sc
elseif not self._data.umbrella then
return
end
-- umbrella
local langcode, sccode, lang, sc = self._data.umbrella.catfix, self._data.umbrella.catfix_sc
if langcode then
langcode = self:substitute_template_specs(langcode)
lang = get_lang(langcode)
end
if sccode then
sccode = self:substitute_template_specs(sccode)
sc = get_script(sccode)
end
return lang, sc
end
function Category:getTOCTemplateName()
-- This should only be invoked if getTOC() returns true, meaning to do the default algorithm, but getTOC()
-- implements its own default algorithm.
error("Internal error: This should never get called")
end
local export = {}
function export.main(info)
local self = setmetatable({_info = info}, Category)
self:initCommon()
return self._data and self or nil
end
export.new = Category.new
return export
ni3ba9brdpjpw8ymbm7o8ntfir87uxm
Module:labels
828
5915
17461
17281
2026-07-15T16:03:10Z
Hiyuune
6766
17461
Scribunto
text/plain
local export = {}
export.lang_specific_data_list_module = "Module:labels/data/lang"
export.lang_specific_data_modules_prefix = "Module:labels/data/lang/"
local load_module = "Module:load"
local parse_utilities_module = "Module:parse utilities"
local string_utilities_module = "Module:string utilities"
local utilities_module = "Module:utilities"
local insert = table.insert
local require_when_needed = require("Module:utilities/require when needed")
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local m_lang_specific_data = mw.loadData(export.lang_specific_data_list_module)
local m_table = require_when_needed("Module:table")
--[==[ intro:
Labels go through several stages of processing to get from the original (raw) label specified in the Wikicode to the
final (formatted) label displayed to the user. The following terminology will help keep things straight:
* The "raw label" is the label specified in the Wikicode.
* The "non-canonical label" is the label extracted from the raw label, used for looking up in the label modules in order
to fetch the associated label data structure and determine the canonical form of the label. Normally this is the same
as the raw label, but it will be different if the raw label is of the form `!<var>label</var>` (e.g. `!Australian`)
`<var>label</var>!<var>display</var>` (e.g. `Southern US!Southern`). The former syntax indicates that the label
should display as-is instead of in its canonical form (which in the example given is `Australia`), and the latter
syntax indicates that the label should display in the form specified after the exclamation point.
* The "canonical label" is the result of applying alias resolution to the non-canonical label. Normally, the
canonical label rather than the non-canonical label is what is shown to the user.
* The "display form of the label" is what is shown to the user, not considering links and HTML that may wrap the
display form to get the formatted form of the label. The display form comes from the `.display` field of the module
label data for the label; if no such field exists in the label data, it is normally the canonical label. However, if
the display override exists (see below), it takes precedence over the `.display` field or canonical label when
determining the display form of the label.
* The "display override", if specified, overrides all other means of determining the display form of the label. It is
specified in two circumstances, i.e. in the `!<var>label</var>` and `<var>label</var>!<var>display</var>` raw label
formats (i.e. in the same cirumstances where the raw label and non-canonical label are different).
* The "formatted form of the label" is the final form of the label shown directly to the user. It generally appears to
the user as the display form of the label, but in the Wikicode, the formatted form may wrap the display form with a
link to Wikipedia, the Wiktionary glossary or another Wiktionary entry, and that link in turn may be wrapped in an
HTML span with a "deprecated" CSS class attached, causing the label to display differently (to indicate that it is
deprecated).
]==]
-- for testing
local force_cat = false
local SUBPAGENAME = mw.title.getCurrentTitle().subpageText
-- Disable tracking on heavy pages to save time.
local pages_where_tracking_is_disabled = {
-- pages that consistently hit timeouts
["a"] = true,
-- pages that sometimes hit timeouts
["de"] = true,
["i"] = true,
["и"] = true,
["山"] = true,
["子"] = true,
["月"] = true,
}
-- Add tracking category for PAGE. The tracking category linked to is [[Wiktionary:Tracking/labels/PAGE]].
-- We also add to [[Wiktionary:Tracking/labels/PAGE/LANGCODE]] and [[Wiktionary:Tracking/labels/PAGE/MODE]] if
-- LANGCODE and/or MODE given.
local function track(page, langcode, mode)
if pages_where_tracking_is_disabled[SUBPAGENAME] then
return true
end
-- avoid including links in pages (may cause error)
page = page:gsub("%[", "("):gsub("%]", ")"):gsub("|", "!")
require("Module:debug/track")("labels/" .. page)
if langcode then
require("Module:debug/track")("labels/" .. page .. "/" .. langcode)
end
if mode then
require("Module:debug/track")("labels/" .. page .. "/" .. mode)
end
-- We don't currently add a tracking label for both langcode and mode to reduce the total number of labels, to
-- save some memory.
return true
end
local function ucfirst(txt)
return mw.getContentLanguage():ucfirst(txt)
end
local mode_to_outer_class = {
["label"] = "usage-label-sense",
["term-label"] = "usage-label-term",
["accent"] = "usage-label-accent",
["form-of"] = "usage-label-form-of",
}
local mode_to_property_prefix = {
["label"] = false,
["term-label"] = false, -- handled specially
["accent"] = "accent_",
["form-of"] = "form_of_",
}
local function validate_mode(mode)
mode = mode or "label"
if not mode_to_outer_class[mode] then
local allowed_values = {}
for key, _ in pairs(mode_to_outer_class) do
insert(allowed_values, "'" .. key .. "'")
end
table.sort(allowed_values)
error(("Invalid value '%s' for `mode`; should be one of %s"):format(mode, table.concat(allowed_values, ", ")))
end
return mode
end
local function getprop(labdata, mode, prop)
local mode_prefix = mode_to_property_prefix[mode]
return mode_prefix and labdata[mode_prefix .. prop] or labdata[prop]
end
-- HACK! For languages in any of the given families, check the specified-language Wikipedia for appropriate
-- Wikipedia articles for the language in question (esp. useful for obscure etymology-only languages that may not
-- have English articles for them, like many Chinese lects).
local families_to_wikipedia_languages = {
{"zhx", "zh"},
{"sem-arb", "ar"},
}
--[==[
Given language `lang` (a full language, etymology-language or family), fetch a list of Wikimedia languages to check
when converting a Wikidata item to a Wikipedia article. English is always first, followed by the Wikimedia language
code(s) of `lang` if `lang` is a language (which may or may not be the same as `lang`'s Wiktionary code), followed
by the macrolanguage of `lang` for certain languages and families (currently, only languages and families in the Chinese
and Arabic families). If `lang` is nil, only return English. Note that the same code may occur more than once in the
list. This is exported because it's also used by [[Module:category tree/poscatboiler/data/language varieties]].
]==]
function export.get_langs_to_extract_wikipedia_articles_from_wikidata(lang)
local wikipedia_langs = {}
insert(wikipedia_langs, "mi")
if lang then
local article_lang = lang
while article_lang do
if article_lang:hasType("language") then
local wmcodes = article_lang:getWikimediaLanguageCodes()
for _, wmcode in ipairs(wmcodes) do
insert(wikipedia_langs, wmcode)
end
end
article_lang = article_lang:getParent()
end
for _, family_to_wp_lang in ipairs(families_to_wikipedia_languages) do
local family, wp_lang = unpack(family_to_wp_lang)
if lang:inFamily(family) then
insert(wikipedia_langs, wp_lang)
end
end
end
return wikipedia_langs
end
--[==[
Fetch the categories to add to a page, given that the label whose canonical form is `canon_label` with language `lang`
has been seen. `labdata` is the label data structure for `label`, fetched from the appropriate submodule. `mode`
specifies how the label was invoked (see {get_label_info()} for more information). The return value is a list of the
actual categories, unless `for_doc` is specified, in which case the categories returned are marked up for display on a
documentation page. If `for_doc` is given, `lang` may be nil to format the categories in a language-independent fashion;
otherwise, it must be specified. If `category_types` is specified, it should be a set object (i.e. with category types
as keys and {true} as values), and only categories of the specified types will be returned.
]==]
function export.fetch_categories(canon_label, labdata, lang, mode, for_doc, category_types)
local categories = {}
mode = validate_mode(mode)
local langcode, canonical_name
if lang then
langcode = lang:getFullCode()
canonical_name = lang:getFullName()
elseif for_doc then
langcode = "<var>[langcode]</var>"
canonical_name = "<var>[language name]</var>"
else
error("Internal error: Must specify `lang` unless `for_doc` is given")
end
local function labprop(prop)
return getprop(labdata, mode, prop)
end
local empty_list = {}
local function get_cats(cat_type)
if category_types and not category_types[cat_type] then
return empty_list
end
local cats = labprop(cat_type)
if not cats then
return empty_list
end
if type(cats) ~= "table" then
return {cats}
end
return cats
end
local topical_categories = get_cats("topical_categories")
local sense_categories = get_cats("sense_categories")
local pos_categories = get_cats("pos_categories")
local regional_categories = get_cats("regional_categories")
local plain_categories = get_cats("plain_categories")
local function insert_cat(cat, sense_cat)
if for_doc then
cat = "<code>" .. cat .. "</code>"
if sense_cat then
if mode == "term-label" then
cat = cat .. " (using {{tl|tlb}})"
else
cat = cat .. " (using {{tl|lb}} or form-of template)"
end
cat = mw.getCurrentFrame():preprocess(cat)
end
end
insert(categories, cat)
end
for _, cat in ipairs(topical_categories) do
insert_cat(langcode .. ":" .. (cat == true and ucfirst(canon_label) or cat))
end
for _, cat in ipairs(sense_categories) do
if cat == true then
cat = canon_label
end
cat = mode == "term-label" and cat .. " terms" or "terms with " .. cat .. " senses"
insert_cat(canonical_name .. " " .. cat, true)
end
for _, cat in ipairs(pos_categories) do
insert_cat(canonical_name .. " " .. (cat == true and canon_label or cat))
end
for _, cat in ipairs(regional_categories) do
insert_cat((cat == true and ucfirst(canon_label) or cat) .. " " .. canonical_name)
end
for _, cat in ipairs(plain_categories) do
insert_cat(cat == true and ucfirst(canon_label) or cat)
end
return categories
end
--[==[
Return the list of all labels data modules for a label whose language is `lang`. The return value is a list of
module names, with overriding modules earlier in the list (that is, if a label occurs in two modules in the list,
the earlier-listed module takes precedence). If `lang` is nil, only return non-language-specific submodules.
]==]
function export.get_submodules(lang)
local submodules = {
"Module:labels/data",
"Module:labels/data/qualifiers",
"Module:labels/data/regional",
"Module:labels/data/topical",
}
if not lang then
return submodules
end
-- get language-specific labels from data module
local langcode = lang:getFullCode()
if m_lang_specific_data.langs_with_lang_specific_modules[langcode] then
-- prefer per-language label in order to pick subvariety labels over regional ones
insert(submodules, 1, export.lang_specific_data_modules_prefix .. langcode)
end
return submodules
end
--[==[
Return the formatted form of a label `label` (which should be the canonical form of the label; see comment at top),
given (a) the label data structure `labdata` from one of the data modules; (b) the language object `lang` of the
language being processed, or nil for no language; (c) `deprecated` (true if the label is deprecated, otherwise the
deprecation information is taken from `labdata`); (d) `override_display` (if specified, override the display form of the
label with the specified string, instead of any value in `labdata.display` or `labdata.special_display` or the canonical
label in `label` itself); (e) `mode` (same as `data.mode` passed to {get_label_info()}). Returns two values: the
formatted label form and a boolean indicating whether the label is deprecated.
'''NOTE: Under normal circumstances, do not use this.''' Instead, use {get_label_info()}, which searches all the data
modules for a given label and handles other complications.
]==]
function export.format_label(label, labdata, lang, deprecated, override_display, mode)
local formatted_label
mode = validate_mode(mode)
local function labprop(prop)
return getprop(labdata, mode, prop)
end
deprecated = deprecated or labprop("deprecated")
if not override_display and labprop("special_display") then
local function add_language_name(str)
if str == "canonical_name" then
if lang then
return lang:getFullName()
else
return "<code><var>[language name]</var></code>"
end
else
return ""
end
end
formatted_label = labprop("special_display"):gsub("<(.-)>", add_language_name)
else
--[=[
If labdata.glossary or labdata.Wikipedia are set to true, there is a glossary definition
with an anchor identical to the label, or a Wikipedia article with a title
identical to the label.
For example, the code
labels["formal"] = {
glossary = true,
}
indicates that there is a glossary entry for "formal".
Otherwise:
* labdata.glossary specifies the anchor in [[Appendix:Glossary]].
* labdata.Wiktionary specifies an arbitrary Wiktionary page or page + anchor (e.g. a separate Appendix
entry).
* labdata.Wikipedia specifies an arbitrary Wikipedia article.
* labdata.Wikidata specifies an arbitrary Wikidata item to retrieve a Wikipedia article from, or a list
of such items (in this case, we select the first one, but other modules using this info might use all
of them). If the item is of the form `wmcode:id`, the Wikipedia article corresponding to `wmcode` is
fetched if available. Otherwise, the English-language Wikipedia article is retrieved if available,
falling back to the Wikimedia language(s) corresponding to `lang` and then (in certain cases) to the
macrolanguage that `lang` is part of.
Note that if `mode` is specified, prefixed properties (e.g. "accent_display" for `mode` == "accent",
"form_display" for `mode` == "form") are checked before the bare equivalent (e.g. "display").
]=]
local display = override_display or labprop("display") or label
-- There are several 'Foo spelling' labels specially designed for use in the |from= param in
-- {{alternative form of}}, {{standard spelling of}} and the like. Often the display includes the word
-- "spelling" at the end (e.g. if it's defaulted), which is useful when the label is used with {{tl|lb}} or
-- {{tl|tlb}}; but it causes redundancy when used with the form-of templates, which add the word "form",
-- "spelling", "standard spelling", etc. after the label.
if mode == "form-of" then
display = display:gsub(" spelling$", "")
end
if display:find("%[%[") then
formatted_label = display
else
local glossary = labprop("glossary")
local Wiktionary = labprop("Wiktionary")
local Wikipedia = labprop("Wikipedia")
local Wikidata = labprop("Wikidata")
if glossary then
local glossary_entry = type(glossary) == "string" and glossary or label
formatted_label = "[[Appendix:Glossary#" .. glossary_entry .. "|" .. display .. "]]"
elseif Wiktionary then
formatted_label = "[[" .. Wiktionary .. "|" .. display .. "]]"
elseif Wikipedia then
local Wikipedia_entry = type(Wikipedia) == "string" and Wikipedia or label
formatted_label = "[[w:" .. Wikipedia_entry .. "|" .. display .. "]]"
elseif Wikidata then
if not mw.wikibase then
error(("Unable to retrieve data from Wikidata ID for label '%s'; `mw.wikibase` not defined"
):format(label))
end
local function make_formatted_label(wmcode, id)
local article = mw.wikibase.sitelink(id, wmcode .. "wiki")
if article then
local link = wmcode == "mi" and "w:" .. article or "w:" .. wmcode .. ":" .. article
return ("[[%s|%s]]"):format(link, display)
else
return nil
end
end
if type(Wikidata) == "table" then
Wikidata = Wikidata[1]
end
local wmcode, id = Wikidata:match("^(.*):(.*)$")
if wmcode then
formatted_label = make_formatted_label(wmcode, id)
else
local langs_to_check = export.get_langs_to_extract_wikipedia_articles_from_wikidata(lang)
for _, wmcode in ipairs(langs_to_check) do
formatted_label = make_formatted_label(wmcode, Wikidata)
if formatted_label then
break
end
end
end
formatted_label = formatted_label or display
else
formatted_label = display
end
end
end
if deprecated then
formatted_label = '<span class="deprecated-label">' .. formatted_label .. '</span>'
end
return formatted_label, deprecated
end
--[==[
Return information on a label. On input `data` is an object with the following fields:
* `label`: The raw label to return information on.
* `lang`: The language of the label. Must be specified unless `for_doc` is given.
* `mode`: How the label was invoked. One of the following:
** {nil} or {"label"}: invoked through {{tl|lb}} or another template whose labels in the same fashion, e.g.
{{tl|alt}}, {{tl|quote}} or {{tl|syn}};
** {"term-label"}: invoked through {{tl|tlb}};
** {"accent"}: invoked through {{tl|a}} or the {{para|a}} or {{para|aa}} parameters of other pronunciation templates,
such as {{tl|IPA}}, {{tl|rhymes}} or {{tl|homophones}};
** {"form-of"}: invoked through {{tl|alt form}}, {{tl|standard spelling of}} or other form-of template.
This changes the display and/or categorization of a minority of labels. (The majority work the same for all modes.)
* `for_doc`: Data is being fetched for documentation purposes. This causes the raw categories returned in
`categories` to be formatted for documentation display.
* `nocat`: If true, don't add the label to any categories.
* `notrack`: Disable all tracking for this label.
* `already_seen`: An object used to track labels already seen, so they aren't displayed twice. Tracking is according
to the display form of the label, so if two labels have the same display form, the second one won't be displayed
(but its categories will still be added). If `already_seen` is {nil}, this tracking doesn't happen.
The return value is an object with the following fields:
* `raw_text`: If specified, the object does not describe a label but simply raw text surrounding labels. This occurs
when double angle bracket (<<...>>) notation is used. {get_label_info()} does not currently return objects with this
field set, but {process_raw_labels()} does. The value is {"begin"} (this is the first raw text portion derived from
a double angle bracket spec, provided there are at least two raw text portions); {"end"} (this is the last raw text
portion derived from a double angle bracket spec, provided there are at least two portions); {"middle"} (this is
neither the first nor the last raw text portion); or {"only"} (this is a raw text portion standing by itself). The
particular value determines the handling of commas and spaces on one or both sides of the raw text. If this field is
specified, only the `label` field (containing the actual raw text) and the `category` field (containing an empty list)
are set; all other fields are {nil}.
* `raw_label`: The raw label that was passed in.
* `non_canonical`: The label prior to canonicalization (i.e. alias resolution). Usually this is the same as `raw_label`,
but if the raw label was preceded by an exclamation point (meaning "display the raw label as-is"), this field will
contain the label stripped of the exclamation point, and if the raw label is of the form
`<var>label</var>!<var>display</var>` (meaning "display the label in the specified form"), this field will contain the
label before the exclamation point.
* `canonical`: If the label in `non_canonical` is an alias, this contains the canonical name of the label; otherwise it
will be {nil}.
* `override_display`: If specified, this contains a string that overrides the normal display form of the label. The
display form of a label is the `.display` field of the label data if present, and otherwise is normally the canonical
form of the label (i.e. after alias resolution). (This is not the same as the formatted form of the label, found in
`label`, which is the final form shown to the user and includes links to Wikipedia, the glossary, etc. as well as an
HTML wrapper if the label is deprecated.) If `override_display` is specified, however, this is used in place of the
normal display form of the label. This currently happens in two circumstances: (1) the label was preceded by ! to
indicate that the raw label should be displayed rather than the canonical form; (2) the label was given in the form
`<var>label</var>!<var>display</var>` (meaning "display the label in the specified `<var>display</var>` form").
* `label`: The formatted form of the label. This is what is actually shown to the user. If the label is recognized
(found in some module), this will typically be in the form of a link.
* `categories`: A list of the categories to add the label to; an empty list of `nocat` was specified.
* `formatted_categories`: A string containing the formatted categories; {nil} if `nocat` or `for_doc` was specified,
or if `categories` is empty. Currently will be an empty string if there are categories to format but the namespace is
one that normally excludes categories (e.g. userspace and discussion pages), and `force_cat` isn't specified.
* `deprecated`: True if the label is deprecated.
* `recognized`: If true, the label was found in some module.
* `data`: The data structure for the label, as fetched from the label modules. For unrecognized labels, this will
be an empty object.
]==]
function export.get_label_info(data)
if not data.label then
error("`data` must now be an object containing the params")
end
local mode = validate_mode(data.mode)
local ret = {categories = {}}
local label = data.label
local raw_label = label
ret.raw_label = raw_label
local override_display
if label:find("^!") then
label = label:gsub("^!", "")
override_display = label
elseif label:find("![^%s]") then
label, override_display = label:match("^(.-)!([^%s].*)$")
if not label then
error(("Internal error: This Lua pattern should never fail to match for label '%s'"):format(raw_label))
end
end
local non_canonical = label
ret.non_canonical = non_canonical
local deprecated = false
local labdata
local submodule
local data_langcode = data.lang and data.lang:getCode() or nil
local submodules_to_check = export.get_submodules(data.lang)
for _, submodule_to_check in ipairs(submodules_to_check) do
submodule = mw.loadData(submodule_to_check)
local this_labdata = submodule[label]
local resolved_label
if type(this_labdata) == "string" then
resolved_label = this_labdata
this_labdata = submodule[this_labdata]
if not this_labdata then
error(("Internal error: Label alias '%s' points to '%s', which is undefined in module [[%s]]"):format(
label, resolved_label, submodule_to_check))
end
if type(this_labdata) == "string" then
error(("Internal error: Label alias '%s' points to '%s', which is also an alias (of '%s') in module [[%s]]"):format(
label, resolved_label, this_labdata, submodule_to_check))
end
end
if this_labdata then
-- Make sure either there's no lang restriction, or we're processing lang-independent, or our language
-- is among the listed languages. Otherwise, continue processing (which could conceivably pick up a
-- lang-appropriate version of the label in another label data module).
local lablangs = getprop(this_labdata, mode, "langs")
if not lablangs or not data_langcode then
labdata = this_labdata
label = resolved_label or label
break
end
local lang_in_list = false
for _, langcode in ipairs(lablangs) do
if langcode == data_langcode then
lang_in_list = true
break
end
end
if lang_in_list then
labdata = this_labdata
label = resolved_label or label
break
elseif not data.notrack then
-- Track use of a label that fails the lang restriction.
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label/LANGCODE]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label/LABEL]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label/LABEL/LANGCODE]]
track("wrong-lang-label", data_langcode)
track("wrong-lang-label/" .. label, data_langcode)
if resolved_label then
track("wrong-lang-label/" .. resolved_label, data_langcode)
end
end
end
end
if labdata then
ret.recognized = true
else
labdata = {}
ret.recognized = false
end
local function labprop(prop)
return getprop(labdata, mode, prop)
end
if labprop("deprecated") then
deprecated = true
end
if label ~= non_canonical then
-- Note that this is an alias and store the canonical version.
ret.canonical = label
end
if not data.notrack then -- labprop("track") then -- track all labels now
-- Track label (after converting aliases to canonical form; but also track raw label (alias) if different
-- from canonical label).
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/label/LABEL]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/label/LABEL/LANGCODE]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/label/LABEL/MODE]]
track("label/" .. label, data_langcode, mode)
if label ~= non_canonical then
track("label/" .. non_canonical, data_langcode, mode)
end
end
local formatted_label
formatted_label, deprecated = export.format_label(label, labdata, data.lang, deprecated, override_display, mode)
ret.deprecated = deprecated
if deprecated then
if not data.nocat then
local depcat = "Entries with deprecated labels"
if data.for_doc then
depcat = "<code>" .. depcat .. "</code>"
end
insert(ret.categories, depcat)
end
end
local label_for_already_seen =
(labprop("topical_categories") or labprop("regional_categories")
or labprop("plain_categories") or labprop("pos_categories")
or labprop("sense_categories")) and formatted_label
or nil
-- Track label text. If label text was previously used, don't show it, but include the categories.
-- For an example, see [[hypocretin]].
if data.already_seen and data.already_seen[label_for_already_seen] then
ret.label = ""
else
if formatted_label:find("{") then
formatted_label = mw.getCurrentFrame():preprocess(formatted_label)
end
ret.label = formatted_label
end
if data.nocat then
-- do nothing
else
local cats = export.fetch_categories(label, labdata, data.lang, mode, data.for_doc)
for _, cat in ipairs(cats) do
insert(ret.categories, cat)
end
if not ret.categories[1] or data.for_doc then
-- Don't try to format categories if we're doing this for documentation ({{label/doc}}), because there
-- will be HTML in the categories.
-- do nothing
else
ret.formatted_categories = require(utilities_module).format_categories(ret.categories, data.lang,
data.sort, nil, force_cat)
end
end
ret.data = labdata
if label_for_already_seen and data.already_seen then
data.already_seen[label_for_already_seen] = true
end
return ret
end
--[==[
Split a string containing comma-separated raw labels into the individual labels. This will not split on a comma
followed by whitespace, and it will not split inside of matched <...> or [...]. The code is written to be efficient, so
that it does not load modules (e.g. [[Module:parse utilities]]) unnecessarily.
]==]
function export.split_labels_on_comma(term)
if term:find("[%[<]") then
-- Do it the "hard way". We don't want to split anything inside of <...> or <<...>> even if there are commas
-- inside of the angle brackets. For good measure we do the same for [...] and [[...]]. We first parse balanced
-- segment runs involving either [...] or <...>. Then we split alternating runs on comma (but not on
-- comma+whitespace). Then we rejoin the split runs. For example, given the following:
-- "regional,older <<non-rhotic,and,non-hoarse-horse>> speakers", the first call to
-- parse_multi_delimiter_balanced_segment_run() produces
--
-- {"regional,older ", "<<non-rhotic,and,non-hoarse-horse>>", " speakers"}
--
-- After calling split_alternating_runs_on_comma(), we get the following:
--
-- {{"regional"}, {"older ", "<<non-rhotic,and,non-hoarse-horse>>", " speakers"}}
--
-- After rejoining each group, we get:
--
-- {"regional", "older <<non-rhotic,and,non-hoarse-horse>> speakers"}
--
-- which is the desired output. When processing the second "label" string, the code in process_raw_labels()
-- will do a similar process to this to pull out the labels inside of the <<...>> notation.
local put = require(parse_utilities_module)
local segments = put.parse_multi_delimiter_balanced_segment_run(term, {{"<", ">"}, {"[", "]"}})
-- This won't split on comma+whitespace.
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
for i, group in ipairs(comma_separated_groups) do
comma_separated_groups[i] = table.concat(group)
end
return comma_separated_groups
elseif term:find(",%s") then
-- This won't split on comma+whitespace.
return require(parse_utilities_module).split_on_comma(term)
elseif term:find(",") then
return require(string_utilities_module).split(term, ",")
else
return {term}
end
end
--[==[
Return a list of objects corresponding to a set of raw labels. Each object returned is of the format returned by
{get_label_info()}. This is similar to looping over the labels and calling {get_label_info()} on each one, but it also
correctly handles embedded double angle bracket specs <<...>> found in the labels. (In such a case, there will be more
objects returned than raw labels passed in.) On input, `data` is an object with the following fields:
* `labels`: The list of labels to process.
* `lang`: The language of the labels. Must be specified.
* `mode`: How the label was invoked; see {get_label_info()} for more information.
* `nocat`: If true, don't add the label to any categories.
* `notrack`: Disable all tracking for this label.
* `sort`: Sort key for categorization.
* `already_seen`: An object used to track labels already seen, so they aren't displayed twice. Tracking is according
to the display form of the label, so if two labels have the same display form, the second one won't be displayed
(but its categories will still be added). If `already_seen` is {nil}, this tracking doesn't happen.
* `ok_to_destructively_modify`: If set, the `data` structure will be destructively modified in the process of this
function running.
]==]
function export.process_raw_labels(data)
local label_infos = {}
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.ok_to_destructively_modify = true
end
local function get_info_and_insert(label)
-- Reuse this structure to save memory.
data.label = label
insert(label_infos, export.get_label_info(data))
end
for _, label in ipairs(data.labels) do
if label:find("<<") then
local segments = require(string_utilities_module).split(label, "<<(.-)>>")
for i, segment in ipairs(segments) do
if i % 2 == 1 then
local raw_text_type = i == 1 and "begin" or i == #segments and "end" or "middle"
insert(label_infos, {raw_text = raw_text_type, label = segment, categories = {}})
else
local segment_labels = export.split_labels_on_comma(segment)
for _, segment_label in ipairs(segment_labels) do
get_info_and_insert(segment_label)
end
end
end
else
get_info_and_insert(label)
end
end
return label_infos
end
--[==[
Split a comma-separated string of raw labels and process each label to get a list of objects suitable for passing to
{format_processed_labels()}. Each object returned is of the format returned by {get_label_info()}. This is equivalent to
calling {split_labels_on_comma()} followed by {process_raw_labels()}. On input, `data` is an object with the following
fields:
* `labels`: The string containing the raw comma-separated labels.
* `lang`: The language of the labels. Must be specified.
* `mode`: How the label was invoked; see {get_label_info()} for more information.
* `nocat`: If true, don't add the label to any categories.
* `notrack`: Disable all tracking for this label.
* `sort`: Sort key for categorization.
* `already_seen`: An object used to track labels already seen, so they aren't displayed twice. Tracking is according
to the display form of the label, so if two labels have the same display form, the second one won't be displayed
(but its categories will still be added). If `already_seen` is {nil}, this tracking doesn't happen.
* `ok_to_destructively_modify`: If set, the `data` structure will be destructively modified in the process of this
function running.
]==]
function export.split_and_process_raw_labels(data)
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.ok_to_destructively_modify = true
end
data.labels = export.split_labels_on_comma(data.labels)
return export.process_raw_labels(data)
end
--[==[
Format one or more already-processed labels for display and categorization. "Already-processed" means that
{get_label_info()} or {process_raw_labels()} has been called on the raw labels to convert them into objects containing
information on how to display and categorize the labels. This is a lower-level alternative to {show_labels()} and is
meant for modules such as [[Module:alternative forms]], [[Module:quote]] and [[Module:etymology/templates/descendant]]
that support displaying labels along with some other information.
On input `data` is an object with the following fields:
* `labels`: List of the label objects to format, in the format returned by {get_label_info()}.
* `lang`: The language of the labels.
* `open`: Open bracket or parenthesis to display before the concatenated labels. If specified, it is wrapped in the
{"ib-brac"} and {"label-brac"} CSS classes. If {nil}, no open bracket is displayed.
* `close`: Close bracket or parenthesis to display after the concatenated labels. If specified, it is wrapped in the
{"ib-brac"} and {"label-brac"} CSS classes. If {nil}, no close bracket is displayed.
* `no_ib_content`: By default, the concatenated formatted labels inside of the open/close brackets are wrapped in the
{"ib-content"} and {"label-content"} CSS classes. Specify this to suppress this wrapping.
* `raw`: Suppress all CSS wrapping of content, including open/close parentheses, content and comma delimiters (which
are normally wrapped in {"ib-comma"} and {"label-comma"} CSS classes).
* `ok_to_destructively_modify`: If set, the `data` structure, and the `data.labels` table inside of it, will be
destructively modified in the process of this function running.
Return value is a string containing the contenated labels, optionally surrounded by open/close brackets or parentheses.
Normally, labels are separated by comma-space sequences, but this may be suppressed for certain labels. If `nocat`
wasn't given to {get_label_info() or process_raw_labels()}, the label objects will contain formatted categories in
them, which will be inserted into the returned text. The concatenated text inside of the open/close brackets is normally
wrapped in the {"ib-content"} CSS class, but this can be suppressed, as mentioned above.
]==]
function export.format_processed_labels(data)
if not data.labels then
error("`data` must now be an object containing the params")
end
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.labels = m_table.deepCopy(data.labels)
data.ok_to_destructively_modify = true
end
local labels = data.labels
if not labels[1] then
error("You must specify at least one label.")
end
-- Show the labels
local omit_preComma = false
local omit_postComma = true
local omit_preSpace = false
local omit_postSpace = true
for _, label in ipairs(labels) do
omit_preComma = omit_postComma
omit_preSpace = omit_postSpace
local raw_text_omit_before = label.raw_text == "middle" or label.raw_text == "end"
local raw_text_omit_after = label.raw_text == "middle" or label.raw_text == "begin"
label.omit_comma = omit_preComma or (label.data and label.data.omit_preComma) or raw_text_omit_before
omit_postComma = (label.data and label.data.omit_postComma) or raw_text_omit_after
label.omit_space = omit_preSpace or (label.data and label.data.omit_preSpace) or raw_text_omit_before
omit_postSpace = (label.data and label.data.omit_postSpace) or raw_text_omit_after
end
if data.lang then
local lang_functions_module = export.lang_specific_data_modules_prefix .. data.lang:getCode() .. "/functions"
local m_lang_functions = require(load_module).safe_require(lang_functions_module)
if m_lang_functions and m_lang_functions.postprocess_handlers then
for _, handler in ipairs(m_lang_functions.postprocess_handlers) do
handler(data)
end
end
end
local function wrap_css(txt, suffix)
if data.raw then
return txt
end
return ("<span class=\"ib-%s label-%s\">%s</span>"):format(suffix, suffix, txt)
end
for i, labelinfo in ipairs(labels) do
local label
-- Need to check for 'not raw_text' here because blank labels may legitimately occur as raw text if a double
-- angle bracket spec occurs at the beginning of a label. In this case we've already taken into account the
-- context and don't want to leave out a preceding comma and space e.g. in a case like
-- {{lb|en|rare|<<dialect>> or <<eye dialect>>}}. FIXME: We should reconsider whether we need this special case
-- at all.
if labelinfo.label == "" and not labelinfo.raw_text then
label = ""
else
label = (labelinfo.omit_comma and "" or wrap_css(",", "comma")) ..
(labelinfo.omit_space and "" or " ") ..
labelinfo.label
end
labels[i] = label .. (labelinfo.formatted_categories or "")
end
local function wrap_open_close(val)
if val then
return wrap_css(val, "brac")
else
return ""
end
end
local concatenated_labels = table.concat(labels, "")
if not data.no_ib_content then
concatenated_labels = wrap_css(concatenated_labels, "content")
end
return wrap_open_close(data.open) .. concatenated_labels .. wrap_open_close(data.close)
end
--[==[
Format one or more labels for display and categorization. This provides the implementation of the
{{tl|label}}/{{tl|lb}}, {{tl|term label}}/{{tl|tlb}} and {{tl|accent}}/{{tl|a}} templates, and can also be called from a
module. The return value is a string to be inserted into the generated page, including the display and categories. On
input `data` is an object with the following fields:
* `labels`: List of the labels to format.
* `lang`: The language of the labels.
* `mode`: How the label was invoked; see {get_label_info()} for more information.
* `nocat`: If true, don't add the labels to any categories.
* `notrack`: Disable all tracking for these labels.
* `sort`: Sort key for categorization.
* `no_track_already_seen`: Don't track already-seen labels. If not specified, already-seen labels are not displayed
again, but still categorize. See the documentation of {get_label_info()}.
* `open`: Open bracket or parenthesis to display before the concatenated labels. If {nil}, defaults to an open
parenthesis. Set to {false} to disable.
* `close`: Close bracket or parenthesis to display after the concatenated labels. If {nil}, defaults to a close
parenthesis. Set to {false} to disable.
* `no_ib_content`: As in `format_processed_labels()`.
* `raw`: As in `format_processed_labels()`. Also suppress wrapping the entire formatted result in a usage label CSS
class (see below).
* `ok_to_destructively_modify`: If set, the `data` structure will be destructively modified in the process of this
function running.
Compared with {format_processed_labels()}, this function has the following differences:
# The labels specified in `labels` are raw labels (i.e. strings) rather than formatted objects.
# The open and close brackets default to parentheses ("round brackets") rather than not being displayed by default.
# Tracking of already-seen labels is enabled unless explicitly turned off using `no_track_already_seen`.
# The entire formatted result is wrapped in a {"usage-label-<var>type</var>"} CSS class (depending on the value of
`mode`), unless `raw` is given.
]==]
function export.show_labels(data)
if not data.labels then
error("`data` must now be an object containing the params")
end
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.ok_to_destructively_modify = true
end
local labels = data.labels
if not labels[1] then
error("You must specify at least one label.")
end
local mode = validate_mode(data.mode)
if not data.no_track_already_seen then
data.already_seen = {}
end
data.labels = export.process_raw_labels(data)
if data.open == nil then
data.open = "("
end
if data.close == nil then
data.close = ")"
end
local formatted = export.format_processed_labels(data)
if data.raw then
return formatted
else
return "<span class=\"" .. mode_to_outer_class[mode] .. "\">" .. formatted .. "</span>"
end
end
--[==[Helper function for the data modules.]==]
function export.alias(labels, key, aliases)
m_table.alias(labels, key, aliases)
end
--[==[
Split the display form of a label. Returns two values: `link` and `display`. If the display form consists of a
two-part link, `link` is the first part and `display` is the second part. If the display form consists of a
single-part link, `link` and `display` are the same. Otherwise (the display form is not a link or contains an
embedded link), `link` is the same as the passed-in `label` and `display` is nil.
]==]
function export.split_display_form(label)
if not label:find("%[%[") then
return label, nil
end
local link, display = label:match("^%[%[([^%[%]|]+)|([^%[%]|]+)%]%]$")
if link then
return link, display
end
link = label:match("^%[%[([^%[%]|])+%]%]$")
if link then
return link, link
end
return label, nil
end
--[==[
Combine the `link` and `display` parts of the display form of a label as returned by {split_display_form()}.
If `display` is nil, `link` is returned directly. Otherwise, a one-part or two-part link is constructed
depending on whether `link` and `display` are the same. (As a special case, if both consist of a blank string,
the return value is a blank string rather than a malformed link.)
]==]
function export.combine_display_form_parts(link, display)
if not display then
return link
end
if link == display then
if link == "" then
return ""
else
return ("[[%s]]"):format(link)
end
end
return ("[[%s|%s]]"):format(link, display)
end
--[==[Used to finalize the data into the form that is actually returned.]==]
function export.finalize_data(labels)
local shallow_copy = m_table.shallowCopy
local aliases = {}
for label, data in pairs(labels) do
if type(data) == "table" then
if data.aliases then
for _, alias in ipairs(data.aliases) do
aliases[alias] = label
end
data.aliases = nil
end
if data.deprecated_aliases then
local data2 = shallow_copy(data)
data2.deprecated = true
data2.canonical = label
for _, alias in ipairs(data2.deprecated_aliases) do
aliases[alias] = data2
end
data.deprecated_aliases = nil
data2.deprecated_aliases = nil
end
end
end
for label, data in pairs(aliases) do
labels[label] = data
end
return labels
end
return export
fhymuhbth2hau338honr67atp06r9qp
Module:category tree/languages
828
5929
17460
17455
2026-07-15T16:00:50Z
Hiyuune
6766
17460
Scribunto
text/plain
local new_title = mw.title.new
local ucfirst = require("Module:string utilities").ucfirst
local split = require("Module:string utilities").split
local raw_categories = {}
local raw_handlers = {}
local m_languages = require("Module:languages")
local m_sc_getByCode = require("Module:scripts").getByCode
local m_table = require("Module:table")
local parse_utilities_module = "Module:parse utilities"
local concat = table.concat
local insert = table.insert
local reverse_ipairs = m_table.reverseIpairs
local serial_comma_join = m_table.serialCommaJoin
local size = m_table.size
local sorted_pairs = m_table.sortedPairs
local to_json = require("Module:JSON").toJSON
local Hang = m_sc_getByCode("Hang")
local Hani = m_sc_getByCode("Hani")
local Hira = m_sc_getByCode("Hira")
local Hrkt = m_sc_getByCode("Hrkt")
local Kana = m_sc_getByCode("Kana")
local function track(page)
-- [[Special:WhatLinksHere/Wiktionary:Tracking/category tree/languages/PAGE]]
return require("Module:debug/track")("category tree/languages/" .. page)
end
-- This handles language categories of the form e.g. [[:Category:French language]] and
-- [[:Category:British Sign Language]]; categories like [[:Category:Languages of Indonesia]]; categories like
-- [[:Category:English-based creole or pidgin languages]]; and categories like
-- [[:Category:English-based constructed languages]].
-----------------------------------------------------------------------------
-- --
-- RAW CATEGORIES --
-- --
-----------------------------------------------------------------------------
raw_categories["Reo"] = {
topright = "{{commonscat|Languages}}\n[[File:Languages world map-transparent background.svg|thumb|right|250px|Rough world map of language families]]",
description = "This category contains the categories for every language on Wiktionary.",
additional = "Not all languages that Wiktionary recognises may have a category here yet. There are many that have " ..
"not yet received any attention from editors, mainly because not all Wiktionary users know about every single " ..
"language. See [[Wiktionary:List of languages]] for a full list.",
parents = {
"Whakaraparapa",
},
}
raw_categories["All extinct languages"] = {
description = "This category contains the categories for every [[extinct language]] on Wiktionary.",
additional = "Do not confuse this category with [[:Category:Extinct languages]], which is an umbrella category for the names of extinct languages in specific other languages (e.g. {{m+|de|Langobardisch}} for the ancient [[Lombardic]] language).",
parents = {
"Reo",
},
}
raw_categories["Languages by country"] = {
topright = "{{commonscat|Languages by continent}}",
description = "Categories that group languages by country.",
additional = "{{{umbrella_meta_msg}}}",
parents = {
"Reo",
},
}
raw_categories["Language isolates"] = {
topright = "{{wikipedia|Language isolate}}\n{{commonscat|Language isolates}}",
description = "Languages with no known relatives.",
parents = {
{name = "Languages by family", sort = "*Isolates"},
{name = "All language families", sort = "Isolates"},
},
}
raw_categories["Languages not sorted into a location category"] = {
description = "Languages which do not specify (in their {{tl|auto cat}} call) the location(s) where they are spoken.",
additional = "This excludes constructed and reconstructed languages; as a result, all languages in this category explicitly specify their location as {{cd|UNKNOWN}}.",
parents = {
{name = "Requests"},
},
hidden = true,
}
-----------------------------------------------------------------------------
-- --
-- RAW HANDLERS --
-- --
-----------------------------------------------------------------------------
-- Given a category (without the "Category:" prefix), look up the page defining the category, find the call to
-- {{auto cat}} (if any), and return a table of its arguments. If the category page doesn't exist or doesn't have
-- an {{auto cat}} invocation, return nil.
--
-- FIXME: Duplicated in [[Module:category tree/lects]].
local function scrape_category_for_auto_cat_args(cat)
local cat_page = mw.title.new("Category:" .. cat)
if cat_page then
local contents = cat_page:getContent()
if contents then
local frame = mw.getCurrentFrame()
for template in require("Module:template parser").find_templates(contents) do
-- The template parser automatically handles redirects and canonicalizes them, so uses of {{autocat}}
-- will also be found.
if template:get_name() == "auto cat" then
return template:get_arguments()
end
end
end
end
return nil
end
local function link_location(location)
local location_no_the = location:match("^the (.*)$")
local bare_location = location_no_the or location
local location_link
local bare_location_parts = split(bare_location, ", ")
for i, part in ipairs(bare_location_parts) do
bare_location_parts[i] = ("[[%s]]"):format(part)
end
location_link = concat(bare_location_parts, ", ")
if location_no_the then
location_link = "the " .. location_link
end
return location_link
end
local function linkbox(lang, setwiki, setwikt, setsister, entryname)
local wiktionarylinks = {}
local canonicalName = lang:getCanonicalName()
local wikimediaLanguages = lang:getWikimediaLanguages()
local wikipediaArticle = setwiki or lang:getWikipediaArticle()
setsister = setsister and ucfirst(setsister) or nil
if setwikt then
track("setwikt")
if setwikt == "-" then
track("setwikt/hyphen")
end
end
if setwikt ~= "-" and wikimediaLanguages and wikimediaLanguages[1] then
for _, wikimedialang in ipairs(wikimediaLanguages) do
local check = new_title(wikimedialang:getCode() .. ":")
if check and check.isExternal then
insert(wiktionarylinks,
(wikimedialang:getCanonicalName() ~= canonicalName and "(''" .. wikimedialang:getCanonicalName() .. "'') " or "") ..
"'''[[:" .. wikimedialang:getCode() .. ":|" .. wikimedialang:getCode() .. ".wiktionary.org]]'''")
end
end
wiktionarylinks = concat(wiktionarylinks, "<br/>")
end
local wikt_plural = wikimediaLanguages[2] and "s" or ""
if #wiktionarylinks == 0 then
wiktionarylinks = "''None.''"
end
if setsister then
track("setsister")
if setsister == "-" then
track("setsister/hyphen")
else
setsister = "Category:" .. setsister
end
else
setsister = lang:getCommonsCategory() or "-"
end
return concat{
[=[<div class="wikitable" style="float: right; clear: right; margin: 0 0 0.5em 1em; width: 300px; padding: 5px;">
<div style="text-align: center; margin-bottom: 10px; margin-top: 5px">''']=], canonicalName, [=[ language links'''</div>
{| style="font-size: 90%"
|-
| style="vertical-align: top; height: 35px; border-bottom: 1px solid lightgray;" | [[File:Wikipedia-logo.png|35px|none|Wikipedia]]
| style="border-bottom: 1px solid lightgray;" | '''English Wikipedia''' has an article on:
<div style="padding: 5px 10px">]=], (setwiki == "-" and "''None.''" or "'''[[w:" .. wikipediaArticle .. "|" .. wikipediaArticle .. "]]'''"), [=[</div>
|-
| style="vertical-align: top; height: 35px; border-bottom: 1px solid lightgray;" | [[File:Wikimedia-logo.svg|35px|none|Wikimedia Commons]]
| style="border-bottom: 1px solid lightgray;" | '''Wikimedia Commons''' has links to ]=], canonicalName, [=[-related content in sister projects:
<div style="padding: 5px 10px">]=], (setsister == "-" and "''None.''" or "'''[[commons:" .. setsister .. "|" .. setsister .. "]]'''"), [=[</div>
|-
| style="vertical-align: top; height: 35px; width: 40px; border-bottom: 1px solid lightgray;" | [[File:Wiktionary-logo-v2.svg|35px|none|Wiktionary]]
|style="border-bottom: 1px solid lightgray;" | '''Wiktionary edition''']=], wikt_plural, [=[ written in ]=], canonicalName, [=[:
<div style="padding: 5px 10px">]=], wiktionarylinks, [=[</div>
|-
| style="vertical-align: top; height: 35px; border-bottom: 1px solid lightgray;" | [[File:Open book nae 02.svg|35px|none|Entry]]
| style="border-bottom: 1px solid lightgray;" | '''Wiktionary entry''' for the language's English name:
<div style="padding: 5px 10px">''']=], require("Module:links").full_link({lang = m_languages.getByCode("mi"), term = entryname or canonicalName}), [=['''</div>
|-
| style="vertical-align: top; height: 35px;" | [[File:Crystal kfind.png|35px|none|Considerations]]
|| '''Wiktionary resources''' for editors contributing to ]=], canonicalName, [=[ entries:
<div style="padding: 5px 0">
* '''[[Wiktionary:]=], canonicalName, [=[ entry guidelines]]'''
* '''[[:Category:]=], canonicalName, [=[ reference templates|Reference templates]] ({{PAGESINCAT:]=], canonicalName, [=[ reference templates}})'''
* '''[[Appendix:]=], canonicalName, [=[ bibliography|Bibliography]]'''
|}
</div>]=]
}
end
local function edit_link(title, text)
return '<span class="plainlinks">['
.. tostring(mw.uri.fullUrl(title, { action = "edit" }))
.. ' ' .. text .. ']</span>'
end
-- Should perhaps use wiki syntax.
local function infobox(lang)
local ret = {}
insert(ret, '<table class="wikitable language-category-info"')
local raw_data = lang:getData("extra")
if raw_data then
local replacements = {
[1] = "canonical-name",
[2] = "wikidata-item",
[3] = "family",
[4] = "scripts",
}
local function replacer(letter1, letter2)
return letter1:lower() .. "-" .. letter2:lower()
end
-- For each key in the language data modules, returns a descriptive
-- kebab-case version (containing ASCII lowercase words separated
-- by hyphens).
local function kebab_case(key)
key = replacements[key] or key
key = key:gsub("(%l)(%u)", replacer):gsub("(%l)_(%l)", replacer)
return key
end
local compress = {compress = true}
local function html_attribute_encode(str)
str = to_json(str, compress)
:gsub('"', """)
-- & in attributes is automatically escaped.
-- :gsub("&", "&")
:gsub("<", "<")
:gsub(">", ">")
return str
end
insert(ret, ' data-code="' .. lang:getCode() .. '"')
for k, v in sorted_pairs(raw_data) do
insert(ret, " data-" .. kebab_case(k)
.. '="'
.. html_attribute_encode(v)
.. '"')
end
end
insert(ret, '>\n')
insert(ret, '<tr class="language-category-data">\n<th colspan="2">'
.. edit_link(lang:getDataModuleName(), "Edit language data")
.. "</th>\n</tr>\n")
insert(ret, "<tr>\n<th>Canonical name</th><td>" .. lang:getCanonicalName() .. "</td>\n</tr>\n")
local otherNames = lang:getOtherNames()
if otherNames then
local names = {}
for _, name in ipairs(otherNames) do
insert(names, "<li>" .. name .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Other names</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local aliases = lang:getAliases()
if aliases then
local names = {}
for _, name in ipairs(aliases) do
insert(names, "<li>" .. name .. "</li>")
end
if #names > 0 then
insert(ret, "<tr>\n<th>Aliases</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
local varieties = lang:getVarieties()
if varieties then
local names = {}
for _, name in ipairs(varieties) do
if type(name) == "string" then
insert(names, "<li>" .. name .. "</li>")
else
assert(type(name) == "table")
local first_var
local subvars = {}
for i, var in ipairs(name) do
if i == 1 then
first_var = var
else
insert(subvars, "<li>" .. var .. "</li>")
end
end
if #subvars > 0 then
insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. concat(subvars, "\n") .. "</ul></dd></dl></li>")
elseif first_var then
insert(names, "<li>" .. first_var .. "</li>")
end
end
end
if #names > 0 then
insert(ret, "<tr>\n<th>Varieties</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
end
end
insert(ret, "<tr>\n<th>[[Wiktionary:Languages|Language code]]</th><td><code>" .. lang:getCode() .. "</code></td>\n</tr>\n")
insert(ret, "<tr>\n<th>[[Wiktionary:Families|Language family]]</th>\n")
local fam = lang:getFamily()
local famCode = fam and fam:getCode()
if not fam then
insert(ret, "<td>unclassified</td>")
elseif famCode == "qfa-iso" then
insert(ret, "<td>[[:Category:Language isolates|language isolate]]</td>")
elseif famCode == "qfa-mix" then
insert(ret, "<td>[[:Category:Mixed languages|mixed language]]</td>")
elseif famCode == "sgn" then
insert(ret, "<td>[[:Category:Sign languages|sign language]]</td>")
elseif famCode == "crp" then
insert(ret, "<td>[[:Category:Creole or pidgin languages|creole or pidgin]]</td>")
elseif famCode == "art" then
insert(ret, "<td>[[:Category:Constructed languages|constructed language]]</td>")
else
insert(ret, "<td>" .. fam:makeCategoryLink() .. "</td>")
end
insert(ret, "\n</tr>\n<tr>\n<th>Ancestors</th>\n<td>")
local ancestors = lang:getAncestors()
if ancestors[2] then
local ancestorList = {}
for i, anc in ipairs(ancestors) do
ancestorList[i] = "<li>" .. anc:makeCategoryLink() .. "</li>"
end
insert(ret, "<ul>\n" .. concat(ancestorList, "\n") .. "</ul>")
else
local ancestorChain = lang:getAncestorChainOld()
if ancestorChain[1] then
local chain = {}
for _, anc in reverse_ipairs(ancestorChain) do
insert(chain, "<li>" .. anc:makeCategoryLink() .. "</li>")
end
insert(ret, "<ul>\n" .. concat(chain, "\n<ul>\n") .. ("</ul>"):rep(#chain))
else
insert(ret, "unknown")
end
end
insert(ret, "</td>\n</tr>\n")
local scripts = lang:getScripts()
if scripts[1] then
local script_text = {}
local function makeScriptLine(sc)
local code = sc:getCode()
local url = tostring(mw.uri.fullUrl('Special:Search', {
search = 'contentmodel:css insource:"' .. code
.. '" insource:/\\.' .. code .. '/',
ns8 = '1'
}))
return sc:makeCategoryLink()
.. ' (<span class="plainlinks" title="Search for stylesheets referencing this script">[' .. url .. ' <code>' .. code .. '</code>]</span>)'
end
local function add_Hrkt(text)
insert(text, "<li>" .. makeScriptLine(Hrkt))
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hira) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Kana) .. "</li>")
insert(text, "</ul>")
insert(text, "</li>")
end
for _, sc in ipairs(scripts) do
local text = {}
local code = sc:getCode()
if code == "Hrkt" then
add_Hrkt(text)
else
insert(text, "<li>" .. makeScriptLine(sc))
if code == "Jpan" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
add_Hrkt(text)
insert(text, "</ul>")
elseif code == "Kore" then
insert(text, "<ul>")
insert(text, "<li>" .. makeScriptLine(Hang) .. "</li>")
insert(text, "<li>" .. makeScriptLine(Hani) .. "</li>")
insert(text, "</ul>")
end
insert(text, "</li>")
end
insert(script_text, concat(text, "\n"))
end
insert(ret, "<tr>\n<th>[[Wiktionary:Scripts|Scripts]]</th>\n<td><ul>\n" .. concat(script_text, "\n") .. "</ul></td>\n</tr>\n")
else
insert(ret, "<tr>\n<th>[[Wiktionary:Scripts|Scripts]]</th>\n<td>not specified</td>\n</tr>\n")
end
local function add_module_info(raw_data, heading)
if raw_data then
local scripts = lang:getScriptCodes()
local module_info, add = {}, false
if type(raw_data) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data))
add = true
else
local raw_data_type = type(raw_data)
if raw_data_type == "table" and size(scripts) == 1 and type(raw_data[scripts[1]]) == "string" then
insert(module_info,
("[[Module:%s]]"):format(raw_data[scripts[1]]))
add = true
elseif raw_data_type == "table" then
insert(module_info, "<ul>")
for script, data in sorted_pairs(raw_data) do
if type(data) == "string" and m_sc_getByCode(script) then
insert(module_info, ("<li><code>%s</code>: [[Module:%s]]</li>"):format(script, data))
end
end
insert(module_info, "</ul>")
add = size(module_info) > 2
end
end
if add then
insert(ret, [=[
<tr>
<th>]=] .. heading .. [=[</th>
<td>]=] .. concat(module_info) .. [=[</td>
</tr>
]=])
end
end
end
add_module_info(raw_data.generate_forms, "Form-generating<br>module")
add_module_info(raw_data.translit, "[[Wiktionary:Transliteration and romanization|Transliteration<br>module]]")
add_module_info(raw_data.display_text, "Display text<br>module")
add_module_info(raw_data.entry_name, "Entry name<br>module")
add_module_info(raw_data.sort_key, "[[sortkey|Sortkey]]<br>module")
local wikidataItem = lang:getWikidataItem()
if lang:getWikidataItem() and mw.wikibase then
local URL = mw.wikibase.getEntityUrl(wikidataItem)
local link
if URL then
link = '[' .. URL .. ' ' .. wikidataItem .. ']'
else
link = '<span class="error">Invalid Wikidata item: <code>' .. wikidataItem .. '</code></span>'
end
insert(ret, "<tr><th>Wikidata</th><td>" .. link .. "</td></tr>")
end
insert(ret, "</table>")
return concat(ret)
end
local function NavFrame(content, title)
return '<div class="NavFrame"><div class="NavHead">'
.. (title or '{{{title}}}') .. '</div>'
.. '<div class="NavContent" style="text-align: left;">'
.. content
.. '</div></div>'
end
local function get_description_topright_additional(lang, locations, extinct, setwiki, setwikt, setsister, entryname)
local nameWithLanguage = lang:getCategoryName("nocap")
if lang:getCode() == "und" then
local description =
"This is the main category of the '''" .. nameWithLanguage .. "''', represented in Wiktionary by the [[Wiktionary:Languages|code]] '''" .. lang:getCode() .. "'''. " ..
"This language contains terms in historical writing, whose meaning has not yet been determined by scholars."
return description, nil, nil
end
local canonicalName = lang:getCanonicalName()
local topright = linkbox(lang, setwiki, setwikt, setsister, entryname)
local the_prefix
if canonicalName:find(" Language$") then
the_prefix = ""
else
the_prefix = "the "
end
local description = "This is the main category of " .. the_prefix .. "'''" .. nameWithLanguage .. "'''."
local location_links = {}
local prep
local saw_embedded_comma = false
for _, location in ipairs(locations) do
local this_prep
if location == "the world" then
this_prep = "across"
insert(location_links, location)
elseif location ~= "UNKNOWN" then
this_prep = "in"
if location:find(",") then
saw_embedded_comma = true
end
insert(location_links, link_location(location))
end
if this_prep then
if prep and this_prep ~= prep then
error("Can't handle location 'the world' along with another location (clashing prepositions)")
end
prep = this_prep
end
end
local location_desc
if #location_links > 0 then
local location_link_text
if saw_embedded_comma and #location_links >= 3 then
location_link_text = mw.text.listToText(location_links, "; ", "; and ")
else
location_link_text = serial_comma_join(location_links)
end
location_desc = ("It is %s %s %s.\n\n"):format(
extinct and "an [[extinct language]] that was formerly spoken" or "spoken", prep, location_link_text)
elseif extinct then
location_desc = "It is an [[extinct language]].\n\n"
else
location_desc = ""
end
local add = location_desc .. "Information about " .. canonicalName .. ":\n\n" .. infobox(lang)
if lang:hasType("reconstructed") then
add = add .. "\n\n" ..
ucfirst(canonicalName) .. " is a reconstructed language. Its words and roots are not directly attested in any written works, but have been reconstructed through the ''comparative method'', " ..
"which finds regular similarities between languages that cannot be explained by coincidence or word-borrowing, and extrapolates ancient forms from these similarities.\n\n" ..
"According to our [[Wiktionary:Criteria for inclusion|criteria for inclusion]], terms in " .. canonicalName ..
" should '''not''' be present in entries in the main namespace, but may be added to the Reconstruction: namespace."
elseif lang:hasType("appendix-constructed") then
add = add .. "\n\n" ..
ucfirst(canonicalName) .. " is a constructed language that is only in sporadic use. " ..
"According to our [[Wiktionary:Criteria for inclusion|criteria for inclusion]], terms in " .. canonicalName ..
" should '''not''' be present in entries in the main namespace, but may be added to the Appendix: namespace. " ..
"All terms in this language may be available at [[Appendix:" .. ucfirst(canonicalName) .. "]]."
end
local about = new_title("Wiktionary:About " .. canonicalName)
if about.exists then
add = add .. "\n\n" ..
"Please see '''[[Wiktionary:About " .. canonicalName .. "]]''' for information and special considerations for creating " .. nameWithLanguage .. " entries."
end
local ok, tree_of_descendants = pcall(
require("Module:family tree").print_children,
lang:getCode(), {
protolanguage_under_family = true,
must_have_descendants = true
})
if ok then
if tree_of_descendants then
add = add .. NavFrame(
tree_of_descendants,
"Family tree")
else
add = add .. "\n\n" .. ucfirst(lang:getCanonicalName())
.. " has no descendants or varieties listed in Wiktionary's language data modules."
end
else
mw.log("error while generating tree: " .. tostring(tree_of_descendants))
end
return description, topright, add
end
local function get_parents(lang, locations, extinct)
local canonicalName = lang:getCanonicalName()
local sortkey = {sort_base = canonicalName, lang = "mi"}
local ret = {{name = "Reo", sort = sortkey}}
local fam = lang:getFamily()
local famCode = fam and fam:getCode()
-- FIXME: Some of the following categories should be added to this module.
if not fam then
insert(ret, {name = "Category:Unclassified languages", sort = sortkey})
elseif famCode == "qfa-iso" then
insert(ret, {name = "Category:Language isolates", sort = sortkey})
elseif famCode == "qfa-mix" then
insert(ret, {name = "Category:Mixed languages", sort = sortkey})
elseif famCode == "sgn" then
insert(ret, {name = "Category:All sign languages", sort = sortkey})
elseif famCode == "crp" then
insert(ret, {name = "Category:Creole or pidgin languages", sort = sortkey})
for _, anc in ipairs(lang:getAncestors()) do
-- Avoid Haitian Creole being categorised in [[:Category:Haitian Creole-based creole or pidgin languages]], as one of its ancestors is an etymology-only variety of it.
-- Use that ancestor's ancestors instead.
if anc:getFullCode() == lang:getCode() then
for _, anc_extra in ipairs(anc:getAncestors()) do
insert(ret, {name = "Category:" .. ucfirst(anc_extra:getFullName()) .. "-based creole or pidgin languages", sort = sortkey})
end
else
insert(ret, {name = "Category:" .. ucfirst(anc:getFullName()) .. "-based creole or pidgin languages", sort = sortkey})
end
end
elseif famCode == "art" then
if lang:hasType("appendix-constructed") then
insert(ret, {name = "Category:Appendix-only constructed languages", sort = sortkey})
else
insert(ret, {name = "Category:Constructed languages", sort = sortkey})
end
for _, anc in ipairs(lang:getAncestors()) do
if anc:getFullCode() == lang:getCode() then
for _, anc_extra in ipairs(anc:getAncestors()) do
insert(ret, {name = "Category:" .. ucfirst(anc_extra:getFullName()) .. "-based constructed languages", sort = sortkey})
end
else
insert(ret, {name = "Category:" .. ucfirst(anc:getFullName()) .. "-based constructed languages", sort = sortkey})
end
end
else
insert(ret, {name = "Category:" .. fam:getCategoryName(), sort = sortkey})
if lang:hasType("reconstructed") then
insert(ret, {
name = "Category:Reconstructed languages",
sort = {sort_base = canonicalName:gsub("^Proto%-", ""), lang = "mi"}
})
end
end
local function add_sc_cat(sc)
insert(ret, {name = "Category:Reo " .. sc:getCategoryName(), sort = sortkey})
end
local function add_Hrkt()
add_sc_cat(Hrkt)
add_sc_cat(Hira)
add_sc_cat(Kana)
end
for _, sc in ipairs(lang:getScripts()) do
if sc:getCode() == "Hrkt" then
add_Hrkt()
else
add_sc_cat(sc)
if sc:getCode() == "Jpan" then
add_sc_cat(Hani)
add_Hrkt()
elseif sc:getCode() == "Kore" then
add_sc_cat(Hang)
add_sc_cat(Hani)
end
end
end
if lang:hasTranslit() then
insert(ret, {name = "Category:Languages with automatic transliteration", sort = sortkey})
end
local function insert_location_language_cat(location)
local cat = "Languages of " .. location
insert(ret, {name = "Category:" .. cat, sort = sortkey})
local auto_cat_args = scrape_category_for_auto_cat_args(cat)
local location_parent = auto_cat_args and auto_cat_args.parent
if location_parent then
local split_parents = require(parse_utilities_module).split_on_comma(location_parent)
for _, parent in ipairs(split_parents) do
parent = parent:match("^(.-):.*$") or parent
insert_location_language_cat(parent)
end
end
end
local saw_location = false
for _, location in ipairs(locations) do
if location ~= "UNKNOWN" then
saw_location = true
insert_location_language_cat(location)
end
end
if extinct then
insert(ret, {name = "Category:All extinct languages", sort = sortkey})
end
if not saw_location and not (lang:hasType("reconstructed") or (fam and fam:getCode() == "art")) then
-- Constructed and reconstructed languages don't need a location specified and often won't have one,
-- so don't put them in this maintenance category.
insert(ret, {name = "Category:Languages not sorted into a location category", sort = sortkey})
end
return ret
end
local function get_children()
local ret = {}
-- FIXME: We should work on the children mechanism so it isn't necessary to manually specify these.
--for _, label in ipairs({"appendices", "entry maintenance", "lemmas", "names", "phrases", "rhymes", "symbols", "templates", "terms by etymology", "terms by usage"}) do
-- insert(ret, {name = label, is_label = true})
--end
--insert(ret, {name = "terms derived from {{{langname}}}", is_label = true, lang = false})
--insert(ret, {name = "{{{langcode}}}:All topics", sort = "all topics"})
--insert(ret, {name = "Varieties of {{{langname}}}"})
--insert(ret, {name = "Requests concerning {{{langname}}}"})
--insert(ret, {name = "Rhymes:{{{langname}}}", description = "Lists of {{{langname}}} words by their rhymes."})
--insert(ret, {name = "User {{{langcode}}}", description = "Wiktionary users categorized by fluency levels in {{{langdisp}}}."})
return ret
end
-- Handle language categories of the form e.g. [[:Category:French language]] and
-- [[:Category:British Sign Language]].
insert(raw_handlers, function(data)
local category = data.category
if not (category:match("[Rr]eo") or category:match("[Ll]ect$")) then
return nil
end
local lang = m_languages.getByCanonicalName(category)
if not lang then
local langname = category:match("^Reo (.*)")
if langname then
lang = m_languages.getByCanonicalName(langname)
end
if not lang then
return nil
end
end
local args = require("Module:parameters").process(data.args, {
[1] = {list = true},
["setwiki"] = true,
["setwikt"] = true,
["setsister"] = true,
["entryname"] = true,
["extinct"] = {type = "boolean"},
})
-- If called from inside, don't require any arguments, as they can't be known
-- in general and aren't needed just to generate the first parent (used for
-- breadcrumbs).
if #args[1] == 0 and not data.called_from_inside then
-- At least one location must be specified unless the language is constructed (e.g. Esperanto) or reconstructed (e.g. Proto-Indo-European).
local fam = lang:getFamily()
if not (lang:hasType("reconstructed") or (fam and fam:getCode() == "art")) then
error("At least one location (param 1=) must be specified for language '" .. lang:getCanonicalName() .. "' (code '" .. lang:getCode() .. "'). " ..
"Use the value UNKNOWN if the language's location is truly unknown.")
end
end
local description, topright, additional = "", "", ""
-- If called from inside the category tree system, it's called when generating
-- parents or children, and we don't need to generate the description or additional
-- text (which is very expensive in terms of memory because it calls [[Module:family tree]],
-- which calls [[Module:languages/data/all]]).
if not data.called_from_inside then
description, topright, additional = get_description_topright_additional(
lang, args[1], args.extinct, args.setwiki, args.setwikt, args.setsister, args.entryname
)
end
return {
canonical_name = lang:getCategoryName(),
description = description,
lang = lang:getCode(),
topright = topright,
additional = additional,
breadcrumb = lang:getCanonicalName(),
parents = get_parents(lang, args[1], args.extinct),
extra_children = get_children(lang),
umbrella = false,
can_be_empty = true,
}, true
end)
-- Handle categories such as [[:Category:Languages of Indonesia]].
insert(raw_handlers, function(data)
local location = data.category:match("^Languages of (.*)$")
if location then
local args = require("Module:parameters").process(data.args, {
["flagfile"] = true,
["commonscat"] = true,
["wp"] = true,
["basename"] = true,
["parent"] = true,
["locationcat"] = true,
["locationlink"] = true,
})
local topright
local basename = args.basename or location:gsub(", .*", "")
if args.flagfile ~= "-" then
local flagfile_arg = args.flagfile or ("Flag of %s.svg"):format(basename)
local files = require(parse_utilities_module).split_on_comma(flagfile_arg)
local topright_parts = {}
for _, file in ipairs(files) do
local flagfile = "File:" .. file
local flagfile_page = new_title(flagfile)
if flagfile_page and flagfile_page.file.exists then
insert(topright_parts, ("[[%s|right|100px|border]]"):format(flagfile))
elseif args.flagfile then
error(("Explicit flagfile '%s' doesn't exist"):format(flagfile))
end
end
topright = concat(topright_parts)
end
if args.wp then
local wp = require("Module:yesno")(args.wp, "+")
if wp == "+" or wp == true then
wp = data.category
end
if wp then
local wp_topright = ("{{wikipedia|%s}}"):format(wp)
if topright then
topright = topright .. wp_topright
else
topright = wp_topright
end
end
end
if args.commonscat then
local commonscat = require("Module:yesno")(args.commonscat, "+")
if commonscat == "+" or commonscat == true then
commonscat = data.category
end
if commonscat then
local commons_topright = ("{{commonscat|%s}}"):format(commonscat)
if topright then
topright = topright .. commons_topright
else
topright = commons_topright
end
end
end
local bare_location = location:match("^the (.*)$") or location
local location_link = args.locationlink or link_location(location)
local bare_basename = basename:match("^the (.*)$") or basename
local parents = {}
if args.parent then
local explicit_parents = require(parse_utilities_module).split_on_comma(args.parent)
for i, parent in ipairs(explicit_parents) do
local actual_parent, sort_key = parent:match("^(.-):(.*)$")
if actual_parent then
parent = actual_parent
sort_key = sort_key:gsub("%+", bare_location)
else
sort_key = " " .. bare_location
end
insert(parents, {name = "Languages of " .. parent, sort = sort_key})
end
else
insert(parents, {name = "Languages by country", sort = {sort_base = bare_location, lang = "mi"}})
end
if args.locationcat then
local explicit_location_cats = require(parse_utilities_module).split_on_comma(args.locationcat)
for i, locationcat in ipairs(explicit_location_cats) do
insert(parents, {name = "Category:" .. locationcat, sort = " Languages"})
end
else
local location_cat = ("Category:%s"):format(bare_location)
local location_page = new_title(location_cat)
if location_page and location_page.exists then
insert(parents, {name = location_cat, sort = "Languages"})
end
end
local description = ("Categories for languages of %s (including sublects)."):format(location_link)
return {
topright = topright,
description = description,
parents = parents,
breadcrumb = bare_basename,
additional = "{{{umbrella_msg}}}",
}, true
end
end)
-- Handle categories such as [[:Category:English-based creole or pidgin languages]].
insert(raw_handlers, function(data)
local langname = data.category:match("(.*)%-based creole or pidgin languages$")
if langname then
local lang = m_languages.getByCanonicalName(langname)
if lang then
return {
lang = lang:getCode(),
description = "Languages which developed as a [[creole]] or [[pidgin]] from " .. lang:makeCategoryLink() .. ".",
parents = {{name = "Creole or pidgin languages", sort = {sort_base = "*" .. langname, lang = "mi"}}},
breadcrumb = lang:getCanonicalName() .. "-based",
}
end
end
end)
-- Handle categories such as [[:Category:English-based constructed languages]].
insert(raw_handlers, function(data)
local langname = data.category:match("(.*)%-based constructed languages$")
if langname then
local lang = m_languages.getByCanonicalName(langname)
if lang then
return {
lang = lang:getCode(),
description = "Constructed languages which are based on " .. lang:makeCategoryLink() .. ".",
parents = {{name = "Constructed languages", sort = {sort_base = "*" .. langname, lang = "mi"}}},
breadcrumb = lang:getCanonicalName() .. "-based",
}
end
end
end)
return {RAW_CATEGORIES = raw_categories, RAW_HANDLERS = raw_handlers}
5uwvv29zh0rj5wtlhjwdk71cxp2jkyi
Module:language-like
828
5963
17464
17399
2026-07-15T16:06:20Z
Hiyuune
6766
17464
Scribunto
text/plain
local export = {}
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
-- In a locally installed Docker container containing Wiktionary, Wikibase is typically not installed because it's
-- a real pain to get working properly. In such a case, `mw.wikibase` returns nil. We now have checks in the code
-- below so it does reasonable things in the absence of Wikibase.
local wikibase = mw.wikibase
local category_name_has_suffix -- defined as export.categoryNameHasSuffix below
local get_entity = wikibase and wikibase.getEntity or nil
local get_entity_id_for_title = wikibase and wikibase.getEntityIdForTitle or nil
local gsub = string.gsub
local ipairs = ipairs
local match = string.match
local select = select
local sitelink = wikibase and wikibase.sitelink or nil
local type = type
local umatch = mw.ustring.match
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function case_insensitive_pattern(...)
case_insensitive_pattern = require(string_utilities_module).case_insensitive_pattern
return case_insensitive_pattern(...)
end
local function table_flatten(...)
table_flatten = require(table_module).flatten
return table_flatten(...)
end
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local content_lang
local function get_content_lang()
content_lang, get_content_lang = mw.getContentLanguage(), nil
return content_lang
end
-- Implementation of getAliases() for languages, etymology languages,
-- families, scripts and writing systems.
function export.getAliases(self)
local aliases = self._aliases
if aliases == nil then
aliases = (self._data or self).aliases or {}
self._aliases = aliases
end
return aliases
end
-- Implementation of getVarieties() for languages, etymology languages,
-- families, scripts and writing systems. If `flatten` is passed in,
-- flatten down to a list of strings; otherwise, keep the structure.
function export.getVarieties(self, flatten)
local varieties = self._varieties
if varieties == nil then
varieties = (self._data or self).varieties or {}
self._varieties = varieties
end
if not flatten then
return varieties
end
local flattened_varieties = self._flattened_varieties
if flattened_varieties == nil then
flattened_varieties = table_flatten(varieties)
self._flattened_varieties = flattened_varieties
end
return flattened_varieties
end
-- Implementation of getOtherNames() for languages, etymology languages,
-- families, scripts and writing systems.
function export.getOtherNames(self)
local other_names = self._other_names
if other_names == nil then
other_names = (self._data or self).other_names or {}
self._other_names = other_names
end
return other_names
end
-- Implementation of getAllNames() for languages, etymology languages,
-- families, scripts and writing systems. If `notCanonical` is set,
-- the canonical name will be excluded.
function export.getAllNames(self)
local all_names = self._allNames
if all_names == nil then
all_names = table_flatten{
self:getCanonicalName(),
self:getAliases(),
self:getVarieties(),
self:getOtherNames(),
}
self._allNames = all_names
end
return all_names
end
function export.hasType(self, ...)
local n = select("#", ...)
if n == 0 then
error("Must specify at least one type.")
end
local types = self:getTypes()
if not types[...] then
return false
elseif n == 1 then
return true
end
local args = {...}
for i = 2, n do
if not types[args[i]] then
return false
end
end
return true
end
-- Implementation of template-callable getByCode() function for languages,
-- etymology languages, families and scripts. `item` is the language,
-- family or script in question; `args` is the arguments passed in by the
-- module invocation; `extra_processing`, if specified, is a function of
-- one argument (the requested property) and should return the value to
-- be returned to the caller, or nil if the property isn't recognized.
-- `extra_processing` is called after special-cased properties are handled
-- and before general-purpose processing code that works for all string
-- properties.
function export.templateGetByCode(args, extra_processing)
-- The item that the caller wanted to look up.
local item, itemname, list = args[1], args[2]
if itemname == "getAllNames" then
list = item:getAllNames()
elseif itemname == "getOtherNames" then
list = item:getOtherNames()
elseif itemname == "getAliases" then
list = item:getAliases()
elseif itemname == "getVarieties" then
list = item:getVarieties(true)
end
if list then
local index = args[3]; if index == "" then index = nil end
index = tonumber(index or error("Numeric index of the desired item in the list (parameter 3) has not been specified."))
return list[index] or ""
end
if itemname == "getFamily" and item.getFamily then
return item:getFamily():getCode()
end
if extra_processing then
local retval = extra_processing(itemname)
if retval then
return retval
end
end
if item[itemname] then
local ret = item[itemname](item)
if type(ret) == "string" then
return ret
end
error("The function \"" .. itemname .. "\" did not return a string value.")
end
error("Requested invalid item name \"" .. itemname .. "\".")
end
-- Implementation of getCommonsCategory() for languages, etymology languages,
-- families, scripts and writing systems.
function export.getWikidataItem(self)
local item = self._WikidataItem
if item == nil then
item = (self._data or self)[2]
-- If the value is nil, it's cached as false.
item = item ~= nil and (type(item) == "number" and "Q" .. item or item) or false
self._WikidataItem = item
end
return item or nil
end
do
local function get_wiki_article(self, project)
local article
-- If the project is miwiki, check the language data.
if project == "miwiki" then
article = (self._data or self).wikipedia_article
if article then
return article
end
end
-- Otherwise, check the Wikidata item for a sitelink.
local item = self:getWikidataItem()
article = item and sitelink and sitelink(item, project) or false
if article then
return article
end
-- If there's still no article, try the parent (if any).
local get_parent = self.getParent
if get_parent then
local parent = get_parent(self)
if parent then
return get_wiki_article(parent, project)
end
end
return false
end
-- Implementation of getWikipediaArticle() for languages, etymology languages,
-- families, scripts and writing systems.
function export.getWikipediaArticle(self, noCategoryFallback, project)
if project == nil then
project = "miwiki"
end
local article
if project == "miwiki" then
article = self._wikipedia_article
if article == nil then
article = get_wiki_article(self, project)
self._wikipedia_article = article
end
else
-- If the project isn't miwiki, default to no category fallback, but
-- this can be overridden by specifying the value `false`.
if noCategoryFallback == nil then
noCategoryFallback = true
end
local non_en_wikipedia_articles = self._non_en_wikipedia_articles
if non_en_wikipedia_articles == nil then
non_en_wikipedia_articles = {}
self._non_en_wikipedia_articles = non_en_wikipedia_articles
else
article = non_en_wikipedia_articles[project]
end
if article == nil then
article = get_wiki_article(self, project)
non_en_wikipedia_articles[project] = article
end
end
if article or noCategoryFallback then
return article or nil
end
return (gsub(self:getCategoryName(), "Creole language", "Creole"))
end
end
do
local function get_commons_cat_claim(item)
if item then
local entity = get_entity and get_entity(item) or nil
if entity then
-- P373 is the "Commons category" property.
local claim = entity:getBestStatements("P373")[1]
return claim and ("Category:" .. claim.mainsnak.datavalue.value) or nil
end
end
end
local function get_commons_cat_sitelink(item)
if item then
local commons_sitelink = sitelink and sitelink(item, "commonswiki") or nil
-- Reject any sitelinks that aren't categories.
return commons_sitelink and match(commons_sitelink, "^Category:") and commons_sitelink or nil
end
end
local function get_commons_cat(self)
-- Checks are in decreasing order of likelihood for a useful match.
-- Get the Commons Category claim from the object's item.
local lang_item = self:getWikidataItem()
local category = get_commons_cat_claim(lang_item)
if category then
return category
end
-- Otherwise, try the object's category's item.
local langcat_item = get_entity_id_for_title and get_entity_id_for_title("Category:" .. self:getCategoryName()) or nil
category = get_commons_cat_claim(langcat_item)
if category then
return category
end
-- If there's no P373 claim, there might be a sitelink on the
-- object's category's item.
category = get_commons_cat_sitelink(langcat_item)
if category then
return category
end
-- Otherwise, try for a sitelink on the object's own item.
category = get_commons_cat_sitelink(lang_item)
if category then
return category
end
-- If there's still no category, try the parent (if any).
local get_parent = self.getParent
if get_parent then
local parent = get_parent(self)
if parent then
return get_commons_cat(parent)
end
end
return false
end
-- Implementation of getCommonsCategory() for languages, etymology
-- languages, families, scripts and writing systems.
function export.getCommonsCategory(self)
local category
category = self._commons_category
-- Nil values cached as false.
if category ~= nil then
return category or nil
end
category = get_commons_cat(self)
self._commons_category = category
return category or nil
end
end
function export.categoryNameHasSuffix(name, suffixes)
for _, suffix in ipairs(suffixes) do
if umatch(name, "%f[%w]" .. case_insensitive_pattern(suffix, "^.") .. "$") then
return false
end
end
return true
end
category_name_has_suffix = export.categoryNameHasSuffix
function export.categoryNameToCode(name, suffix, data, suffixes)
local truncated = match(name, "(.*)" .. suffix .. "$")
if truncated and category_name_has_suffix(truncated, suffixes) then
local code = data[truncated] or data[(content_lang or get_content_lang()):lcfirst(truncated)]
if code ~= nil then
return code
end
end
if not category_name_has_suffix(name, suffixes) then
return data[name] or data[(content_lang or get_content_lang()):lcfirst(name)]
end
return nil
end
return export
f1t32wxmg8oi99mv10izneimqunnlgr
User talk:Hiyuune
3
6015
17465
2026-07-15T21:15:32Z
Stuartyeates
6287
/* cutting and pasting without attribution is copyright infringement */ new section
17465
wikitext
text/x-wiki
== cutting and pasting without attribution is copyright infringement ==
I see that you're pasting Lua code into mi.wiktionary. Please note that you need to include in the edit summary where it's coming from, for copyright attribution purposes. [[User:Stuartyeates|Stuartyeates]] ([[User talk:Stuartyeates|talk]]) 21:15, 15 Hōngongoi 2026 (UTC)
tdulhq9i4bf7tu06ztl0siiipj9qegt