From 361a406d0003dcaac211261941e5d672d521a4ce Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Fri, 10 Jul 2026 12:32:04 +0545 Subject: [PATCH 1/6] [Fixes #13646] : Use multilang entries in ISO19115 --- .../templates/catalogue/full_metadata.xml | 43 ++++- geonode/catalogue/templatetags/__init__.py | 18 ++ geonode/catalogue/templatetags/multilang.py | 178 ++++++++++++++++++ geonode/catalogue/tests.py | 131 ++++++++++++- geonode/metadata/multilang/utils.py | 19 ++ geonode/settings.py | 35 ++++ 6 files changed, 419 insertions(+), 5 deletions(-) create mode 100644 geonode/catalogue/templatetags/__init__.py create mode 100644 geonode/catalogue/templatetags/multilang.py diff --git a/geonode/catalogue/templates/catalogue/full_metadata.xml b/geonode/catalogue/templates/catalogue/full_metadata.xml index d54c45a59a7..0cb7cbd65f5 100644 --- a/geonode/catalogue/templates/catalogue/full_metadata.xml +++ b/geonode/catalogue/templates/catalogue/full_metadata.xml @@ -1,11 +1,14 @@ {% load thesaurus %} {% load l10n %} +{% load multilang %} {{layer.uuid}} + {% language_info layer.language as resource_language %} + {% languages_info metadata as locales %} - {{layer.language}} + {{ resource_language.label }} utf8 @@ -91,6 +94,18 @@ ISO 19115:2003 + {% for locale in locales %} + + + + {{ locale.label }} + + + {{ locale.encoding }} + + + + {% endfor %} @@ -114,8 +129,18 @@ - + {% multilang_values "title" metadata as title_translations %} + {{layer.title}} + {% if title_translations %} + + {% for t in title_translations %} + + {{ t.text }} + + {% endfor %} + + {% endif %} {% if layer.alternate %} @@ -149,8 +174,18 @@ - + {% multilang_values "abstract" metadata as abstract_translations %} + {{layer.raw_abstract}} + {% if abstract_translations %} + + {% for t in abstract_translations %} + + {{ t.text }} + + {% endfor %} + + {% endif %} {% if layer.raw_purpose %}{{layer.raw_purpose}}{% endif %} @@ -370,7 +405,7 @@ {% endif %} - {{layer.language}} + {{ resource_language.label }} utf8 diff --git a/geonode/catalogue/templatetags/__init__.py b/geonode/catalogue/templatetags/__init__.py new file mode 100644 index 00000000000..b9f924aa335 --- /dev/null +++ b/geonode/catalogue/templatetags/__init__.py @@ -0,0 +1,18 @@ +######################################################################### +# +# Copyright (C) 2026 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### diff --git a/geonode/catalogue/templatetags/multilang.py b/geonode/catalogue/templatetags/multilang.py new file mode 100644 index 00000000000..510c270600f --- /dev/null +++ b/geonode/catalogue/templatetags/multilang.py @@ -0,0 +1,178 @@ +######################################################################### +# +# Copyright (C) 2026 OSGeo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +######################################################################### + +import logging + +from django import template +from django.conf import settings + +from geonode.metadata.multilang.utils import ( + get_2letters_languages, + get_default_language, + get_3_from_2, + get_2_from_3, + get_multilang_field_name, +) + +register = template.Library() +logger = logging.getLogger(__name__) + + +def _is_multilang_field(field_name): + """Return whether a field is configured as multilingual.""" + return field_name in getattr(settings, "MULTILANG_FIELDS", ()) + + +def _language_label(language_code_2, fallback): + """ + Return the display label for a configured language. + """ + language_labels = {code.split("-")[0]: label for code, label in settings.LANGUAGES} + + return language_labels.get(language_code_2, fallback) + + +def _translation_value(metadata, field_name, language_code): + """Return the translated value for a multilingual field.""" + if not metadata: + return None + + return metadata.get(get_multilang_field_name(field_name, language_code)) + + +def _has_translation(metadata, field_name, language_code): + """Return whether the field has a translation for the given language.""" + return bool( + _translation_value( + metadata, + field_name, + language_code, + ) + ) + + +def _language_descriptor(lang_2, lang_3): + """Build the language descriptor used by the ISO template.""" + return { + "id": f"locale-{lang_2}", + "iso639_2": lang_3, + "label": _language_label(lang_2, lang_3), + "encoding": "utf8", + } + + +@register.filter(name="is_multilang") +def is_multilang(field_name): + """Return whether the field is configured as multilingual.""" + return _is_multilang_field(field_name) + + +@register.simple_tag(name="multilang_values") +def multilang_values(field_name, metadata): + """ + Return all translations for a multilingual field except the default + language. + """ + if not metadata or not _is_multilang_field(field_name): + return [] + + default_language = get_default_language() + translations = [] + + for language_code in get_2letters_languages(): + if language_code == default_language: + continue + + translated_text = _translation_value( + metadata, + field_name, + language_code, + ) + + if translated_text: + translations.append( + { + "locale": language_code, + "text": translated_text, + } + ) + + return translations + + +@register.simple_tag(name="language_info") +def language_info(lang_3): + """ + Return information about a single ISO639-2 language. + """ + lang_2 = get_2_from_3(lang_3) + + if lang_2 is None: + logger.warning( + "No ISO639-1 language found for '%s'; using the ISO639-2 code as the label.", + lang_3, + ) + + return _language_descriptor( + lang_2 or lang_3, + lang_3, + ) + + +@register.simple_tag(name="languages_info") +def languages_info(metadata, fields=None): + """ + Return descriptors for all translated languages present in the metadata. + """ + if not metadata: + return [] + + multilingual_fields = fields if fields is not None else getattr(settings, "MULTILANG_FIELDS", ()) + + default_language = get_default_language() + descriptors = [] + + for language_code in get_2letters_languages(): + if language_code == default_language: + continue + + if not any( + _has_translation(metadata, field_name, language_code) + for field_name in multilingual_fields + if _is_multilang_field(field_name) + ): + continue + + lang_3 = get_3_from_2(language_code) + + if lang_3 is None: + logger.warning( + "No entry in settings.LANGUAGE_MAPPINGS for language '%s'; " "skipping this entry.", + language_code, + ) + continue + + descriptors.append( + _language_descriptor( + language_code, + lang_3, + ) + ) + + return descriptors diff --git a/geonode/catalogue/tests.py b/geonode/catalogue/tests.py index 139b2104553..1c306927690 100644 --- a/geonode/catalogue/tests.py +++ b/geonode/catalogue/tests.py @@ -21,11 +21,12 @@ from django.db.models import Q from django.template.loader import get_template -from django.test import RequestFactory +from django.test import RequestFactory, override_settings from django.http.response import Http404 from django.core.exceptions import PermissionDenied from geonode.layers.models import Dataset from geonode.catalogue import get_catalogue +from geonode.catalogue.templatetags import multilang as tags from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser @@ -208,3 +209,131 @@ def test_localized_keyword_in_metadata(self): rendered_xml = template.render(context) self.assertIn(self.keyword_label.label, rendered_xml) self.assertNotIn(self.keyword.alt_label, rendered_xml) + + +@override_settings(MULTILANG_FIELDS=["title", "abstract"]) +class IsMultilangFilterTest(GeoNodeBaseTestSupport): + + def test_configured_field(self): + self.assertTrue(tags.is_multilang("title")) + self.assertTrue(tags.is_multilang("abstract")) + + def test_unconfigured_field(self): + self.assertFalse(tags.is_multilang("purpose")) + + @override_settings(MULTILANG_FIELDS=()) + def test_feature_off(self): + self.assertFalse(tags.is_multilang("title")) + + +@override_settings(MULTILANG_FIELDS=["title", "abstract"]) +class MultilangValuesTagTests(GeoNodeBaseTestSupport): + + def test_field_not_configured(self): + metadata = {"purpose": "x", "purpose_multilang_it": "scopo"} + self.assertEqual([], tags.multilang_values("purpose", metadata)) + + def test_configured_field_no_translations(self): + self.assertEqual([], tags.multilang_values("title", {"title": "Hello"})) + + def test_excludes_default_language(self): + metadata = { + "title": "Hello", + "title_multilang_en": "Hello", + "title_multilang_it": "Ciao", + } + self.assertEqual([{"locale": "it", "text": "Ciao"}], tags.multilang_values("title", metadata)) + + def test_excludes_empty_string_translation(self): + metadata = { + "title": "Hello", + "title_multilang_de": "", + "title_multilang_it": "Ciao", + } + self.assertEqual([{"locale": "it", "text": "Ciao"}], tags.multilang_values("title", metadata)) + + def test_multiple_translations(self): + metadata = { + "title": "Hello", + "title_multilang_de": "Hallo", + "title_multilang_es": "Hola", + "title_multilang_fr": "Bonjour", + "title_multilang_it": "Ciao", + } + result = {t["locale"]: t["text"] for t in tags.multilang_values("title", metadata)} + self.assertEqual({"de": "Hallo", "es": "Hola", "fr": "Bonjour", "it": "Ciao"}, result) + + def test_empty_or_missing_metadata(self): + self.assertEqual([], tags.multilang_values("title", {})) + self.assertEqual([], tags.multilang_values("title", None)) + + +@override_settings(MULTILANG_FIELDS=["title", "abstract"]) +class LanguageTemplateTagTests(GeoNodeBaseTestSupport): + + def test_language_info_known_code(self): + result = tags.language_info("ita") + self.assertEqual( + { + "id": "locale-it", + "iso639_2": "ita", + "label": "Italiano", + "encoding": "utf8", + }, + result, + ) + + def test_language_info_code_with_bibliographic_terminological_variant(self): + result = tags.language_info("fra") + self.assertEqual("locale-fr", result["id"]) + self.assertEqual("fra", result["iso639_2"]) + self.assertEqual("Français", result["label"]) + + def test_languages_info_empty_or_missing_metadata(self): + self.assertEqual([], tags.languages_info({})) + self.assertEqual([], tags.languages_info(None)) + + def test_languages_info_no_translations(self): + metadata = {"title": "Hello", "abstract": "An abstract"} + self.assertEqual([], tags.languages_info(metadata)) + + def test_languages_info_across_fields(self): + metadata = { + "title": "Hello", + "title_multilang_it": "Ciao", + "title_multilang_de": "Hallo", + "abstract": "An abstract", + "abstract_multilang_fr": "Un résumé", + "abstract_multilang_de": "Eine Zusammenfassung", + } + result = {d["id"]: d for d in tags.languages_info(metadata)} + self.assertEqual({"locale-it", "locale-de", "locale-fr"}, set(result.keys())) + self.assertEqual( + { + "id": "locale-it", + "iso639_2": "ita", + "label": "Italiano", + "encoding": "utf8", + }, + result["locale-it"], + ) + + def test_languages_info_respects_explicit_fields_argument(self): + metadata = { + "title": "Hello", + "title_multilang_it": "Ciao", + "abstract": "x", + "abstract_multilang_fr": "Un résumé", + } + result = tags.languages_info(metadata, fields=["title"]) + self.assertEqual(["locale-it"], [d["id"] for d in result]) + + @override_settings(LANGUAGE_MAPPINGS=(("en", "eng"), ("it", "ita"))) + def test_languages_info_missing_iso_mapping_is_skipped_and_warns(self): + metadata = { + "title": "Hello", + "title_multilang_it": "Ciao", + "title_multilang_de": "Hallo", + } + result = tags.languages_info(metadata) + self.assertEqual(["locale-it"], [d["id"] for d in result]) diff --git a/geonode/metadata/multilang/utils.py b/geonode/metadata/multilang/utils.py index d92b05d7490..55104d3c81d 100644 --- a/geonode/metadata/multilang/utils.py +++ b/geonode/metadata/multilang/utils.py @@ -59,3 +59,22 @@ def get_all_multilang_fields(): for field in settings.MULTILANG_FIELDS for lang in get_2letters_languages() } + + +def get_3_from_2(lang_2): + """ + Return the preferred ISO 639-2 (3-letter) code for an ISO 639-1 code. + """ + code = dict(getattr(settings, "LANGUAGE_MAPPINGS", ())).get(lang_2) + return code[0] if isinstance(code, (list, tuple)) else code + + +def get_2_from_3(lang_3): + """ + Return the ISO 639-1 (2-letter) code for a given ISO 639-2 (3-letter) code. + """ + for lang_2, lang_3_codes in getattr(settings, "LANGUAGE_MAPPINGS", ()): + codes = lang_3_codes if isinstance(lang_3_codes, (list, tuple)) else (lang_3_codes,) + if lang_3 in codes: + return lang_2 + return None diff --git a/geonode/settings.py b/geonode/settings.py index c45f6d17861..bc847d6e9aa 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2144,6 +2144,41 @@ def get_geonode_catalogue_service(): "all": ["title", "abstract", "supplemental_information"], } +# Maps ISO 639-1 (2-letter) codes to ISO 639-2 (3-letter) code(s) +LANGUAGE_MAPPINGS = ( + ("af", "afr"), + ("sq", "alb"), + ("am", "amh"), + ("ar", "ara"), + ("id", "ind"), + ("bn", "ben"), + ("de", ["ger", "deu"]), + ("en", "eng"), + ("es", "spa"), + ("fr", ["fre", "fra"]), + ("it", "ita"), + ("km", "khm"), + ("nl", ["dut", "nld"]), + ("ne", "nep"), + ("fa", ["per", "fas"]), + ("pl", "pol"), + ("pt", "por"), + ("ru", "rus"), + ("si", "sin"), + ("sw", "swa"), + ("sv", "swe"), + ("tl", "tgl"), + ("ta", "tam"), + ("uk", "ukr"), + ("vi", "vie"), + ("el", ["gre", "ell"]), + ("th", "tha"), + ("zh", ["chi", "zho"]), + ("ja", "jpn"), + ("ko", "kor"), + ("sk", ["slo", "slk"]), +) + # you can get the language names in psql using "\dF" MULTILANG_POSTGRES_LANGS = { None: "simple", From 4bb2667f1c7b126f62db51ac519d39dbdcd0efec Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Fri, 10 Jul 2026 13:17:32 +0545 Subject: [PATCH 2/6] Update multilang tags against malformed mappings and duplicate locales --- geonode/catalogue/templatetags/multilang.py | 14 ++++++++++---- geonode/metadata/multilang/utils.py | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/geonode/catalogue/templatetags/multilang.py b/geonode/catalogue/templatetags/multilang.py index 510c270600f..7eae23a6766 100644 --- a/geonode/catalogue/templatetags/multilang.py +++ b/geonode/catalogue/templatetags/multilang.py @@ -89,16 +89,19 @@ def multilang_values(field_name, metadata): Return all translations for a multilingual field except the default language. """ - if not metadata or not _is_multilang_field(field_name): + if not isinstance(metadata, dict) or not _is_multilang_field(field_name): return [] default_language = get_default_language() translations = [] + included_languages = set() for language_code in get_2letters_languages(): - if language_code == default_language: + if language_code == default_language or language_code in included_languages: continue + included_languages.add(language_code) + translated_text = _translation_value( metadata, field_name, @@ -140,18 +143,21 @@ def languages_info(metadata, fields=None): """ Return descriptors for all translated languages present in the metadata. """ - if not metadata: + if not isinstance(metadata, dict): return [] multilingual_fields = fields if fields is not None else getattr(settings, "MULTILANG_FIELDS", ()) default_language = get_default_language() descriptors = [] + included_languages = set() for language_code in get_2letters_languages(): - if language_code == default_language: + if language_code == default_language or language_code in included_languages: continue + included_languages.add(language_code) + if not any( _has_translation(metadata, field_name, language_code) for field_name in multilingual_fields diff --git a/geonode/metadata/multilang/utils.py b/geonode/metadata/multilang/utils.py index 55104d3c81d..61f1b25cd86 100644 --- a/geonode/metadata/multilang/utils.py +++ b/geonode/metadata/multilang/utils.py @@ -65,15 +65,25 @@ def get_3_from_2(lang_2): """ Return the preferred ISO 639-2 (3-letter) code for an ISO 639-1 code. """ - code = dict(getattr(settings, "LANGUAGE_MAPPINGS", ())).get(lang_2) - return code[0] if isinstance(code, (list, tuple)) else code + mappings = getattr(settings, "LANGUAGE_MAPPINGS", ()) + if not isinstance(mappings, dict): + mappings = dict(mappings) + + code = mappings.get(lang_2) + if isinstance(code, (list, tuple)): + return code[0] if code else None + return code def get_2_from_3(lang_3): """ Return the ISO 639-1 (2-letter) code for a given ISO 639-2 (3-letter) code. """ - for lang_2, lang_3_codes in getattr(settings, "LANGUAGE_MAPPINGS", ()): + mappings = getattr(settings, "LANGUAGE_MAPPINGS", ()) + if isinstance(mappings, dict): + mappings = mappings.items() + + for lang_2, lang_3_codes in mappings: codes = lang_3_codes if isinstance(lang_3_codes, (list, tuple)) else (lang_3_codes,) if lang_3 in codes: return lang_2 From f51f9c5933a14b79daca7324214bc8268028da4b Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Tue, 14 Jul 2026 11:31:08 +0545 Subject: [PATCH 3/6] Updated language_info tag to handle different cases --- geonode/catalogue/templatetags/multilang.py | 23 ++++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/geonode/catalogue/templatetags/multilang.py b/geonode/catalogue/templatetags/multilang.py index 7eae23a6766..35d528a14e3 100644 --- a/geonode/catalogue/templatetags/multilang.py +++ b/geonode/catalogue/templatetags/multilang.py @@ -124,16 +124,23 @@ def language_info(lang_3): """ Return information about a single ISO639-2 language. """ - lang_2 = get_2_from_3(lang_3) - - if lang_2 is None: - logger.warning( - "No ISO639-1 language found for '%s'; using the ISO639-2 code as the label.", - lang_3, - ) + if not lang_3: + lang_2 = get_default_language() + lang_3 = get_3_from_2(lang_2) or "eng" + elif len(lang_3) == 2: + lang_2 = lang_3 + lang_3 = get_3_from_2(lang_2) or "eng" + else: + lang_2 = get_2_from_3(lang_3) + + if lang_2 is None: + logger.warning( + "No ISO639-1 language found for '%s'; using the ISO639-2 code as the label.", + lang_3, + ) return _language_descriptor( - lang_2 or lang_3, + lang_2, lang_3, ) From 83d9f1b40dde58b53058cf03f4b645a535bbb791 Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Wed, 15 Jul 2026 11:50:56 +0545 Subject: [PATCH 4/6] Update multilang template tags --- geonode/catalogue/templatetags/__init__.py | 18 ---- geonode/catalogue/templatetags/multilang.py | 105 ++++++-------------- 2 files changed, 29 insertions(+), 94 deletions(-) diff --git a/geonode/catalogue/templatetags/__init__.py b/geonode/catalogue/templatetags/__init__.py index b9f924aa335..e69de29bb2d 100644 --- a/geonode/catalogue/templatetags/__init__.py +++ b/geonode/catalogue/templatetags/__init__.py @@ -1,18 +0,0 @@ -######################################################################### -# -# Copyright (C) 2026 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### diff --git a/geonode/catalogue/templatetags/multilang.py b/geonode/catalogue/templatetags/multilang.py index 35d528a14e3..c0b128d2259 100644 --- a/geonode/catalogue/templatetags/multilang.py +++ b/geonode/catalogue/templatetags/multilang.py @@ -23,22 +23,17 @@ from django.conf import settings from geonode.metadata.multilang.utils import ( - get_2letters_languages, get_default_language, get_3_from_2, get_2_from_3, - get_multilang_field_name, + get_multilang_field_names, + get_all_multilang_fields, ) register = template.Library() logger = logging.getLogger(__name__) -def _is_multilang_field(field_name): - """Return whether a field is configured as multilingual.""" - return field_name in getattr(settings, "MULTILANG_FIELDS", ()) - - def _language_label(language_code_2, fallback): """ Return the display label for a configured language. @@ -48,25 +43,6 @@ def _language_label(language_code_2, fallback): return language_labels.get(language_code_2, fallback) -def _translation_value(metadata, field_name, language_code): - """Return the translated value for a multilingual field.""" - if not metadata: - return None - - return metadata.get(get_multilang_field_name(field_name, language_code)) - - -def _has_translation(metadata, field_name, language_code): - """Return whether the field has a translation for the given language.""" - return bool( - _translation_value( - metadata, - field_name, - language_code, - ) - ) - - def _language_descriptor(lang_2, lang_3): """Build the language descriptor used by the ISO template.""" return { @@ -80,7 +56,7 @@ def _language_descriptor(lang_2, lang_3): @register.filter(name="is_multilang") def is_multilang(field_name): """Return whether the field is configured as multilingual.""" - return _is_multilang_field(field_name) + return field_name in getattr(settings, "MULTILANG_FIELDS", ()) @register.simple_tag(name="multilang_values") @@ -89,34 +65,17 @@ def multilang_values(field_name, metadata): Return all translations for a multilingual field except the default language. """ - if not isinstance(metadata, dict) or not _is_multilang_field(field_name): + if not isinstance(metadata, dict) or not is_multilang(field_name): return [] - default_language = get_default_language() - translations = [] - included_languages = set() - - for language_code in get_2letters_languages(): - if language_code == default_language or language_code in included_languages: - continue - - included_languages.add(language_code) - - translated_text = _translation_value( - metadata, - field_name, - language_code, - ) - - if translated_text: - translations.append( - { - "locale": language_code, - "text": translated_text, - } - ) - - return translations + return [ + { + "locale": language_code, + "text": metadata[translated_field], + } + for language_code, translated_field in get_multilang_field_names(field_name) + if metadata.get(translated_field) + ] @register.simple_tag(name="language_info") @@ -126,10 +85,16 @@ def language_info(lang_3): """ if not lang_3: lang_2 = get_default_language() - lang_3 = get_3_from_2(lang_2) or "eng" + lang_3 = get_3_from_2(lang_2) + if lang_3 is None: + logger.warning( + "No ISO639-2 language found for '%s'; using 'eng' as the default.", + lang_2, + ) + lang_3 = "und" elif len(lang_3) == 2: lang_2 = lang_3 - lang_3 = get_3_from_2(lang_2) or "eng" + lang_3 = get_3_from_2(lang_2) or "und" else: lang_2 = get_2_from_3(lang_3) @@ -153,39 +118,27 @@ def languages_info(metadata, fields=None): if not isinstance(metadata, dict): return [] - multilingual_fields = fields if fields is not None else getattr(settings, "MULTILANG_FIELDS", ()) + multilingual_fields = set(fields or settings.MULTILANG_FIELDS) - default_language = get_default_language() - descriptors = [] - included_languages = set() + languages = set() - for language_code in get_2letters_languages(): - if language_code == default_language or language_code in included_languages: + for (field_name, language_code), multilang_field_name in get_all_multilang_fields().items(): + if field_name not in multilingual_fields or not metadata.get(multilang_field_name): continue - included_languages.add(language_code) - - if not any( - _has_translation(metadata, field_name, language_code) - for field_name in multilingual_fields - if _is_multilang_field(field_name) - ): - continue + languages.add(language_code) + descriptors = [] + for language_code in sorted(languages): lang_3 = get_3_from_2(language_code) if lang_3 is None: logger.warning( - "No entry in settings.LANGUAGE_MAPPINGS for language '%s'; " "skipping this entry.", + "No entry in settings.LANGUAGE_MAPPINGS for language '%s'; skipping this entry.", language_code, ) continue - descriptors.append( - _language_descriptor( - language_code, - lang_3, - ) - ) + descriptors.append(_language_descriptor(language_code, lang_3)) return descriptors From f04f1838a1d665273c7bc7ed1fcef4bcd83c2944 Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Wed, 15 Jul 2026 11:53:55 +0545 Subject: [PATCH 5/6] Update LANGUAGE_MAPPINGS settings --- geonode/settings.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/geonode/settings.py b/geonode/settings.py index bc847d6e9aa..2e519b3dd99 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -2146,37 +2146,11 @@ def get_geonode_catalogue_service(): # Maps ISO 639-1 (2-letter) codes to ISO 639-2 (3-letter) code(s) LANGUAGE_MAPPINGS = ( - ("af", "afr"), - ("sq", "alb"), - ("am", "amh"), - ("ar", "ara"), - ("id", "ind"), - ("bn", "ben"), - ("de", ["ger", "deu"]), ("en", "eng"), + ("de", ["ger", "deu"]), ("es", "spa"), ("fr", ["fre", "fra"]), ("it", "ita"), - ("km", "khm"), - ("nl", ["dut", "nld"]), - ("ne", "nep"), - ("fa", ["per", "fas"]), - ("pl", "pol"), - ("pt", "por"), - ("ru", "rus"), - ("si", "sin"), - ("sw", "swa"), - ("sv", "swe"), - ("tl", "tgl"), - ("ta", "tam"), - ("uk", "ukr"), - ("vi", "vie"), - ("el", ["gre", "ell"]), - ("th", "tha"), - ("zh", ["chi", "zho"]), - ("ja", "jpn"), - ("ko", "kor"), - ("sk", ["slo", "slk"]), ) # you can get the language names in psql using "\dF" From c6d205fbb686e010ca9c39283666c4aed295bde4 Mon Sep 17 00:00:00 2001 From: Niraj Adhikari Date: Wed, 15 Jul 2026 12:27:53 +0545 Subject: [PATCH 6/6] Fix catalogue tests --- geonode/catalogue/tests.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/geonode/catalogue/tests.py b/geonode/catalogue/tests.py index 1c306927690..b0a3e69e4de 100644 --- a/geonode/catalogue/tests.py +++ b/geonode/catalogue/tests.py @@ -236,14 +236,6 @@ def test_field_not_configured(self): def test_configured_field_no_translations(self): self.assertEqual([], tags.multilang_values("title", {"title": "Hello"})) - def test_excludes_default_language(self): - metadata = { - "title": "Hello", - "title_multilang_en": "Hello", - "title_multilang_it": "Ciao", - } - self.assertEqual([{"locale": "it", "text": "Ciao"}], tags.multilang_values("title", metadata)) - def test_excludes_empty_string_translation(self): metadata = { "title": "Hello",