This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new f05c09868da Add progress summary to translation_completeness script
(#51537)
f05c09868da is described below
commit f05c09868da4b7db058b4392545d0c935cf858fc
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon Jun 9 19:03:46 2025 +0200
Add progress summary to translation_completeness script (#51537)
---
.../ui/src/i18n/check_translations_completeness.py | 145 ++++++++++++++++++++-
1 file changed, 139 insertions(+), 6 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/i18n/check_translations_completeness.py
b/airflow-core/src/airflow/ui/src/i18n/check_translations_completeness.py
index a325a3e2d93..1b241178cbb 100755
--- a/airflow-core/src/airflow/ui/src/i18n/check_translations_completeness.py
+++ b/airflow-core/src/airflow/ui/src/i18n/check_translations_completeness.py
@@ -27,6 +27,7 @@ from __future__ import annotations
import json
import sys
+from collections import defaultdict
from pathlib import Path
from typing import Any, NamedTuple
@@ -106,16 +107,21 @@ def flatten_keys(d: dict, prefix: str = "") -> list[str]:
return keys
-def compare_keys(locale_files: list[LocaleFiles]) -> dict[str, LocaleSummary]:
+def compare_keys(
+ locale_files: list[LocaleFiles],
+) -> tuple[dict[str, LocaleSummary], dict[str, dict[str, int]]]:
"""
Compare all non-English locales with English locale only.
- Returns a summary dict: filename -> LocaleSummary(missing, extra)
+ Returns a tuple:
+ - summary dict: filename -> LocaleSummary(missing, extra)
+ - missing_counts dict: filename -> {locale: count_of_missing_keys}
"""
all_files: set[str] = set()
for lf in locale_files:
all_files.update(lf.files)
summary: dict[str, LocaleSummary] = {}
+ missing_counts: dict[str, dict[str, int]] = {}
for filename in all_files:
key_sets: list[LocaleKeySet] = []
for lf in locale_files:
@@ -134,17 +140,21 @@ def compare_keys(locale_files: list[LocaleFiles]) ->
dict[str, LocaleSummary]:
en_keys = keys_by_locale.get("en", set()) or set()
missing_keys: dict[str, list[str]] = {}
extra_keys: dict[str, list[str]] = {}
+ missing_counts[filename] = {}
for ks in key_sets:
if ks.locale == "en":
continue
if ks.keys is None:
missing_keys[ks.locale] = list(en_keys)
extra_keys[ks.locale] = []
+ missing_counts[filename][ks.locale] = len(en_keys)
else:
- missing_keys[ks.locale] = list(en_keys - ks.keys)
+ missing = list(en_keys - ks.keys)
+ missing_keys[ks.locale] = missing
extra_keys[ks.locale] = list(ks.keys - en_keys)
+ missing_counts[filename][ks.locale] = len(missing)
summary[filename] = LocaleSummary(missing_keys=missing_keys,
extra_keys=extra_keys)
- return summary
+ return summary, missing_counts
def print_locale_file_table(
@@ -235,6 +245,123 @@ def get_outdated_entries_for_language(
return outdated
+def count_todo_and_total(obj):
+ """Recursively count TODO: translate entries and total entries in a dict
or list."""
+ todo = 0
+ total = 0
+ if isinstance(obj, dict):
+ for v in obj.values():
+ t, n = count_todo_and_total(v)
+ todo += t
+ total += n
+ elif isinstance(obj, list):
+ for v in obj:
+ t, n = count_todo_and_total(v)
+ todo += t
+ total += n
+ else:
+ total = 1
+ if isinstance(obj, str) and obj.strip().startswith("TODO: translate"):
+ todo = 1
+ return todo, total
+
+
+def print_translation_progress(console, locale_files, missing_counts, summary):
+ from rich.table import Table
+
+ tables = defaultdict(lambda: Table(show_lines=True))
+ all_files = set()
+ for lf in locale_files:
+ all_files.update(lf.files)
+ for lf in locale_files:
+ lang = lf.locale
+ table = tables[lang]
+ table.title = f"Translation Progress: {lang}"
+ table.add_column("File", style="bold cyan")
+ table.add_column("Missing", style="red")
+ table.add_column("Extra", style="yellow")
+ table.add_column("TODOs", style="magenta")
+ table.add_column("Translated", style="green")
+ table.add_column("Total", style="bold")
+ table.add_column("Percent", style="bold")
+ total_missing = 0
+ total_extra = 0
+ total_todos = 0
+ total_translated = 0
+ total_total = 0
+ for filename in sorted(all_files):
+ file_path = Path(LOCALES_DIR / lang / filename)
+ # Always get total from English version
+ en_path = Path(LOCALES_DIR / "en" / filename)
+ if en_path.exists():
+ with open(en_path) as f:
+ en_data = json.load(f)
+ file_total = sum(1 for _ in flatten_keys(en_data))
+ else:
+ file_total = 0
+ if file_path.exists():
+ with open(file_path) as f:
+ data = json.load(f)
+ file_missing = missing_counts.get(filename, {}).get(lang, 0)
+ file_extra = len(summary.get(filename, LocaleSummary({},
{})).extra_keys.get(lang, []))
+
+ # Count TODOs
+ def count_todos(obj):
+ if isinstance(obj, dict):
+ return sum(count_todos(v) for v in obj.values())
+ if isinstance(obj, list):
+ return sum(count_todos(v) for v in obj)
+ if isinstance(obj, str) and obj.strip().startswith("TODO:
translate"):
+ return 1
+ return 0
+
+ file_todos = count_todos(data)
+ file_translated = file_total - file_missing
+ percent = 100 * file_translated / file_total if file_total
else 100
+ style = (
+ "bold green"
+ if file_missing == 0 and file_extra == 0 and file_todos == 0
+ else (
+ "yellow" if file_missing < file_total or file_extra >
0 or file_todos > 0 else "red"
+ )
+ )
+ else:
+ file_missing = file_total
+ file_extra = len(summary.get(filename, LocaleSummary({},
{})).extra_keys.get(lang, []))
+ file_todos = 0
+ file_translated = 0
+ percent = 0
+ style = "red"
+ table.add_row(
+ f"[bold reverse]{filename}[/bold reverse]",
+ str(file_missing),
+ str(file_extra),
+ str(file_todos),
+ str(file_translated),
+ str(file_total),
+ f"{percent:.1f}%",
+ style=style,
+ )
+ total_missing += file_missing
+ total_extra += file_extra
+ total_todos += file_todos
+ total_translated += file_translated
+ total_total += file_total
+ percent = 100 * total_translated / total_total if total_total else 100
+ table.add_row(
+ "All files",
+ str(total_missing),
+ str(total_extra),
+ str(total_todos),
+ str(total_translated),
+ str(total_total),
+ f"{percent:.1f}%",
+ style="red" if percent < 100 else "bold green",
+ )
+ for _lang, table in tables.items():
+ console.print(table)
+
+
@click.command()
@click.option(
"--language", "-l", default=None, help="Show summary for a single language
(e.g. en, de, pl, etc.)"
@@ -250,7 +377,7 @@ def cli(language: str | None = None, add_missing: bool =
False):
console = Console(force_terminal=True, color_system="auto")
print_locale_file_table(locale_files, console, language)
found_difference = print_file_set_differences(locale_files, console,
language)
- summary = compare_keys(locale_files)
+ summary, missing_counts = compare_keys(locale_files)
console.print("\n[bold underline]Summary of differences by language:[/bold
underline]", style="cyan")
if language:
locales = [lf.locale for lf in locale_files]
@@ -276,8 +403,14 @@ def cli(language: str | None = None, add_missing: bool =
False):
else:
lang_diff = print_language_summary(locale_files, summary, console)
found_difference = found_difference or lang_diff
+ print_translation_progress(
+ console,
+ [lf for lf in locale_files if language is None or lf.locale ==
language],
+ missing_counts,
+ summary,
+ )
if not found_difference:
- console.print("[green]All translations are complete and
consistent![/green]")
+ console.print("[green]All translations are complete and
consistent![green]")
if found_difference:
sys.exit(1)