MediaWiki:Gadget-TargetedTranslations.js

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Note: You may have to bypass your browser’s cache to see the changes. In addition, after saving a sitewide CSS file such as MediaWiki:Common.css, it will take 5-10 minutes before the changes take effect, even if you clear your cache.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

See also: Special:Gadgets.


// <nowiki>
// Initialize preferred languages from local storage.
let preferredLanguages = new Set(JSON.parse(localStorage.getItem("targetedTranslationsLangs")));

// Empty and filled-in bookmark icons.
const unselectedIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="17" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor" style="vertical-align: text-top; cursor: pointer"><g><path d="M5 1a2 2 0 00-2 2v16l7-5 7 5V3a2 2 0 00-2-2zm10 14.25-5-3.5-5 3.5V3h10z"></path></g></svg>`;
const selectedIcon = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="25" height="17" viewBox="0 0 20 20" aria-hidden="true" fill="currentColor" style="vertical-align: text-top; cursor: pointer"><g><path d="M5 1a2 2 0 00-2 2v16l7-5 7 5V3a2 2 0 00-2-2z"></path></g></svg>`;

function loopOverTranslationLines(translationTable, callback) {
	for (let translationLine of translationTable.querySelectorAll(".translations-cell li, .translations-cell dd")) {
		// Filter out empty lines, e.g. for macrolanguages like Chinese.
		if (!translationLine.textContent.match("^.+?: .+"))
			continue;

		// Encode sublanguages of a macrolanguage with `>`, e.g. `Chinese>Mandarin`.
		let translationLang = translationLine.textContent.split(":")[0];
		if (translationLine.parentElement.parentElement.tagName === "LI")
			translationLang = translationLine.parentElement.parentElement.textContent.split(":")[0] + ">" + translationLang;

		callback(translationLine, translationLang);
	}
}

function displayTranslations() {
	// Remove all existing displays.
	document.querySelectorAll(".targeted-translations").forEach(elem => elem.remove());

	// Loop over each translation table.
	for (let translationTable of document.querySelectorAll("table.translations")) {
		let NavHead = translationTable.parentElement.previousElementSibling;
		if (!NavHead.matches("div.NavHead"))
			continue; // malformed template

		let targetedTranslations = document.createElement("span");
		targetedTranslations.className = "targeted-translations";
		targetedTranslations.append(" — ");

		// Find translation lines matching a preferred language.
		loopOverTranslationLines(translationTable, (translationLine, translationLang) => {
			if (preferredLanguages.has(translationLang)) {
				if (targetedTranslations.childElementCount)
					targetedTranslations.append("; ");

				let translation = document.createElement("span");
				targetedTranslations.append(translation);

				// In the case of a macrolanguage, display with brackets, e.g. `Chinese (Mandarin)`.
				if (translationLang.includes(">"))
					translation.append(translationLang.split(">")[0] + " (" + translationLang.split(">")[1] + "): ");
				else
					translation.append(translationLang + ": ");

				// Don't include i = 0 (the language name) or <dl> (which contains sub-entries of a macrolanguage).
				for (let i = 1; i < translationLine.childNodes.length; i++) {
					if (translationLine.childNodes[i].tagName !== "DL") {
						translation.append(translationLine.childNodes[i].cloneNode(true));
					}
				}
			}
		});

		if (targetedTranslations.childElementCount)
			NavHead.append(targetedTranslations);
	}
}

// Setup.
for (let translationTable of document.querySelectorAll("table.translations")) {
	translationTable.firstElementChild.insertAdjacentHTML("afterbegin", 
		`<tr>
			<td colspan="100%" style="font-size: 85%">
				[<a role="button" class="tt-button-select">
					Select preferred languages
				</a>]
				<span style="display: none">
					&#32;
					[<a role="button" class="tt-button-clear">
						Clear all
					</a>]
				</span>
			</td>
		</tr>`
		.replace(/[\n\t]/g, "")
	);
	let selectButton = translationTable.querySelector(".tt-button-select");
	let clearButton = translationTable.querySelector(".tt-button-clear");

	selectButton.addEventListener("click", () => {
		// The button can be in two states: "select" and "save".

		if (selectButton.textContent === "Select preferred languages") {
			clearButton.parentElement.style.display = "";

			// Make sure you can't have multiple translation tables in "select" mode simultaneously.
			for (let button of document.querySelectorAll(".tt-button-select")) {
				if (button.textContent === "Save preferred languages") {
					// Trigger the saving procedure.
					button.click();
				}
			}

			selectButton.textContent = "Save preferred languages";

			// Add icons.
			loopOverTranslationLines(translationTable, (translationLine, translationLang) => {
				let iconContainer = document.createElement("span");
				iconContainer.role = "button";
				translationLine.insertAdjacentElement("afterbegin", iconContainer);

				if (preferredLanguages.has(translationLang)) {
					iconContainer.className = "tt-icon-selected";
					iconContainer.innerHTML = selectedIcon;
				} else {
					iconContainer.className = "tt-icon-unselected";
					iconContainer.innerHTML = unselectedIcon;
				}

				iconContainer.addEventListener("click", () => {
					if (preferredLanguages.has(translationLang)) {
						preferredLanguages.delete(translationLang);
						iconContainer.className = "tt-icon-unselected";
						iconContainer.innerHTML = unselectedIcon;
					} else {
						preferredLanguages.add(translationLang);
						iconContainer.className = "tt-icon-selected";
						iconContainer.innerHTML = selectedIcon;
					}
				});
			});
		} else {
			// Save to local storage.
			localStorage.setItem("targetedTranslationsLangs", JSON.stringify(Array.from(preferredLanguages)));

			// Reset state.
			translationTable.querySelectorAll(".tt-icon-selected, .tt-icon-unselected").forEach(elem => elem.remove());
			selectButton.textContent = "Select preferred languages";
			clearButton.parentElement.style.display = "none";
			displayTranslations();
		}
	});

	clearButton.addEventListener("click", () => {
		preferredLanguages = new Set();
		translationTable.querySelectorAll(".tt-icon-selected").forEach(iconContainer => {
			iconContainer.innerHTML = unselectedIcon;
			iconContainer.className = "tt-icon-unselected";
		});
	});
}

displayTranslations();
// </nowiki>