This is an automated email from the ASF dual-hosted git repository.

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new ae835335730 [v3-4-test] Fix breeze translation key sorting to match 
the UI ESLint rule (#75065) (#75192)
ae835335730 is described below

commit ae83533573048085c4d08afe00249adb68328251
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 20 14:31:34 2026 +0530

    [v3-4-test] Fix breeze translation key sorting to match the UI ESLint rule 
(#75065) (#75192)
    
    (cherry picked from commit bd29fed01b684acffcbe569ae137eddc753f5a80)
    
    Co-authored-by: CA <[email protected]>
---
 .../src/airflow_breeze/commands/ui_commands.py     | 65 +++++++++++++---------
 dev/breeze/tests/test_ui_commands.py               | 28 +++++++---
 2 files changed, 60 insertions(+), 33 deletions(-)

diff --git a/dev/breeze/src/airflow_breeze/commands/ui_commands.py 
b/dev/breeze/src/airflow_breeze/commands/ui_commands.py
index df906c2f7fd..a7820963031 100644
--- a/dev/breeze/src/airflow_breeze/commands/ui_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/ui_commands.py
@@ -35,37 +35,50 @@ from airflow_breeze.utils.run_utils import 
assert_prek_installed, run_compile_ui
 LOCALES_DIR = AIRFLOW_ROOT_PATH / "airflow-core" / "src" / "airflow" / "ui" / 
"public" / "i18n" / "locales"
 
 
-def natural_sort_key(text: str) -> tuple:
-    """
-    Create a sort key that matches eslint-plugin-jsonc behavior with natural: 
true.
+# natural-compare's sort order of ASCII: NUL-"," keep their ASCII order, then 
"." "/",
+# the remaining punctuation, "-" (just before digits), digits, uppercase, 
lowercase.
+# A character's position in this string is its natural-compare sort code.
+_NATURAL_CHAR_ORDER = (
+    "".join(chr(code) for code in range(32))  # control characters
+    + " !\"#$%&'()*+,"
+    + "./"
+    + ":;<=>?@"
+    + "[\\]^_`"
+    + "{|}~\x7f"
+    + "-"
+    + "0123456789"
+    + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    + "abcdefghijklmnopqrstuvwxyz"
+)
+_CHAR_CODES = {char: code for code, char in enumerate(_NATURAL_CHAR_ORDER)}
 
-    This mimics JavaScript's localeCompare with numeric: true, which:
-    1. Does case-insensitive comparison character-by-character
-    2. Uses original case as tiebreaker when characters are equal (ignoring 
case)
-    3. Handles numbers naturally (2 < 10)
 
-    For each character position, we create a tuple: (lowercase_char, 
original_char)
-    This ensures case-insensitive primary sort with case-sensitive tiebreaker.
-    """
+def _get_char_code(char: str) -> int:
+    """Return natural-compare's sort code for a character; non-ASCII by code 
point."""
+    return _CHAR_CODES.get(char, ord(char))
 
-    def char_key(c: str) -> tuple:
-        if c.isdigit():
-            return 0, int(c), c  # Numbers sort before letters
-        return 1, c.lower(), c  # Lowercase for primary, original for 
tiebreaker
 
-    # Split on numbers to handle natural number ordering
-    parts: list[tuple[int, tuple[tuple[Any, ...], ...] | int]] = []
-    for part in re.split(r"(\d+)", text):
-        if not part:
-            continue
-        if part.isdigit():
-            # For numeric parts, use integer value
-            parts.append((0, int(part)))
-        else:
-            # For text parts, convert each character
-            parts.append((1, tuple(char_key(c) for c in part)))
+def natural_sort_key(text: str) -> tuple:
+    """Create a sort key matching eslint-plugin-jsonc with natural: true.
 
-    return tuple(parts)
+    natural-compare, the comparator behind the UI's ``jsonc/sort-keys`` rule, 
compares
+    digit runs numerically and other characters by ``_get_char_code``. Each 
token also
+    carries a bucket so numbers sort between characters below "0" and above 
"9".
+    """
+    tokens: list[tuple] = []
+    for part in re.split(r"([0-9]+)", text):
+        if part.isascii() and part.isdigit():
+            digits = part.lstrip("0")
+            # natural-compare treats a leading "0" as a plain character and 
only the
+            # rest of the run as a number.
+            tokens.extend([(0, _get_char_code("0"))] * (len(part) - 
len(digits)))
+            if digits:
+                tokens.append((1, int(digits)))
+        else:
+            for char in part:
+                code = _get_char_code(char)
+                tokens.append((0, code) if code < 66 else (2, code))
+    return tuple(tokens)
 
 
 MOST_COMMON_PLURAL_SUFFIXES = ["_one", "_other"]
diff --git a/dev/breeze/tests/test_ui_commands.py 
b/dev/breeze/tests/test_ui_commands.py
index 0bb68978a93..1aab1edd0de 100644
--- a/dev/breeze/tests/test_ui_commands.py
+++ b/dev/breeze/tests/test_ui_commands.py
@@ -29,6 +29,7 @@ from airflow_breeze.commands.ui_commands import (
     flatten_keys,
     get_locale_files,
     get_plural_base,
+    natural_sort_key,
 )
 
 
@@ -429,7 +430,7 @@ class TestRemoveUnusedTranslations:
 
 class TestNaturalSorting:
     def test_natural_sort_matches_eslint(self, tmp_path):
-        """Test that keys are sorted like eslint-plugin-jsonc with natural: 
true (case-insensitive with case-sensitive tiebreaker)."""
+        """Test that keys are sorted like eslint-plugin-jsonc with natural: 
true."""
         from airflow_breeze.commands.ui_commands import 
add_missing_translations
 
         en_dir = tmp_path / "en"
@@ -464,14 +465,27 @@ class TestNaturalSorting:
             )
             add_missing_translations("de", {"test.json": summary})
 
-            # Check that keys are sorted using natural sort (case-insensitive 
with case-sensitive tiebreaker)
+            # Check that keys are sorted like eslint-plugin-jsonc with 
natural: true
             de_data_updated = json.loads((de_dir / "test.json").read_text())
             keys = list(de_data_updated.keys())
 
-            # Expected order matches eslint-plugin-jsonc with natural: true:
-            # - "apple" < "asset_few" < "assetEvent_few" (case-insensitive: 
apple < asset < assetevent)
-            # - "parseDuration" < "parsedAt" (case-insensitive equal at 
"parse", then D < d at position 5)
-            # - "Zebra" at the end (case-insensitive: z comes last)
-            assert keys == ["apple", "asset_few", "assetEvent_few", 
"parseDuration", "parsedAt", "Zebra"]
+            # natural-compare sorts uppercase before lowercase, so "Zebra" 
comes first.
+            assert keys == ["Zebra", "apple", "asset_few", "assetEvent_few", 
"parseDuration", "parsedAt"]
         finally:
             ui_commands.LOCALES_DIR = original_locales_dir
+
+    @pytest.mark.parametrize(
+        ("keys", "expected"),
+        [
+            # Real key pairs from the UI locales where a case-insensitive sort
+            # disagrees with natural-compare's case-sensitive ordering.
+            (["logicalDate", "logLevel"], ["logLevel", "logicalDate"]),
+            (["selected", "selectLanguage"], ["selectLanguage", "selected"]),
+            (["modes", "modeTooltip"], ["modeTooltip", "modes"]),
+            (["sortedAscending", "sortMultiColumnHint"], 
["sortMultiColumnHint", "sortedAscending"]),
+            # Digit runs sort numerically, except runs starting with "0".
+            (["a2", "a10", "a02"], ["a02", "a2", "a10"]),
+        ],
+    )
+    def test_natural_sort_key_matches_eslint(self, keys, expected):
+        assert sorted(keys, key=natural_sort_key) == expected

Reply via email to