Module:category tree/families

Revision as of 11:22, 21 April 2026 by Sware (talk | contribs) (1 revision imported)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:category tree/families/doc

local raw_categories = {}
local raw_handlers = {}

local concat = table.concat
local insert = table.insert

-----------------------------------------------------------------------------
--                                                                         --
--                              RAW CATEGORIES                             --
--                                                                         --
-----------------------------------------------------------------------------


raw_categories["All language families"] = {
	topright = "{{commonscat|Languages by family}}\n{{wp|Language family,List of language families}}",
	description = "This category lists all [[language family|language families]].",
	parents = {"Fundamental"},
}

raw_categories["Languages by family"] = {
	topright = "{{commonscat|Languages by family}}\n{{wp|Language family,List of language families}}",
	description = "This category contains all languages categorized hierarchically according to the [[language family]] they belong to.",
	additional = "Only top-level language families are shown here. For a full list of all language families, see [[:Category:All language families]] or [[Wiktionary:List of families]].",
	parents = {
		{name = "All languages", sort = " "},
		{name = "All language families", sort = " "},
	},
}

raw_categories["Unassigned languages"] = {
	description = "Languages that have not yet been assigned to any family by Wiktionary editors, usually due to oversight.",
	additional = [=[This should be distinguished from:
* [[:Category:Unclassifiable languages]] (languages that cannot be confidently assigned to any family, typically because the language is extinct or unresearched and has little available data on it);
* [[:Category:Language isolates]] (where there is general agreement that the language has no relatives); and
* [[:Category:Languages of disputed affiliation]] (languages where there is no consensus concerning which family, if any, they belong to).]=],
	parents = {
		{name = "Languages by family", sort = "*"},
		"All language families",
	},
}

-----------------------------------------------------------------------------
--                                                                         --
--                                RAW HANDLERS                             --
--                                                                         --
-----------------------------------------------------------------------------


local function family_is_not_a_family(fam)
	if not fam then
		return false
	elseif fam:getCode() == "qfa-not" then
		return true
	else
		return family_is_not_a_family(fam:getFamily())
	end
end

local function family_has_no_category(fam)
	local famcode = fam:getCode()
	if famcode == "paa" then
		return false -- Papuan languages are not a family but have a category
	elseif famcode == "qfa-iso" or famcode == "qfa-not" then
		return true
	else
		local parfam = fam:getFamily()
		if parfam and parfam:getCode() == "qfa-not" then
			-- Constructed languages, sign languages, etc.; no category for them
			return true
		end
	end
	return false
end

-- Currently all Papuan families begin with "paa" or "ngf",
local function family_is_papuan(fam)
	local famcode = fam:getCode()
	return famcode ~= "paa" and (famcode:find("^paa") or famcode:find("^ngf"))
end

local function infobox(fam)
	local ret = {}
	
	insert(ret, "<table class=\"wikitable\">\n")
	insert(ret, "<tr>\n<th colspan=\"2\" class=\"plainlinks\">[//en.wiktionary.org/w/index.php?title=Module:families/data&action=edit Edit family data]</th>\n</tr>\n")
	insert(ret, "<tr>\n<th>Canonical name</th><td>" .. fam:getCanonicalName() .. "</td>\n</tr>\n")
	
	local otherNames = fam:getOtherNames()
	if otherNames then
		local names = {}
		
		for _, name in ipairs(otherNames) do
			insert(names, "<li>" .. name .. "</li>")
		end
		
		if #names > 0 then
			insert(ret, "<tr>\n<th>Other names</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
		end
	end
	
	local aliases = fam:getAliases()
	if aliases then
		local names = {}
		
		for _, name in ipairs(aliases) do
			insert(names, "<li>" .. name .. "</li>")
		end
		
		if #names > 0 then
			insert(ret, "<tr>\n<th>Aliases</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
		end
	end

	local varieties = fam:getVarieties()
	if varieties then
		local names = {}
		
		for _, name in ipairs(varieties) do
			if type(name) == "string" then
				insert(names, "<li>" .. name .. "</li>")
			else
				assert(type(name) == "table")
				local first_var
				local subvars = {}
				for i, var in ipairs(name) do
					if i == 1 then
						first_var = var
					else
						insert(subvars, "<li>" .. var .. "</li>")
					end
				end
				if #subvars > 0 then
					insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. concat(subvars, "\n") .. "</ul></dd></dl></li>")
				elseif first_var then
					insert(names, "<li>" .. first_var .. "</li>")
				end
			end
		end
		
		if #names > 0 then
			insert(ret, "<tr>\n<th>Varieties</th><td><ul>" .. concat(names, "\n") .. "</ul></td>\n</tr>\n")
		end
	end

	insert(ret, "<tr>\n<th>[[Wiktionary:Families|Family code]]</th><td><code>" .. fam:getCode() .. "</code></td>\n</tr>\n")
	insert(ret, "<tr>\n<th>[[w:Proto-language|Common ancestor]]</th><td>")
	
	local protoLanguage = fam:getProtoLanguage()
	
	if protoLanguage then
		insert(ret, "[[:Category:" .. protoLanguage:getCategoryName() .. "|" .. protoLanguage:getCanonicalName() .. "]]")
	else
		insert(ret, "none")
	end
	
	insert(ret, "</td>\n")
	insert(ret, "\n</tr>\n")
	
	local parent = fam:getFamily()
	
	if not parent then
		insert(ret, "<tr>\n<th>[[Wiktionary:Families|Parent family]]</th>\n<td>")
		insert(ret, "unassigned")
	elseif parent:getCode() == "qfa-not" then
		insert(ret, "<tr>\n<th>[[Wiktionary:Families|Parent family]]</th>\n<td>")
		insert(ret, "not a family")
	else
		local chain = {}
		while parent do
			if family_has_no_category(parent) then
				break
			end
			insert(chain, "[[:Category:" .. parent:getCategoryName() .. "|" .. parent:getCanonicalName() .. "]]")
			parent = parent:getFamily()
		end
		if #chain == 0 then
			insert(ret, "<tr>\n<th>[[Wiktionary:Families|Parent family]]</th>\n<td>")
			insert(ret, "no parents")
		else
			insert(ret, "<tr>\n<th>[[Wiktionary:Families|Parent famil"
				.. (#chain == 1 and "y" or "ies") .. "]]</th>\n<td>")
			
			for i = #chain, 1, -1 do
				insert(ret, "<ul><li>" .. chain[i])
			end
			insert(ret, string.rep("</li></ul>", #chain))
		end
	end
	
	insert(ret, "</td>\n</tr>\n")
	
	if fam:getWikidataItem() and mw.wikibase then
		local link = '[' .. mw.wikibase.getEntityUrl(fam:getWikidataItem()) .. ' ' .. fam:getWikidataItem() .. ']'
		insert(ret, "<tr><th>Wikidata</th><td>" .. link .. "</td></tr>")
	end
	
	insert(ret, "</table>")
	
	return concat(ret)
end

local function NavFrame_for_family_tree(content, title)
	return '<div class="NavFrame"><div class="NavHead">'
		.. (title or '{{{title}}}') .. '</div>'
		.. '<div class="NavContent" style="text-align: left; font-size: calc(1em / 0.95); padding: 0.3em">'
		.. content
		.. '</div></div>'
end

local additional_information = {
	["qfa-dis"] = "These are languages where there is no consensus concerning which family, if any, they belong to.",
	["qfa-iso"] = "These are languages where there is general agreement that the language has no known relatives.",
	["qfa-mix"] = "A [[mixed language]] is a language which is composed of two different languages.",
	["qfa-unc"] = "These are languages that cannot be confidently assigned to a family due to lack of sufficient linguistic data. " ..
		"They are also commonly called {{w|unclassified language|unclassified languages}}, but this is ambiguous between " ..
		"languages that cannot be classified (due to insufficient data) and those that merely have not been classified " ..
		"(due to insufficient research).",
}

local preceding_information = {
	["qfa-dis"] = "{{also|Category:Unclassifiable languages|Category:Unassigned languages|Category:Language isolates}}",
	["qfa-iso"] = "{{also|Category:Languages of disputed affiliation|Category:Unclassifiable languages|Category:Unassigned languages}}",
	["qfa-unc"] = "{{also|Category:Languages of disputed affiliation|Category:Unassigned languages|Category:Language isolates}}",
	["qfa-mix"] = "{{also|Category:Creole or pidgin languages}}",
	["crp"] = "{{also|Category:Mixed languages}}",
}

local specially_named_families = {
	["Languages of disputed affiliation"] = "qfa-dis",
	["Language isolates"] = "qfa-iso",
}

local specially_named_family_sort_keys = {
	["Languages of disputed affiliation"] = "Disputed affiliation",
	["Language isolates"] = "Isolate",
}

insert(raw_handlers, function(data)
	local family = require("Module:families").getByCategoryName(data.category)
	if not family then
		local special_code = specially_named_families[data.category]
		if special_code then
			family = require("Module:families").getByCode(special_code)
			if not family then
				error(("Internal error: Family code '%s' is an invalid family code."):format(special_code))
			end
		end
	end
	if not family then
		return nil
	end

	local parent_fam = family:getFamily()
	local first_parent, parent_sort_key, first_parent_sort_key
	
	if not parent_fam or family_has_no_category(parent_fam) then
		first_parent = "Languages by family"
		parent_sort_key = specially_named_family_sort_keys[data.category]
		first_parent_sort_key = "*" .. (parent_sort_key or "")
	else
		first_parent = parent_fam:getCategoryName()
	end

	local description, additional = "", ""
	local topright
	local preceding = preceding_information[family:getCode()]
	local additional_preface = additional_information[family:getCode()]
	if additional_preface then
		additional_preface = additional_preface .. "\n\n"
	else
		additional_preface = ""
	end
	if family_is_not_a_family(family) then
		additional_preface = additional_preface ..
			"This is a pseudo-family, used for grouping purposes but not forming a linguistically valid [[clade]] " ..
			"(i.e. a set of linguistically related languages descending from a common parent).\n\n" ..
			"Information about this family:\n\n"
	else
		additional_preface = "Information about " .. family:getCanonicalName() .. ":\n\n"
	end
	if not data.called_from_inside then
		topright = {}
		local wikipedia_art = family:getWikipediaArticle("noCategoryFallback")
		if wikipedia_art then
			insert(topright, "{{wp|" .. wikipedia_art .. "}}")
		end
		local commons_cat = family:getCommonsCategory()
		if commons_cat then
			insert(topright, "{{commonscat|" .. commons_cat:gsub("^Category:", "") .. "}}")
		end
		topright = #topright > 0 and concat(topright, "\n") or nil
		description = "This is the main category of the '''" .. family:getDisplayForm() .. "'''."
		additional = additional_preface .. infobox(family)
	end
	
	local ok, tree_of_descendants = pcall(
		require("Module:family tree").print_children,
		family:getCode(), {
			protolanguage_under_family = true,
			must_have_descendants = true
		})
	
	if ok then
		if tree_of_descendants then
			additional = additional .. NavFrame_for_family_tree(
				tree_of_descendants,
				"Family tree")
		else
			additional = additional .. "\n\n" .. ucfirst(family:getCanonicalName())
				.. " has no descendants or varieties listed in Wiktionary's language data modules."
		end
	else
		mw.log("error while generating tree: " .. tostring(tree_of_descendants))
	end

	local parents = {
		{name = first_parent, sort = first_parent_sort_key},
		{name = "All language families", sort = parent_sort_key},
	}
	if parent_fam and parent_fam:getCode() == "sgn" then
		insert(parents, "All sign languages")
	end
	if family_is_papuan(family) then
		insert(parents, "Papuan languages")
	end

	return {
		preceding = preceding,
		topright = topright,
		description = description,
		additional = additional,
		parents = parents,
		breadcrumb = family:getCanonicalName(),
		can_be_empty = true,
	}
end)

return {RAW_CATEGORIES = raw_categories, RAW_HANDLERS = raw_handlers}