From 0d562ca2accb4ef5164cb99663eedb82b6b5de5e Mon Sep 17 00:00:00 2001 From: Jarek Potiuk Date: Wed, 22 Oct 2025 14:37:38 +0200 Subject: [PATCH] Add possibility of removing extra translations missing in en Sometimes we rename/move translation - we did not have a tool to remove such dangling translations. Now we have. --- .../src/airflow/ui/public/i18n/README.md | 12 ++++ .../ui/public/i18n/locales/ar/components.json | 1 - .../ui/public/i18n/locales/ar/dags.json | 3 +- .../ui/public/i18n/locales/el/common.json | 1 - .../ui/public/i18n/locales/es/components.json | 1 - .../ui/public/i18n/locales/es/dags.json | 3 +- .../ui/public/i18n/locales/fr/dags.json | 3 +- .../ui/public/i18n/locales/he/components.json | 1 - .../ui/public/i18n/locales/he/dags.json | 3 +- .../ui/public/i18n/locales/hi/common.json | 3 - .../ui/public/i18n/locales/hi/components.json | 1 - .../ui/public/i18n/locales/hi/dag.json | 5 +- .../ui/public/i18n/locales/hi/dags.json | 3 +- .../ui/public/i18n/locales/hu/common.json | 2 - .../ui/public/i18n/locales/hu/components.json | 1 - .../ui/public/i18n/locales/hu/dags.json | 3 +- .../ui/public/i18n/locales/it/components.json | 1 - .../ui/public/i18n/locales/it/dags.json | 3 +- .../ui/public/i18n/locales/nl/components.json | 1 - .../ui/public/i18n/locales/nl/dags.json | 3 +- .../ui/public/i18n/locales/pt/components.json | 1 - .../ui/public/i18n/locales/pt/dags.json | 3 +- .../ui/public/i18n/locales/tr/components.json | 1 - .../ui/public/i18n/locales/tr/dags.json | 3 +- .../public/i18n/locales/zh-CN/components.json | 1 - .../ui/public/i18n/locales/zh-CN/dags.json | 3 +- dev/i18n/check_translations_completeness.py | 70 ++++++++++++++++++- 27 files changed, 93 insertions(+), 43 deletions(-) diff --git a/airflow-core/src/airflow/ui/public/i18n/README.md b/airflow-core/src/airflow/ui/public/i18n/README.md index 1d1c21f0a2af9..e2fe51ff73938 100644 --- a/airflow-core/src/airflow/ui/public/i18n/README.md +++ b/airflow-core/src/airflow/ui/public/i18n/README.md @@ -331,6 +331,18 @@ Adding missing translations (with `TODO: translate` prefix): uv run dev/i18n/check_translations_completeness.py --language --add-missing ``` +You can also remove extra translations from the language of your choice: + +```bash +uv run dev/i18n/check_translations_completeness.py --language --remove-extra +``` + +Or from all languages: + +```bash +uv run dev/i18n/check_translations_completeness.py --remove-extra +``` + The script is also added as a prek hook (manual) so that it can be run from within `prek` and CI: ```bash diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json index 0b1c5d74aa9c8..df1b9d69d9b09 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/ar/components.json @@ -120,7 +120,6 @@ "limitedList.clickToInteract": "انقر على علامة لتصفية Dags", "limitedList.clickToOpenFull": "انقر \"+{{count}} المزيد\" لعرض القائمة الكاملة", "limitedList.copyPasteText": "يمكنك نسخ ولصق النص أعلاه", - "limitedList.showingItems": "عرض {{count}} عنصرًا", "logs": { "file": "ملف", "location": "سطر {{line}} في {{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ar/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/ar/dags.json index b7f8e60bbd8b5..12961f79af68d 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/ar/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/ar/dags.json @@ -20,8 +20,7 @@ "all": "الكل", "paused": "متوقف" }, - "runIdPatternFilter": "بحث تشغيلات الDag", - "triggeringUserNameFilter": "البحث حسب المستخدم صاحب الاطلاق" + "runIdPatternFilter": "بحث تشغيلات الDag" }, "ownerLink": "رابط المالك لـ{{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json b/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json index f3b725d02fd98..eddc6a8f5a236 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/el/common.json @@ -163,7 +163,6 @@ }, "tooltip": "Πατήστε {{hotkey}} για κύλιση προς τα {{direction}}" }, - "seconds": "{{count}}δ", "security": { "actions": "Ενέργειες", "permissions": "Δικαιώματα", diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json index 20f31033ee064..ad1edcd1b3967 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/es/components.json @@ -99,7 +99,6 @@ "limitedList.clickToInteract": "Haz clic en una etiqueta para filtrar Dags", "limitedList.clickToOpenFull": "Haz clic en \"+{{count}} más\" para ver la lista completa", "limitedList.copyPasteText": "Puedes copiar y pegar el texto de arriba", - "limitedList.showingItems": "Mostrando {{count}} elementos", "logs": { "file": "Archivo", "location": "línea {{line}} en {{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/es/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/es/dags.json index 7d93addccfc72..353ae56534a77 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/es/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/es/dags.json @@ -20,8 +20,7 @@ "all": "Todos", "paused": "Pausado" }, - "runIdPatternFilter": "Buscar Ejecuciones de Dag", - "triggeringUserNameFilter": "Buscar por usuario que activó" + "runIdPatternFilter": "Buscar Ejecuciones de Dag" }, "ownerLink": "Enlace de Propietario para {{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/fr/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/fr/dags.json index cd6b2c2735631..b8a68745b81f5 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/fr/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/fr/dags.json @@ -20,8 +20,7 @@ "all": "Tous", "paused": "En pause" }, - "runIdPatternFilter": "Rechercher des exécutions de Dag", - "triggeringUserNameFilter": "Rechercher par utilisateur déclencheur" + "runIdPatternFilter": "Rechercher des exécutions de Dag" }, "ownerLink": "Lien du propriétaire pour {{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json index 424d5f675b14d..453f381980163 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/he/components.json @@ -99,7 +99,6 @@ "limitedList.clickToInteract": "לחץ על תגית כדי לסנן Dags", "limitedList.clickToOpenFull": "לחץ על \"+{{count}} נוספים\" כדי לפתוח את התצוגה המלאה", "limitedList.copyPasteText": "ניתן להעתיק ולהדביק את הטקסט שמעל", - "limitedList.showingItems": "מציג {{count}} פריטים", "logs": { "file": "קובץ", "location": "שורה {{line}} ב{{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/he/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/he/dags.json index 15729c5f1e866..1b3b06d4bf6b3 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/he/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/he/dags.json @@ -20,8 +20,7 @@ "all": "הכל", "paused": "מושהה" }, - "runIdPatternFilter": "חפש הרצת Dag", - "triggeringUserNameFilter": "חפש לפי שם המשתמש המפעיל" + "runIdPatternFilter": "חפש הרצת Dag" }, "ownerLink": "קישור בעלים ל-{{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json b/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json index a506b9198b85c..1dcef4cb0cc1b 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/common.json @@ -25,7 +25,6 @@ "dag_other": "डैग्स", "dagDetails": { "catchup": "पकड़ना", - "concurrency": "समानांतरता", "dagRunTimeout": "डैग रन टाइमआउट", "defaultArgs": "डिफ़ॉल्ट तर्क", "description": "विवरण", @@ -188,8 +187,6 @@ "up_for_retry": "पुनः प्रयास के लिए", "upstream_failed": "अपस्ट्रीम विफल" }, - "switchToDarkMode": "डार्क मोड में स्विच करें", - "switchToLightMode": "लाइट मोड में स्विच करें", "table": { "completedAt": "पर पूर्ण", "createdAt": "पर बनाया", diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json index 4efe768e9bebf..d3caf8c34c5eb 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/components.json @@ -92,7 +92,6 @@ "limitedList.clickToInteract": "टैग पर क्लिक करके Dags फ़िल्टर करें", "limitedList.clickToOpenFull": "\"+{{count}} और\" पर क्लिक करके पूरी सूची खोलें", "limitedList.copyPasteText": "आप ऊपर दिए गए पाठ को कॉपी और पेस्ट कर सकते हैं", - "limitedList.showingItems": "{{count}} आइटम दिखाए जा रहे हैं", "logs": { "file": "फ़ाइल", "location": "{{name}} में लाइन {{line}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json b/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json index 253ed9585407c..36e5d3adfca97 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json @@ -40,7 +40,6 @@ "warning": "WARNING" }, "navigation": { - "jump": "जंप: Shift+{{arrow}}", "navigation": "नेवीगेशन: {{arrow}}", "toggleGroup": "ग्रुप टॉगल करें: Space" }, @@ -64,9 +63,7 @@ }, "panel": { "buttons": { - "options": "विकल्प", - "showGraph": "ग्राफ़ दिखाएं", - "showGrid": "ग्रिड दिखाएं" + "options": "विकल्प" }, "dagRuns": { "label": "डैग रन्स की संख्या" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/hi/dags.json index 54cac11533683..74c112d0dfbcb 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/dags.json @@ -20,8 +20,7 @@ "all": "सभी", "paused": "रोका गया" }, - "runIdPatternFilter": "डैग रन्स खोजें", - "triggeringUserNameFilter": "ट्रिगर करने वाले उपयोगकर्ता द्वारा खोजें" + "runIdPatternFilter": "डैग रन्स खोजें" }, "ownerLink": "{{owner}} के लिए स्वामी लिंक", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json b/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json index f26a1540f08ac..084a73f3c448e 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hu/common.json @@ -189,8 +189,6 @@ "up_for_retry": "Újrapróbálkozásra vár", "upstream_failed": "Előfeltétel sikertelen" }, - "switchToDarkMode": "Váltás sötét módra", - "switchToLightMode": "Váltás világos módra", "table": { "completedAt": "Befejezve ekkor", "createdAt": "Létrehozva ekkor", diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json index ded34869e9dac..014e8242b43a7 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hu/components.json @@ -92,7 +92,6 @@ "limitedList.clickToInteract": "Kattintson egy címkére a Dag-ok szűréséhez", "limitedList.clickToOpenFull": "Kattintson a \"+{{count}} további\" gombra a teljes nézethez", "limitedList.copyPasteText": "A fenti szöveg másolható és beilleszthető", - "limitedList.showingItems": "{{count}} elem megjelenítése", "logs": { "file": "Fájl", "location": "{{name}} fájl {{line}}. sora" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hu/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/hu/dags.json index 93a925c7c4d7e..6394092f74718 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/hu/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/hu/dags.json @@ -20,8 +20,7 @@ "all": "Összes", "paused": "Szüneteltetett" }, - "runIdPatternFilter": "Dag futások keresése", - "triggeringUserNameFilter": "Keresés indító felhasználó alapján" + "runIdPatternFilter": "Dag futások keresése" }, "ownerLink": "Tulajdonosi hivatkozás: {{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json index e8903d951a2a8..3978d69905b7e 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/it/components.json @@ -106,7 +106,6 @@ "limitedList.clickToInteract": "Fai clic su un'etichetta per filtrare i Dag", "limitedList.clickToOpenFull": "Fai clic su \"+{{count}} altro\" per visualizzare tutto", "limitedList.copyPasteText": "Puoi copiare e incollare il testo sopra", - "limitedList.showingItems": "Visualizzazione di {{count}} elementi", "logs": { "file": "File", "location": "linea {{line}} in {{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/it/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/it/dags.json index 0cb4eac8ac761..6c6ab552ddc9c 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/it/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/it/dags.json @@ -20,8 +20,7 @@ "all": "Tutti", "paused": "In Pausa" }, - "runIdPatternFilter": "Cercare Dag Runs", - "triggeringUserNameFilter": "Cercare per Utente che ha Attivato" + "runIdPatternFilter": "Cercare Dag Runs" }, "ownerLink": "Link dell'Owner per {{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json index 83785a7112a41..9997e06717296 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/nl/components.json @@ -92,7 +92,6 @@ "limitedList.clickToInteract": "Klik op een label om Dags te filteren", "limitedList.clickToOpenFull": "Klik op \"+{{count}} meer\" om de volledige lijst te openen", "limitedList.copyPasteText": "Je kunt de bovenstaande tekst kopiëren en plakken", - "limitedList.showingItems": "{{count}} items weergegeven", "logs": { "file": "Bestand", "location": "regel {{line}} in {{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/nl/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/nl/dags.json index b57b2290a9232..23fdb3bc2d480 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/nl/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/nl/dags.json @@ -20,8 +20,7 @@ "all": "Alles", "paused": "Gepauzeerd" }, - "runIdPatternFilter": "Zoek Dag Runs", - "triggeringUserNameFilter": "Zoek op Triggering User" + "runIdPatternFilter": "Zoek Dag Runs" }, "ownerLink": "Eigenaarslink voor {{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json index ae83855677a6c..5b30826d8b716 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/pt/components.json @@ -106,7 +106,6 @@ "limitedList.clickToInteract": "Clique em uma etiqueta para filtrar os Dags", "limitedList.clickToOpenFull": "Clique em \"+{{count}} mais\" para ver a lista completa", "limitedList.copyPasteText": "Você pode copiar e colar o texto acima", - "limitedList.showingItems": "Exibindo {{count}} itens", "logs": { "file": "Arquivo", "location": "linha {{line}} em {{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pt/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/pt/dags.json index 683cf090a8b15..6ac6422c19ed1 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/pt/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/pt/dags.json @@ -20,8 +20,7 @@ "all": "Todos", "paused": "Pausado" }, - "runIdPatternFilter": "Pesquisar Execuções de DAG", - "triggeringUserNameFilter": "Pesquisar por Usuário que Disparou" + "runIdPatternFilter": "Pesquisar Execuções de DAG" }, "ownerLink": "Link do Proprietário para {{owner}}", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json index 69d80a7672ba9..0a32afc28b301 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/tr/components.json @@ -92,7 +92,6 @@ "limitedList.clickToInteract": "Bir etikete tıklayarak Dag'leri filtreleyin", "limitedList.clickToOpenFull": "\"+{{count}} daha\" tıklayarak tam görünümü açın", "limitedList.copyPasteText": "Yukarıdaki metni kopyalayıp yapıştırabilirsiniz", - "limitedList.showingItems": "{{count}} öğe gösteriliyor", "logs": { "file": "Dosya", "location": "{{name}} içinde satır {{line}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/tr/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/tr/dags.json index 2974a4d4f4860..8c6d021305232 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/tr/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/tr/dags.json @@ -20,8 +20,7 @@ "all": "Tümü", "paused": "Duraklatılmış" }, - "runIdPatternFilter": "Dag Çalıştırmalarında Ara", - "triggeringUserNameFilter": "Tetikleyen Kullanıcıya Göre Ara" + "runIdPatternFilter": "Dag Çalıştırmalarında Ara" }, "ownerLink": "{{owner}} için sahip bağlantısı", "runAndTaskActions": { diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json index e7255110e8f3b..652011da2bea8 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/components.json @@ -90,7 +90,6 @@ "limitedList.clickToInteract": "点击标签以筛选 DAGs", "limitedList.clickToOpenFull": "点击 \"+{{count}} 更多\" 打开完整视图", "limitedList.copyPasteText": "你可以复制并粘贴上方文本", - "limitedList.showingItems": "显示 {{count}} 项", "logs": { "file": "文件", "location": "第 {{line}} 行,位于 {{name}}" diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/dags.json index b8e4f0929d4e0..90ba4ff9588cd 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-CN/dags.json @@ -20,8 +20,7 @@ "all": "全部", "paused": "暂停" }, - "runIdPatternFilter": "搜索 Dag 执行", - "triggeringUserNameFilter": "搜索触发用户名称" + "runIdPatternFilter": "搜索 Dag 执行" }, "ownerLink": "拥有者 {{owner}} 的地址", "runAndTaskActions": { diff --git a/dev/i18n/check_translations_completeness.py b/dev/i18n/check_translations_completeness.py index e3c2e0d841863..5972087b9ff7c 100755 --- a/dev/i18n/check_translations_completeness.py +++ b/dev/i18n/check_translations_completeness.py @@ -458,7 +458,13 @@ def print_translation_progress(console, locale_files, missing_counts, summary): default=False, help="Add missing translations for all languages except English, prefixed with 'TODO: translate:'.", ) -def cli(language: str | None = None, add_missing: bool = False): +@click.option( + "--remove-extra", + is_flag=True, + default=False, + help="Remove extra translations that are present in the language but missing in English.", +) +def cli(language: str | None = None, add_missing: bool = False, remove_extra: bool = False): locale_files = get_locale_files() console = Console(force_terminal=True, color_system="auto") print_locale_file_table(locale_files, console, language) @@ -481,6 +487,22 @@ def cli(language: str | None = None, add_missing: bool = False): add_missing_translations(lf.locale, filtered_summary, console) # After adding, re-run the summary for all languages summary, missing_counts = compare_keys(get_locale_files(), console) + if remove_extra and language != "en": + # Loop through all languages except 'en' and remove extra translations + if language: + language_files = [lf for lf in locale_files if lf.locale == language] + else: + language_files = [lf for lf in locale_files if lf.locale != "en"] + for lf in language_files: + filtered_summary = {} + for filename, diff in summary.items(): + filtered_summary[filename] = LocaleSummary( + missing_keys={lf.locale: diff.missing_keys.get(lf.locale, [])}, + extra_keys={lf.locale: diff.extra_keys.get(lf.locale, [])}, + ) + remove_extra_translations(lf.locale, filtered_summary, console) + # After removing, re-run the summary for all languages + summary, missing_counts = compare_keys(get_locale_files(), console) if language: locales = [lf.locale for lf in locale_files] if language not in locales: @@ -594,5 +616,51 @@ def natural_key_sort(obj): console.print(f"[green]Added missing translations to {lang_path}[/green]") +def remove_extra_translations(language: str, summary: dict[str, LocaleSummary], console: Console): + """ + Remove extra translations for the selected language. + + Removes keys that are present in the language file but missing in the English file. + """ + for filename, diff in summary.items(): + extra_keys = set(diff.extra_keys.get(language, [])) + if not extra_keys: + continue + lang_path = LOCALES_DIR / language / filename + try: + lang_data = load_json(lang_path) + except Exception as e: + console.print(f"[yellow]Failed to load {language} file {lang_path}: {e}[/yellow]") + continue + + # Helper to recursively remove extra keys + def remove_keys(dst, prefix=""): + keys_to_remove = [] + for k, v in list(dst.items()): + full_key = f"{prefix}.{k}" if prefix else k + if full_key in extra_keys: + keys_to_remove.append(k) + elif isinstance(v, dict): + remove_keys(v, full_key) + # Remove empty dictionaries after recursion + if not v: + keys_to_remove.append(k) + for k in keys_to_remove: + del dst[k] + + remove_keys(lang_data) + + def natural_key_sort(obj): + if isinstance(obj, dict): + return {k: natural_key_sort(obj[k]) for k in sorted(obj)} + return obj + + lang_data = natural_key_sort(lang_data) + with open(lang_path, "w", encoding="utf-8") as f: + json.dump(lang_data, f, ensure_ascii=False, indent=2) + f.write("\n") # Ensure newline at the end of the file + console.print(f"[green]Removed {len(extra_keys)} extra translations from {lang_path}[/green]") + + if __name__ == "__main__": cli()