diff --git a/integreat_cms/cms/templates/dashboard/_todo_dashboard_widget.html b/integreat_cms/cms/templates/dashboard/_todo_dashboard_widget.html
index 15e8b244b8..b376b1393c 100644
--- a/integreat_cms/cms/templates/dashboard/_todo_dashboard_widget.html
+++ b/integreat_cms/cms/templates/dashboard/_todo_dashboard_widget.html
@@ -30,5 +30,8 @@
{% include "dashboard/todo_dashboard_rows/_automatically_saved_pages_row.html" %}
{% include "dashboard/todo_dashboard_rows/_drafted_pages_row.html" %}
{% endif %}
+ {% if perms.cms.change_page %}
+ {% include "dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html" %}
+ {% endif %}
{% endblock collapsible_box_content %}
diff --git a/integreat_cms/cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html b/integreat_cms/cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html
new file mode 100644
index 0000000000..29cbfafe6d
--- /dev/null
+++ b/integreat_cms/cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html
@@ -0,0 +1,38 @@
+{% extends "../_todo_dashboard_row.html" %}
+{% load i18n %}
+{% block todo_dashboard_icon %}
+ languages
+{% endblock todo_dashboard_icon %}
+{% block todo_dashboard_title_link %}
+ {% url 'translation_coverage' region_slug=request.region.slug %}
+{% endblock todo_dashboard_title_link %}
+{% block todo_dashboard_title %}
+ {% translate "Outdated and missing translations" %}
+{% endblock todo_dashboard_title %}
+{% block todo_dashboard_number %}
+ {% with total=number_of_missing_translations %}
+ {{ block.super }}
+ {% endwith %}
+{% endblock todo_dashboard_number %}
+{% block todo_dashboard_description %}
+ {% if number_of_missing_translations > 0 %}
+ {% blocktranslate trimmed %}
+ Your pages currently have {{ number_of_missing_translations }} pages that have an outdated or no translation.
+ In order for the users to benefit from your content you should translate them or have them translated.
+ {% endblocktranslate %}
+ {% else %}
+ {% blocktranslate trimmed %}
+ At the moment all pages have up-to-date translations. Good job!
+ {% endblocktranslate %}
+ {% endif %}
+{% endblock todo_dashboard_description %}
+{% block todo_dashboard_button_link %}
+ {% if outdated_pages %}
+
+ {% translate "Go to
translation coverage" %}
+
+ {% else %}
+
+ {% endif %}
+{% endblock todo_dashboard_button_link %}
diff --git a/integreat_cms/cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html b/integreat_cms/cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
new file mode 100644
index 0000000000..ac0e295a54
--- /dev/null
+++ b/integreat_cms/cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
@@ -0,0 +1,53 @@
+{% extends "../_todo_dashboard_row.html" %}
+{% load i18n %}
+{% block todo_dashboard_ajax_url %}
+ {{ translation_coverage_ajax }}
+{% endblock todo_dashboard_ajax_url %}
+{% block todo_dashboard_id %}
+ translation-coverage
+{% endblock todo_dashboard_id %}
+{% block todo_dashboard_icon %}
+ languages
+{% endblock todo_dashboard_icon %}
+{% block todo_dashboard_title_link %}
+ {% url 'translation_coverage' region_slug=request.region.slug %}
+{% endblock todo_dashboard_title_link %}
+{% block todo_dashboard_title %}
+ {% translate "Outdated and missing translations" %}
+{% endblock todo_dashboard_title %}
+{% block todo_dashboard_number %}
+ {% with total=number_of_missing_or_outdated_translations %}
+ {{ block.super }}
+ {% endwith %}
+{% endblock todo_dashboard_number %}
+{% block todo_dashboard_description %}
+
+ {# djlint:off #}
+ {% blocktranslate trimmed %}
+ Your pages currently have pages that have an outdated or no translation.
+ In order for the users to benefit from your content you should translate them or have them translated.
+ {% endblocktranslate %}
+ {# djlint:on #}
+
+
+ {% blocktranslate trimmed %}
+ At the moment all pages have up-to-date translations. Good job!
+ {% endblocktranslate %}
+
+
+ {% blocktranslate trimmed %}
+ We are loading your outdated and missing translations in the background. Please be patient.
+ {% endblocktranslate %}
+
+{% endblock todo_dashboard_description %}
+{% block todo_dashboard_button_link %}
+
+
+
+
+{% endblock todo_dashboard_button_link %}
diff --git a/integreat_cms/cms/urls/protected.py b/integreat_cms/cms/urls/protected.py
index 4e3ae8c9b8..0536de7aa9 100644
--- a/integreat_cms/cms/urls/protected.py
+++ b/integreat_cms/cms/urls/protected.py
@@ -584,7 +584,12 @@
"broken-links/",
dashboard.DashboardView.get_broken_links_context,
name="get_broken_links_ajax",
- )
+ ),
+ path(
+ "translation-coverage/",
+ dashboard.DashboardView.get_translation_coverage_context,
+ name="get_translation_coverage_ajax",
+ ),
]
),
),
diff --git a/integreat_cms/cms/views/dashboard/dashboard_view.py b/integreat_cms/cms/views/dashboard/dashboard_view.py
index 8444b81573..c7a4f3a030 100644
--- a/integreat_cms/cms/views/dashboard/dashboard_view.py
+++ b/integreat_cms/cms/views/dashboard/dashboard_view.py
@@ -61,6 +61,10 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
"get_broken_links_ajax",
kwargs={"region_slug": self.request.region.slug},
),
+ "translation_coverage_ajax": reverse(
+ "get_translation_coverage_ajax",
+ kwargs={"region_slug": self.request.region.slug},
+ ),
}
)
@@ -242,3 +246,31 @@ def get_drafted_pages(
"drafted_pages": drafted_pages,
"single_drafted_page": single_drafted_page,
}
+
+ @json_response
+ # pylint: disable=unused-argument, disable=no-self-argument
+ def get_translation_coverage_context(
+ request: HttpRequest, region_slug: str
+ ) -> JsonResponse:
+ r"""
+ Extend context by info on translation coverage of pages
+
+ :return: Dictionary containing the context for translation coverage of pages in a region
+ """
+ non_archived_pages = request.region.non_archived_pages
+ language_count = len(request.region.languages)
+ possible_translations = non_archived_pages.count() * language_count
+
+ all_translations = PageTranslation.objects.filter(
+ page__in=non_archived_pages
+ ).only("pk")
+ non_outdated_translations = sum(
+ 1 for translation in all_translations if not translation.is_outdated
+ )
+
+ return JsonResponse(
+ data={
+ "number_of_missing_or_outdated_translations": possible_translations
+ - non_outdated_translations
+ }
+ )
diff --git a/integreat_cms/locale/de/LC_MESSAGES/django.po b/integreat_cms/locale/de/LC_MESSAGES/django.po
index 5ca752db97..13aea1a632 100644
--- a/integreat_cms/locale/de/LC_MESSAGES/django.po
+++ b/integreat_cms/locale/de/LC_MESSAGES/django.po
@@ -5718,6 +5718,32 @@ msgid "At the moment all pages are ready for machine translation. Good job!"
msgstr ""
"Aktuell sind alle Seiten bereit zur maschinellen Übersetzung. Gute Arbeit!"
+#: cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html
+#: cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
+msgid "Outdated and missing translations"
+msgstr "Veraltete und fehlende Übersetzungen"
+
+#: cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html
+#, python-format
+msgid ""
+"Your pages currently have %(number_of_missing_translations)s pages "
+"b>that have an outdated or no translation. In order for the users to benefit "
+"from your content you should translate them or have them translated."
+msgstr ""
+"Ihre Inhalte haben momentan %(number_of_missing_translations)s Seiten "
+"mit fehlender oder veralteter Übersetzung. Damit die Nutzer:innen von Ihren "
+"Inhalten profitieren können, sollten Sie diese übersetzen (lassen)."
+
+#: cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html
+#: cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
+msgid "At the moment all pages have up-to-date translations. Good job!"
+msgstr "Aktuell haben alle Ihre Seiten aktuelle Übersetzungen. Gute Arbeit!"
+
+#: cms/templates/dashboard/todo_dashboard_rows/_missing_translations_row.html
+#: cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
+msgid "Go to
translation coverage"
+msgstr "Zum Bericht"
+
#: cms/templates/dashboard/todo_dashboard_rows/_outdated_pages_row.html
msgid "Outdated pages"
msgstr "Veraltete Seiten"
@@ -5778,6 +5804,24 @@ msgstr ""
msgid "At the moment there is no page waiting for approval. Good job!"
msgstr "Aktuell liegen keine Seiten zur Freigabe vor. Gute Arbeit!"
+#: cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
+msgid ""
+"Your pages currently have pages that have an outdated or no "
+"translation. In order for the users to benefit from your content you should "
+"translate them or have them translated."
+msgstr ""
+"Ihre Inhalte haben momentan Seiten mit fehlender oder "
+"veralteter Übersetzung. Damit die Nutzer:innen von Ihren Inhalten "
+"profitieren können, sollten Sie diese übersetzen (lassen)."
+
+#: cms/templates/dashboard/todo_dashboard_rows/number_of_missing_or_outdated_translations_row.html
+msgid ""
+"We are loading your outdated and missing translations in the background. "
+"Please be patient."
+msgstr ""
+"Wir laden im Hintergrund gerade Ihre kaputten Links. Bitte haben Sie noch "
+"ein wenig Geduld."
+
#: cms/templates/emails/_base_email.html
#: cms/templates/emails/password_reset_email.txt
#: cms/templates/emails/welcome_email.txt
@@ -11285,6 +11329,25 @@ msgstr ""
#~ msgid "Contents"
#~ msgstr "Inhalte"
+#~ msgid ""
+#~ "We are loading your translation coverage in the background. Please be "
+#~ "patient."
+#~ msgstr ""
+#~ "Wir laden im Hintergrund gerade Ihre veralteten und fehlenden "
+#~ "Übersetzungen. Bitte haben Sie noch ein wenig Geduld."
+
+#, python-format
+#~ msgid ""
+#~ "Your pages currently have "
+#~ "%(number_of_missing_or_outdated_translations)s pages that have an "
+#~ "outdated or no translation. In order for the users to benefit from your "
+#~ "content you should translate them or have them translated."
+#~ msgstr ""
+#~ "Ihre Inhalten haben aktuell "
+#~ "%(number_of_missing_or_outdated_translations)s Seiten mit "
+#~ "fehlender oder veralteter Übersetzung. Damit die Nutzer:innen von Ihren "
+#~ "Inhalten profitieren können, sollten Sie diese übersetzen (lassen)."
+
#~ msgid "View location"
#~ msgstr "Ort ansehen"
@@ -12568,9 +12631,6 @@ msgstr ""
#~ msgid "Translations missing"
#~ msgstr "Fehlende Übersetzungen"
-#~ msgid "Translation Coverage"
-#~ msgstr "Abdeckung der Übersetzungen"
-
#~ msgid "Settings for"
#~ msgstr "Einstellungen für"
diff --git a/integreat_cms/static/src/index.ts b/integreat_cms/static/src/index.ts
index b81aae6203..ff68c6c3a2 100644
--- a/integreat_cms/static/src/index.ts
+++ b/integreat_cms/static/src/index.ts
@@ -103,6 +103,7 @@ import "./js/menu";
import "./js/poi-categories/poicategory-colors-icons";
import "./js/dashboard/broken-links";
+import "./js/dashboard/translation-coverage";
// IE11: fetch
/* eslint-disable-next-line @typescript-eslint/no-var-requires */
diff --git a/integreat_cms/static/src/js/dashboard/translation-coverage.ts b/integreat_cms/static/src/js/dashboard/translation-coverage.ts
new file mode 100644
index 0000000000..ed41d473d2
--- /dev/null
+++ b/integreat_cms/static/src/js/dashboard/translation-coverage.ts
@@ -0,0 +1,84 @@
+import { getCsrfToken } from "../utils/csrf-token";
+
+type Content = {
+ number_of_missing_or_outdated_translations: number;
+};
+
+const getContent = async (url: string): Promise => {
+ const response = await fetch(url, {
+ method: "POST",
+ headers: {
+ "X-CSRFToken": getCsrfToken(),
+ },
+ });
+ return response.json();
+};
+
+const showAllTotalNumbers = () => {
+ const elements = document.querySelectorAll(".total-results");
+
+ elements.forEach((element) => {
+ if (!element.closest("#translation-coverage")) {
+ const el = element;
+ el.classList.remove("hidden");
+ }
+ });
+};
+
+window.addEventListener("load", async () => {
+ showAllTotalNumbers();
+
+ const translationCoverageElement = document.getElementById("translation-coverage");
+
+ if (!translationCoverageElement) {
+ return;
+ }
+
+ const url = translationCoverageElement.dataset.url;
+ const hideWaitingMessage = () => {
+ translationCoverageElement.querySelector(".waiting-message").classList.add("hidden");
+ };
+
+ const showSuccessMessage = () => {
+ translationCoverageElement.querySelector(".success-message").classList.remove("hidden");
+ };
+
+ const showSuccessIcon = () => {
+ translationCoverageElement.querySelector(".success-icon").classList.remove("hidden");
+ };
+
+ const showDescription = (affectedPageTitle: string) => {
+ translationCoverageElement.querySelector(".todo-message").classList.remove("hidden");
+ (translationCoverageElement.querySelector(".todo-message b") as HTMLElement).innerText = affectedPageTitle;
+ };
+
+ const showNumberOfPagesElement = () => {
+ translationCoverageElement.querySelector(".total-results").classList.remove("hidden");
+ };
+
+ const updateNumberOfPages = (numberOfPages: number) => {
+ (translationCoverageElement.querySelector(".total-results span") as HTMLElement).innerText =
+ numberOfPages.toString();
+ };
+
+ const showButton = () => {
+ translationCoverageElement.querySelector(".todo-button").classList.remove("hidden");
+ };
+
+ if (url) {
+ console.log(url);
+ const json = await getContent(url);
+ console.log(json);
+ hideWaitingMessage();
+ showNumberOfPagesElement();
+ if (json.number_of_missing_or_outdated_translations > 0) {
+ showDescription(String(json.number_of_missing_or_outdated_translations));
+ updateNumberOfPages(json.number_of_missing_or_outdated_translations);
+ showButton();
+ } else {
+ showSuccessMessage();
+ showSuccessIcon();
+ updateNumberOfPages(0);
+ }
+ }
+});