User:Nikki/LexemeTranslations.js

From Wikidata
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * This script shows translations for a lexeme.
 * The translations are inferred from statements on senses,
 * such as P5137 (item for this sense).
 * 
 * To use it, add the following line to your common.js:
 * mw.loader.load("//www.wikidata.org/w/index.php?title=User:Nikki/LexemeTranslations.js&action=raw&ctype=text/javascript");
 * 
 * License: CC0
*/
// jshint esversion: 6
(function () {
	"use strict";

	let translations = {
		en: {
			"translations": "Translations",
			"synonyms": "Synonyms",
			"colon": ": ",
			"comma": ", ",
			"slash": "/",
		},
        bn: {
            "translations": "অনুবাদ",
            "synonyms": "সমার্থক"
        },
		de: {
			"translations": "Übersetzungen",
			"synonyms": "Synonyme",
		},
		eo: {
			"translations": "Tradukoj",
			"synonyms": "Samsencaĵoj",
		},
		ja: {
			"translations": "翻訳",
			"synonyms": "類義語",
			"colon": ":",
			"comma": "、",
		},
		nb: {
			"translations": "Oversettelser",
			"synonyms": "Synonymer",
		},
		nn: {
			"translations": "Omsetjingar",
			"synonyms": "Synonym",
		},
		pa: {
			"translations": "ਤਰਜਮੇ",
			"synonyms": "ਸਮਾਨਾਰਥੀ",
		},
		pnb: {
			"translations": "ترجمے",
			"synonyms": "سمان ارتھی",
			"comma": "، ",
		},
	};

	mw.util.addCSS(`
		.userjs-lexemetranslations {
			border: 1px solid lightgrey;
			clear: both;
		}

		.userjs-lexemetranslations h4 {
			background: #f8f8f8;
			color: dimgrey;
			margin: 0;
			padding: 0.3em;
		}

		.userjs-lexemetranslations ul {
			list-style: none;
			list-style-type: none;
			margin: 0;
			padding: 0.3em;
		}

		.userjs-lexemetranslations .translations ul {
			column-width: 15em;
		}

		.userjs-lexemetranslations .translations ul li {
			break-inside: avoid;
		}

		.userjs-lexemetranslations .loc {
			color: dimgrey;
		}

		.userjs-lexemetranslations .sgn .lexemes {
			display: flex;
			flex-direction: row;
			flex-wrap: wrap;
			gap: 1em;
		}

		.userjs-lexemetranslations .sgn .lexemes bdi {
			margin-top: 0.5em;
			margin-bottom: 0.5em;
		}

		.ltr .userjs-lexemetranslations .sgn .lexemes > bdi + bdi {
			border-left: 3px solid lightgrey;
			padding-left: 1em;
		}
		.rtl .userjs-lexemetranslations .sgn .lexemes > bdi + bdi {
			border-right: 3px solid lightgrey;
			padding-right: 1em;
		}


		.userjs-lexemetranslations .sgn .signwriting:hover {
			filter: brightness(1.5);
		}

	`);

	// see https://www.wikidata.org/wiki/User:Nikki/ExMusica.js
	let langs = [...new Set([
		mw.language.getFallbackLanguageChain().shift(),
		mw.language.getFallbackLanguageChain().slice(1, -1),
		mw.config.get("wgULSBabelLanguages") || [],
		mw.config.get("wgULSAcceptLanguageList") || [],
		"en"
	].flat().map(function (x) { return x.toLowerCase(); }))].join(",");

	// Languages where pronunciation can be displayed as ruby text
	const ruby = {
		"ja": "ja-hira",
		"mvi": "mvi-hira",
		"ojp": "ojp-hira",
		"rys": "rys-hira",
		"ryu": "ryu-hira",
		"yoi": "yoi-hira",
		"yox": "yox-hira",
	};

	// Languages which ULS doesn't know are sign languages
	const signlanguages = [
		"bfi", "bzs", "fsl", "gsg", "pks",
		"mis-x-q29525", // Auslan (asf)
		"mis-x-q36668", // Austrian (asq)
		"mis-x-q35768", // Catalan (csc)
		"mis-x-q5201809", // Czech (cse)
		"mis-x-q2201099", // Dutch (dse)
		"mis-x-q2605298", // Danish (dsl)
		"mis-x-q33225", // Finnish (fse)
		"mis-x-q5450448", // Finland-Swedish (fss)
		"mis-x-q3915462", // Hausa (hsl)
		"mis-x-q113754817", // Indian (ins)
		"mis-x-q35601", // Japanese (jsl)
		"mis-x-q3073428", // Korean (kvk)
		"mis-x-q6744816", // Maltese (mdl)
		"mis-x-q3915511", // Mexican (mfs)
		"mis-x-q1781613", // Norwegian (nsl)
		"mis-x-q36239", // NZSL (nzs)
		"mis-x-q3915194", // Polish (pso)
		"mis-x-q3915472", // Portuguese (psr)
		"mis-x-q3322093", // South African (sfs)
		"mis-x-q35150", // DSGS (sgg)
		"mis-x-q3100814", // Spanish (ssp)
		"mis-x-q12953483", // Swiss French (ssr)
		"mis-x-q2107617", // Flemish (vgt)
		"mis-x-q20523999", // Slovenian (ysl-SI)
		"mis-x-q113195519", // Kosovar (ysl-XK)
        "mis-x-q123465950" // Bengali
	];

	let langnames = {};
	function sort_language_names(a, b) {
		return langnames[a].toLowerCase().localeCompare(langnames[b].toLowerCase());
	}

	function fetch_translations(e) {
		if (!e.language) return; // not a lexeme

		let eid = e.id;
		let query = `
			select ?s ?s1 ?lemma ?lang ?langLabel ?loc ?locLabel ?cc {
				values ?p { wdt:P5137 wdt:P6271 wdt:P9970 }
				wd:${ eid } ontolex:sense ?s.
				?s ?p ?item.
				?s1 ?p ?item.
				?l ontolex:sense ?s1;
					wikibase:lemma ?lemma;
					dct:language ?lang.
				filter (?l != wd:${ eid }).
				optional {
					?s1 wdt:P6084 ?loc.
					optional { ?loc wdt:P297 ?cc }
				}
				service wikibase:label { bd:serviceParam wikibase:language "${ langs }" }
			}
		`;
		$.getJSON("https://query.wikidata.org/sparql?format=json&query=" + encodeURI(query), function (data) {
			let senses = {};
			for (let e of data.results.bindings) {
				let sid = e.s.value.replace(/.*-/, "");
				let langid = e.lang.value.replace(/.*\//, "");
				langnames[langid] = e.langLabel.value;

				if (!senses[sid])
					senses[sid] = [];
				if (!senses[sid][langid])
					senses[sid][langid] = [];
				if (!senses[sid][langid][e.s1.value])
					senses[sid][langid][e.s1.value] = [];

				let exists = false;
				for (let sense of senses[sid][langid][e.s1.value]) {
					if (sense.lemma === e.lemma.value && sense.lang === e.lemma["xml:lang"]) {
						if (e.loc) {
							let loc_label = e.locLabel ? e.locLabel.value : e.loc.value.replace(/.*\//, "");
							let loc_lang = e.locLabel ? e.locLabel["xml:lang"] : "und";
							sense.loc.push({ name: e.locLabel.value, lang: e.locLabel["xml:lang"] });
						}
						exists = true;
					}
				}
				if (!exists) {
					let sense = {
						lemma: e.lemma.value,
						lang: e.lemma["xml:lang"],
						loc: [],
					}
					if (e.locLabel) {
						sense.loc.push({ name: e.locLabel.value, lang: e.locLabel["xml:lang"] });
					}
					senses[sid][langid][e.s1.value].push(sense);
				}
			}

			for (let sid of Object.keys(senses).sort()) {
				let output = [];
				let synonyms = [];
				let output_sgn = [];
				for (let langid of Object.keys(senses[sid]).sort(sort_language_names)) {
					let langname = langnames[langid];
					let row = [];
					let is_sgn = false;

		 			for (let sense of Object.keys(senses[sid][langid]).sort(function (a, b) {
		 				return senses[sid][langid][a][0].lemma.toLowerCase().localeCompare(senses[sid][langid][b][0].lemma.toLowerCase());
		 			})) {
						let lemmas = senses[sid][langid][sense].sort(function (a, b) { return a.lang.localeCompare(b.lang) }).map(function (x) {
							let el = document.createElement("bdi");
							el.lang = mw.language.bcp47(x.lang);
							el.textContent = x.lemma;

							if (
								ruby.hasOwnProperty(x.lang)
								&& senses[sid][langid][sense].length >= 2
								&& senses[sid][langid][sense][1].lang === ruby[x.lang]
							) {
								el.innerHTML = `<ruby>${x.lemma}<rt>${senses[sid][langid][sense][1].lemma}</ruby>`;
							} else if (
								ruby.hasOwnProperty(senses[sid][langid][sense][0].lang)
								&& x.lang === ruby[ senses[sid][langid][sense][0].lang ]) {
								// TODO: figure out how this code actually works
								return;
							}

							if (
								($.uls.data.getScript(x.lang) === "Sgnw" || signlanguages.includes(x.lang))
								&& el.textContent.match(/^[A-Za-z0-9 ]+$/)
							) {
								el.innerHTML = process_signwriting(el.textContent);
								is_sgn = true;
							}

							return el.outerHTML;
						}).filter(x => x).join($.i18n("slash"));

						let loc = "";
						if (senses[sid][langid][sense][0].loc.length > 0) {
							loc = ` <span class="loc">(${ senses[sid][langid][sense][0].loc.map(x => `<span lang="${ x.lang }" dir="auto">${ x.name }</span>`).join($.i18n("comma")) })</span>`;
						}

						let url = sense.replace(/.*\/entity\/(L[1-9][0-9]*)-(S[1-9][0-9]*)/, "/wiki/Lexeme:$1#$2");
						row.push(`<bdi><a href="${url}">${lemmas}</a></bdi>${loc}`);
		 			}

		 			if (e.language === langid) {
			 			let v = `<li>${ row.join($.i18n("comma")) }</li>`;
		 				synonyms.push(v);
		 			} else if (is_sgn) {
		 				let v = `<li><span class="langname"><bdi>${ langname }</bdi>${ $.i18n("colon") }</span><span class="lexemes">${ row.join("") }</span></li>`;
						output_sgn.push(v);	
		 			} else {
			 			let v = `<li><bdi>${ langname }</bdi>${ $.i18n("colon") }${ row.join($.i18n("comma")) }</li>`;
						output.push(v);
		 			}
		 		}

				if (!synonyms.length && !output.length && !output_sgn.length) {
					continue;
				}

				let ststart = '<div class="userjs-lexemetranslations">';
				if (synonyms.length) {
					ststart += '<div class="synonyms">'
						+ `<h4 dir="auto">${ $.i18n("synonyms") }</h4>`
						+ `<ul>${ synonyms.join("") }</ul>`
						+ '</div>';
				}
				if (output.length || output_sgn.length) {
					ststart += '<div class="translations">'
					+ '<h4 dir="auto">' + $.i18n("translations") + '</h4>'
					if (output.length) {
						ststart += "<ul>" + output.join("") + "</ul>";
					}
					if (output_sgn.length) {
						ststart += "<ul class=\"sgn\">" + output_sgn.join("") + "</ul>";
					}
					ststart += '</div>';
				}
				ststart += '</div>';

				document.querySelector("#" + sid + " .wikibase-lexeme-sense-statements").insertAdjacentHTML("beforeend", ststart);
			}
		});
	}

	function process_signwriting(word) {
		return "<span class=\"signwriting\">"
			+ "<span class=\"text\" style=\"font-size: 0%\">"
			+ word
			+ "</span>"
			+ "<img src=\"https://swis.wmflabs.org/glyphogram.php?font=svg&text=" + word + "&line=0645ad\" alt=\"" + word + "\"/>"
			+ "</span>"
			+ "</span>";
	}

	mw.loader.using("jquery.i18n").then(function () {
	mw.hook("wikibase.entityPage.entityView.rendered").add(function () {
		mw.hook("wikibase.entityPage.entityLoaded").add(function (e) {
			$.i18n().load(translations);
			fetch_translations(e);
		});
	});
	});

})();