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 "&#32;") .. 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('"', "&quot;") -- & in attributes is automatically escaped. -- :gsub("&", "&amp;") :gsub("<", "&lt;") :gsub(">", "&gt;") 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