summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonnie van den Crommenacker <ronnie@owello.nl>2022-03-17 12:02:59 +0100
committerCarlton Gibson <carlton.gibson@noumenal.es>2022-06-08 16:17:12 +0200
commitc32858a8ce961d276215a040ae0ab1e4409b70f8 (patch)
tree87f3eed5729768bbb8bd23e5ef63d5daf710a636
parent49b470b9187b6be60e573fed08c8f4a87f133750 (diff)
Fixed #33565 -- Improved locale format validation for the makemessages command.
-rw-r--r--AUTHORS1
-rw-r--r--django/core/management/commands/makemessages.py45
-rw-r--r--docs/releases/4.2.txt3
-rw-r--r--tests/i18n/test_extraction.py84
4 files changed, 124 insertions, 9 deletions
diff --git a/AUTHORS b/AUTHORS
index 8c20814b13..b726a8a67b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -829,6 +829,7 @@ answer newbie questions, and generally made Django that much better:
Rodrigo Pinheiro Marques de Araújo <fenrrir@gmail.com>
Rohith P R <https://rohithpr.com>
Romain Garrigues <romain.garrigues.cs@gmail.com>
+ Ronnie van den Crommenacker
Ronny Haryanto <https://ronny.haryan.to/>
Ross Poulton <ross@rossp.org>
Roxane Bellot <https://github.com/roxanebellot/>
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index 1c68fb453e..1d4947fb30 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -40,6 +40,10 @@ def check_programs(*programs):
)
+def is_valid_locale(locale):
+ return re.match(r"^[a-z]+$", locale) or re.match(r"^[a-z]+_[A-Z].*$", locale)
+
+
@total_ordering
class TranslatableFile:
def __init__(self, dirpath, file_name, locale_dir):
@@ -427,14 +431,41 @@ class Command(BaseCommand):
# Build po files for each selected locale
for locale in locales:
- if "-" in locale:
- self.stdout.write(
- "invalid locale %s, did you mean %s?"
- % (
- locale,
- locale.replace("-", "_"),
- ),
+ if not is_valid_locale(locale):
+ # Try to guess what valid locale it could be
+ # Valid examples are: en_GB, shi_Latn_MA and nl_NL-x-informal
+
+ # Search for characters followed by a non character (i.e. separator)
+ match = re.match(
+ r"^(?P<language>[a-zA-Z]+)"
+ r"(?P<separator>[^a-zA-Z])"
+ r"(?P<territory>.+)$",
+ locale,
)
+ if match:
+ locale_parts = match.groupdict()
+ language = locale_parts["language"].lower()
+ territory = (
+ locale_parts["territory"][:2].upper()
+ + locale_parts["territory"][2:]
+ )
+ proposed_locale = f"{language}_{territory}"
+ else:
+ # It could be a language in uppercase
+ proposed_locale = locale.lower()
+
+ # Recheck if the proposed locale is valid
+ if is_valid_locale(proposed_locale):
+ self.stdout.write(
+ "invalid locale %s, did you mean %s?"
+ % (
+ locale,
+ proposed_locale,
+ ),
+ )
+ else:
+ self.stdout.write("invalid locale %s" % locale)
+
continue
if self.verbosity > 0:
self.stdout.write("processing locale %s" % locale)
diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt
index b510eaa78f..345f4d4ad8 100644
--- a/docs/releases/4.2.txt
+++ b/docs/releases/4.2.txt
@@ -155,7 +155,8 @@ Logging
Management Commands
~~~~~~~~~~~~~~~~~~~
-* ...
+* :djadmin:`makemessages` command now supports locales with private sub-tags
+ such as ``nl_NL-x-informal``.
Migrations
~~~~~~~~~~
diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py
index c2ae10efd8..5287677bab 100644
--- a/tests/i18n/test_extraction.py
+++ b/tests/i18n/test_extraction.py
@@ -175,7 +175,43 @@ class BasicExtractorTests(ExtractorTests):
self.assertIn("processing locale de", out.getvalue())
self.assertIs(Path(self.PO_FILE).exists(), True)
- def test_invalid_locale(self):
+ def test_valid_locale_with_country(self):
+ out = StringIO()
+ management.call_command(
+ "makemessages", locale=["en_GB"], stdout=out, verbosity=1
+ )
+ self.assertNotIn("invalid locale en_GB", out.getvalue())
+ self.assertIn("processing locale en_GB", out.getvalue())
+ self.assertIs(Path("locale/en_GB/LC_MESSAGES/django.po").exists(), True)
+
+ def test_valid_locale_tachelhit_latin_morocco(self):
+ out = StringIO()
+ management.call_command(
+ "makemessages", locale=["shi_Latn_MA"], stdout=out, verbosity=1
+ )
+ self.assertNotIn("invalid locale shi_Latn_MA", out.getvalue())
+ self.assertIn("processing locale shi_Latn_MA", out.getvalue())
+ self.assertIs(Path("locale/shi_Latn_MA/LC_MESSAGES/django.po").exists(), True)
+
+ def test_valid_locale_private_subtag(self):
+ out = StringIO()
+ management.call_command(
+ "makemessages", locale=["nl_NL-x-informal"], stdout=out, verbosity=1
+ )
+ self.assertNotIn("invalid locale nl_NL-x-informal", out.getvalue())
+ self.assertIn("processing locale nl_NL-x-informal", out.getvalue())
+ self.assertIs(
+ Path("locale/nl_NL-x-informal/LC_MESSAGES/django.po").exists(), True
+ )
+
+ def test_invalid_locale_uppercase(self):
+ out = StringIO()
+ management.call_command("makemessages", locale=["PL"], stdout=out, verbosity=1)
+ self.assertIn("invalid locale PL, did you mean pl?", out.getvalue())
+ self.assertNotIn("processing locale pl", out.getvalue())
+ self.assertIs(Path("locale/pl/LC_MESSAGES/django.po").exists(), False)
+
+ def test_invalid_locale_hyphen(self):
out = StringIO()
management.call_command(
"makemessages", locale=["pl-PL"], stdout=out, verbosity=1
@@ -184,6 +220,52 @@ class BasicExtractorTests(ExtractorTests):
self.assertNotIn("processing locale pl-PL", out.getvalue())
self.assertIs(Path("locale/pl-PL/LC_MESSAGES/django.po").exists(), False)
+ def test_invalid_locale_lower_country(self):
+ out = StringIO()
+ management.call_command(
+ "makemessages", locale=["pl_pl"], stdout=out, verbosity=1
+ )
+ self.assertIn("invalid locale pl_pl, did you mean pl_PL?", out.getvalue())
+ self.assertNotIn("processing locale pl_pl", out.getvalue())
+ self.assertIs(Path("locale/pl_pl/LC_MESSAGES/django.po").exists(), False)
+
+ def test_invalid_locale_private_subtag(self):
+ out = StringIO()
+ management.call_command(
+ "makemessages", locale=["nl-nl-x-informal"], stdout=out, verbosity=1
+ )
+ self.assertIn(
+ "invalid locale nl-nl-x-informal, did you mean nl_NL-x-informal?",
+ out.getvalue(),
+ )
+ self.assertNotIn("processing locale nl-nl-x-informal", out.getvalue())
+ self.assertIs(
+ Path("locale/nl-nl-x-informal/LC_MESSAGES/django.po").exists(), False
+ )
+
+ def test_invalid_locale_plus(self):
+ out = StringIO()
+ management.call_command(
+ "makemessages", locale=["en+GB"], stdout=out, verbosity=1
+ )
+ self.assertIn("invalid locale en+GB, did you mean en_GB?", out.getvalue())
+ self.assertNotIn("processing locale en+GB", out.getvalue())
+ self.assertIs(Path("locale/en+GB/LC_MESSAGES/django.po").exists(), False)
+
+ def test_invalid_locale_end_with_underscore(self):
+ out = StringIO()
+ management.call_command("makemessages", locale=["en_"], stdout=out, verbosity=1)
+ self.assertIn("invalid locale en_", out.getvalue())
+ self.assertNotIn("processing locale en_", out.getvalue())
+ self.assertIs(Path("locale/en_/LC_MESSAGES/django.po").exists(), False)
+
+ def test_invalid_locale_start_with_underscore(self):
+ out = StringIO()
+ management.call_command("makemessages", locale=["_en"], stdout=out, verbosity=1)
+ self.assertIn("invalid locale _en", out.getvalue())
+ self.assertNotIn("processing locale _en", out.getvalue())
+ self.assertIs(Path("locale/_en/LC_MESSAGES/django.po").exists(), False)
+
def test_comments_extractor(self):
management.call_command("makemessages", locale=[LOCALE], verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))