Module:Translation Page

local p = {} local h = {}

local File = require("Module:File") local Franchise = require("Module:Franchise") local Language = require("Module:Language") local Term = require("Module:Term") local utilsArg = require("Module:UtilsArg") local utilsCargo = require("Module:UtilsCargo") local utilsLayout = require("Module:UtilsLayout") local utilsMarkup = require("Module:UtilsMarkup") local utilsPage = require("Module:UtilsPage") local utilsString = require("Module:UtilsString") local utilsTable = require("Module:UtilsTable") local utilsVar = require("Module:UtilsVar")

local VAR_SUBPAGE = "subpage" local VAR_LANGUAGES = "languages"

local subpages = { {		name = "Characters", content = "every named character", },	{		name = "Enemies", content = "every named Series:", },	{		name = "Items", content = "every named Series: and Series:", },	{		name = "Locations", content = "every named location" },	{		name = "Miscellaneous", content = "miscellaneous subjects", }, } local subpagesWithAll = utilsTable.concat(subpages, {	{		name = "All",		content = "every named character, boss, enemy, location and item"	} }) local subpagesLookup = utilsTable.keyBy(subpagesWithAll, "name")

function p.BasepageHeader(frame) local templateSpec = p.Templates["Translation Header Basepage"] -- Allow utilsArg to parse params for providing references to supplementary material -- We don't put them in the p.Templates object with the rest to avoid polluting the doc local refParams = {} for i, langCode in ipairs(Language.enum) do		refParams[langCode] = { type = "string" }	end templateSpec.params = utilsTable.merge(templateSpec.params, refParams) local args, err = utilsArg.parse(frame:getParent.args, templateSpec) local errorCategories = utilsMarkup.categories(err and err.categories or {}) local result = p.basePageHeader(args.game, args.languagesGame, args.languagesSupplementary, args) return result..errorCategories end

function p.SubpageHeader(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Translation Header Subpage"]) if err then return utilsMarkup.categories(err.categories) else return p.subpageHeader(args.game, args.subpage) end end

function p.AllHeader(frame) local args, err = utilsArg.parse(frame:getParent.args, p.Templates["Translation Header All"]) if err then return utilsMarkup.categories(err.categories) else return p.allHeader(args.game) end end

-- Template:Translation Page function p.CreateTranslationTables(frame) local args = frame:getParent.args local languageGroups = {} local index = 0 while true do		index = index + 1 if utilsString.isEmpty(args["tab" .. index]) then break else table.insert(languageGroups, utilsString.split(args["tab" .. index])) end end if #languageGroups == 0 then -- Trying the new method of getting languages local languages = utilsVar.get(VAR_LANGUAGES) if not languages then error("Unable to determine available languages") end -- sort languges into Zelda Wiki's order (Japanese first, then alphabetical by language name) languages = utilsTable.intersection(Language.enum({ includeWikiLanguage = true }), languages)

languageGroups = p.computeLanguageGroups(languages) end local subjects = args["subjects"] return p.createTranslationTables(args["game"], args["filetype"], args["header"], args["subjects"], languageGroups) end

function p.basePageHeader(game, languagesGame, languagesSupplementary, supplementaryRefs) -- Store variable of all languages (except the wiki language, American English) for retrieval by subpage local languages = utilsTable.concat(languagesGame, languagesSupplementary) languages = utilsTable.difference(languages, {"enAm"}) utilsVar.set(VAR_LANGUAGES, languages) local header = "" -- This template is used on the base page but the base page itself is transcluded from the subpage. The output must differ depending on where we are currently local title = mw.title.getCurrentTitle local page = title.text local isBasePage = title.text == title.subpageText local isAllSubpage = title.subpageText == "All" local gameLink = Franchise.link(game) or game local gameDisplay = Franchise.display(game) or game if isBasePage then if title.nsText ~= "Template" then -- we don't want examples in the template doc to change the display title header = header .. string.format("\n", gameDisplay) end header = header..":Notice: Zelda Wiki splits game translations into sections.\n\n" end

local gameLanguages, supplementaryLanguages = p.printLanguageLists(languagesGame or {}, languagesSupplementary or {}, isBasePage and nil or supplementaryRefs, true) -- we only show supplementary refs on the basepage

local auxiliaryVerb local releaseDate = Franchise.releaseDate(game) if not releaseDate or os.date("%Y-%m-%d") < releaseDate then auxiliaryVerb = "will be" else auxiliaryVerb = "has been" end header = header..string.format("%s %s released in the following languages: %s", isBasePage and gameLink or gameDisplay, auxiliaryVerb, gameLanguages)

if supplementaryLanguages ~= "" then header = header..string.format(" Supplementary material for %s has been released in %s", gameDisplay, supplementaryLanguages) end local subpageLinks = utilsTable.map(subpages, function(subpage)		return string.format("%s", page, subpage.name, subpage.name)	end) local subpageList = utilsMarkup.bulletList(subpageLinks) if isBasePage then header = header..string.format("\n\nThe translation sections for %s are as follows: %s", gameDisplay, subpageList) local allPage = page.."/All" if utilsPage.exists(allPage) then -- Only show /All page if it exists, as games like BotW are too large to have one header = header..string.format("\n\nAlternatively, you may view all translations at once, although this is not recommended due to the immense size of the page.", allPage) end end header = mw.getCurrentFrame:preprocess(header) return header end

function p.printLanguageLists(languagesGame, languagesSupplementary, supplementaryRefs) local gameLanguagesList = utilsTable.map(languagesGame, function(langCode)		local lect, errorCategory = Language.getLect(langCode)		return {			text = lect and lect.name or errorCategory		}	end) gameLanguagesList = p.phraseList(gameLanguagesList) local supplementaryLanguagesList = utilsTable.map(languagesSupplementary or {}, function(langCode)		local refs = supplementaryRefs[langCode]		refs = refs and mw.text.decode(mw.text.unstripNoWiki(refs))		local lect, errorCategory = Language.getLect(langCode)		return {			text = lect and lect.name or errorCategory,			refs = refs,		}	end) supplementaryLanguagesList = p.phraseList(supplementaryLanguagesList) return gameLanguagesList, supplementaryLanguagesList end

function p.phraseList(items) if #items == 0 then return "" end local result = "" for i, item in ipairs(items) do		if item.text then result = result..item.text local refs = item.refs or "" if i == #items then local previousRefs = items[i - 1] and items[i - 1].refs or "" result = result.."."..previousRefs..refs elseif i == (#items - 1) then result = result..refs .." and " else result = result..","..refs.." " end end end return result end

function p.subpageHeader(game, subpage) local currentTitle = mw.title.getCurrentTitle if subpage ~= currentTitle.subpageText then return "" end utilsVar.set(VAR_SUBPAGE, subpage) local headerText = string.format("\n", Franchise.display(game), subpage) headerText = headerText..string.format("The following page is a list of the names of every %s appearing in %s in every release of the game. ", subpagesLookup[subpage].content, Franchise.link(game)) headerText = headerText .. mw.getCurrentFrame:expandTemplate({		title = ":"..currentTitle.baseText -- Transclude the base page header to get its text and the language variables	}) headerText = headerText.."\n\n" headerText = headerText.."*Empty cells indicate that a name has not yet been found for that particular term in that language.\n" headerText = headerText.."*Cells with an em dash (−) indicate there is no given name for that particular term in that language." headerText = mw.getCurrentFrame:preprocess(headerText) return headerText end

function p.allHeader(game) local allTranslations = p.subpageHeader(game, "All") local frame = mw.getCurrentFrame for i, otherSubpage in ipairs(subpages) do		local subpageName = mw.title.getCurrentTitle.baseText.."/"..otherSubpage.name if utilsPage.exists(subpageName) then allTranslations = allTranslations .. frame:expandTemplate({				title = ":"..subpageName			}) end end return allTranslations end

function p.createTranslationTables(game, filetype, header, subjects, languageGroups) subjects = utilsString.split(subjects) if filetype == "Screenshot" then filetype = "" end if utilsString.isEmpty(header) then header = "Subject" end local tabData = {} for key, languageGroup in ipairs(languageGroups) do		local languages = languageGroup --Creating tab contents local headerRow = mw.html.create("tr") :node(mw.html.create("th"):wikitext(header)):done for key2, language in ipairs(languages) do			local lect = Language.getLect(language, { flagSize = "x20px" }) local flags = table.concat(lect.flags, " ") headerRow:node(				mw.html.create("th")					:css("width", 100 / (#languages + 1) .. "%")					:tag("div")						:wikitext(flags)						:done					:tag("div")						:wikitext(lect.abbr)						:done				) :done end local content = mw.html.create("table") :addClass("wikitable") :css("width", "100%") :node(headerRow) --Creating rows local errs = {} local translations = h.fetchTranslationsByGame(game, subjects) for key2, subject in ipairs(subjects) do			local term, err = Term.fetchTerm(subject, game) if not term then local utilsError = require("Module:UtilsError") utilsError.warn(string.format("Page %s does not store any terms.", utilsMarkup.code(subject))) errs = utilsTable.concat(errs, err) else -- Assume that by default, the file is named according to the term. If term+game cannot uniquely identify the subject, assume the file name matches the page name. -- Example A: Given subject Wood (Character) and game ST, the filename is inferred to be "ST Wood Model.png". In ST, the term "Wood" uniquely identifies the subject. -- Example B: Given subject Link (Goron) and game MM, the file name is inferred to be "MM Link (Goron) Model.png. In MM, the term 'Link' refers to two characters and does not uniquely identify the subject.				local fileIdentifier = term				if utilsString.endsWith(subject, ")") and #Term.fetchSubjects(term, game) > 1 then					fileIdentifier = subject 				end				local img = File.gameImage(game, fileIdentifier, filetype, {					size = "150x150px",					checkExists = false, -- checking for file existence has too much of a time cost for translation pages				})				local row = h.printRow(img, subject, game, languages, translations[subject] or {})				content:node(row)			end		end		local tabContent = tostring(content) .. " Return to top " .. utilsMarkup.categories(errs)		-- Formatting tab names		for key2, language in ipairs(languages) do			languages[key2] = Language.getLect(language).lang		end languages = utilsTable.unique(languages) local tabLabel = table.concat(languages, ", ") table.insert(tabData, {			label = tabLabel,			content = tabContent,		}) end return utilsLayout.tabs(tabData, {		tabOptions = {			collapse = true,			stretch = true,		}	}) end function h.printRow(img, subject, game, languages, translations) local term = Term.link(subject, game) local row = mw.html.create("tr") :attr("id", subject) :node(mw.html.create("td")			:css("text-align", "center")			:tag("div")				:wikitext(img)				:done			:tag("div")				:css("font-weight", "bold")				:wikitext(term)				:done		) for _, language in ipairs(languages) do		outputs = {} for _, translation in ipairs(translations) do			if translation["lang"] == language then table.insert(outputs, translation["translation"]) end end

local cell = mw.html.create("td"):css("text-align", "center") if outputs[1] == "N/A" then cell:wikitext("—") else cell:wikitext(table.concat(outputs, " ")) end row:node(cell):done end return row end

function h.fetchTranslationsByGame(game, subjects) local queryArgs = { where = utilsCargo.allOf(       	{game = game},         	utilsCargo.IN("subject", subjects)    	) }   local cargoData = utilsCargo.query("Translations", "subject, lang, translation", queryArgs) cargoData = utilsTable.groupBy(cargoData, "subject") return cargoData end

-- Algoritm that attemps to spread languages as evenly as possible between tabs -- with a maximum of five foreign languages per tab function p.computeLanguageGroups(languages) local groupSizes = p.computeGroupSizes(#languages) if not groupSizes then error("Unable to compute language tabs") end local languageGroups = {} local i = 0 for j, groupSize in ipairs(groupSizes) do		languageGroups[j] = {} for k = 1, groupSize do			i = i + 1 languageGroups[j][k] = languages[i] end end return languageGroups end function p.computeGroupSizes(numLanguages) if numLanguages <= 5 then return {numLanguages} end -- There's probably a smarter math solution to this return ({		[6] = {3, 3},		[7] = {4, 3},		[8] = {4, 4},		[9] = {3, 3, 3},		[10] = {5, 5},		[11] = {4, 4, 3},		[12] = {4, 4, 4},		[13] = {4, 4, 5},		[14] = {5, 5, 4},		[15] = {5, 5, 5},		[16] = {4, 4, 4, 4},		[17] = {4, 4, 4, 5},		[18]= {3, 3, 3, 3, 3, 3},		[19] = {5, 5, 5, 4},		[20] = {5, 5, 5, 5},		[21] = {3, 3, 3, 3, 3, 3, 3},	})[numLanguages] end

p.Templates = { ["Translation Page"] = { format = "block", purpose = "This template works in conjunction with its header to generate tables that display subjects (such as characters or items), images of those subjects, and their names in various languages.", usage = "The following code is to be placed in each section of a game's translation page. This template supports up to six tabs.", boilerplate = { separateRequiredParams = false, },		paramOrder = {"game", "filetype", "header", "tab", "subjects"}, repeatedGroup = { name = "tabs", params = {"tab"}, counts = {2, 3, 4, 5, 6}, },		params = { game = { required = true, type = "string", enum = Franchise.enum, desc = "The relevant game code.", },			filetype = { required = true, type = "string", desc = "The type of image displayed for each subject.", },			header = { required = true, type = "string", desc = "The type of subject being translated and displayed." },			tab = { required = false, type = "string", desc = "Depends on the number of languages defined in the Header template. Not needed if using Template:Translation Header Subpage - tabs are computed automatically in this case", },			subjects = { required = true, type = "string", desc = "A list of all the subjects to be translated.", },		},	},	["Translation Header Basepage"] = { format = "block", purpose = "This template is to be used on translation base pages such as The Legend of Zelda Translations. It works in conjuction with Template:Translation Page to autogenerate translation pages. The intention is to replace the currently used Template:Translation Page Header with this template.", boilerplate = { after = " \n\n\n ", },		paramOrder = {"game", "languagesGame", "languagesSupplementary"}, params = { game = { required = true, type = "string", enum = Franchise.enum, desc = "A game code." },			languagesGame = { required = true, type = "string", enum = Language.enum({ includeWikiLanguage = true }), desc = "A comma-separated list of language codes indicating which languages the game was officially released in.", split = true, trim = true, nilIfEmpty = true, },			languagesSupplementary = { required = false, type = "string", enum = Language.enum({ includeWikiLanguage = true }), desc = " A comma-separated list of language codes indicating languages in which there exists official supplementary material for the game, though the game itself was not released in that language. One or more references to the sources of supplementary material may be added as a separate argument. See examples below. ", split = true, trim = true, nilIfEmpty = true, },		},		examples = { wrapLines = true, {				desc = "The use of nowiki tags around the references is needed for correct rendering.", args = { game = "BotW", languagesGame = "ja, zhS, zhT, nl, frC, frF, de, he, hu, it, ko, pl, ptP, ru, esS, esL", languagesSupplementary = "he, hu, pl, ptP", he = " ", hu = " ", },			},			{				game = "BotW", languagesGame = "ja, zhS, zhT, nl, frC, frF, de, he, hu, it, ko, pl, ptP, ru, esS, esL", languagesSupplementary = "he, hu, pl, ptP", notALanguage = " ", },			{				game = "notAGame", languagesGame = "ja", },			{				game = "BotW", languagesGame = "ja, frC, frF, notALanguage", },		},	},	["Translation Header Subpage"] = { purpose = "This template is to be used on translation subpages such as The Legend of Zelda Translations/Characters. It only works if Template:Translation Header Basepage is placed on the base page. It works in conjuction with Template:Translation Page to autogenerate translation pages. The intention is to replace the currently used Template:Translation Page Header with these new templates.", params = { [1] = {				name = "game", required = true, type = "string", enum = Franchise.enum, desc = "A game code." }, 			[2] = {				name = "subpage", required = true, desc = "The name of the subpage. For The Legend of Zelda Translations/Characters, this would be .", type = "string", enum = utilsTable.map(subpagesWithAll, "name"), }		}	},	["Translation Header All"] = { purpose = "This template is to be used on the /All translation subpages. It only works if Template:Translation Header Subpage are used on the other subpges and Template:Translation Header Basepage is used on the base page. The intention is to replace the currently used Template:Translation Page Header with these new templates.", params = { [1] = {				name = "game", required = true, type = "string", enum = Franchise.enum, desc = "A game code." },		},	}, }

return p