Jelajahi Sumber

Fixed #33565 -- Improved locale format validation for the makemessages command.

Ronnie van den Crommenacker 3 tahun lalu
induk
melakukan
c32858a8ce

+ 1 - 0
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/>

+ 38 - 7
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)

+ 2 - 1
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
 ~~~~~~~~~~

+ 83 - 1
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))