Script error: The function "main" does not exist.

local export = {}

local function makeCategoryLink(object)
	return "[[:Категорија:" .. object:getCategoryName() .. "|" .. object:getCanonicalName() .. "]]"
end

local function linkbox(lang, setwiki, setwikt, setsister, entryname)
	local wiktionarylinks = "''None.''"
	
	local canonicalName = lang:getCanonicalName()
	local wikimediaLanguages = lang:getWikimediaLanguages()
	local categoryName = lang:getCategoryName()
	local wikipediaArticle = setwiki or lang:getWikipediaArticle()
	
	if setwikt then
		require "Модул:debug".track "langcatboiler/setwikt"
		if setwikt == "-" then
			require "Модул:debug".track "langcatboiler/setwikt/hyphen"
		end
	end
	
	if setwikt ~= "-" and wikimediaLanguages and wikimediaLanguages[1] then
		wiktionarylinks = {}
		
		for _, wikimedialang in ipairs(wikimediaLanguages) do
			table.insert(wiktionarylinks,
				(wikimedialang:getCanonicalName() ~= canonicalName and "(''" .. wikimedialang:getCanonicalName() .. "'') " or "") ..
				"'''[[:" .. wikimedialang:getCode() .. ":|" .. wikimedialang:getCode() .. ".wiktionary.org]]'''")
		end
		
		wiktionarylinks = table.concat(wiktionarylinks, "<br/>")
	end
	
	local plural = wikimediaLanguages[2] and "s" or ""
	
	return table.concat{
[=[<div style="clear: right; border: solid #aaa 1px; margin: 1 1 1 1; background: #f9f9f9; width: 270px; padding: 5px; margin: 5px; text-align: left; float: right">
<div style="text-align: center; margin-bottom: 10px; margin-top: 5px">''']=], categoryName, [=['''</div>

{| style="font-size: 90%; background: #f9f9f9;"
|-
| style="vertical-align: middle; height: 35px; width: 35px;" | [[File:Wiktionary-logo-v2.svg|35px|none|Викиречник]]
|| '']=], categoryName, [=[ издање]=], plural, [=[ Викиречник''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid lightgray;" | ]=], wiktionarylinks, [=[

|-
| style="vertical-align: middle; height: 35px" | [[File:Wikipedia-logo.png|35px|none|Википедија]]
|| ''Википедијин чланак о ]=], categoryName, [=[''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid lightgray;" | ]=], (setwiki == "-" and "''None.''" or "'''[[w:" .. wikipediaArticle .. "|" .. wikipediaArticle .. "]]'''"), [=[

|-
| style="vertical-align: middle; height: 35px" | [[File:Wikimedia-logo.svg|35px|none|Wikimedia Commons]]
|| ''Links related to ]=], categoryName, [=[ in sister projects at Wikimedia Commons''
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid lightgray;" | ]=], (setsister == "-" and "''None.''" or "'''[[commons:Категорија:" .. (setsister or categoryName) .. "|" .. (setsister or categoryName) .. "]]'''"), [=[

|-
| style="vertical-align: middle; height: 35px" | [[File:Crystal kfind.png|35px|none|Considerations]]
|| ]=], categoryName, [=[ considerations
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid lightgray;" | '''[[Викиречник:О ]=], canonicalName, [=[]]'''
|-
| style="vertical-align: middle; height: 35px" | [[File:Incomplete list.svg|35px|none|Индекс]]
|| ]=], categoryName, [=[ index
|-
| colspan="2" style="padding-left: 10px; border-bottom: 1px solid lightgray;" | '''[[Индекс:]=], canonicalName, [=[]]'''
|-
| style="vertical-align: middle; height: 35px" | [[File:Open book nae 02.svg|35px|none|Entry]]
|| ]=], categoryName, [=[ entry
|-
| colspan="2" style="padding-left: 10px;" | ''']=], require("Модул:links").full_link({lang = require("Модул:languages").getByCode("sr"), term = entryname or canonicalName}), [=['''
|}
</div>]=]
}
end

local function edit_link(title, text)
	return '<span class="plainlinks">['
		.. tostring(mw.uri.fullUrl(title, { action = "edit" }))
		.. ' ' .. text .. ']</span>'
end

-- Should perhaps use wiki syntax.
local function infobox(lang)
	local ret = {}
	
	table.insert(ret, '<table class="wikitable language-category-info"')
	
	if type(lang.getRawData) == "function" then
		local raw_data = lang:getRawData()
		if raw_data then
			local replacements = {
				[1] = "canonical-name",
				[2] = "wikidata-item",
				[3] = "family",
			}
			local function replacer(letter1, letter2)
				return letter1:lower() .. "-" .. letter2:lower()
			end
			-- For each key in the language data modules, returns a descriptive
			-- kebab-case version (containing ASCII lowercase words separated
			-- by hyphens).
			local function kebab_case(key)
				key = replacements[key] or key
				key = key:gsub("(%l)(%u)", replacer):gsub("(%l)_(%l)", replacer)
				return key
			end
			local function html_attribute_encode(str)
				str = mw.text.jsonEncode(str)
					:gsub('"', "&quot;")
					-- & in attributes is automatically escaped.
					-- :gsub("&", "&amp;")
					:gsub("<", "&lt;")
					:gsub(">", "&gt;")
				return str
			end
			pcall(function ()
				table.insert(ret, ' data-code="' .. lang:getCode() .. '"')
				for k, v in require("Модул:table").sortedPairs(lang:getRawData()) do
					table.insert(ret, " data-" .. kebab_case(k)
						.. '="'
						.. html_attribute_encode(v)
						.. '"')
				end
			end)
		end
	end
	table.insert(ret, '>\n')
	table.insert(ret, '<tr class="language-category-data">\n<th colspan="2">'
		.. edit_link("Модул:" .. require("Модул:languages").getDataModuleName(lang:getCode()),
			"Edit language data")
		.. "</th>\n</tr>\n")
	table.insert(ret, "<tr>\n<th>Канонско име</th><td>" .. lang:getCanonicalName() .. "</td>\n</tr>\n")

	local otherNames = lang:getOtherNames(true)
	if otherNames then
		local names = {}
		
		for _, name in ipairs(otherNames) do
			table.insert(names, "<li>" .. name .. "</li>")
		end
		
		if #names > 0 then
			table.insert(ret, "<tr>\n<th>Друга имена</th><td><ul>" .. table.concat(names, "\n") .. "</ul></td>\n</tr>\n")
		end
	end
	
	local aliases = lang:getAliases()
	if aliases then
		local names = {}
		
		for _, name in ipairs(aliases) do
			table.insert(names, "<li>" .. name .. "</li>")
		end
		
		if #names > 0 then
			table.insert(ret, "<tr>\n<th>Алијаси</th><td><ul>" .. table.concat(names, "\n") .. "</ul></td>\n</tr>\n")
		end
	end

	local varieties = lang:getVarieties()
	if varieties then
		local names = {}
		
		for _, name in ipairs(varieties) do
			if type(name) == "string" then
				table.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
						table.insert(subvars, "<li>" .. var .. "</li>")
					end
				end
				if #subvars > 0 then
					table.insert(names, "<li><dl><dt>" .. first_var .. "</dt>\n<dd><ul>" .. table.concat(subvars, "\n") .. "</ul></dd></dl></li>")
				elseif first_var then
					table.insert(names, "<li>" .. first_var .. "</li>")
				end
			end
		end
		
		if #names > 0 then
			table.insert(ret, "<tr>\n<th>Сорте</th><td><ul>" .. table.concat(names, "\n") .. "</ul></td>\n</tr>\n")
		end
	end

	table.insert(ret, "<tr>\n<th>[[Викиречник:Језици|Код језика]]</th><td><code>" .. lang:getCode() .. "</code></td>\n</tr>\n")
	table.insert(ret, "<tr>\n<th>[[Викиречник:Фамилије|Језичка породица]]</th>\n")
	
	local fam = lang:getFamily()
	local famCode = fam and fam:getCode()
	
	if not fam then
		table.insert(ret, "<td>unclassified</td>")
	elseif famCode == "qfa-iso" then
		table.insert(ret, "<td>[[:Категорија:Језик isolates|језик isolate]]</td>")
	elseif famCode == "qfa-mix" then
		table.insert(ret, "<td>[[:Категорија:Mixed languages|mixed језик]]</td>")
	elseif famCode == "sgn" then
		table.insert(ret, "<td>[[:Категорија:Знаковни језици|знаковни језик]]</td>")
	elseif famCode == "crp" then
		table.insert(ret, "<td>[[:Категорија:Creole или pidgin језици|creole или pidgin]]</td>")
	elseif famCode == "art" then
		table.insert(ret, "<td>[[:Категорија:Конструисани језици|конструисани језици]]</td>")
	else
		table.insert(ret, "<td>" .. makeCategoryLink(fam) .. "</td>")
	end
	
	table.insert(ret, "\n</tr>\n<tr>\n<th>Преци</th>\n")
	
	local ancestors, ancestorChain = lang:getAncestors(), lang:getAncestorChain()
	if ancestors[2] then
		local ancestorList = {}
		
		for i, anc in ipairs(ancestors) do
			ancestorList[i] = "<li>" .. makeCategoryLink(anc) .. "</li>"
		end
		
		table.insert(ret, "<td><ul>\n" .. table.concat(ancestorList, "\n") .. "</ul></td>\n")
	elseif ancestorChain[1] then
		table.insert(ret, "<td><ul>\n")
		
		local chain = {}
		
		for i, anc in ipairs(ancestorChain) do
			chain[i] = "<li>" .. makeCategoryLink(anc) .. "</li>"
		end
		
		table.insert(ret, table.concat(chain, "\n<ul>\n"))
		
		for _, _ in ipairs(chain) do
			table.insert(ret, "</ul>")
		end
		
		table.insert(ret, "</td>\n")
	else
		table.insert(ret, "<td>unknown</td>\n")
	end
	
	table.insert(ret, "</tr>\n")
	
	local scripts = lang:getScripts()
	
	if scripts[1] then
		local script_text = {}
		
		for _, sc in ipairs(scripts) do
			local text = {}
			local code = sc:getCode()
			if code ~= "Hira" then
				table.insert(text, "<li>" .. makeCategoryLink(sc))
			end
			
			if code == "Jpan" then
				local m_scripts = require("Модул:scripts")
				local Hani = m_scripts.getByCode("Hani")
				local Hira = m_scripts.getByCode("Hira")
				local Kana = m_scripts.getByCode("Kana")
				table.insert(text, "<ul>")
				table.insert(text, "<li>" .. makeCategoryLink(Hani) .. "</li>")
				table.insert(text, "<li>" .. makeCategoryLink(Hira) .. "</li>")
				table.insert(text, "<li>" .. makeCategoryLink(Kana) .. "</li>")
				table.insert(text, "</ul>")
			elseif code == "Kore" then
				local m_scripts = require("Модул:scripts")
				local Hang = m_scripts.getByCode("Hang")
				local Hani = m_scripts.getByCode("Hani")
				table.insert(text, "<ul>")
				table.insert(text, "<li>" .. makeCategoryLink(Hang) .. "</li>")
				table.insert(text, "<li>" .. makeCategoryLink(Hani) .. "</li>")
				table.insert(text, "</ul>")
			end
			
			table.insert(text, "</li>")
			
			table.insert(script_text, table.concat(text, "\n"))
		end
		
		table.insert(ret, "<tr>\n<th>[[Викиречник:Писма|Писма]]</th>\n<td><ul>\n" .. table.concat(script_text, "\n") .. "</ul></td>\n</tr>\n")
	else
		table.insert(ret, "<tr>\n<th>[[Викиречник:Писма|Писма]]</th>\n<td>нису прецизирани</td>\n</tr>\n")
	end
	
	if lang._rawData.translit_module then
		local translit_module = lang._rawData.translit_module
		local translit_module_info = {}
		table.insert(translit_module_info,
			("[[Модул:%s]]"):format(translit_module))
		
		if translit_module == "translit-redirect" then
			local data = mw.loadData("Модул:translit-redirect/data")[lang:getCode()]
			if data then
				table.insert(translit_module_info, ":")
				local redirects_to = {}
				local m_scripts = require "Модул:scripts"
				for script, data in require "Модул:table".sortedPairs(data) do
					-- Skip boolean fields like "debug_mode" and "noError".
					if type(data) == "table" then
						table.insert(redirects_to,
							("\n* <code>%s</code>: %s"):format(
								script,
								data.module and ("[[Модул:%s]]"):format(data.module)
									or "(none)"))
					end
				end
				table.insert(translit_module_info, table.concat(redirects_to))
			end
		end
		
		table.insert(ret, [=[
<tr>
<th>[[Викиречник:Транзиција и романизација|Транслитерациони<br>модули]]</th>
<td>]=] .. table.concat(translit_module_info) .. [=[</td>
</tr>
]=])
end
	local wikidataItem = lang:getWikidataItem()
	if lang:getWikidataItem() and mw.wikibase then
		local URL = mw.wikibase.getEntityUrl(wikidataItem)
		local link
		if URL then
			link = '[' .. URL .. ' ' .. wikidataItem .. ']'
		else
			link = '<span class="error">Invalid Wikidata item: <code>' .. wikidataItem .. '</code></span>'
		end
		table.insert(ret, "<tr><th>Википодаци</th><td>" .. link .. "</td></tr>")
	end
	
	table.insert(ret, "</table>")
	
	return table.concat(ret)
end

local function NavFrame(content, title)
	return '<div class="NavFrame"><div class="NavHead">'
		.. (title or '{{{title}}}') .. '</div>'
		.. '<div class="NavContent" style="text-align: left;">'
		.. content
		.. '</div></div>'
end

function export.country_categories(frame)
	local categories = {}
	
	for i, country in ipairs(frame:getParent().args) do
		if i > 1 then
			table.insert(categories, "[[Категорија:Језици " .. country .. "]]")
		end
	end
	
	if #categories > 0 then
		return table.concat(categories)
	else
		return "[[Категорија:Језици који нису сортирани у категорију земаља]]"
	end
end


-- Category object

local Category = {}
Category.__index = Category


function Category.new(info)
	for key, val in pairs(info) do
		if not (key == "code" or key == "entryname" or key == "setsister" or key == "setwiki" or key == "setwikt") then
			error("The parameter \"" .. key .. "\" was not recognized.")
		end
	end
	
	local self = setmetatable({}, Category)
	self._info = info
	
	if not self._info.code then
		error("No language code was specified.")
	else
		self._lang = require("Модул:languages").getByCode(self._info.code) or error("The language code \"" .. self._info.code .. "\" is not valid.")
	end
	
	return self
end

export.new = Category.new


function Category:getInfo()
	return self._info
end


function Category:getBreadcrumbName()
	return self._lang:getCanonicalName()
end


function Category:getDataModule()
	return "Модул:category tree/langcatboiler"
end


function Category:canBeEmpty()
	return true
end


function Category:isHidden()
	return false
end


function Category:getCategoryName()
	return mw.getContentLanguage():ucfirst(self._lang:getCategoryName())
end


function Category:getDescription()
	if self._lang:getCode() == "und" then
		return
			"This is the main category of the '''" .. self._lang:getCategoryName() .. "''', represented in Wiktionary by the [[Викиречник:Језици|код]] '''" .. self._lang:getCode() .. "'''. " ..
			"This language contains terms in historical writing, whose meaning has not yet been determined by scholars."
	end
	
	local canonicalName = self._lang:getCanonicalName()
	
	local ret = linkbox(self._lang, self._info.setwiki, self._info.setwikt, self._info.setsister, self._info.entryname)
	
	ret = ret .. "This is the main category of the '''" .. self._lang:getCategoryName() .. "'''.\n\nInformation about " .. canonicalName .. ":\n\n" .. infobox(self._lang)
	
	if self._lang:getType() == "reconstructed" then
		ret = ret .. "\n\n" ..
			canonicalName .. " is a reconstructed language. Its words and roots are not directly attested in any written works, but have been reconstructed through the ''comparative method'', " ..
			"which finds regular similarities between languages that cannot be explained by coincidence or word-borrowing, and extrapolates ancient forms from these similarities.\n\n" ..
			"According to our [[Wiktionary:Criteria for inclusion|criteria for inclusion]], terms in " .. canonicalName ..
			" should '''not''' be present in entries in the main namespace, but may be added to the Reconstruction: namespace."
	elseif self._lang:getType() == "appendix-constructed" then
		ret = ret .. "\n\n" ..
			canonicalName .. " is a constructed language that is only in sporadic use. " ..
			"According to our [[Викиречник:Критеријуми за укључивање|критеријуми за укључивање]], израза у " .. canonicalName ..
			" '''не''' требају бити присутни у записима у главном простору имена, али могу бити додани у Додатак: именски простор. " ..
			"Сви термини на овом језику могу бити доступни на [[Додатак:" .. canonicalName .. "]]."
	end
	
	local about = mw.title.new("Викиречник:О " .. canonicalName)
	
	if about.exists then
		ret = ret .. "\n\n" ..
			"Молим видите '''[[Викиречник:О " .. canonicalName .. "]]''' за информације и посебна разматрања за креирање " .. self._lang:getCategoryName() .. " уноса."
	end
	
	local ok, tree_of_descendants = pcall(
		require("Модул:family tree").print_children,
		self._lang:getCode(), {
			protolanguage_under_family = true,
			must_have_descendants = true
		})
	
	if ok then
		if tree_of_descendants then
			ret = ret .. NavFrame(
				tree_of_descendants,
				"Породично дрво")
		else
			ret = ret .. "\n\n" .. self._lang:getCanonicalName()
				.. " has no descendants or varieties listed in Wiktionary's language data modules."
		end
	else
		mw.log("error while generating tree: " .. tostring(tree_of_descendants))
	end
ret = ret .. " __EXPECTUNUSEDCATEGORY__" -- it's OK if this category is empty	
	return ret
end


function Category:getParents()
	local canonicalName = self._lang:getCanonicalName()
	
	local ret = {{name = "Категорија:Сви језици", sort = canonicalName}}
	
	local fam = self._lang:getFamily()
	local famCode = fam and fam:getCode()
	
	if not fam then
		table.insert(ret, {name = "Категорија:Unclassified језици", sort = canonicalName})
	elseif famCode == "qfa-iso" then
		table.insert(ret, {name = "Категорија:Изоловани језици", sort = canonicalName})
	elseif famCode == "qfa-mix" then
		table.insert(ret, {name = "Категорија:Мешани језици", sort = canonicalName})
	elseif famCode == "sgn" then
		table.insert(ret, {name = "Категорија:Сви знаковни језици", sort = canonicalName})
	elseif famCode == "crp" then
		table.insert(ret, {name = "Категорија:Креолски или пиџин језици", sort = canonicalName})
		
		for _, anc in ipairs(self._lang:getAncestors()) do
			table.insert(ret, {name = "Категорија:" .. anc:getCanonicalName() .. "-based creole or pidgin languages", sort = canonicalName})
		end
	elseif famCode == "art" then
		if self._lang:getType() == "appendix-constructed" then
			table.insert(ret, {name = "Категорија:Додатак-само конструисани језици", sort = canonicalName})
		else
			table.insert(ret, {name = "Категорија:Конструисани језици", sort = canonicalName})
		end
		
		for _, anc in ipairs(self._lang:getAncestors()) do
			table.insert(ret, {name = "Категорија:" .. anc:getCanonicalName() .. "- засновани језици", sort = canonicalName})
		end
	else
		table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(fam:getCategoryName()), sort = canonicalName})
		
		if self._lang:getType() == "reconstructed" then
			table.insert(ret, {name = "Категорија:Реконструисани језици", sort = (mw.ustring.gsub(canonicalName, "^Пра%-", ""))})
		end
	end
	
	for _, sc in ipairs(self._lang:getScripts()) do
		table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(sc:getCategoryName() .. " језици"), sort = canonicalName})
		
		if sc:getCode() == "Jpan" then
			table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(require("Модул:scripts").getByCode("Hani"):getCategoryName() .. " језици"), sort = canonicalName})
			table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(require("Модул:scripts").getByCode("Hira"):getCategoryName() .. " језици"), sort = canonicalName})
			table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(require("Модул:scripts").getByCode("Kana"):getCategoryName() .. " језици"), sort = canonicalName})
		elseif sc:getCode() == "Kore" then
			table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(require("Модул:scripts").getByCode("Hang"):getCategoryName() .. " језици"), sort = canonicalName})
			table.insert(ret, {name = "Категорија:" .. mw.getContentLanguage():ucfirst(require("Модул:scripts").getByCode("Hani"):getCategoryName() .. " језици"), sort = canonicalName})
		end
	end
	
	if self._lang:hasTranslit() then
		table.insert(ret, {name = "Категорија:Језици са аутоматском транслитерацијом", sort = canonicalName})
	end
	
	return ret
end


function Category:getChildren()
	local ret = {}
	
	local m_poscatboiler = require("Модул:category tree/poscatboiler")
	
	for _, label in ipairs({"додатци", "приступ сервисирању", "леме", "имена", "фразе", "риме", "симболи", "шаблони", "термини по етимологији", "термини по коришћењу"}) do
		local child = m_poscatboiler.new({code = self._lang:getCode(), label = label})
		local parents = child:getParents()
		
		if parents then
			-- Find the current category among the child's parents, to find its sort key
			for _, parent in ipairs(parents) do
				if type(parent.name) == "string" and parent.name == "Категорија:" .. self:getCategoryName() then
					table.insert(ret, {name = child, sort = parent.sort})
					break
				end
			end
		end
	end
	
	local m_derivcatboiler = require("Модул:category tree/derived cat")
	local child = m_derivcatboiler.new({code = nil, label = self._lang:getCode()})
	local sortkey = child._info.label
	local parents = child:getParents()
	
	if parents then
		-- Find the current category among the child's parents, to find its sort key
		for _, parent in ipairs(parents) do
			if type(parent.name) == "string" and parent.name == "Категорија:" .. self:getCategoryName() then
				sortkey = parent.sort
				break
			end
		end
	end
	
	table.insert(ret, {name = child, sort = sortkey})
	
	local m_topic_cat = require("Модул:category tree/topic cat")
	local child = m_topic_cat.new({code = self._lang:getCode(), label = "Све теме"})
	local sortkey = child._info.label
	local parents = child:getParents()
	
	if parents then
		-- Find the current category among the child's parents, to find its sort key
		for _, parent in ipairs(parents) do
			if type(parent.name) == "string" and parent.name == "Категорија:" .. self:getCategoryName() then
				sortkey = parent.sort
				break
			end
		end
	end
	
	table.insert(ret, {name = child, sort = sortkey})
	
	-- FIXME: This is hacky, but it works as a stopgap measure.
	-- We should fix this when these categories get their own category tree modules.
	table.insert(ret, {name = {
		_lang = self._lang,
		getCategoryName = function(self) return "Локални " .. self._lang:getCanonicalName() end,
		getDescription = function(self) return self._lang:getCanonicalName() .. " изрази који се користе у одређеним регионима или дијалектима." end,
		}, sort = "regional"})
	
	table.insert(ret, {name = {
		_lang = self._lang,
		getCategoryName = function(self) return "Захтеви (" .. self._lang:getCanonicalName() .. ")" end,
		getDescription = function(self) return self._lang:getCanonicalName() .. " уноси којима је потребна пажња искусних уредника." end,
		}, sort = "requests"})
	
	table.insert(ret, {name = {
		_lang = self._lang,
		getCategoryName = function(self) return "Корисник " .. self._lang:getCode() end,
		getDescription = function(self) return "корисници викиречника категоризовани према степену познавања језика, за " .. self._lang:getCanonicalName() .. " језик." end,
		}, sort = "user"})
	
	return ret
end


function Category:getUmbrella()
	return nil
end


return export