fix: standardize i18n pluralization to two-part format (#9371)

## Summary

- Converts redundant three-part pluralization patterns (`zero | singular
| plural`) to standard two-part format (`singular | plural`) across 11
locale files
- Only converts patterns where the zero form duplicates the singular or
plural form
- Retains three-part patterns where the zero form provides distinct
content (e.g. `"No items selected"`) or where the language
linguistically requires all three forms (Russian singular/paucal/plural)

## Context

Vue i18n selects plural forms based on the number of choices:

**3-part** `"{count} nodes | {count} node | {count} nodes"`:
| count | index | result |
|-------|-------|--------|
| 0 | 0 (zero) | "0 nodes" |
| 1 | 1 (singular) | "1 node" |
| 2+ | 2 (plural) | "N nodes" |

**2-part** `"{count} node | {count} nodes"`:
| count | index | result |
|-------|-------|--------|
| 0 | 1 (plural) | "0 nodes" |
| 1 | 0 (singular) | "1 node" |
| 2+ | 1 (plural) | "N nodes" |

Output is identical — the zero form was always a duplicate of the plural
form. This PR removes that redundancy.

## Changes

| Locale | Keys changed |
|--------|-------------|
| en, es, fr, pt-BR, ar, tr | 5 (`nodesCount`, `asset`, `errorCount`,
`downloadsFailed`, `exportFailed`) |
| ja, ko, fa | 3 (`asset`, `nodesCount`, `downloadsFailed`) |
| ru | 2 (`downloadsFailed`, `exportFailed`) |
| zh-TW | 2 (`nodesCount`, `downloadsFailed`) |

## Test plan

- [ ] Verify pluralization renders correctly for count=0, count=1,
count=2+ in affected UI areas
- [ ] Spot-check non-English locales (especially Russian which retains
3-part for linguistically distinct forms)

- Fixes #9277

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
Dante
2026-03-13 10:16:59 +09:00
committed by GitHub
parent d82bce90ea
commit 649b9b3fe3
9 changed files with 36 additions and 36 deletions

View File

@@ -862,7 +862,7 @@
"promptExecutionError": "فشل تنفيذ الطلب"
},
"errorOverlay": {
"errorCount": "{count} أخطاء | {count} خطأ | {count} أخطاء",
"errorCount": "{count} خطأ | {count} أخطاء",
"seeErrors": "عرض الأخطاء"
},
"essentials": {
@@ -909,7 +909,7 @@
"downloadFailed": "فشل تحميل \"{name}\"",
"exportCompleted": "تحميل ZIP جاهز",
"exportError": "فشل التصدير",
"exportFailed": "{count} فشل في التصدير | {count} فشل في التصدير | {count} عمليات تصدير فشلت",
"exportFailed": "{count} فشل في التصدير | {count} عمليات تصدير فشلت",
"exportFailedSingle": "فشل إنشاء تصدير ZIP",
"exportStarted": "يتم تجهيز تحميل ZIP...",
"exportingAssets": "جاري تصدير العناصر",
@@ -928,7 +928,7 @@
"amount": "الكمية",
"apply": "تطبيق",
"architecture": "الهندسة المعمارية",
"asset": "{count} أصل | {count} أصل | {count} أصول",
"asset": "{count} أصل | {count} أصول",
"audioFailedToLoad": "فشل تحميل الصوت",
"audioProgress": "تقدم الصوت",
"author": "المؤلف",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "خطأ في فتحات العقدة",
"nodeWidgetsError": "خطأ في عناصر واجهة العقدة",
"nodes": "العُقَد",
"nodesCount": "{count} عقدة | {count} عقدة | {count} عقدة",
"nodesCount": "{count} عقدة | {count} عقدة",
"nodesRunning": "العُقَد قيد التشغيل",
"none": "لا شيء",
"nothingToCopy": "لا يوجد ما يمكن نسخه",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "اكتملت جميع التنزيلات",
"downloadingModel": "جاري تنزيل النموذج...",
"downloadsFailed": "{count} فشل في التنزيل | {count} فشل في التنزيل | {count} فشل في التنزيل",
"downloadsFailed": "{count} فشل في التنزيل | {count} فشل في التنزيل",
"failed": "فشل",
"filter": {
"all": "الكل",

View File

@@ -862,7 +862,7 @@
"promptExecutionError": "La ejecución del prompt falló"
},
"errorOverlay": {
"errorCount": "{count} ERRORES | {count} ERROR | {count} ERRORES",
"errorCount": "{count} ERROR | {count} ERRORES",
"seeErrors": "Ver errores"
},
"essentials": {
@@ -909,7 +909,7 @@
"downloadFailed": "No se pudo descargar \"{name}\"",
"exportCompleted": "Descarga ZIP lista",
"exportError": "Error en la exportación",
"exportFailed": "{count} exportación fallida | {count} exportación fallida | {count} exportaciones fallidas",
"exportFailed": "{count} exportación fallida | {count} exportaciones fallidas",
"exportFailedSingle": "No se pudo crear la exportación ZIP",
"exportStarted": "Preparando descarga ZIP...",
"exportingAssets": "Exportando recursos",
@@ -928,7 +928,7 @@
"amount": "Cantidad",
"apply": "Aplicar",
"architecture": "Arquitectura",
"asset": "{count} recursos | {count} recurso | {count} recursos",
"asset": "{count} recurso | {count} recursos",
"audioFailedToLoad": "No se pudo cargar el audio",
"audioProgress": "Progreso de audio",
"author": "Autor",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "Error de Ranuras del Nodo",
"nodeWidgetsError": "Error de Widgets del Nodo",
"nodes": "Nodos",
"nodesCount": "{count} nodos | {count} nodo | {count} nodos",
"nodesCount": "{count} nodo | {count} nodos",
"nodesRunning": "nodos en ejecución",
"none": "Ninguno",
"nothingToCopy": "Nada para copiar",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "Todas las descargas completadas",
"downloadingModel": "Descargando modelo...",
"downloadsFailed": "{count} descargas fallidas | {count} descarga fallida | {count} descargas fallidas",
"downloadsFailed": "{count} descarga fallida | {count} descargas fallidas",
"failed": "Fallido",
"filter": {
"all": "Todo",

View File

@@ -928,7 +928,7 @@
"amount": "مقدار",
"apply": "اعمال",
"architecture": "معماری",
"asset": "{count} دارایی | {count} دارایی | {count} دارایی",
"asset": "{count} دارایی | {count} دارایی",
"audioFailedToLoad": "بارگذاری صوت ناموفق بود",
"audioProgress": "پیشرفت صوت",
"author": "نویسنده",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "خطا در slotهای node",
"nodeWidgetsError": "خطا در ابزارک‌های node",
"nodes": "nodeها",
"nodesCount": "{count} نود | {count} نود | {count} نود",
"nodesCount": "{count} نود | {count} نود",
"nodesRunning": "nodeها در حال اجرا هستند",
"none": "هیچ‌کدام",
"nothingToCopy": "موردی برای کپی وجود ندارد",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "همه دانلودها تکمیل شدند",
"downloadingModel": "در حال دانلود مدل...",
"downloadsFailed": "{count} دانلود ناموفق بود | {count} دانلود ناموفق بود | {count} دانلود ناموفق بودند",
"downloadsFailed": "{count} دانلود ناموفق بود | {count} دانلود ناموفق بودند",
"failed": "ناموفق",
"filter": {
"all": "همه",

View File

@@ -862,7 +862,7 @@
"promptExecutionError": "L'exécution de l'invite a échoué"
},
"errorOverlay": {
"errorCount": "{count} ERREURS | {count} ERREUR | {count} ERREURS",
"errorCount": "{count} ERREUR | {count} ERREURS",
"seeErrors": "Voir les erreurs"
},
"essentials": {
@@ -909,7 +909,7 @@
"downloadFailed": "Échec du téléchargement de « {name} »",
"exportCompleted": "Téléchargement ZIP prêt",
"exportError": "Échec de lexportation",
"exportFailed": "{count} exportation échouée | {count} exportation échouée | {count} exportations échouées",
"exportFailed": "{count} exportation échouée | {count} exportations échouées",
"exportFailedSingle": "Échec de la création de lexport ZIP",
"exportStarted": "Préparation du téléchargement ZIP...",
"exportingAssets": "Exportation des ressources",
@@ -928,7 +928,7 @@
"amount": "Quantité",
"apply": "Appliquer",
"architecture": "Architecture",
"asset": "{count} ressources | {count} ressource | {count} ressources",
"asset": "{count} ressource | {count} ressources",
"audioFailedToLoad": "Échec du chargement de l'audio",
"audioProgress": "Progression audio",
"author": "Auteur",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "Erreur d'emplacements du nœud",
"nodeWidgetsError": "Erreur de widgets du nœud",
"nodes": "Nœuds",
"nodesCount": "{count} nœuds | {count} nœud | {count} nœuds",
"nodesCount": "{count} nœud | {count} nœuds",
"nodesRunning": "nœuds en cours dexécution",
"none": "Aucun",
"nothingToCopy": "Rien à copier",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "Tous les téléchargements sont terminés",
"downloadingModel": "Téléchargement du modèle...",
"downloadsFailed": "{count} téléchargements échoués | {count} téléchargement échoué | {count} téléchargements échoués",
"downloadsFailed": "{count} téléchargement échoué | {count} téléchargements échoués",
"failed": "Échec",
"filter": {
"all": "Tous",

View File

@@ -928,7 +928,7 @@
"amount": "量",
"apply": "適用する",
"architecture": "アーキテクチャ",
"asset": "{count} 個のアセット | {count} 個のアセット | {count} 個のアセット",
"asset": "{count} 個のアセット | {count} 個のアセット",
"audioFailedToLoad": "オーディオの読み込みに失敗しました",
"audioProgress": "オーディオの進捗",
"author": "作者",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "ノードスロットエラー",
"nodeWidgetsError": "ノードウィジェットエラー",
"nodes": "ノード",
"nodesCount": "{count} ノード | {count} ノード | {count} ノード",
"nodesCount": "{count} ノード | {count} ノード",
"nodesRunning": "ノードが実行中",
"none": "なし",
"nothingToCopy": "コピーするものがありません",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "すべてのダウンロードが完了しました",
"downloadingModel": "モデルをダウンロード中...",
"downloadsFailed": "{count}件のダウンロードに失敗 | {count}件のダウンロードに失敗 | {count}件のダウンロードに失敗",
"downloadsFailed": "{count}件のダウンロードに失敗 | {count}件のダウンロードに失敗",
"failed": "失敗",
"filter": {
"all": "すべて",

View File

@@ -928,7 +928,7 @@
"amount": "수량",
"apply": "적용",
"architecture": "아키텍처",
"asset": "{count}개 에셋 | {count}개 에셋 | {count}개 에셋",
"asset": "{count}개 에셋 | {count}개 에셋",
"audioFailedToLoad": "오디오를 불러오지 못했습니다",
"audioProgress": "오디오 진행률",
"author": "작성자",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "노드 슬롯 오류",
"nodeWidgetsError": "노드 위젯 오류",
"nodes": "노드",
"nodesCount": "{count}개 노드 | {count}개 노드 | {count}개 노드",
"nodesCount": "{count}개 노드 | {count}개 노드",
"nodesRunning": "노드 실행 중",
"none": "없음",
"nothingToCopy": "복사할 항목 없음",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "모든 다운로드가 완료되었습니다",
"downloadingModel": "모델 다운로드 중...",
"downloadsFailed": "{count}개 다운로드 실패 | {count}개 다운로드 실패 | {count}개 다운로드 실패",
"downloadsFailed": "{count}개 다운로드 실패 | {count}개 다운로드 실패",
"failed": "실패",
"filter": {
"all": "전체",

View File

@@ -862,7 +862,7 @@
"promptExecutionError": "Falha na execução do prompt"
},
"errorOverlay": {
"errorCount": "{count} ERROS | {count} ERRO | {count} ERROS",
"errorCount": "{count} ERRO | {count} ERROS",
"seeErrors": "Ver erros"
},
"essentials": {
@@ -909,7 +909,7 @@
"downloadFailed": "Falha ao baixar \"{name}\"",
"exportCompleted": "Download ZIP pronto",
"exportError": "Falha na exportação",
"exportFailed": "{count} exportação falhou | {count} exportação falhou | {count} exportações falharam",
"exportFailed": "{count} exportação falhou | {count} exportações falharam",
"exportFailedSingle": "Falha ao criar exportação ZIP",
"exportStarted": "Preparando download ZIP...",
"exportingAssets": "Exportando ativos",
@@ -928,7 +928,7 @@
"amount": "Quantidade",
"apply": "Aplicar",
"architecture": "Arquitetura",
"asset": "{count} ativos | {count} ativo | {count} ativos",
"asset": "{count} ativo | {count} ativos",
"audioFailedToLoad": "Falha ao carregar áudio",
"audioProgress": "Progresso do áudio",
"author": "Autor",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "Erro nos slots do nó",
"nodeWidgetsError": "Erro nos widgets do nó",
"nodes": "Nós",
"nodesCount": "{count} nós | {count} nó | {count} nós",
"nodesCount": "{count} nó | {count} nós",
"nodesRunning": "nós em execução",
"none": "Nenhum",
"nothingToCopy": "Nada para copiar",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "Todos os downloads concluídos",
"downloadingModel": "Baixando modelo...",
"downloadsFailed": "{count} downloads falharam | {count} download falhou | {count} downloads falharam",
"downloadsFailed": "{count} download falhou | {count} downloads falharam",
"failed": "Falhou",
"filter": {
"all": "Todos",

View File

@@ -862,7 +862,7 @@
"promptExecutionError": "İstem yürütmesi başarısız oldu"
},
"errorOverlay": {
"errorCount": "{count} HATA | {count} HATA | {count} HATA",
"errorCount": "{count} HATA | {count} HATA",
"seeErrors": "Hataları Gör"
},
"essentials": {
@@ -909,7 +909,7 @@
"downloadFailed": "\"{name}\" indirilemedi",
"exportCompleted": "ZIP indirme hazır",
"exportError": "Dışa aktarma başarısız",
"exportFailed": "{count} dışa aktarma başarısız oldu | {count} dışa aktarma başarısız oldu | {count} dışa aktarma başarısız oldu",
"exportFailed": "{count} dışa aktarma başarısız oldu | {count} dışa aktarma başarısız oldu",
"exportFailedSingle": "ZIP dışa aktarımı oluşturulamadı",
"exportStarted": "ZIP indirme hazırlanıyor...",
"exportingAssets": "Varlıklar dışa aktarılıyor",
@@ -928,7 +928,7 @@
"amount": "Miktar",
"apply": "Uygula",
"architecture": "Mimari",
"asset": "{count} varlık | {count} varlık | {count} varlık",
"asset": "{count} varlık | {count} varlık",
"audioFailedToLoad": "Ses yüklenemedi",
"audioProgress": "Ses ilerlemesi",
"author": "Yazar",
@@ -1114,7 +1114,7 @@
"nodeSlotsError": "Düğüm Yuva Hatası",
"nodeWidgetsError": "Düğüm Widget Hatası",
"nodes": "Düğümler",
"nodesCount": "{count} düğüm | {count} düğüm | {count} düğüm",
"nodesCount": "{count} düğüm | {count} düğüm",
"nodesRunning": "düğüm çalışıyor",
"none": "Hiçbiri",
"nothingToCopy": "Kopyalanacak bir şey yok",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "Tüm indirmeler tamamlandı",
"downloadingModel": "Model indiriliyor...",
"downloadsFailed": "{count} indirme başarısız oldu | {count} indirme başarısız oldu | {count} indirme başarısız oldu",
"downloadsFailed": "{count} indirme başarısız oldu | {count} indirme başarısız oldu",
"failed": "Başarısız",
"filter": {
"all": "Tümü",

View File

@@ -1114,7 +1114,7 @@
"nodeSlotsError": "節點插槽錯誤",
"nodeWidgetsError": "節點小工具錯誤",
"nodes": "節點",
"nodesCount": "{count} 個節點 | {count} 個節點 | {count} 個節點",
"nodesCount": "{count} 個節點 | {count} 個節點",
"nodesRunning": "節點執行中",
"none": "無",
"nothingToCopy": "沒有可複製的項目",
@@ -2295,7 +2295,7 @@
"progressToast": {
"allDownloadsCompleted": "所有下載已完成",
"downloadingModel": "正在下載模型...",
"downloadsFailed": "{count} 個下載失敗 | {count} 個下載失敗 | {count} 個下載失敗",
"downloadsFailed": "{count} 個下載失敗 | {count} 個下載失敗",
"failed": "失敗",
"filter": {
"all": "全部",