Module:documentation: Difference between revisions

From Linguifex
Jump to navigation Jump to search
No edit summary
No edit summary
 
(8 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local p = {}
local export = {}
local data = mw.loadData( 'Module:Interlinear/data' )
local gloss_override = {} -- for custom gloss abbreviations
local getArgs = require('Module:Arguments').getArgs
local yesno = require('Module:Yesno')
local lang_data = mw.loadData( 'Module:Lang/data' )


--------------------------
local array_module = "Module:array"
-- Almost-global variables
local frame_module = "Module:frame"
--------------------------
local fun_is_callable_module = "Module:fun/isCallable"
local glossing_type, displaying_messages, free_translation, msg, buffer
local languages_module = "Module:languages"
local links_module = "Module:links"
local load_module = "Module:load"
local module_categorization_module = "Module:module categorization"
local number_list_show_module = "Module:number list/show"
local pages_module = "Module:pages"
local parameters_module = "Module:parameters"
local scripts_module = "Module:scripts"
local string_endswith_module = "Module:string/endswith"
local string_gline_module = "Module:string/gline"
local string_insert_module = "Module:string/insert"
local string_startswith_module = "Module:string/startswith"
local string_utilities_module = "Module:string utilities"
local template_parser_module = "Module:template parser"
local title_exists_module = "Module:title/exists"
local title_new_title_module = "Module:title/newTitle"


-------------------
local concat = table.concat
-- General settings
local error = error
-------------------
local full_url = mw.uri.fullUrl
local conf = { --settings
local get_current_title = mw.title.getCurrentTitle
WordSeparator = " \n\r\t", -- Don't replace with %s as this would include non-breaking spaces
local insert = table.insert
GlossAbbrPattern = "^([Ø0-9A-Z]+)$", -- this isn't a full regex, but a Lua pattern
local ipairs = ipairs
-- NOTE: The following characters must be formatted for use in a pattern set.
local list_to_text = mw.text.listToText
GlossAbbrBoundary = "-.,;:<>‹›/\\~+=%?%s%[%]()%_\127'",
local new_message = mw.message.new
GlossExcludeTable = {I = true,}, --strings not be treated as glossing abbreviations
local pcall = pcall
GlossExcludePattern = '^[0-9][0-9]+$', -- excludes strings consisting entirely of digits
local require = require
GlossSmallCapsExclude = "^[AOPS]$", -- glossing abbreviations matching this pattern will not be rendered in small caps
local tonumber = tonumber
GlossingType = "label", -- if set to "label" gloss abbreviations are formatted as an <abbr> with the "label" appearing in a tooltip
local tostring = tostring
-- if set to "wikilink" the abbreviation is formatted as a wikilink to the relevant wikipedia article
local type = type
-- if set to "none" abbreviations aren't formatted at all
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
ErrorCategory = "[[Category:Pages with errors in interlinear text]]",
 
AmbiguousGlossCategory = "[[Category:Articles with ambiguous glossing abbreviations]]",
local function Array(...)
MessageGlossingError = "Error(s) in interlinear glossing",
Array = require(array_module)
combining_gender_numbers = "[0-9][0-9]?$", --e.g. G4 '4th gender' or CL7 'class 7'
return Array(...)
combining_gender_prefixes = {G = "gender", CL = "class"},
end
combining_person = {["1"] = "first person", ["2"] = "second person", ["3"] = "third person"},
 
combining_number = {S = "singular", SG = "singular", P = "plural", PL = "plural", D = "dual", DU = "dual", TRI = "trial"},
local function categorize_module(...)
combining_gender = {F = "feminine", M = "masculine", N = "neuter"},
categorize_module = require(module_categorization_module).categorize
LowerCaseGlosses = {["1sg"] = true, ["2sg"] = true, ["3sg"] = true, ["1du"] = true, ["2du"] = true, ["3du"] = true, ["1pl"] = true, ["2pl"] = true,
return categorize_module(...)
["3pl"] = true, ["Fsg"] = true, ["Fpl"] = true, ["Msg"] = true, ["Mpl"] = true,}, -- these are the non-all-upper-case strings that will be recognised as glossing abbreviations
end
ErrorHelpLocation = "Template:Interlinear",
 
}
local function endswith(...)
endswith = require(string_endswith_module)
return endswith(...)
end
 
local function expand_template(...)
expand_template = require(frame_module).expandTemplate
return expand_template(...)
end
 
local function find_templates(...)
find_templates = require(template_parser_module).find_templates
return find_templates(...)
end
 
local function full_link(...)
full_link = require(links_module).full_link
return full_link(...)
end
 
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
 
local function get_pagetype(...)
get_pagetype = require(pages_module).get_pagetype
return get_pagetype(...)
end
 
local function get_script(...)
get_script = require(scripts_module).getByCode
return get_script(...)
end
 
local function gline(...)
gline = require(string_gline_module)
return gline(...)
end
 
local function is_callable(...)
is_callable = require(fun_is_callable_module)
return is_callable(...)
end
 
local function is_documentation(...)
is_documentation = require(pages_module).is_documentation
return is_documentation(...)
end
 
local function is_sandbox(...)
is_sandbox = require(pages_module).is_sandbox
return is_sandbox(...)
end
 
local function new_title(...)
new_title = require(title_new_title_module)
return new_title(...)
end
 
local function number_list_show_table(...)
number_list_show_table = require(number_list_show_module).table
return number_list_show_table(...)
end


---------------------
local function preprocess(...)
-- CSS styles and classes
preprocess = require(frame_module).preprocess
---------------------
return preprocess(...)
conf.style = { --CSS styles
WordDiv = "float: left; margin-bottom: 0.3em;",
WordMargin = "margin-right: 1em;",
WordP = "margin: 0px;", -- the style for the word <p> elements
GlossAbbr = "font-variant: small-caps; font-variant-numeric: oldstyle-nums; text-transform: lowercase; ", -- won't be applied to gloss abbreviations containing lower-case characters
HiddenText = "display: none;",
EndDiv = "clear: left; display: block;", -- style of the <div> element at the end of the interlinear display
ErrorMessage = "font-size: inherit",
}
conf.class = { --CSS classes
Interlinear = "interlinear",
GlossAbbr  = "gloss-abbr",
GlossAbbrAmb = "gloss-abbr-ambiguous",
GlossAbbrError = "gloss-abbr-error",
ErrorMessage = "error",
}
---------------------
-- Section transclusion
---------------------
local page_content = nil -- lazy initilization
local function get_section(frame, section_name)
if page_content == nil then
local current_title = mw.title.getCurrentTitle()
page_content = current_title:getContent()
end
if page_content then
if mw.ustring.find(page_content, section_name, 1, true) then
return frame:preprocess('{{#section:{{FULLPAGENAME}}|' .. section_name .. '}}')
end
end
return ''
end
end
---------------------
 
-- Sundry small functions
local function process_params(...)
---------------------
process_params = require(parameters_module).process
local function normalise(str)
return process_params(...)
return mw.ustring.gsub(str,"[" .. conf.WordSeparator .. "]+"," ")
end
end


local function tidyCss(str)
local function safe_load_data(...)
str = mw.ustring.gsub(str, '^[\"\']*(.-)[\"\']*$', "%1") -- trims quotation marks
safe_load_data = require(load_module).safe_load_data
if mw.ustring.sub(str, -1) ~= ";" then str = str .. ";" end -- appends ";" if missing
return safe_load_data(...)
return str
end
end


local function highlight(text)
local function split(...)
if text then
split = require(string_utilities_module).split
return '<span style="color:#C00;font-weight:bold;">' .. text .. '</span>'
return split(...)
else return "" end
end
end


local function tone_sup(str)
local function startswith(...)
return mw.ustring.gsub(str, "([^%p%s0-9])([0-9]+)", "%1<sup>%2</sup>")
startswith = require(string_startswith_module)
return startswith(...)
end
end


local function is_empty(str) -- returns "false" if its argument is a string containing chars other than spaces &c.
local function string_insert(...)
if not str then return true end
string_insert = require(string_insert_module)
if mw.ustring.find(str, "[^" .. conf.WordSeparator .. "]")
return string_insert(...)
then return false
else return true end
end
end


local function help_link (anchor)
local function title_exists(...)
if anchor then
title_exists = require(title_exists_module)
return " ([[" .. conf.ErrorHelpLocation .. "#" .. anchor .. "|help]])"
return title_exists(...)
else return "" end
end
end


-- the following is part of a trial implementation of automatic transliteration:
local function ugsub(...)
local function transliterate (str, lang_from, lang_to,  scheme)
ugsub = require(string_utilities_module).gsub
local lookup = {grc = {module = 'Module:Ancient Greek', funct = "transliterate", } }
return ugsub(...)
if not lang_from then
end
msg:add("error", "Source language for transliteration is not set")
else
local t = lookup[lang_from]
if t then
local module = require(t.module)
return module[t.funct](str)
else msg:add("error", "Can't find transliterator for language '" .. lang_from .. "'")
end
end
return ""
end -- end of trial block


--------------------
local function umatch(...)
-- The following two functions update the glossing settings based on the received
umatch = require(string_utilities_module).match
-- template arguments. set_global_glossing_settings() updates the global settings
return umatch(...)
-- that are valid for all gloss abbreviations. set_glossing_type()
-- returns the glossing type, which can vary between the different lines.
--------------------
local function set_global_glossing_settings(a)
local style = ""
if a.style then style = tidyCss(a.style) end
if a.underline == "no" then
style = style .. "text-decoration: none;" end
if a.small_caps == "no" then
style = style .. "font-variant:normal; text-transform: none;" end
if style ~= "" then conf.style.GlossAbbr = conf.style.GlossAbbr .. style end
end
end


local function set_glossing_type(glossing)
local skins = {
if glossing then
["common"] = "",
local GlossingType
["vector"] = "Vector",
glossing = mw.ustring.lower(mw.text.trim(glossing))
["monobook"] = "Monobook",
if mw.ustring.find(glossing, 'link') then
["cologneblue"] = "Cologne Blue",
GlossingType = "wikilink"
["modern"] = "Modern",
elseif mw.ustring.find(glossing, 'label')
}
or  mw.ustring.find(glossing, 'no link') then
 
GlossingType = 'label'
local function compare_pages(page1, page2, text)
elseif mw.ustring.find(glossing, 'no abbr') then
return "[" .. tostring(
GlossingType = "no abbr"
full_url("Special:ComparePages", { page1 = page1, page2 = page2 }))
elseif yesno(glossing) == false then
.. " " .. text .. "]"
GlossingType = nil
elseif yesno(glossing) then
GlossingType = conf.GlossingType
else
msg:add('error', 'Glossing type "' .. glossing .. '" not recognised') end
return GlossingType
else error("set_glossing_type: 'glossing' is nil or false", 2)
end
end
end


local function set_custom_glosses(list)
-- Avoid transcluding [[Module:languages/cache]] everywhere.
local abbs = mw.text.split(list, '[;\n\t]')
local lang_cache = setmetatable({}, {
for _,v in pairs(abbs) do
__index = function(self, k)
local gloss = mw.text.split(v, ':')
return require("Module:languages/cache")[k]
local a = mw.text.trim(gloss[1])
if a and a ~= "" then
gloss_override[a] = {}
gloss_override[a].expansion = gloss[2]
gloss_override[a].wikipage = gloss[3]
end
end
end
})
local function zh_link(word)
return full_link {
lang = lang_cache.zh,
term = word
}
end
end


---------------------
local function make_languages_data_documentation(title, cats, division)
-- The UserMessages object contains and processes error messages and warnings
local doc_template, module_cat
---------------------
if endswith(division, "/extra") then
local UserMessages = {errors = {}, warnings = {}, gloss_messages = {}}
division = division:sub(1, -7)
function UserMessages:add(msgtype, text, gloss)
doc_template = "language extradata documentation"
if msgtype == "gloss_message" then
module_cat = "Language extra data modules"
self.gloss_messages[gloss] = text
else
elseif msgtype == "warning" then
doc_template = "language data documentation"
table.insert(self.warnings, text)
module_cat = "Language data modules"
elseif msgtype == "non-repeating error" then
self.errors.nre = text
elseif msgtype == "ambiguous gloss" then
self.if_ambiguous_glosses = true
elseif msgtype == "error" then
table.insert(self.errors, text)
else return error("UserMessages:add(): unknown message type", 2)
end
end
local sort_key
if division == "exceptional" then
sort_key = "x"
else
sort_key = division:gsub("/", "")
end
cats:insert(module_cat .. "|" .. sort_key)
return {
title = doc_template
}
end
end
function UserMessages:print_errors()
 
local out = ""
local function make_Unicode_data_documentation(title, cats)
local namespace = mw.title.getCurrentTitle().namespace
local subpage, first_three_of_code_point
if next(self.errors) or self.warnings[1] then
= title.fullText:match("^Module:Unicode data/([^/]+)/(%x%x%x)$")
local err_span = mw.html.create("span")
if subpage == "names" or subpage == "images" or subpage == "emoji images" then
err_span:attr("style", conf.style.ErrorMessage)
local low, high =
err_span:addClass(conf.class.ErrorMessage)
tonumber(first_three_of_code_point .. "000", 16),
for _,v in pairs(self.errors) do
tonumber(first_three_of_code_point .. "FFF", 16)
err_span:wikitext(" " .. v .. ";") end
local text, text_type
if namespace % 2 == 0 and namespace ~= 2 -- non-talk namespaces, excluding user pages; if modifying please update the description on the category page
if subpage == "names" then
then err_span:wikitext(conf.ErrorCategory)
text_type = "titles of images"
elseif subpage == "images" then
text_type = "titles of images"
elseif subpage == "emoji images" then
text_type = "emoji-style images"
end
end
out = tostring(err_span)
text = string.format(
mw.addWarning(conf.MessageGlossingError)
"This data module contains the " .. text_type .. " of " ..
end
"[[Appendix:Unicode|Unicode]] code points within the range U+%04X to U+%04X.",
if self.if_ambiguous_glosses then
low, high)
if namespace == 0 -- article namespace
if subpage == "images" and safe_load_data("Module:Unicode data/emoji images/" .. first_three_of_code_point) then
then out = out .. conf.AmbiguousGlossCategory -- this category will only track articles
text = text ..
" This list includes the text variants of emojis. For the list of emoji variants of those characters, see [[Module:Unicode data/emoji images/" ..
first_three_of_code_point .. "]]."
elseif subpage == "emoji images" then
text = text ..
" For text-style images, see [[Module:Unicode data/images/" .. first_three_of_code_point .. "]]."
end
end
return text
end
end
return out
end
end
function UserMessages:print_warnings()
 
local out = ""
local function insert_lang_data_module_cats(cats, langcode, overall_data_module_cat)
-- Messages and warnings get displayed only if the page is being viewed in "preview" mode:
local lang = lang_cache[langcode]
if displaying_messages and (next(self.gloss_messages) or next(self.warnings)) then
if lang then
local div = mw.html.create("div")
local langname
div:addClass("interlinear-preview-warning")
if lang._fullCode then
:cssText('border: 1px solid #a2a9b1; background-color: #f8f9fa; width: 80%; padding: 0.2em;')
langname = lang_cache[lang._fullCode]:getCanonicalName()
:wikitext("<i>This message box is shown only in preview:</i>")
else
:newline()
langname = lang:getCanonicalName()
for _,v in ipairs(self.warnings) do
local p = div:tag("p")
p:addClass(conf.class.ErrorMessage)
p:attr("style", conf.style.ErrorMessage)
p:wikitext(v)
end
if self.gloss_messages then
div:wikitext("<p>  To change any of the following default expansions, see [[Template:Interlinear/doc#Custom abbreviations|the template's documentation]]:</p>")
end
for _,v in pairs(self.gloss_messages) do
div:wikitext("<p>" .. v .. "</p>")
end
end
out = out .. "\n\n" .. tostring(div)
cats:insert(overall_data_module_cat .. "|" .. langname)
cats:insert(langname .. " modules")
cats:insert(langname .. " data modules")
return lang, langname
end
end
return out
end
end


---------------------
--[=[
-- gloss_lookup() receives a gloss abbreviation and tries to uncover its meaning.
This provides categories and documentation for various data modules, so that [[Category:Uncategorized modules]] isn't
---------------------
unnecessarily cluttered. It is a list of tables, each of which have the following possible fields:
local function gloss_lookup(a, label, wikilink)
 
local _label, _wikilink, _lookup, source = nil, nil, nil, nil
`regex` (required): A Lua pattern to match the module's title. If it matches, the data in this entry will be used.
if gloss_override[a] then
Any captures in the pattern can by referenced in the `cat` field using %1 for the first capture, %2 for the
_lookup = gloss_override[a]
second, etc. (often used for creating the sortkey for the category). In addition, the captures are passed to the
source = "local"
`process` function as the third and subsequent parameters.
elseif data.abbreviations[a] then _lookup = data.abbreviations[a] end
 
if _lookup and _lookup.expansion ~= "" then
`process` (optional): This may be a function or a string. If it is a function, it is called as follows:
_label, _wikilink = _lookup.expansion, _lookup.wikipage
  `process(TITLE, CATS, CAPTURE1, CAPTURE2, ...)`
else
where:
local prefix = mw.ustring.sub(a,1,1)
  * TITLE is a title object describing the module's title; see
local suffix = mw.ustring.sub(a,2)
[https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#Title_objects].
if conf.combining_person[prefix] then -- is it of the form 1PL or 3FS?
  * CATS is an array object (see [[Module:array]]) of categories that the module will be added to.
_label = conf.combining_person[prefix]
  * CAPTURE1, CAPTURE2, ... contain any captures in the `regex` field.
local _suffix = conf.combining_number[suffix] or conf.combining_gender[suffix]
The return value of `process` should either be a string (which will be used as the module's documentation), or a
if _suffix then
table specifying the name of a template to expand to get the documentation, along with the arguments to that
_label = _label .. ", " .. _suffix
template. In the latter format, the template name (bare, without the "Template:" prefix) should be in the `title`
else
field, and any arguments should be in `args; in this case, the template name will be listed above the generated
local suffix1 = mw.ustring.sub(suffix,1,1)
documentation as the source of the documentation, along with an edit button to edit the template's contents.
local suffix2 = mw.ustring.sub(suffix,2)
If, however, the return value of the `process` function is a string, any template invocations will be expanded
if conf.combining_gender[suffix1]
using frame:preprocess(), and [[Module:documentation]] will be listed as the source of the documentation.
and  conf.combining_number[suffix2] then
 
_label = _label .. ", " .. conf.combining_gender[suffix1] .. ", " .. conf.combining_number[suffix2]
If `process` itself is a string rather than a function, it should name a submodule under
else _label = nil end
[[Module:documentation/functions/]] which returns a function, of the same type as described above. This submodule
will be specified as the source of the documentation (unless it returns a table naming a template to expand to get
the documentation, as described above).
 
If `process` is omitted entirely, the module will have no documentation.
 
`cat` (optional): A string naming the category into which the module should be placed, or a list of such strings.
Captures specified in `regex` may be referenced in this string using %1 for the first capture, %2 for the second,
etc. It is also possible to add categories in the `process` function by inserting them into the passed-in CATS
array (the second parameter).
]=]
 
local module_regex = {
{
regex = "^Module:languages/data/(3/%l/extra)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(3/%l)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(2/extra)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(2)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(exceptional/extra)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/data/(exceptional)$",
process = make_languages_data_documentation,
},
{
regex = "^Module:languages/.+$",
cat = "Language and script modules",
},
{
regex = "^Module:scripts/.+$",
cat = "Language and script modules",
},
{
regex = "^Module:data tables/data..?.?.?$",
cat = "Reference module sharded data tables",
},
{
regex = "^Module:zh/data/dial%-pron/.+$",
cat = "Chinese dialectal pronunciation data modules",
process = "zh dial or syn",
},
{
regex = "^Module:zh/data/dial%-syn/.+$",
cat = "Chinese dialect synonyms data modules",
process = "zh dial or syn",
},
{
regex = "^Module:zh/data/glyph%-data/.+$",
cat = "Chinese historical character forms data modules",
process = function(title, cats)
local character = title.fullText:match("^Module:zh/data/glyph%-data/(.+)")
if character then
return ("This module contains data on historical forms of the Chinese character %s.")
:format(zh_link(character))
end
end,
},
{
regex = "^Module:zh/data/ltc%-pron/(.+)$",
cat = "Middle Chinese pronunciation data modules|%1",
process = "zh data",
},
{
regex = "^Module:zh/data/och%-pron%-BS/(.+)$",
cat = "Old Chinese (Baxter-Sagart) pronunciation data modules|%1",
process = "zh data",
},
{
regex = "^Module:zh/data/och%-pron%-ZS/(.+)$",
cat = "Old Chinese (Zhengzhang) pronunciation data modules|%1",
process = "zh data",
},
{
-- capture rest of zh/data submodules
regex = "^Module:zh/data/(.+)$",
cat = "Chinese data modules|%1",
},
{
regex = "^Module:mul/guoxue%-data/cjk%-?(.*)$",
process = "guoxue-data",
},
{
regex = "^Module:Unicode data/(.+)$",
cat = "Unicode data modules|%1",
process = make_Unicode_data_documentation,
},
{
regex = "^Module:number list/data/(.+)$",
process = function(title, cats, lang_code)
local lang = insert_lang_data_module_cats(cats, lang_code, "Number data modules")
if lang then
return ("This module contains data on various types of numbers in %s.\n%s")
:format(lang:makeCategoryLink(), number_list_show_table() or "")
end
end,
},
{
regex = "^Module:accel/(.+)$",
process = function(title, cats)
local lang_code = title.subpageText
local lang = lang_cache[lang_code]
if lang then
cats:insert(lang:getCanonicalName() .. " modules|accel")
cats:insert(("Accel submodules|%s"):format(lang:getCanonicalName()))
return ("This module contains new entry creation rules for %s; see [[WT:ACCEL]] for an overview, and [[Module:accel]] for information on creating new rules.")
:format(lang:makeCategoryLink())
end
end,
},
{
regex = "^Module:inc%-ash/dial/data/(.+)$",
cat = "Ashokan Prakrit modules|%1",
process = function(title, cats)
local word = title.fullText:match("^Module:inc%-ash/dial/data/(.+)$")
if word then
local lang = lang_cache["inc-ash"]
return ("This module contains data on the pronunciation of %s in dialects of %s.")
:format(full_link({ term = word, lang = lang }, "term"),
lang:makeCategoryLink())
end
end,
},
{
regex = "^.+%-translit$",
process = "translit",
},
{
regex = "^Module:form of/lang%-data/(.+)$",
process = function(title, cats, lang_code)
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Language-specific form-of modules")
if lang then
-- FIXME, display more info.
return "This module contains language-specific form-of data (tags, shortcuts, base lemma params. etc.) for " ..
langname .. "."
end
end
},
{
regex = "^Module:labels/data/lang/(.+)$",
process = function(title, cats, lang_code)
local lang = insert_lang_data_module_cats(cats, lang_code, "Language-specific label data modules")
if lang then
return {
title = "label language-specific data documentation",
args = { [1] = lang_code },
}
end
end
elseif mw.ustring.match(suffix,conf.combining_gender_numbers) then -- cases like G4 = gender 4
local _i,_j = mw.ustring.find(a, conf.combining_gender_numbers)
local _pre = mw.ustring.sub(a, 1, _i - 1)
local _suff = mw.ustring.sub(a, _i)
if conf.combining_gender_prefixes[_pre] then
_label = conf.combining_gender_prefixes[_pre] .. " " .. _suff
end
end
elseif prefix == "N" then -- dealing with cases like NPST = non-past
},
local s = gloss_override[suffix] or data.abbreviations[suffix]
{
if s ~= nil and not s.ExcludeNegation then
regex = "^Module:category tree/lang/(.+)$",
_label = "non-" .. s.expansion
process = function(title, cats, lang_code)
_wikilink = s.wikipage
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Category tree data modules/lang")
if lang then
return "This module handles generating the descriptions and categorization for " ..
langname .. " category pages "
.. "of the format \"" .. langname .. " LABEL\" where LABEL can be any text. Examples are "
.. "[[:Category:Bulgarian conjugation 2.1 verbs]] and [[:Category:Russian velar-stem neuter-form nouns]]. "
.. "This module is part of the category tree system, which is a general framework for generating the "
.. "descriptions and categorization of category pages.\n\n"
.. "For more information, see [[wikt:Module:category tree/lang/documentation]].\n\n"
.. "'''NOTE:''' If you add a new language-specific module, you must add the language code to the "
.. "list at the top of [[wikt:Module:category tree/lang]] in order for the module to be recognized."
end
end
s = nil
end
end
end
},
if _label == "" then _label = nil end
{
if _wikilink == "" then _wikilink = nil end
regex = "^Module:category tree/topic/(.+)$",
if not label then label = _label end
process = function(title, cats, submodule)
if not wikilink then wikilink = _wikilink end
cats:insert("Category tree data modules/topic| ")
return label, wikilink, source
return {
end
title = "topic cat data submodule documentation"
 
}
---------------------
end
-- format_gloss() calls gloss_lookup() to find the meaning of a gloss
},
-- abbreviation, which it then proceeds to format
{
---------------------
regex = "^Module:category tree/(.+)$",
local function format_gloss(gloss, label, wikilink)
process = function(title, cats, submodule)
if string.sub(gloss,1,3) == "000" then -- checks for a common component of exposed strip markers (see [[:mw:Strip marker]])
cats:insert("Category tree data modules| ")
return gloss
return {
end
title = "category tree data submodule documentation"
local gloss2 = mw.ustring.gsub(gloss,"<.->","") -- remove any html fluff
}
gloss2 = mw.ustring.gsub(gloss2, "%'%'+", "") -- remove wiki bold/italic formatting
end
gloss2 = mw.text.trim(mw.ustring.upper(gloss2))
},
if not (label or wikilink)
{
or (not label and glossing_type == "label")
regex = "^Module:ja/data/(.+)$",
or (not wikilink  and glossing_type == "wikilink")
cat = "Japanese data modules|%1",
then
},
if glossing_type ~= "no abbr"
{
then label, wikilink, source = gloss_lookup(gloss2, label, wikilink)
regex = "^Module:fi%-dialects/data/feature/Kettunen1940 ([0-9]+)$",
cat = "Finnish dialectal data atlas modules|%1",
process = function(title, cats, shard)
return "This module contains shard " .. shard .. " of the online version of Lauri Kettunen's 1940 work " ..
"''Suomen murteet III A. Murrekartasto'' (\"Finnish dialects III A: Dialect atlas\"). " ..
"It was imported and converted from urn:nbn:fi:csc-kata20151130145346403821, published by the " ..
"''Kotimaisten kielten keskus'' under the CC BY 4.0 license."
end
},
{
regex = "^Module:fi%-dialects/data/feature/(.+)",
cat = "Finnish dialectal data modules|%1",
},
{
regex = "^Module:fi%-dialects/data/word/(.+)",
cat = "Finnish dialectal data modules|%1",
},
{
regex = "^Module:Swadesh/data/([%l-]+)$",
process = function(title, cats, lang_code)
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
if lang then
return "This module contains the [[Swadesh list]] of basic vocabulary in " .. langname .. "."
end
end
end
local gloss_node
if glossing_type == "no abbr"
then gloss_node = mw.html.create("span")
else gloss_node = mw.html.create("abbr") end
gloss_node:addClass(conf.class.GlossAbbr)
if label or wikilink then
if not mw.ustring.match(gloss, "%l") -- excluding glosses that contain lower-case characters
and not mw.ustring.match(gloss,conf.GlossSmallCapsExclude) -- and also excluding A, O etc. from rendering in small caps
then gloss_node:attr("style", conf.style.GlossAbbr)
end
end
local abbr_label
},
if label then abbr_label = label
{
else abbr_label = wikilink end
regex = "^Module:Swadesh/data/([%l-]+)/([^/]*)$",
gloss_node:attr("title", abbr_label)
process = function(title, cats, lang_code, variety)
if source ~= "local" and data.abbreviations[gloss2] then
local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
if data.abbreviations[gloss2].ambiguous then
if lang then
gloss_node:addClass(conf.class.GlossAbbrAmb)
local prefix = "This module contains the [[Swadesh list]] of basic vocabulary in the "
msg:add("ambiguous gloss")
local etym_lang = get_lang(variety, nil, "allow etym")
if etym_lang then
return ("%s %s variety of %s."):format(prefix, etym_lang:getCanonicalName(), langname)
end
end
local script = get_script(variety)
if script then
return ("%s %s %s script."):format(prefix, langname, script:getCanonicalName())
end
return ("%s %s variety of %s."):format(prefix, variety, langname)
end
end
end
if glossing_type == "wikilink" and wikilink
},
then gloss_node:wikitext("[[", wikilink, "|" , gloss, "]]")
{
else gloss_node:wikitext(gloss) end
regex = "^Module:typing%-aids",
if source ~= "local" and displaying_messages then -- logging gloss lookups:
process = function(title, cats)
local message = ""
local data_suffix = title.fullText:match("^Module:typing%-aids/data/(.+)$")
if label then
local sortkey
message = "assuming " .. gloss2 .. " means \"" .. abbr_label .. "\";" end
if data_suffix then
if glossing_type == "wikilink" and wikilink then
if data_suffix:find "^[%l-]+$" then
message = message .. " linking to [[" .. wikilink .. "]];"
local lang = get_lang(data_suffix)
if lang then
sortkey = lang:getCanonicalName()
cats:insert(sortkey .. " data modules")
end
elseif data_suffix:find "^%u%l%l%l$" then
local script = get_script(data_suffix)
if script then
sortkey = script:getCanonicalName()
cats:insert(script:getCategoryName())
end
end
cats:insert("Character insertion data modules|" .. (sortkey or data_suffix))
end
end,
},
{
regex = "^Module:R:([%l-]+):(.+)$",
process = function(title, cats, lang_code, refname)
local lang = lang_cache[lang_code]
if lang then
cats:insert(lang:getCanonicalName() .. " modules|" .. refname)
cats:insert(("Reference modules|%s"):format(lang:getCanonicalName()))
return "This module implements the reference template {{temp|R:" .. lang_code .. ":" .. refname .. "}}."
end
end,
},
{
regex = "^Module:Quotations/([%l-]+)/?(.*)",
process = "Quotation",
},
{
regex = "^Module:affix/lang%-data/([%l-]+)",
process = "affix lang-data",
},
{
regex = "^Module:dialect synonyms/([%l-]+)$",
process = function(title, cats, lang_code)
local lang = lang_cache[lang_code]
if lang then
local langname = lang:getCanonicalName()
cats:insert("Dialect synonyms data modules|" .. langname)
cats:insert(langname .. " dialect synonyms data modules| ")
return "This module contains data on specific varieties of " .. langname .. ", for use by " ..
"{{tl|dialect synonyms}}. The actual synonyms themselves are contained in submodules.\n\n" ..
"==== Language data module structure ====\n" ..
"* <code>export.title</code> — optional; table title template (e.g. \"Regional synonyms of %s\").\n" ..
"* <code>export.columns</code> — optional; list of column headers for location hierarchy (e.g. {\"Dialect group\", \"Dialect\", \"Location\"}).\n" ..
"* <code>export.notes</code> — optional; table of note keys to text.\n" ..
"* <code>export.sources</code> — optional; table of source keys to text.\n" ..
"* <code>export.note_aliases</code> — optional; alias map for notes.\n" ..
"* <code>export.varieties</code> — required; nested table of variety nodes. Each node must have <code>name</code>; array part holds children. Node keys can include <code>text_display</code>, <code>color</code>, <code>code</code>, <code>wikidata</code>, <code>lat</code>, <code>long</code>, and language-specific keys (e.g. <code>persian</code>, <code>armenian</code>, <code>chinese</code>).\n\n" ..
expand_template({ title = 'dial syn', args = { lang_code, ["demo mode"] = "y" } })
end
end,
},
{
regex = "^Module:dialect synonyms/([%l-]+)/([^/]+)$",
process = function(title, cats, lang_code, term)
local lang = lang_cache[lang_code]
if lang then
local langname = lang:getCanonicalName()
cats:insert("Dialect synonyms data modules|" .. langname)
cats:insert(langname .. " dialect synonyms data modules|" .. term)
return ("%s\n\n%s"):format(
"==== Term/sense module structure ====\n" ..
"* <code>export.title</code> — optional; custom table title (e.g. \"Realization of 'strong R' between vowels\"). Overrides the language default.\n" ..
"* <code>export.meaning</code> — optional; meaning/gloss (alternative to <code>gloss</code>).\n" ..
"* <code>export.gloss</code> — optional; short meaning for the table.\n" ..
"* <code>export.note</code> — optional; single note key or string, or list of note keys.\n" ..
"* <code>export.notes</code> — optional; list of note keys.\n" ..
"* <code>export.source</code> / <code>export.sources</code> — optional; source keys.\n" ..
"* <code>export.last_column</code> — optional; label for the data column (default \"Words\"; e.g. \"Realization\").\n" ..
"* <code>export.syns</code> — required; table mapping variety/location names (keys from the language data module) to a list of term entries. Each entry can be a string or a table (e.g. <code>{ ipa = \"[ɽ]\" }</code> or <code>{ term = \"word\" }</code>).\n\n" ..
"Example (custom title and data column, IPA realizations):\n" ..
"<pre>\nlocal export = {}\n\nexport.title = \"Realization of 'strong R' between vowels\"\n" ..
"export.meaning = \"\"\nexport.note = \"realization of 'strong R' between vowels\"\n" ..
"export.last_column = \"Realization\"\n\nexport.syns = {\n\t[\"ALERS-158\"] = { { ipa = \"[ɽ]\" } },\n\t[\"ALERS-175\"] = { { ipa = \"[x]\" } },\n}\n\nreturn export\n</pre>\n\n",
expand_template({ title = 'dial syn', args = { lang_code, term } }))
end
end,
},
{
regex = "^Module:dialect synonyms/([%l-]+)/([^/]+)/([^/]+)$",
process = function(title, cats, lang_code, term, id)
local lang = lang_cache[lang_code]
if lang then
local langname = lang:getCanonicalName()
cats:insert("Dialect synonyms data modules|" .. langname)
cats:insert(langname .. " dialect synonyms data modules|" .. term)
return ("%s\n\n%s"):format(
"==== Term/sense module structure ====\n" ..
"* <code>export.title</code> — optional; custom table title (e.g. \"Realization of 'strong R' between vowels\"). Overrides the language default.\n" ..
"* <code>export.meaning</code> — optional; meaning/gloss (alternative to <code>gloss</code>).\n" ..
"* <code>export.gloss</code> — optional; short meaning for the table.\n" ..
"* <code>export.note</code> — optional; single note key or string, or list of note keys.\n" ..
"* <code>export.notes</code> — optional; list of note keys.\n" ..
"* <code>export.source</code> / <code>export.sources</code> — optional; source keys.\n" ..
"* <code>export.last_column</code> — optional; label for the data column (default \"Words\"; e.g. \"Realization\").\n" ..
"* <code>export.syns</code> — required; table mapping variety/location names (keys from the language data module) to a list of term entries. Each entry can be a string or a table (e.g. <code>{ ipa = \"[ɽ]\" }</code> or <code>{ term = \"word\" }</code>).\n\n" ..
"Example (custom title and data column, IPA realizations):\n" ..
"<pre>\nlocal export = {}\n\nexport.title = \"Realization of 'strong R' between vowels\"\n" ..
"export.meaning = \"\"\nexport.note = \"realization of 'strong R' between vowels\"\n" ..
"export.last_column = \"Realization\"\n\nexport.syns = {\n\t[\"ALERS-158\"] = { { ipa = \"[ɽ]\" } },\n\t[\"ALERS-175\"] = { { ipa = \"[x]\" } },\n}\n\nreturn export\n</pre>\n\n",
expand_template({ title = 'dial syn', args = { lang_code, term, id = id } }))
end
end,
},
{
regex = "^Module:bibliography/data/([%l-]+)$",
process = function(title, cats, lang_code)
if lang_code == "preload" then
return 'Used as a base model for other languages when the button "create new language submodule" is clicked.'
end
local page = require(title.fullText).bib_page
if not page then
page = lang_cache[lang_code]:getCanonicalName()
if page then
cats:insert(page .. " modules")
end
end
cats:insert("Reference modules")
return "This module holds bibliographical data for " ..
page .. ". For the formatted bibliography see '''[[Appendix:Bibliography/" .. page .. "]]'''."
end,
},
}
 
function export.show(frame)
local boolean_default_false = { type = "boolean", default = false }
local args = process_params(frame.args, {
["hr"] = true,
["for"] = true,
["from"] = true,
["allowondoc"] = boolean_default_false, -- Don't throw an error if used on a documentation subpage.
["notsubpage"] = boolean_default_false,
["nodoc"] = boolean_default_false,
["nolinks"] = boolean_default_false, -- suppress all "Useful links"
["nosandbox"] = boolean_default_false, -- supress sandbox
})
 
local output = Array('\n<div class="documentation" style="display:block; clear:both">\n')
local cats = Array()
 
local nodoc = args.nodoc
 
if (not args.hr) or (args.hr == "above") then
output:insert("----\n")
end
 
local title = args["for"] and new_title(args["for"]) or get_current_title()
local doc_title = args.from ~= "-" and new_title(args.from or title.fullText .. '/doc') or nil
local contentModel = title.contentModel
local pagetype, is_script_or_stylesheet = get_pagetype(title)
local preload, fallback_docs, doc_content, old_doc_title, user_name, skin_name, needs_doc
local doc_content_source = "Module:documentation"
local auto_generated_cat_source
local cats_auto_generated = false
 
if not args.allowondoc and is_documentation(title) then
-- TODO: merge with {{documentation subpage}}, and choose behaviour based on the page type.
error("This template should not be used on a documentation page. Please use [[Template:documentation subpage]].")
elseif is_sandbox(title) then
local sandbox_ns = title.nsText
preload = ("Template:documentation/preload%s%sSandbox"):format(
sandbox_ns == "Module" and sandbox_ns or "Template",
title.rootText:match("^[Uu]ser:(.+)") and "User" or ""
)
elseif pagetype:match("%f[%w]gadget%f[%W]") then
preload = "Template:documentation/preloadGadget"
elseif pagetype:match("%f[%w]script%f[%W]") then -- .js
if title.nsText == "MediaWiki" then
preload = "Template:documentation/preloadMediaWikiJavaScript"
else
preload = "Template:documentation/preloadTemplate" -- XXX
if title.nsText == "User" then
user_name = title.rootText
end
end
msg:add("gloss_message", message, gloss)
end
end
elseif glossing_type == "no abbr"
is_script_or_stylesheet = true
then gloss_node
elseif pagetype:match("%f[%w]stylesheet%f[%W]") then -- .css
:attr("style", conf.style.GlossAbbr)
preload = "Template:documentation/preloadTemplate" -- XXX
:wikitext(gloss)
if title.nsText == "User" then
else
user_name = title.rootText
if displaying_messages then
msg:add("warning", "Gloss abbreviation " .. highlight(gloss2) .. "  not recognised" .. help_link("gloss abbr"))
end
end
msg:add("non-repeating error", "Unknown glossing abbreviation(s)" .. help_link("gloss abbr"))
is_script_or_stylesheet = true
gloss_node
elseif contentModel == "Scribunto" then -- Exclude pages in Module: which aren't Scribunto.
:addClass(conf.class.GlossAbbrError)
preload = "Template:documentation/preloadModule"
:addClass("error")
elseif pagetype:match("%f[%w]template%f[%W]") or pagetype:match("%f[%w]project%f[%W]") then
:css("font-size", "100%")
preload = "Template:documentation/preloadTemplate"
:attr("title", gloss2 .. ": glossing abbreviation not found")
:attr("style", conf.style.ErrorMessage)
:wikitext(gloss)
end
end
return tostring(gloss_node)
end


---------------------
if doc_title and doc_title.isRedirect then
-- find_gloss() parses a word into morphemes, and it calls format_gloss()
old_doc_title = doc_title
-- for anything that looks like a glossing abbreviation.
doc_title = doc_title.redirectTarget
---------------------
local function find_gloss(word)
local function scan_gloss(boundary, gloss_abbr) -- checks a morpheme if it is a gloss abbreviation
if (mw.ustring.match(gloss_abbr, conf.GlossAbbrPattern)
or conf.LowerCaseGlosses[gloss_abbr])
and not (conf.GlossExcludeTable[gloss_abbr]
or mw.ustring.match(gloss_abbr, conf.GlossExcludePattern))
then gloss_abbr = format_gloss(gloss_abbr)
end
return boundary .. gloss_abbr
end
end
local word = mw.text.decode(word, true)
if word == "I" -- for the case of the English word "I", the 1SG pronoun
then return word end
local pattern = "([" .. conf.GlossAbbrBoundary .. "]?)([^" .. conf.GlossAbbrBoundary .. "]+)"
word = mw.ustring.gsub(word, pattern, scan_gloss) -- splits into morphemes
return word
end


---------------------
output:insert("<dl class=\"plainlinks\" style=\"font-size: smaller;\">")
-- The main purpose of the bletcherous parse() is to split a line into words and and then for each eligible word
-- to call find_gloss(). The parser outputs the individual words (with any gloss abbreviation formatting applied).
-- The simple job of splitting at whitespaces has been made complicated by a) the fact that the input can contain
-- whitespaces inside the various html elements that are the result of the application of various formatting templates;
-- and b) the need to be able to recognise the output of the template that formats custom gloss abbreviations
-- (and hence skip passing it on to find_gloss). See talk for a suggestion about its future.
---------------------
local function parse(cline, i, tags_found,ifglossing)


local function issue_error(message, culprit)
local function get_module_doc_and_cats(categories_only)
UserMessages:add("error",  message .. ": ''" .. mw.ustring.sub(cline.whole, 1, i-1) .. "'''" .. culprit  .. "'''''")
cats_auto_generated = true
end
local automatic_cats = nil
if i > cline.length then return i end --this will only be triggered if the current line has less words than line 1
if user_name then
local next_step, j, _, chunk
fallback_docs = "documentation/fallback/user module"
local probe = mw.ustring.sub(cline.whole,i,i)
automatic_cats = { "User sandbox modules" }
if mw.ustring.match(probe,"[" .. conf.WordSeparator .. "]") and tags_found == 0
then next_step = i-1
elseif probe == "[" then --Wikilink?
if mw.ustring.sub(cline.whole,i+1,i+1) == "[" then
_,j,chunk = mw.ustring.find(cline.whole,"(%[%[.-%]%])", i)
else chunk = "["; j = i end --not a wikilink then
buffer = buffer .. chunk
next_step = parse(cline, j+1,tags_found,ifglossing)
elseif probe == "{" and tags_found == 0 then --curly brackets enclose a sequence of words to be treated as a single unit
_,j,chunk = mw.ustring.find(cline.whole,"(.-)(})", i+1)
if not chunk then
issue_error("Unclosed curly bracket", "{")
chunk = highlight("{"); j = i
elseif ifglossing==true then
chunk = find_gloss(chunk)
else
else
if cline.tone_sup then chunk = tone_sup(chunk) end
for _, data in ipairs(module_regex) do
local captures = { umatch(title.fullText, data.regex) }
if #captures > 0 then
local cat, process_function
if is_callable(data.process) then
process_function = data.process
elseif type(data.process) == "string" then
doc_content_source = "Module:documentation/functions/" .. data.process
process_function = require(doc_content_source)
end
 
if process_function then
doc_content = process_function(title, cats, unpack(captures))
end
if type(doc_content) == "table" then
doc_content_source = doc_content.title and "Template:" .. doc_content.title or doc_content_source
doc_content = expand_template(doc_content)
elseif doc_content ~= nil then
doc_content = preprocess(doc_content)
end
cat = data.cat
 
if cat then
if type(cat) == "string" then
cat = { cat }
end
for _, c in ipairs(cat) do
insert(cats, (ugsub(title.fullText, data.regex, c)))
end
end
break
end
end
end
 
if title.subpageText == "templates" then
cats:insert("Template interface modules")
end
end
buffer = buffer .. chunk
 
next_step =  parse(cline, j+1,tags_found,ifglossing)
if automatic_cats then
elseif probe == "<" then -- We've encountered an HTML tag. What do we do now?
for _, c in ipairs(automatic_cats) do
local _,j,chunk = mw.ustring.find(cline.whole,"(<.->)",i)
cats:insert(c)
if not chunk then
end
issue_error("Unclosed angle bracket", "<")
chunk = highlight("<"); j = i
elseif mw.ustring.sub(cline.whole,i,i+1) == "</" then -- It's a CLOSING tag
if cline.glossing
and ifglossing==false
and mw.ustring.match(chunk,"</abbr>")
then ifglossing=true end
tags_found = tags_found - 1
elseif not mw.ustring.match(chunk, "/>$") -- It's an OPENING tag, unless it opens a self-closing element (in which case the element is ignored)
then if ifglossing == true -- the following checks for the output of {{ggl}}:
and mw.ustring.find(chunk, conf.class.GlossAbbr, 1, true) -- it's important that the "find" function uses literal strings and not patterns
then ifglossing = false end
tags_found = tags_found + 1
end
end
buffer = buffer .. chunk
 
next_step = parse(cline, j+1,tags_found,ifglossing)
if #cats == 0 then
else -- No HTML tags, so we only need to find where the word ends
local auto_cats = categorize_module(frame, "return raw", "noerror")
local _,k,chunk = mw.ustring.find(cline.whole,"(..-)([ <[])",i)
if #auto_cats > 0 then
if k then --ordinary text
auto_generated_cat_source = "Module:module categorization"
if ifglossing==true then
end
buffer = buffer .. find_gloss(chunk)
for _, category in ipairs(auto_cats) do
cats:insert(category)
end
end
 
-- meaning module is not in user’s sandbox or one of many datamodule boring series
needs_doc = not categories_only and not (automatic_cats or doc_content or fallback_docs)
end
 
-- Override automatic documentation, if present.
if doc_title and doc_title.exists then
local cats_auto_generated_text = ""
if contentModel == "Scribunto" then
local doc_page_content = doc_title.content
-- Track then do nothing if there are uses of includeonly. The
-- pattern is slightly too permissive, but any false-positives are
-- obvious typos that should be corrected.
if doc_page_content:lower():match("</?includeonly%f[%s/>][^>]*>") then
else
else
if cline.tone_sup then chunk = tone_sup(chunk) end
-- Check for uses of {{module cat}}. find_templates treats the
buffer = buffer .. chunk
-- input as transcluded by default (i.e. it parses the wikitext
-- which will be transcluded through to the module page).
local module_cat
for template in find_templates(doc_page_content) do
if template:get_name() == "module cat" then
module_cat = true
break
end
end
if not module_cat then
get_module_doc_and_cats("categories only")
auto_generated_cat_source = auto_generated_cat_source or doc_content_source
cats_auto_generated_text = " Categories were auto-generated by [[" ..
auto_generated_cat_source .. "]]. <sup>[[" ..
new_title(auto_generated_cat_source):fullUrl { action = "edit" } .. " edit]]</sup>"
end
end
end
 
output:insert(
"<dd><i style=\"font-size: larger;\">The following " ..
"[[wikt:Help:Documenting templates and modules|documentation]] is located at [[" ..
doc_title.fullText .. "]]. " .. "<sup>[[" .. doc_title:fullUrl { action = "edit" } .. " edit]]</sup>" ..
cats_auto_generated_text .. "</i></dd>")
else
if contentModel == "Scribunto" then
get_module_doc_and_cats(false)
elseif title.nsText == "Template" then
--cats:insert("Uncategorized templates")
needs_doc = not (fallback_docs or nodoc)
elseif user_name and is_script_or_stylesheet then
skin_name = skins[title.text:sub(#title.rootText + 1):match("^/(%l+)%.[jc]ss?$")]
if skin_name then
fallback_docs = "documentation/fallback/user " .. contentModel
end
end
next_step = parse(cline, k, tags_found, ifglossing)
end
else -- reached end of string
 
if ifglossing == true then
if doc_content then
chunk = find_gloss(mw.ustring.sub(cline.whole,i))
output:insert(
"<dd><i style=\"font-size: larger;\">The following " ..
"[[wikt:Help:Documenting templates and modules|documentation]] is " ..
"generated by [[" .. doc_content_source .. "]]. <sup>[[" ..
new_title(doc_content_source):fullUrl { action = "edit" } ..
" edit]]</sup> </i></dd>")
elseif not nodoc then
if doc_title then
output:insert(
"<dd><i style=\"font-size: larger;\">This " .. pagetype ..
" lacks a [[wikt:Help:Documenting templates and modules|documentation subpage]]. " ..
(fallback_docs and "You may " or "Please ") ..
"[" .. doc_title:fullUrl { action = "edit", preload = preload }
.. " create it].</i></dd>\n")
else
else
chunk = mw.ustring.sub(cline.whole,i)
output:insert(
if cline.tone_sup then chunk = tone_sup(chunk) end
"<dd><i style=\"font-size: larger; color: var(--wikt-palette-red-9,#FF0000);\">Unable to auto-generate " ..
"documentation for this " .. pagetype .. ".</i></dd>\n")
end
end
buffer = buffer .. chunk
next_step = cline.length
end
end
end
end
return next_step
end
--------------------
-- The following function is called by Template:gcl and is used for formatting an individual glossing abbreviation
--------------------
function p.gcl(frame)
local args = getArgs(frame,{
trim = true,
removeBlanks = false,
parentOnly = true,
wrappers = {'Template:Gcl'},
})
msg = UserMessages
set_global_glossing_settings{style = args.style, underline = args.underline, small_caps = args['small-caps']}
if not args.glossing then
glossing_type = conf.GlossingType -- a global variable
else glossing_type = set_glossing_type(args.glossing)
end
local gloss, label, wikilink = args[1], args[2], args[3]
if not gloss then UserMessages:add("error", "No gloss supplied")
return UserMessages:print() end
if wikilink and not args.glossing then -- if a wikilink is supplied and glossing isn't set to 'label'...
glossing_type = 'wikilink' end --    .. then the wikilink will be formatted as such
if label == "" then label = nil end
if wikilink == "" then wikilink = nil end
local result = format_gloss(gloss, label, wikilink)
return result
end


--------------------
if startswith(title.fullText, "MediaWiki:Gadget-") then
-- The following is the function called by Template:Interlinear.
local is_gadget = false
-- It processes the template arguments, then calls parse() to split the input lines into words
for line in gline(new_title("MediaWiki:Gadgets-definition").content) do
-- and it then builds the output html.
local gadget, items = line:match("^%*%s*(%a[%w_-]*)%[.-%]|(.+)$")
--------------------
if not gadget then
function p.interlinearise(frame)
gadget, items = line:match("^%*%s*(%a[%w_-]*)|(.+)$")
---------------------
end
-- Prepare arguments
if gadget then
---------------------
items = Array(split(items, "|"))
local if_auto_translit = false
for i, item in ipairs(items) do
local args = getArgs(frame, { -- configuration for Module:Arguments
if title.fullText == ("MediaWiki:Gadget-" .. item) then
trim = true,
is_gadget = true
removeBlanks = false,
 
parentFirst = true,
output:insert("<dd> ''This script is a part of the <code>")
wrappers = {'Template:Interlinear', 'Template:Fs interlinear'},
output:insert(gadget)
})
output:insert("</code> gadget ([")
local template_name = frame:getParent():getTitle()
output:insert(tostring(full_url("MediaWiki:Gadgets-definition", { action = "edit" })))
if template_name == 'Template:Fs interlinear' then
output:insert(" edit definitions])'' <dl>")
args.italics1 = args.italics1 or "no"
 
args.italics2 = args.italics2 or "yes"
output:insert("<dd> ''Description ([")
args.glossing3 = args.glossing3 or "yes"
output:insert(tostring(full_url("MediaWiki:Gadget-" .. gadget, { action = "edit" })))
if args.lang and not args.lang2 then args.lang2 = args.lang .."-Latn" end
output:insert(" edit])'': ")
if args.transl and not args.transl2 then args.transl2 = args.transl end
 
if_auto_translit = true
output:insert(preprocess(new_message('Gadget-' .. gadget):plain()))
output:insert(" </dd>")
 
items:remove(i)
if #items > 0 then
for j, item in ipairs(items) do
items[j] = '[[MediaWiki:Gadget-' .. item .. '|' .. item .. ']]'
end
output:insert("<dd> ''Other parts'': ")
output:insert(list_to_text(items))
output:insert("</dd>")
end
 
output:insert("</dl></dd>")


end
break
local revid = frame:preprocess( "{{REVISIONID}}" )
end
if  revid == "" then
end
if not args['display-messages'] or yesno(args['display-messages']) then
end
displaying_messages = true end-- messages will be displayed only in preview mode
end
end
msg = UserMessages
local line = {}


local function set_italics(n)
if not is_gadget then
line[n].attr.style = line[n].attr.style .. "font-style: italic;"
output:insert("<dd> ''This script is not a part of any [")
line[n].tone_sup = true -- single digits are assumed to be tone markers and will hence be superscripted
output:insert(tostring(full_url("Special:Gadgets", { uselang = "en" })))
if args['tone-superscripting'] and not yesno(args['tone-superscripting'])
output:insert(' gadget] ([')
then line[n].tone_sup = false end
output:insert(tostring(full_url("MediaWiki:Gadgets-definition", { action = "edit" })))
output:insert(' edit definitions]).</dd>')
-- else
-- cats:insert("Wiktionary gadgets")
end
end
end


if args.glossing then -- the glossing= parameter sets the default glossing type
if old_doc_title then
local _gl = set_glossing_type(args.glossing)
output:insert("<dd> ''Redirected from'' [")
if _gl then conf.GlossingType = _gl end
output:insert(old_doc_title:fullUrl { redirect = "no" })
output:insert(" ")
output:insert(old_doc_title.fullText)
output:insert("] ([")
output:insert(old_doc_title:fullUrl { action = "edit" })
output:insert(" edit]).</dd>\n")
end
end
--this looks for a list of glossing abbreviations on the page that transcludes the template:
local _ablist_section = get_section(frame, 'list-of-glossing-abbreviations')
if _ablist_section and _ablist_section ~= "" then
local _a = mw.ustring.gsub(_ablist_section, '</?div [^\n]*>', '') -- strips off the div tags
set_custom_glosses(_a)
end
--and this looks looks for a list of abbreviations set within the template:
local _ablist = args.abbreviations
if _ablist and _ablist ~= ""
then set_custom_glosses(_ablist) end
local _ablist = args.ablist
if _ablist and _ablist ~= ""
then set_custom_glosses(_ablist) end


local _spacing = tonumber(args.spacing)
if not args.nolinks then
if _spacing and _spacing <= 20
local links = Array()
then conf.style.WordDiv = conf.style.WordDiv .. 'margin-right: ' .. _spacing .. 'em;'
else conf.style.WordDiv = conf.style.WordDiv .. conf.style.WordMargin
end


local offset, last_line = 0, 0
if title.isSubpage and not args.notsubpage then
for j,v in ipairs(args) do -- iterates over the unnamed parameters from the template
links:insert("[[:" .. title.nsText .. ":" .. title.rootText .. "|root page]]")
last_line = last_line +1
links:insert("[[Special:PrefixIndex/" .. title.nsText .. ":" .. title.rootText .. "/|root page’s subpages]]")
if is_empty(v)
then offset = offset + 1
else
else
local i = j - offset
links:insert("[[Special:PrefixIndex/" .. title.fullText .. "/|subpage list]]")
line[i] = {}
end
v = normalise(v)
 
-- the following is part of a trial implementation of automatic transliteration:
if if_auto_translit and v == "auto" and i > 1 then
local source_line = line[i-1]
local src_lang = source_line.lang
if not src_lang then src_lang = args.lang end
if src_lang then
v = transliterate(source_line.whole, src_lang)
else v = ""; msg:add("error", "No language specified for automatic transliteration")
end
end -- end of trial block


line[i].whole = v
links:insert(
line[i].length = mw.ustring.len(v)
"[" ..
tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidetrans = true, hideredirs = true })) ..
" links]")


local _c = args["c" .. i]
if contentModel ~= "Scribunto" then
if _c and _c ~= "" then
links:insert(
line.hasComments = true
"[" ..
line[i].c = _c
tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidelinks = true, hidetrans = true })) ..
" redirects]")
end
end


---prepare style arguments----
if is_script_or_stylesheet then
line[i].class = ""
if user_name then
local _style = args["style" .. i]
links:insert("[[Special:MyPage" .. title.text:sub(#title.rootText + 1) .. "|your own]]")
if not _style then _style = ""
else _style = tidyCss(_style) end
--line[i].attr holds the attributes for the <p> elements that enclose the words in line i
line[i].attr = {style = conf.style.WordP .. _style}
 
local _lang = args["lang" .. i]
if _lang and #_lang > 1 then
line[i].lang = _lang
else _lang = args.lang
if _lang and #_lang > 1 and i == 1 then -- if a lang= parameter is supplied, it's assumed to apply to line 1
line[i].lang = _lang
end
end
else
links:insert(
"[" ..
tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidelinks = true, hideredirs = true })) ..
" transclusions]")
end
end
line[i].attr.lang = line[i].lang
 
--the following emulates the behaviour of {{Bo-textonly}} (see Template talk:Fs interlinear#Tibetan):
if contentModel == "Scribunto" then
if template_name == 'Template:Fs interlinear' then
local is_testcases = title.isSubpage and title.subpageText == "testcases"
if _lang == "bo" and i == 1 then
local without_subpage = title.nsText .. ":" .. title.baseText
line[1].class = line[1].class .. " uchen"
if is_testcases then
line[1].attr.style = line[1].attr.style .. "font-size:1.25em; word-wrap:break-word;"
links:insert("[[:" .. without_subpage .. "|tested module]]")
else
links:insert("[[" .. title.fullText .. "/testcases|testcases]]")
end
end
end


if yesno(args["italics" .. i]) then
if user_name then
set_italics(i)
links:insert("[[User:" .. user_name .. "|user page]]")
end
links:insert("[[User talk:" .. user_name .. "|user talk page]]")
links:insert("[[Special:PrefixIndex/User:" .. user_name .. "/|userspace]]")
-- If sandbox module, add a link to the module that this is a sandbox of.
-- Exclude user sandbox modules like [[User:Dine2016/sandbox]].
elseif title.text:find("^sandbox%d*/") or title.text:find("/sandbox%d*%f[/%z]") then
cats:insert("Sandbox modules")
 
-- Sandbox modules don’t really need documentation.
needs_doc = false
 
-- Don't track user sandbox modules.
local text_title = new_title(title.text)
if not (text_title and text_title.nsText == "User") then
local diff
local sandbox_of = title.text:match("^(.*)/sandbox%d*%f[/%z]")
if sandbox_of then
else
sandbox_of = title.text:match("^sandbox%d*/(.*)$")
end
if not sandbox_of then
error(("Internal error: Something wrong, couldn't extract sandbox-of module from title '%s'")
:format(title.text))
end
sandbox_of = title.nsText .. ":" .. sandbox_of
if title_exists(sandbox_of) then
diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
end


local _transl = args["transl" .. i]
links:insert("[[:" .. sandbox_of .. "|sandbox of]]" .. (diff or ""))
if _transl and #_transl > 1 then
_transl = mw.ustring.lower(_transl)
local _lookup = lang_data.translit_title_table[_transl]
if _lookup then
if _lang and  _lookup[_lang] then
_transl = _lookup[_lang]
else _transl = _lookup.default
end
end
if _transl then
-- If not a sandbox module, add link to sandbox module.
line[i].attr.title = _transl
-- Sometimes there are multiple sandboxes for a single module:
-- [[Module:sandbox/sa-pronunc]],  [[Module:sandbox2/sa-pronunc]].
else
local sandbox_title
local user_prefix, user_rest = title.text:match("^(User:.-/)(.*)$")
if not user_prefix then
user_prefix = ""
user_rest = title.text
end
end
else  msg:add("error", "Transliteration scheme '" .. _transl .. "' not recognised")
sandbox_title = title.nsText .. ":" .. user_prefix .. "sandbox/" .. user_rest
local sandbox_link = "[[:" .. sandbox_title .. "|sandbox]]"
 
local diff
if title_exists(sandbox_title) then
diff = " (" .. compare_pages(title.fullText, sandbox_title, "diff") .. ")"
end
 
links:insert(sandbox_link .. (diff or ""))
end
end
end
end


local _glossing = args["glossing" .. i]
if title.nsText == "Template" then
if _glossing then
-- Error search: all(any namespace), hastemplate (show pages using the template), insource (show source code), incategory (any/specific error) -- [[mw:Help:CirrusSearch]], [[w:Help:Searching/Regex]]
line[i].glossing = set_glossing_type(_glossing)
-- apparently same with/without: &profile=advanced&fulltext=1
-- Do not treat default glossing settings as custom.
local errorq = 'searchengineselect=mediawiki&search=all: hastemplate:\"' ..
if not ((i == 1 and not yesno(_glossing)) or (i == 2 and yesno(_glossing))) then
title.rootText .. '\" insource:\"' .. title.rootText .. '\" incategory:'
line.HasCustomGlossing = true
local eincategory =
"Pages_with_module_errors|ParserFunction_errors|DisplayTitle_errors|Pages_with_ISBN_errors|Pages_with_ISSN_errors|Pages_with_reference_errors|Pages_with_syntax_highlighting_errors|Pages_with_TemplateStyles_errors"
 
links:insert(
'[' .. tostring(full_url('Special:Search', errorq .. eincategory)) .. ' errors]'
.. ' (' ..
'[' .. tostring(full_url('Special:Search', errorq .. 'ParserFunction_errors')) .. ' parser]'
.. '/' ..
'[' .. tostring(full_url('Special:Search', errorq .. 'Pages_with_module_errors')) .. ' module]'
.. ')'
)
 
if title.isSubpage and title.text:find("/sandbox%d*%f[/%z]") then -- This is a sandbox template.
-- At the moment there are no user sandbox templates with subpage
-- “/sandbox”.
cats:insert("Sandbox templates")
 
-- Sandbox templates don’t really need documentation.
needs_doc = false
 
-- Will behave badly if “/sandbox” occurs twice in title!
local sandbox_of = title.fullText:gsub("/sandbox%d*%f[/%z]", "")
 
local diff
if title_exists(sandbox_of) then
diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
end
 
links:insert("[[:" .. sandbox_of .. "|sandbox of]]" .. (diff or ""))
-- This is a template that can have a sandbox.
elseif not args.nosandbox then -- unless we tell it not to
local sandbox_title = title.fullText .. "/sandbox"
 
local diff
if title_exists(sandbox_title) then
diff = " (" .. compare_pages(title.fullText, sandbox_title, "diff") .. ")"
end
 
links:insert("[[:" .. sandbox_title .. "|sandbox]]" .. (diff or ""))
end
end
end
end


local _ipa = args['ipa' .. i]
if #links > 0 then
if yesno(_ipa) then
output:insert("<dd> ''Useful links'': " .. links:concat(" • ") .. "</dd>")
line[i].class = "IPA"
end
end
end


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


if line[i].class == ""
if doc_title and doc_title.exists then
then line[i].class = nil end
-- Override automatic documentation, if present.
end -- ends the first if-statement in the loop
doc_content = expand_template { title = doc_title.fullText }
end -- ends the FOR cycle
elseif not doc_content and fallback_docs then
doc_content = expand_template {
title = fallback_docs,
args = {
['user'] = user_name,
['page'] = title.fullText,
['skin name'] = skin_name,
},
}
end


local line_count = #line
if doc_content then
if line_count == 0 then
output:insert(doc_content)
msg:add("error", template_name .. ": no lines supplied.")
return msg:print_errors()
end
end


if line_count > 1 then
output:insert(('\n<%s style="clear: both;" />'):format(args.hr == "below" and "hr" or "br"))
local _italics = args.italics
 
local n = tonumber(_italics)
if cats_auto_generated and not cats[1] and (not doc_content or not doc_content:find("%[%[Category:")) then
if n and n > 0 then
if contentModel == "Scribunto" then
set_italics(n)
cats:insert("Uncategorized modules")
elseif not (_italics and not yesno(_italics)) and not (args["italics1"] and not yesno(args["italics1"])) then
-- elseif title.nsText == "Template" then
set_italics(1) -- by default, the first line will get italicised, unless italics=no or italics1=no
-- cats:insert("Uncategorized templates")
end
end
-- the last unnamed parameter is assumed to be the free translation:
free_translation = args[last_line]
if not is_empty(free_translation) then
line [line_count] = nil  end  --... and is thus excluded from interlinearising
end
end


-- If glossing isn't specified for any line, then it's chosen by default to occur
if needs_doc then
-- in the second line, unless only a single line has been supplied, in which case
cats:insert("Templates and modules needing documentation")
-- the assumption is that it is the one containing grammatical glosses
end
if yesno(args.glossing) == false then
 
line.HasCustomGlossing = true
for _, cat in ipairs(cats) do
output:insert("[[Category:" .. cat .. "]]")
end
 
output:insert("</div>\n")
 
return output:concat()
end
 
function export.module_auto_doc_table()
local parts = {}
local function ins(text)
insert(parts, text)
end
end
if not line.HasCustomGlossing then
ins('{|class="wikitable"')
if line_count == 1 then
ins("! Regex !! Category !! Handling modules")
line[1].glossing = conf.GlossingType
for _, spec in ipairs(module_regex) do
elseif line[2] then
local cat_text
line[2].glossing = conf.GlossingType
local cats = spec.cat
if cats then
local cat_parts = {}
if type(cats) == "string" then
cats = { cats }
end
for _, cat in ipairs(cats) do
insert(cat_parts, ("<code>%s</code>"):format((cat:gsub("|", "&#124;"))))
end
cat_text = concat(cat_parts, ", ")
else
cat_text = "''(unspecified)''"
end
end
ins("|-")
ins(("| <code>%s</code> || %s || %s"):format(spec.regex, cat_text,
is_callable(spec.process) and "''(handled internally)''" or
type(spec.process) == "string" and ("[[Module:documentation/functions/%s]]"):format(spec.process) or
"''(no documentation generator)''"))
end
end
set_global_glossing_settings{style = args['glossing-style'], underline = args.underline, small_caps = args['small-caps']}
ins("|}")
return concat(parts, "\n")
end


---------------------
-- Used by {{translit module documentation}}.
-- Segment lines into words
function export.translitModuleLangList(frame)
---------------------
local pagename, subpage
for i,v in ipairs(line) do
 
local ifglossing = false
if frame.args[1] then
if line[i].glossing then
pagename = frame.args[1]
ifglossing = true -- if true the parser will attempt to format gloss abbreviations in the current line
else
glossing_type = line[i].glossing -- neccessarily a global variable
local title = get_current_title()
subpage = title.subpageText
pagename = title.text
 
if subpage ~= pagename then
pagename = title.rootText
end
end
local wc, n = 1, 1
end
line[i].words = {}
 
while n <= line[i].length do
local translitModule = pagename
buffer = ""
 
n = parse(line[i], n, 0, ifglossing)+2
local languageObjects = require("Module:languages/byTranslitModule")(translitModule)
line[i].words[wc] = buffer
local codeInPagename = pagename:match("^([%l-]+)%-.*translit$")
wc = wc + 1
 
local categories = Array()
local codeInPagenameInList = false
if codeInPagename then
if languageObjects[1] and subpage ~= "documentation" then
local agreement = languageObjects[2] and "s" or ""
categories:insert("[[Category:Transliteration modules used by " ..
#languageObjects .. " language" .. agreement .. "]]")
end
end
languageObjects = Array(languageObjects)
:filter(
function(lang)
local result = lang:getCode() ~= codeInPagename
codeInPagenameInList = codeInPagenameInList or result
return result
end)
end
end


----Check for mismatches in number of words across lines----
if subpage ~= "documentation" then
local number_of_words, mismatch_found = 0, false
for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
for i,v in ipairs(line) do -- find the maximum number of words in any line
local script = get_script(script_code)
local wc = #line[i].words
if script then
if wc ~= number_of_words then
categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
if i ~= 1 and wc ~= 0 then
mismatch_found = true
end
if wc > number_of_words then
number_of_words = wc
end
end
end
end
end
end
----Deal with mismatches---
 
if mismatch_found then
if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
local error_text = "Mismatch in the number of words between lines: "
categories:insert("[[Category:Transliteration modules without a testcases subpage]]")
for i,v in ipairs(line) do
end
local wc = #line[i].words
 
error_text = error_text .. wc .. " word(s) in line " .. i .. ", "
if not languageObjects[1] then
if wc ~= number_of_words then
return categories:concat()
for current_word = wc+1, number_of_words do
end
line[i].words[current_word] = "&nbsp;"
 
end
local langs = Array(languageObjects)
end
:sort(
function(lang1, lang2)
return lang1:getCode() < lang2:getCode()
end)
-- This will not error because languageObjects is not empty.
:map(languageObjects[1].makeCategoryLink)
:serialCommaJoin()
 
return "It is " .. (codeInPagenameInList and "also" or "") ..
" used to transliterate " .. langs .. "." .. categories:concat()
end
 
-- Used by {{strip diacritics module documentation}}.
function export.stripDiacriticsModuleLangList(frame)
local pagename, subpage
 
if frame.args[1] then
pagename = frame.args[1]
else
local title = get_current_title()
subpage = title.subpageText
pagename = title.text
 
if subpage ~= pagename then
pagename = title.rootText
end
end
if string.sub(error_text, -2) == ", "
end
then error_text = string.sub(error_text, 1, #error_text - 2) .. " "
 
local stripDiacriticsModule = pagename
 
local languageObjects = require("Module:languages/byStripDiacriticsModule")(stripDiacriticsModule)
local codeInPagename = pagename:match("^([%l-]+)%-.*stripdiacritics$")
 
local categories = Array()
local codeInPagenameInList = false
if codeInPagename then
if languageObjects[1] and subpage ~= "documentation" then
local agreement = languageObjects[2] and "s" or ""
categories:insert("[[Category:Diacritic-stripping modules used by " ..
#languageObjects .. " language" .. agreement .. "]]")
end
end
error_text = error_text .. help_link("mismatch")
 
UserMessages:add("error", error_text)
languageObjects = Array(languageObjects)
:filter(
function(lang)
local result = lang:getCode() ~= codeInPagename
codeInPagenameInList = codeInPagenameInList or result
return result
end)
end
end


---------------------
if subpage ~= "documentation" then
-- Build the HTML
for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
---------------------
local script = get_script(script_code)
---- If just a single line was supplied, format it as inline text
if script then
if line_count == 1 then
categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
local span = mw.html.create('span')
end
span:attr(line[1].attr)
for wi = 1, number_of_words do
local space
if wi < number_of_words then space = " " else space = "" end
span:wikitext(line[1].words[wi] .. space)
end
end
return tostring(span)
end
end


---- More than one line supplied, so we'll produce interlinear display
if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
local div = mw.html.create("div")
categories:insert("[[Category:Diacritic-stripping modules without a testcases subpage]]")
div:addClass(conf.class.Interlinear)
end


-- For stuff to be displayed in the left margin, like example numbering
if not languageObjects[1] then
local number, indent = nil, nil
return categories:concat()
if args.number and args.number ~= ""
end
then number = args.number end
 
if args.indent and args.indent ~=""
local langs = Array(languageObjects)
then indent = args.indent end
:sort(
if indent or number then
function(lang1, lang2)
if not indent then indent = "4" end --default value
return lang1:getCode() < lang2:getCode()
div:css("margin-left", indent .. 'em')
end)
if number then
-- This will not error because languageObjects is not empty.
div:tag("div")
:map(languageObjects[1].makeCategoryLink)
:css("position", "absolute")
:serialCommaJoin()
:css("left", "1em")
 
:wikitext(args.number)
return "It is " .. (codeInPagenameInList and "also" or "") ..
" used to strip diacritics for " .. langs .. "." .. categories:concat()
end
 
-- Used by {{sortkey module documentation}}.
function export.sortkeyModuleLangList(frame)
local pagename, subpage
 
if frame.args[1] then
pagename = frame.args[1]
else
local title = get_current_title()
subpage = title.subpageText
pagename = title.text
 
if subpage ~= pagename then
pagename = title.rootText
end
end
end
end


if args.box and args.box ~= "" then
local sortkeyModule = pagename
div:css("background-color", "#f8f9fa")
 
:css("border", "1px solid #eaecf0")
local languageObjects = require("Module:languages/bySortkeyModule")(sortkeyModule)
:css("padding", "1em") end
local codeInPagename = pagename:match("^([%l-]+)%-.*sortkey$")
if args.top and args.top ~= "" then --lines to display above the interlinear block
div:tag("div")
:wikitext(args.top)
end


-- Producing the interlinear block
local categories = Array()
for wi = 1, number_of_words do
local codeInPagenameInList = false
local div2 = div:tag("div")
if codeInPagename then
:attr("style", conf.style.WordDiv)
if languageObjects[1] and subpage ~= "documentation" then
for i,_ in ipairs (line) do
local agreement = languageObjects[2] and "s" or ""
if line[i].whole ~= "" then -- skipping empty lines
categories:insert("[[Category:Sortkey-generating modules used by " ..
local p = div2:tag("p")
#languageObjects .. " language" .. agreement .. "]]")
p:attr(line[i].attr)
if line[i].class then
p:addClass(line[i].class)
end
local _text = line[i].words[wi]
if _text == "" or _text == " "
then _text = "&nbsp;" end -- <p> elements without content mess up the interlinear display
p:wikitext(_text)
end
end
end
languageObjects = Array(languageObjects)
:filter(
function(lang)
local result = lang:getCode() ~= codeInPagename
codeInPagenameInList = codeInPagenameInList or result
return result
end)
end
end


--- If any "comments" have been specified, add them at the end of each line
if subpage ~= "documentation" then
if line.hasComments then
for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
local divc = div:tag("div")
local script = get_script(script_code)
:attr("style", conf.style.WordDiv)
if script then
for i,_ in ipairs (line) do
categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
local p = divc:tag("p")
p:attr("style", conf.style.WordP)
if line[i].c then
p:wikitext(line[i].c)
else p:wikitext("&nbsp;")
end
end
end
end
end
end


--Add hidden lines containing the content of each line of interlinear text: this is for accessibility
if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
for i,v in ipairs(line) do
categories:insert("[[Category:Sortkey-generating modules without a testcases subpage]]")
local hidden_line = div:tag("p")
hidden_line:attr("style", conf.style.HiddenText)
:wikitext(v.whole)
end
end


-- Format the free translation
if not languageObjects[1] then
local ft_line = div:tag("p")
return categories:concat()
if free_translation and free_translation ~= "" then
ft_line:attr("style", "clear: left;")
ft_line:wikitext(free_translation)
end
end
if args.bottom and args.bottom ~= ""
then local bottom = div:tag('p')
bottom:css('margin-top', '0')
bottom:wikitext(args.bottom)
end
ft_line:node(msg:print_errors()) -- for error messages


local end_div = div:tag("div")
local langs = Array(languageObjects)
end_div:attr("style", conf.style.EndDiv)
:sort(
div:newline()
function(lang1, lang2)
local temp_track = ""
return lang1:getCode() < lang2:getCode()
if last_line == 2
end)
then temp_track = "[[Category:Pages with interlinear glosses using two unnamed parameters]]"
-- This will not error because languageObjects is not empty.
end
:map(languageObjects[1].makeCategoryLink)
if last_line > 3 and template_name ~= 'Template:Fs interlinear'
:serialCommaJoin()
then  temp_track = "[[Category:Pages with interlinear glosses using more than three unnamed parameters]]"
 
end
return "It is " .. (codeInPagenameInList and "also" or "") ..
return tostring(div) .. temp_track .. msg:print_warnings()
" used to sort " .. langs .. "." .. categories:concat()
end
end


return p
return export

Latest revision as of 23:04, 28 March 2026


Template:Maintenance box


local export = {}

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

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

local function Array(...)
	Array = require(array_module)
	return Array(...)
end

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

local function string_insert(...)
	string_insert = require(string_insert_module)
	return string_insert(...)
end

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

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

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

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

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

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

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

local function make_languages_data_documentation(title, cats, division)
	local doc_template, module_cat
	if endswith(division, "/extra") then
		division = division:sub(1, -7)
		doc_template = "language extradata documentation"
		module_cat = "Language extra data modules"
	else
		doc_template = "language data documentation"
		module_cat = "Language data modules"
	end
	local sort_key
	if division == "exceptional" then
		sort_key = "x"
	else
		sort_key = division:gsub("/", "")
	end
	cats:insert(module_cat .. "|" .. sort_key)
	return {
		title = doc_template
	}
end

local function make_Unicode_data_documentation(title, cats)
	local subpage, first_three_of_code_point
	= title.fullText:match("^Module:Unicode data/([^/]+)/(%x%x%x)$")
	if subpage == "names" or subpage == "images" or subpage == "emoji images" then
		local low, high =
			tonumber(first_three_of_code_point .. "000", 16),
			tonumber(first_three_of_code_point .. "FFF", 16)
		local text, text_type
		if subpage == "names" then
			text_type = "titles of images"
		elseif subpage == "images" then
			text_type = "titles of images"
		elseif subpage == "emoji images" then
			text_type = "emoji-style images"
		end
		text = string.format(
			"This data module contains the " .. text_type .. " of " ..
			"[[Appendix:Unicode|Unicode]] code points within the range U+%04X to U+%04X.",
			low, high)
		if subpage == "images" and safe_load_data("Module:Unicode data/emoji images/" .. first_three_of_code_point) then
			text = text ..
				" This list includes the text variants of emojis. For the list of emoji variants of those characters, see [[Module:Unicode data/emoji images/" ..
				first_three_of_code_point .. "]]."
		elseif subpage == "emoji images" then
			text = text ..
				" For text-style images, see [[Module:Unicode data/images/" .. first_three_of_code_point .. "]]."
		end
		return text
	end
end

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

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

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

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

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

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

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

local module_regex = {
	{
		regex = "^Module:languages/data/(3/%l/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(3/%l)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(2/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(2)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(exceptional/extra)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/data/(exceptional)$",
		process = make_languages_data_documentation,
	},
	{
		regex = "^Module:languages/.+$",
		cat = "Language and script modules",
	},
	{
		regex = "^Module:scripts/.+$",
		cat = "Language and script modules",
	},
	{
		regex = "^Module:data tables/data..?.?.?$",
		cat = "Reference module sharded data tables",
	},
	{
		regex = "^Module:zh/data/dial%-pron/.+$",
		cat = "Chinese dialectal pronunciation data modules",
		process = "zh dial or syn",
	},
	{
		regex = "^Module:zh/data/dial%-syn/.+$",
		cat = "Chinese dialect synonyms data modules",
		process = "zh dial or syn",
	},
	{
		regex = "^Module:zh/data/glyph%-data/.+$",
		cat = "Chinese historical character forms data modules",
		process = function(title, cats)
			local character = title.fullText:match("^Module:zh/data/glyph%-data/(.+)")
			if character then
				return ("This module contains data on historical forms of the Chinese character %s.")
					:format(zh_link(character))
			end
		end,
	},
	{
		regex = "^Module:zh/data/ltc%-pron/(.+)$",
		cat = "Middle Chinese pronunciation data modules|%1",
		process = "zh data",
	},
	{
		regex = "^Module:zh/data/och%-pron%-BS/(.+)$",
		cat = "Old Chinese (Baxter-Sagart) pronunciation data modules|%1",
		process = "zh data",
	},
	{
		regex = "^Module:zh/data/och%-pron%-ZS/(.+)$",
		cat = "Old Chinese (Zhengzhang) pronunciation data modules|%1",
		process = "zh data",
	},
	{
		-- capture rest of zh/data submodules
		regex = "^Module:zh/data/(.+)$",
		cat = "Chinese data modules|%1",
	},
	{
		regex = "^Module:mul/guoxue%-data/cjk%-?(.*)$",
		process = "guoxue-data",
	},
	{
		regex = "^Module:Unicode data/(.+)$",
		cat = "Unicode data modules|%1",
		process = make_Unicode_data_documentation,
	},
	{
		regex = "^Module:number list/data/(.+)$",
		process = function(title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "Number data modules")
			if lang then
				return ("This module contains data on various types of numbers in %s.\n%s")
					:format(lang:makeCategoryLink(), number_list_show_table() or "")
			end
		end,
	},
	{
		regex = "^Module:accel/(.+)$",
		process = function(title, cats)
			local lang_code = title.subpageText
			local lang = lang_cache[lang_code]
			if lang then
				cats:insert(lang:getCanonicalName() .. " modules|accel")
				cats:insert(("Accel submodules|%s"):format(lang:getCanonicalName()))
				return ("This module contains new entry creation rules for %s; see [[WT:ACCEL]] for an overview, and [[Module:accel]] for information on creating new rules.")
					:format(lang:makeCategoryLink())
			end
		end,
	},
	{
		regex = "^Module:inc%-ash/dial/data/(.+)$",
		cat = "Ashokan Prakrit modules|%1",
		process = function(title, cats)
			local word = title.fullText:match("^Module:inc%-ash/dial/data/(.+)$")
			if word then
				local lang = lang_cache["inc-ash"]
				return ("This module contains data on the pronunciation of %s in dialects of %s.")
					:format(full_link({ term = word, lang = lang }, "term"),
						lang:makeCategoryLink())
			end
		end,
	},
	{
		regex = "^.+%-translit$",
		process = "translit",
	},
	{
		regex = "^Module:form of/lang%-data/(.+)$",
		process = function(title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Language-specific form-of modules")
			if lang then
				-- FIXME, display more info.
				return "This module contains language-specific form-of data (tags, shortcuts, base lemma params. etc.) for " ..
					langname .. "."
			end
		end
	},
	{
		regex = "^Module:labels/data/lang/(.+)$",
		process = function(title, cats, lang_code)
			local lang = insert_lang_data_module_cats(cats, lang_code, "Language-specific label data modules")
			if lang then
				return {
					title = "label language-specific data documentation",
					args = { [1] = lang_code },
				}
			end
		end
	},
	{
		regex = "^Module:category tree/lang/(.+)$",
		process = function(title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Category tree data modules/lang")
			if lang then
				return "This module handles generating the descriptions and categorization for " ..
					langname .. " category pages "
					.. "of the format \"" .. langname .. " LABEL\" where LABEL can be any text. Examples are "
					.. "[[:Category:Bulgarian conjugation 2.1 verbs]] and [[:Category:Russian velar-stem neuter-form nouns]]. "
					.. "This module is part of the category tree system, which is a general framework for generating the "
					.. "descriptions and categorization of category pages.\n\n"
					.. "For more information, see [[wikt:Module:category tree/lang/documentation]].\n\n"
					.. "'''NOTE:''' If you add a new language-specific module, you must add the language code to the "
					.. "list at the top of [[wikt:Module:category tree/lang]] in order for the module to be recognized."
			end
		end
	},
	{
		regex = "^Module:category tree/topic/(.+)$",
		process = function(title, cats, submodule)
			cats:insert("Category tree data modules/topic| ")
			return {
				title = "topic cat data submodule documentation"
			}
		end
	},
	{
		regex = "^Module:category tree/(.+)$",
		process = function(title, cats, submodule)
			cats:insert("Category tree data modules| ")
			return {
				title = "category tree data submodule documentation"
			}
		end
	},
	{
		regex = "^Module:ja/data/(.+)$",
		cat = "Japanese data modules|%1",
	},
	{
		regex = "^Module:fi%-dialects/data/feature/Kettunen1940 ([0-9]+)$",
		cat = "Finnish dialectal data atlas modules|%1",
		process = function(title, cats, shard)
			return "This module contains shard " .. shard .. " of the online version of Lauri Kettunen's 1940 work " ..
				"''Suomen murteet III A. Murrekartasto'' (\"Finnish dialects III A: Dialect atlas\"). " ..
				"It was imported and converted from urn:nbn:fi:csc-kata20151130145346403821, published by the " ..
				"''Kotimaisten kielten keskus'' under the CC BY 4.0 license."
		end
	},
	{
		regex = "^Module:fi%-dialects/data/feature/(.+)",
		cat = "Finnish dialectal data modules|%1",
	},
	{
		regex = "^Module:fi%-dialects/data/word/(.+)",
		cat = "Finnish dialectal data modules|%1",
	},
	{
		regex = "^Module:Swadesh/data/([%l-]+)$",
		process = function(title, cats, lang_code)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
			if lang then
				return "This module contains the [[Swadesh list]] of basic vocabulary in " .. langname .. "."
			end
		end
	},
	{
		regex = "^Module:Swadesh/data/([%l-]+)/([^/]*)$",
		process = function(title, cats, lang_code, variety)
			local lang, langname = insert_lang_data_module_cats(cats, lang_code, "Swadesh modules")
			if lang then
				local prefix = "This module contains the [[Swadesh list]] of basic vocabulary in the "
				local etym_lang = get_lang(variety, nil, "allow etym")
				if etym_lang then
					return ("%s %s variety of %s."):format(prefix, etym_lang:getCanonicalName(), langname)
				end
				local script = get_script(variety)
				if script then
					return ("%s %s %s script."):format(prefix, langname, script:getCanonicalName())
				end
				return ("%s %s variety of %s."):format(prefix, variety, langname)
			end
		end
	},
	{
		regex = "^Module:typing%-aids",
		process = function(title, cats)
			local data_suffix = title.fullText:match("^Module:typing%-aids/data/(.+)$")
			local sortkey
			if data_suffix then
				if data_suffix:find "^[%l-]+$" then
					local lang = get_lang(data_suffix)
					if lang then
						sortkey = lang:getCanonicalName()
						cats:insert(sortkey .. " data modules")
					end
				elseif data_suffix:find "^%u%l%l%l$" then
					local script = get_script(data_suffix)
					if script then
						sortkey = script:getCanonicalName()
						cats:insert(script:getCategoryName())
					end
				end
				cats:insert("Character insertion data modules|" .. (sortkey or data_suffix))
			end
		end,
	},
	{
		regex = "^Module:R:([%l-]+):(.+)$",
		process = function(title, cats, lang_code, refname)
			local lang = lang_cache[lang_code]
			if lang then
				cats:insert(lang:getCanonicalName() .. " modules|" .. refname)
				cats:insert(("Reference modules|%s"):format(lang:getCanonicalName()))
				return "This module implements the reference template {{temp|R:" .. lang_code .. ":" .. refname .. "}}."
			end
		end,
	},
	{
		regex = "^Module:Quotations/([%l-]+)/?(.*)",
		process = "Quotation",
	},
	{
		regex = "^Module:affix/lang%-data/([%l-]+)",
		process = "affix lang-data",
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)$",
		process = function(title, cats, lang_code)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				cats:insert("Dialect synonyms data modules|" .. langname)
				cats:insert(langname .. " dialect synonyms data modules| ")
				return "This module contains data on specific varieties of " .. langname .. ", for use by " ..
					"{{tl|dialect synonyms}}. The actual synonyms themselves are contained in submodules.\n\n" ..
					"==== Language data module structure ====\n" ..
					"* <code>export.title</code> — optional; table title template (e.g. \"Regional synonyms of %s\").\n" ..
					"* <code>export.columns</code> — optional; list of column headers for location hierarchy (e.g. {\"Dialect group\", \"Dialect\", \"Location\"}).\n" ..
					"* <code>export.notes</code> — optional; table of note keys to text.\n" ..
					"* <code>export.sources</code> — optional; table of source keys to text.\n" ..
					"* <code>export.note_aliases</code> — optional; alias map for notes.\n" ..
					"* <code>export.varieties</code> — required; nested table of variety nodes. Each node must have <code>name</code>; array part holds children. Node keys can include <code>text_display</code>, <code>color</code>, <code>code</code>, <code>wikidata</code>, <code>lat</code>, <code>long</code>, and language-specific keys (e.g. <code>persian</code>, <code>armenian</code>, <code>chinese</code>).\n\n" ..
					expand_template({ title = 'dial syn', args = { lang_code, ["demo mode"] = "y" } })
			end
		end,
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)/([^/]+)$",
		process = function(title, cats, lang_code, term)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				cats:insert("Dialect synonyms data modules|" .. langname)
				cats:insert(langname .. " dialect synonyms data modules|" .. term)
				return ("%s\n\n%s"):format(
					"==== Term/sense module structure ====\n" ..
					"* <code>export.title</code> — optional; custom table title (e.g. \"Realization of 'strong R' between vowels\"). Overrides the language default.\n" ..
					"* <code>export.meaning</code> — optional; meaning/gloss (alternative to <code>gloss</code>).\n" ..
					"* <code>export.gloss</code> — optional; short meaning for the table.\n" ..
					"* <code>export.note</code> — optional; single note key or string, or list of note keys.\n" ..
					"* <code>export.notes</code> — optional; list of note keys.\n" ..
					"* <code>export.source</code> / <code>export.sources</code> — optional; source keys.\n" ..
					"* <code>export.last_column</code> — optional; label for the data column (default \"Words\"; e.g. \"Realization\").\n" ..
					"* <code>export.syns</code> — required; table mapping variety/location names (keys from the language data module) to a list of term entries. Each entry can be a string or a table (e.g. <code>{ ipa = \"[ɽ]\" }</code> or <code>{ term = \"word\" }</code>).\n\n" ..
					"Example (custom title and data column, IPA realizations):\n" ..
					"<pre>\nlocal export = {}\n\nexport.title = \"Realization of 'strong R' between vowels\"\n" ..
					"export.meaning = \"\"\nexport.note = \"realization of 'strong R' between vowels\"\n" ..
					"export.last_column = \"Realization\"\n\nexport.syns = {\n\t[\"ALERS-158\"] = { { ipa = \"[ɽ]\" } },\n\t[\"ALERS-175\"] = { { ipa = \"[x]\" } },\n}\n\nreturn export\n</pre>\n\n",
					expand_template({ title = 'dial syn', args = { lang_code, term } }))
			end
		end,
	},
	{
		regex = "^Module:dialect synonyms/([%l-]+)/([^/]+)/([^/]+)$",
		process = function(title, cats, lang_code, term, id)
			local lang = lang_cache[lang_code]
			if lang then
				local langname = lang:getCanonicalName()
				cats:insert("Dialect synonyms data modules|" .. langname)
				cats:insert(langname .. " dialect synonyms data modules|" .. term)
				return ("%s\n\n%s"):format(
					"==== Term/sense module structure ====\n" ..
					"* <code>export.title</code> — optional; custom table title (e.g. \"Realization of 'strong R' between vowels\"). Overrides the language default.\n" ..
					"* <code>export.meaning</code> — optional; meaning/gloss (alternative to <code>gloss</code>).\n" ..
					"* <code>export.gloss</code> — optional; short meaning for the table.\n" ..
					"* <code>export.note</code> — optional; single note key or string, or list of note keys.\n" ..
					"* <code>export.notes</code> — optional; list of note keys.\n" ..
					"* <code>export.source</code> / <code>export.sources</code> — optional; source keys.\n" ..
					"* <code>export.last_column</code> — optional; label for the data column (default \"Words\"; e.g. \"Realization\").\n" ..
					"* <code>export.syns</code> — required; table mapping variety/location names (keys from the language data module) to a list of term entries. Each entry can be a string or a table (e.g. <code>{ ipa = \"[ɽ]\" }</code> or <code>{ term = \"word\" }</code>).\n\n" ..
					"Example (custom title and data column, IPA realizations):\n" ..
					"<pre>\nlocal export = {}\n\nexport.title = \"Realization of 'strong R' between vowels\"\n" ..
					"export.meaning = \"\"\nexport.note = \"realization of 'strong R' between vowels\"\n" ..
					"export.last_column = \"Realization\"\n\nexport.syns = {\n\t[\"ALERS-158\"] = { { ipa = \"[ɽ]\" } },\n\t[\"ALERS-175\"] = { { ipa = \"[x]\" } },\n}\n\nreturn export\n</pre>\n\n",
					expand_template({ title = 'dial syn', args = { lang_code, term, id = id } }))
			end
		end,
	},
	{
		regex = "^Module:bibliography/data/([%l-]+)$",
		process = function(title, cats, lang_code)
			if lang_code == "preload" then
				return 'Used as a base model for other languages when the button "create new language submodule" is clicked.'
			end
			local page = require(title.fullText).bib_page
			if not page then
				page = lang_cache[lang_code]:getCanonicalName()
				if page then
					cats:insert(page .. " modules")
				end
			end
			cats:insert("Reference modules")
			return "This module holds bibliographical data for " ..
				page .. ". For the formatted bibliography see '''[[Appendix:Bibliography/" .. page .. "]]'''."
		end,
	},
}

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

	local output = Array('\n<div class="documentation" style="display:block; clear:both">\n')
	local cats = Array()

	local nodoc = args.nodoc

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

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

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

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

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

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

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

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

		if title.subpageText == "templates" then
			cats:insert("Template interface modules")
		end

		if automatic_cats then
			for _, c in ipairs(automatic_cats) do
				cats:insert(c)
			end
		end

		if #cats == 0 then
			local auto_cats = categorize_module(frame, "return raw", "noerror")
			if #auto_cats > 0 then
				auto_generated_cat_source = "Module:module categorization"
			end
			for _, category in ipairs(auto_cats) do
				cats:insert(category)
			end
		end

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

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

		output:insert(
			"<dd><i style=\"font-size: larger;\">The following " ..
			"[[wikt:Help:Documenting templates and modules|documentation]] is located at [[" ..
			doc_title.fullText .. "]]. " .. "<sup>[[" .. doc_title:fullUrl { action = "edit" } .. " edit]]</sup>" ..
			cats_auto_generated_text .. "</i></dd>")
	else
		if contentModel == "Scribunto" then
			get_module_doc_and_cats(false)
		elseif title.nsText == "Template" then
			--cats:insert("Uncategorized templates")
			needs_doc = not (fallback_docs or nodoc)
		elseif user_name and is_script_or_stylesheet then
			skin_name = skins[title.text:sub(#title.rootText + 1):match("^/(%l+)%.[jc]ss?$")]
			if skin_name then
				fallback_docs = "documentation/fallback/user " .. contentModel
			end
		end

		if doc_content then
			output:insert(
				"<dd><i style=\"font-size: larger;\">The following " ..
				"[[wikt:Help:Documenting templates and modules|documentation]] is " ..
				"generated by [[" .. doc_content_source .. "]]. <sup>[[" ..
				new_title(doc_content_source):fullUrl { action = "edit" } ..
				" edit]]</sup> </i></dd>")
		elseif not nodoc then
			if doc_title then
				output:insert(
					"<dd><i style=\"font-size: larger;\">This " .. pagetype ..
					" lacks a [[wikt:Help:Documenting templates and modules|documentation subpage]]. " ..
					(fallback_docs and "You may " or "Please ") ..
					"[" .. doc_title:fullUrl { action = "edit", preload = preload }
					.. " create it].</i></dd>\n")
			else
				output:insert(
					"<dd><i style=\"font-size: larger; color: var(--wikt-palette-red-9,#FF0000);\">Unable to auto-generate " ..
					"documentation for this " .. pagetype .. ".</i></dd>\n")
			end
		end
	end

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

						output:insert("<dd> ''This script is a part of the <code>")
						output:insert(gadget)
						output:insert("</code> gadget ([")
						output:insert(tostring(full_url("MediaWiki:Gadgets-definition", { action = "edit" })))
						output:insert(" edit definitions])'' <dl>")

						output:insert("<dd> ''Description ([")
						output:insert(tostring(full_url("MediaWiki:Gadget-" .. gadget, { action = "edit" })))
						output:insert(" edit])'': ")

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

						items:remove(i)
						if #items > 0 then
							for j, item in ipairs(items) do
								items[j] = '[[MediaWiki:Gadget-' .. item .. '|' .. item .. ']]'
							end
							output:insert("<dd> ''Other parts'': ")
							output:insert(list_to_text(items))
							output:insert("</dd>")
						end

						output:insert("</dl></dd>")

						break
					end
				end
			end
		end

		if not is_gadget then
			output:insert("<dd> ''This script is not a part of any [")
			output:insert(tostring(full_url("Special:Gadgets", { uselang = "en" })))
			output:insert(' gadget] ([')
			output:insert(tostring(full_url("MediaWiki:Gadgets-definition", { action = "edit" })))
			output:insert(' edit definitions]).</dd>')
			-- else
			-- cats:insert("Wiktionary gadgets")
		end
	end

	if old_doc_title then
		output:insert("<dd> ''Redirected from'' [")
		output:insert(old_doc_title:fullUrl { redirect = "no" })
		output:insert(" ")
		output:insert(old_doc_title.fullText)
		output:insert("] ([")
		output:insert(old_doc_title:fullUrl { action = "edit" })
		output:insert(" edit]).</dd>\n")
	end

	if not args.nolinks then
		local links = Array()

		if title.isSubpage and not args.notsubpage then
			links:insert("[[:" .. title.nsText .. ":" .. title.rootText .. "|root page]]")
			links:insert("[[Special:PrefixIndex/" .. title.nsText .. ":" .. title.rootText .. "/|root page’s subpages]]")
		else
			links:insert("[[Special:PrefixIndex/" .. title.fullText .. "/|subpage list]]")
		end

		links:insert(
			"[" ..
			tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidetrans = true, hideredirs = true })) ..
			" links]")

		if contentModel ~= "Scribunto" then
			links:insert(
				"[" ..
				tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidelinks = true, hidetrans = true })) ..
				" redirects]")
		end

		if is_script_or_stylesheet then
			if user_name then
				links:insert("[[Special:MyPage" .. title.text:sub(#title.rootText + 1) .. "|your own]]")
			end
		else
			links:insert(
				"[" ..
				tostring(full_url("Special:WhatLinksHere/" .. title.fullText, { hidelinks = true, hideredirs = true })) ..
				" transclusions]")
		end

		if contentModel == "Scribunto" then
			local is_testcases = title.isSubpage and title.subpageText == "testcases"
			local without_subpage = title.nsText .. ":" .. title.baseText
			if is_testcases then
				links:insert("[[:" .. without_subpage .. "|tested module]]")
			else
				links:insert("[[" .. title.fullText .. "/testcases|testcases]]")
			end

			if user_name then
				links:insert("[[User:" .. user_name .. "|user page]]")
				links:insert("[[User talk:" .. user_name .. "|user talk page]]")
				links:insert("[[Special:PrefixIndex/User:" .. user_name .. "/|userspace]]")
				-- If sandbox module, add a link to the module that this is a sandbox of.
				-- Exclude user sandbox modules like [[User:Dine2016/sandbox]].
			elseif title.text:find("^sandbox%d*/") or title.text:find("/sandbox%d*%f[/%z]") then
				cats:insert("Sandbox modules")

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

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

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

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

				links:insert(sandbox_link .. (diff or ""))
			end
		end

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

			links:insert(
				'[' .. tostring(full_url('Special:Search', errorq .. eincategory)) .. ' errors]'
				.. ' (' ..
				'[' .. tostring(full_url('Special:Search', errorq .. 'ParserFunction_errors')) .. ' parser]'
				.. '/' ..
				'[' .. tostring(full_url('Special:Search', errorq .. 'Pages_with_module_errors')) .. ' module]'
				.. ')'
			)

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

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

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

				local diff
				if title_exists(sandbox_of) then
					diff = " (" .. compare_pages(title.fullText, sandbox_of, "diff") .. ")"
				end

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

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

				links:insert("[[:" .. sandbox_title .. "|sandbox]]" .. (diff or ""))
			end
		end

		if #links > 0 then
			output:insert("<dd> ''Useful links'': " .. links:concat(" • ") .. "</dd>")
		end
	end

	output:insert("</dl>\n")

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

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

	if doc_content then
		output:insert(doc_content)
	end

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

	if cats_auto_generated and not cats[1] and (not doc_content or not doc_content:find("%[%[Category:")) then
		if contentModel == "Scribunto" then
			cats:insert("Uncategorized modules")
			-- elseif title.nsText == "Template" then
			-- cats:insert("Uncategorized templates")
		end
	end

	if needs_doc then
		cats:insert("Templates and modules needing documentation")
	end

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

	output:insert("</div>\n")

	return output:concat()
end

function export.module_auto_doc_table()
	local parts = {}
	local function ins(text)
		insert(parts, text)
	end
	ins('{|class="wikitable"')
	ins("! Regex !! Category !! Handling modules")
	for _, spec in ipairs(module_regex) do
		local cat_text
		local cats = spec.cat
		if cats then
			local cat_parts = {}
			if type(cats) == "string" then
				cats = { cats }
			end
			for _, cat in ipairs(cats) do
				insert(cat_parts, ("<code>%s</code>"):format((cat:gsub("|", "&#124;"))))
			end
			cat_text = concat(cat_parts, ", ")
		else
			cat_text = "''(unspecified)''"
		end
		ins("|-")
		ins(("| <code>%s</code> || %s || %s"):format(spec.regex, cat_text,
			is_callable(spec.process) and "''(handled internally)''" or
			type(spec.process) == "string" and ("[[Module:documentation/functions/%s]]"):format(spec.process) or
			"''(no documentation generator)''"))
	end
	ins("|}")
	return concat(parts, "\n")
end

-- Used by {{translit module documentation}}.
function export.translitModuleLangList(frame)
	local pagename, subpage

	if frame.args[1] then
		pagename = frame.args[1]
	else
		local title = get_current_title()
		subpage = title.subpageText
		pagename = title.text

		if subpage ~= pagename then
			pagename = title.rootText
		end
	end

	local translitModule = pagename

	local languageObjects = require("Module:languages/byTranslitModule")(translitModule)
	local codeInPagename = pagename:match("^([%l-]+)%-.*translit$")

	local categories = Array()
	local codeInPagenameInList = false
	if codeInPagename then
		if languageObjects[1] and subpage ~= "documentation" then
			local agreement = languageObjects[2] and "s" or ""
			categories:insert("[[Category:Transliteration modules used by " ..
				#languageObjects .. " language" .. agreement .. "]]")
		end

		languageObjects = Array(languageObjects)
			:filter(
				function(lang)
					local result = lang:getCode() ~= codeInPagename
					codeInPagenameInList = codeInPagenameInList or result
					return result
				end)
	end

	if subpage ~= "documentation" then
		for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
			local script = get_script(script_code)
			if script then
				categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
			end
		end
	end

	if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
		categories:insert("[[Category:Transliteration modules without a testcases subpage]]")
	end

	if not languageObjects[1] then
		return categories:concat()
	end

	local langs = Array(languageObjects)
		:sort(
			function(lang1, lang2)
				return lang1:getCode() < lang2:getCode()
			end)
		-- This will not error because languageObjects is not empty.
		:map(languageObjects[1].makeCategoryLink)
		:serialCommaJoin()

	return "It is " .. (codeInPagenameInList and "also" or "") ..
		" used to transliterate " .. langs .. "." .. categories:concat()
end

-- Used by {{strip diacritics module documentation}}.
function export.stripDiacriticsModuleLangList(frame)
	local pagename, subpage

	if frame.args[1] then
		pagename = frame.args[1]
	else
		local title = get_current_title()
		subpage = title.subpageText
		pagename = title.text

		if subpage ~= pagename then
			pagename = title.rootText
		end
	end

	local stripDiacriticsModule = pagename

	local languageObjects = require("Module:languages/byStripDiacriticsModule")(stripDiacriticsModule)
	local codeInPagename = pagename:match("^([%l-]+)%-.*stripdiacritics$")

	local categories = Array()
	local codeInPagenameInList = false
	if codeInPagename then
		if languageObjects[1] and subpage ~= "documentation" then
			local agreement = languageObjects[2] and "s" or ""
			categories:insert("[[Category:Diacritic-stripping modules used by " ..
				#languageObjects .. " language" .. agreement .. "]]")
		end

		languageObjects = Array(languageObjects)
			:filter(
				function(lang)
					local result = lang:getCode() ~= codeInPagename
					codeInPagenameInList = codeInPagenameInList or result
					return result
				end)
	end

	if subpage ~= "documentation" then
		for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
			local script = get_script(script_code)
			if script then
				categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
			end
		end
	end

	if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
		categories:insert("[[Category:Diacritic-stripping modules without a testcases subpage]]")
	end

	if not languageObjects[1] then
		return categories:concat()
	end

	local langs = Array(languageObjects)
		:sort(
			function(lang1, lang2)
				return lang1:getCode() < lang2:getCode()
			end)
		-- This will not error because languageObjects is not empty.
		:map(languageObjects[1].makeCategoryLink)
		:serialCommaJoin()

	return "It is " .. (codeInPagenameInList and "also" or "") ..
		" used to strip diacritics for " .. langs .. "." .. categories:concat()
end

-- Used by {{sortkey module documentation}}.
function export.sortkeyModuleLangList(frame)
	local pagename, subpage

	if frame.args[1] then
		pagename = frame.args[1]
	else
		local title = get_current_title()
		subpage = title.subpageText
		pagename = title.text

		if subpage ~= pagename then
			pagename = title.rootText
		end
	end

	local sortkeyModule = pagename

	local languageObjects = require("Module:languages/bySortkeyModule")(sortkeyModule)
	local codeInPagename = pagename:match("^([%l-]+)%-.*sortkey$")

	local categories = Array()
	local codeInPagenameInList = false
	if codeInPagename then
		if languageObjects[1] and subpage ~= "documentation" then
			local agreement = languageObjects[2] and "s" or ""
			categories:insert("[[Category:Sortkey-generating modules used by " ..
				#languageObjects .. " language" .. agreement .. "]]")
		end

		languageObjects = Array(languageObjects)
			:filter(
				function(lang)
					local result = lang:getCode() ~= codeInPagename
					codeInPagenameInList = codeInPagenameInList or result
					return result
				end)
	end

	if subpage ~= "documentation" then
		for script_code in pagename:gmatch("%f[^-%z]%u%l%l%l%f[-]") do
			local script = get_script(script_code)
			if script then
				categories:insert("[[Category:" .. script:getCategoryName() .. "]]")
			end
		end
	end

	if subpage ~= "documentation" and not title_exists("Module:" .. pagename .. "/testcases") then
		categories:insert("[[Category:Sortkey-generating modules without a testcases subpage]]")
	end

	if not languageObjects[1] then
		return categories:concat()
	end

	local langs = Array(languageObjects)
		:sort(
			function(lang1, lang2)
				return lang1:getCode() < lang2:getCode()
			end)
		-- This will not error because languageObjects is not empty.
		:map(languageObjects[1].makeCategoryLink)
		:serialCommaJoin()

	return "It is " .. (codeInPagenameInList and "also" or "") ..
		" used to sort " .. langs .. "." .. categories:concat()
end

return export