/g, "")
.replace(/<\/code>/g, "")
.replace(/>/g, ">")
.replace(/</g, "<");
- a.attr("class", "suggesting").html("...");
-
- $.getJSON(
- rosetta_settings.translate_text_url,
- {
- from: sourceLang,
- to: destLang,
- text: orig,
- },
- function (data) {
+ const params = new URLSearchParams({
+ from: rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE,
+ to: rosetta_settings.rosetta_i18n_lang_code_normalized,
+ text: origUnescaped,
+ });
+ const url = `${rosetta_settings.translate_text_url}?${params.toString()}`;
+ fetch(url)
+ .then((r) => r.json())
+ .then((data) => {
if (data.success) {
- trans.val(
+ setTranslation(
unescape(data.translation)
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/%\s+(\([^)]+\))\s*s/g, " %$1s "),
);
- a.hide();
} else {
- a.text(data.error);
+ setError(data);
}
- },
- );
+ })
+ .catch(setError);
});
} else if (rosetta_settings.YANDEX_TRANSLATE_KEY) {
- $("a.suggest").click(function (e) {
- e.preventDefault();
- var a = $(this);
- var orig = $(".original .message", a.parents("tr")).html();
- var trans = $("textarea", a.parent());
- var apiUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate";
- var destLangRoot = rosetta_settings.rosetta_i18n_lang_code.split("-")[0];
- var lang = rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE + "-" + destLangRoot;
-
- a.attr("class", "suggesting").html("...");
-
- var apiData = {
+ suggest((orig, setTranslation, setError) => {
+ const apiUrl = "https://translate.yandex.net/api/v1.5/tr.json/translate";
+ const destLangRoot = rosetta_settings.rosetta_i18n_lang_code.split("-")[0];
+ const lang = rosetta_settings.MESSAGES_SOURCE_LANGUAGE_CODE + "-" + destLangRoot;
+ const apiData = {
error: "onTranslationError",
success: "onTranslationComplete",
lang: lang,
@@ -72,86 +112,127 @@ $(document).ready(function () {
format: "html",
text: orig,
};
-
- $.ajax({
- url: apiUrl,
- data: apiData,
- dataType: "jsonp",
- success: function (response) {
- if (response.code == 200) {
- trans.val(
- response.text[0]
- .replace(/
/g, "\n")
- .replace(/<\/?code>/g, "")
- .replace(/</g, "<")
- .replace(/>/g, ">"),
- );
- a.hide();
- } else {
- a.text(response);
- }
- },
- error: function (response) {
- a.text(response);
- },
+ jsonp(apiUrl, apiData, (response) => {
+ if (response.code === 200) {
+ setTranslation(
+ response.text[0]
+ .replace(/< ?br>/g, "\n")
+ .replace(/< ?\/? ?code>/g, "")
+ .replace(/</g, "<")
+ .replace(/>/g, ">"),
+ );
+ } else {
+ setError(response);
+ }
});
});
}
}
- $("td.plural").each(function () {
- var td = $(this);
- var trY = parseInt(td.closest("tr").offset().top);
- $("textarea", $(this).closest("tr")).each(function (j) {
- var textareaY = parseInt($(this).offset().top) - trY;
- $($(".part", td).get(j)).css("top", textareaY + "px");
+ // Make textarea height adapt to the contents
+ function autofitTextarea(textarea) {
+ textarea.style.height = "auto";
+ textarea.style.height = textarea.scrollHeight + "px";
+ }
+
+ // If there are multiple textareas for plurals then align the originals vertically with the textareas
+ function alignPlurals() {
+ document.querySelectorAll(".results td.plural").forEach((td) => {
+ const tr = td.closest("tr");
+ const trY = tr.getBoundingClientRect().top + window.scrollY;
+ tr.querySelectorAll("textarea").forEach((textarea, i) => {
+ const part = td.querySelectorAll(".part")[i];
+ if (part) {
+ const textareaY = textarea.getBoundingClientRect().top + window.scrollY - trY;
+ part.style.top = textareaY + "px";
+ }
+ });
});
+ }
+
+ // Show warning if the variables in the original and the translation don't match
+ function validateTranslation(textarea) {
+ const orig = originalForTextarea(textarea);
+ const variablePattern = /%(?:\([^\s)]*\))?[sdf]|\{[^\s}]*\}/g;
+ const origVars = orig.match(variablePattern) || [];
+ const transVars = textarea.value.match(variablePattern) || [];
+ const everyOrigVarUsed = origVars.every((origVar) => transVars.includes(origVar));
+ const onlyValidVarsUsed = transVars.every((transVar) => origVars.includes(transVar));
+ const valid = everyOrigVarUsed && onlyValidVarsUsed;
+ textarea.previousElementSibling.classList.toggle("hidden", valid);
+ }
+
+ // Select all the textareas that are used for translations
+ const textareas = document.querySelectorAll(".translation textarea");
+
+ // For each translation field textarea
+ textareas.forEach((textarea) => {
+ // On page load make textarea height adapt to its contents
+ autofitTextarea(textarea);
+
+ // On input
+ textarea.addEventListener("input", () => {
+ // Make textarea height adapt to its contents
+ autofitTextarea(textarea);
+
+ // If there are multiple textareas for plurals then align the originals vertically with the textareas
+ alignPlurals();
+
+ // Once users start editing the translation untick the fuzzy checkbox automatically
+ textarea.closest("tr").querySelector('td.c input[type="checkbox"]').checked = false;
+ });
+
+ // On blur show warnings for unmatched variables in translations
+ textarea.addEventListener("blur", () => validateTranslation(textarea));
});
- $(".translation textarea")
- .blur(function () {
- if ($(this).val()) {
- $(".alert", $(this).parents("tr")).remove();
- var RX = /%(?:\([^\s)]*\))?[sdf]|\{[\w\d_]+?\}/g;
- var origs = $(this).parents("tr").find(".original span").html().match(RX);
- var trads = $(this).val().match(RX);
- var error = $('Unmatched variables');
-
- if (origs && trads) {
- for (var i = trads.length; i--; ) {
- var key = trads[i];
- if (-1 == $.inArray(key, origs)) {
- $(this).before(error);
- return false;
- }
- }
- return true;
- } else {
- if (!(origs === null && trads === null)) {
- $(this).before(error);
- return false;
- }
- }
- return true;
- }
- })
- .keyup(function () {
- var cb = $(this).parents("tr").find('td.c input[type="checkbox"]');
- if (cb.is(":checked")) {
- cb[0].checked = false;
- cb.removeAttr("checked");
- }
- })
- .eq(0)
- .focus();
-
- $("#action-toggle").change(function () {
- $('tbody td.c input[type="checkbox"]').each(function (i, e) {
- if ($("#action-toggle").is(":checked")) {
- $(e).attr("checked", "checked");
- } else {
- $(e).removeAttr("checked");
- }
+ // On window resize make textarea height adapt to their contents
+ window.addEventListener("resize", () => textareas.forEach(autofitTextarea), { passive: true });
+
+ // On page load if there are multiple textareas in a cell for plurals then align the originals vertically with them
+ alignPlurals();
+
+ // Reload page when changing ref-language
+ document.getElementById("ref-language-selector")?.addEventListener("change", function () {
+ window.location.href = this.value;
+ });
+
+ // Toggle fuzzy state for all entries on the current page
+ document.getElementById("action-toggle")?.addEventListener("change", function () {
+ const checkboxes = document.querySelectorAll('tbody td.c input[type="checkbox"]');
+ checkboxes.forEach((checkbox) => (checkbox.checked = this.checked));
+ });
+
+ // Toggle additional locations that are initially hidden
+ document.querySelectorAll(".location a").forEach((link) => {
+ link.addEventListener("click", (event) => {
+ event.preventDefault();
+ const prevText = link.innerText;
+ link.innerText = link.dataset.prevText;
+ link.dataset.prevText = prevText;
+ link.parentElement.querySelectorAll(".hide").forEach((loc) => {
+ const hidden = loc.style.display === "none" || loc.style.display === "";
+ loc.style.display = hidden ? "block" : "none";
+ });
});
});
+
+ // Warn about any unsaved changes before navigating away from the page
+ const form = document.querySelector("form.results");
+ function formToJsonString() {
+ const obj = {};
+ new FormData(form).forEach((value, key) => (obj[key] = value));
+ return JSON.stringify(obj);
+ }
+ if (form) {
+ const initialDataJson = formToJsonString();
+ let isSubmitting = false;
+ form.addEventListener("submit", () => (isSubmitting = true));
+ window.addEventListener("beforeunload", (event) => {
+ if (!isSubmitting && initialDataJson !== formToJsonString()) {
+ event.preventDefault();
+ event.returnValue = "";
+ }
+ });
+ }
});
diff --git a/rosetta/templates/rosetta/base.html b/rosetta/templates/rosetta/base.html
index e7156bc..19207d7 100644
--- a/rosetta/templates/rosetta/base.html
+++ b/rosetta/templates/rosetta/base.html
@@ -1,7 +1,7 @@
{% load static %}
-
+
{% block pagetitle %}Rosetta{% endblock %}
@@ -11,7 +11,6 @@
{% block extra_styles %}{% endblock %}
-
{{ rosetta_settings_js|json_script:"rosetta-settings-js" }}
diff --git a/rosetta/templates/rosetta/form.html b/rosetta/templates/rosetta/form.html
index d58a237..95b5101 100644
--- a/rosetta/templates/rosetta/form.html
+++ b/rosetta/templates/rosetta/form.html
@@ -56,7 +56,7 @@ {% blocktrans %}Translate into {{ rosetta_i18n_lang_name }}{% endblocktrans
{% if rosetta_settings.ENABLE_REFLANG %}
-