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 <ja...@potiuk.com> 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)