mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 17:10:50 -10:00
fix(tui): localize activity and file receipts [skip ci]
Use the shared locale catalog for grouped activity, tool status and file mutation receipts. Rebuild cached activity summaries when the UI locale changes and retain raw paths and diffs.
Refs #7071 (CW-7527). On frozen main 9fc5c898c7 plus 62 inventoried dirty paths: TUI lib and feature registry compiled; 197 focused nextest cases passed, including locale cache and receipt cases. Localization lib: 54 passed on unchanged localization inputs in the preceding frozen run. All 15 packs have 2732 keys; diff check and changelog sync passed. Earlier broader mixed-tree run had 2118 passed and one unrelated compaction failure. No native-speaker, real-provider, Windows, installed-artifact or release CI qualification is claimed.
This commit is contained in:
1 parent
9fc5c898c7
commit
896e781fbb
37 files changed
+1611
-307
No files matched your search
@@ -122,6 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Localized activity and file receipts:** grouped tool activity, file counts, patch outcomes and tool status use the shared 15-language catalog. Language changes refresh cached summaries; filenames, diffs and execution receipts keep their original data.
|
||||
- **Automatic compaction stays off when disabled:** preflight and provider context-overflow recovery preserve the conversation and report the setting instead of forcing a summary. Manual compaction remains available, and a rejected recovery request retains its actual cause in both paths. (#7142)
|
||||
|
||||
- **VS Code packaging security:** use patched MSAL Node in the extension packager, addressing [GHSA-xcx6-jx8p-x3h5](https://github.com/AzureAD/microsoft-authentication-library-for-js/security/advisories/GHSA-xcx6-jx8p-x3h5).
|
||||
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "supera el límit de {count} fitxers de regles per directori",
|
||||
"InstructionDetailSymlinkedRules": "es rebutja el directori de regles perquè és un enllaç simbòlic",
|
||||
"InstructionBudgetRefused": "el pressupost d'instruccions ha rebutjat aquesta sol·licitud: {reason}",
|
||||
"InstructionBudgetOverGuard": "el prefix estàtic de ~{measured} tokens estimats supera el límit {guard}"
|
||||
"InstructionBudgetOverGuard": "el prefix estàtic de ~{measured} tokens estimats supera el límit {guard}",
|
||||
"ActivityFileOne": "{count} fitxer",
|
||||
"ActivityFileMany": "{count} fitxers",
|
||||
"ActivitySearchOne": "{count} cerca",
|
||||
"ActivitySearchMany": "{count} cerques",
|
||||
"ActivityCommandOne": "{count} ordre",
|
||||
"ActivityCommandMany": "{count} ordres",
|
||||
"ActivityTaskOne": "{count} tasca",
|
||||
"ActivityTaskMany": "{count} tasques",
|
||||
"ActivityDirectoryOne": "{count} directori",
|
||||
"ActivityDirectoryMany": "{count} directoris",
|
||||
"ActivityPatternOne": "{count} patró",
|
||||
"ActivityPatternMany": "{count} patrons",
|
||||
"ActivityExplored": "Ha explorat {items}",
|
||||
"ActivityRan": "ha executat {items}",
|
||||
"ActivityEdited": "ha editat {items}",
|
||||
"ActivityDelegated": "ha delegat {items}",
|
||||
"ActivityUpdatedMetadata": "ha actualitzat les metadades",
|
||||
"ActivityRead": "Ha llegit {items}",
|
||||
"ActivityListed": "Ha llistat {items}",
|
||||
"ActivitySearched": "Ha cercat {items}",
|
||||
"ActivityWrote": "Ha escrit {items}",
|
||||
"ToolReceiptRunning": "en execució",
|
||||
"ToolReceiptHydrated": "carregat, no executat",
|
||||
"ToolReceiptStopped": "aturat",
|
||||
"ToolReceiptIssue": "incidència",
|
||||
"ToolReceiptFailed": "ha fallat",
|
||||
"AgentActivityChecking": "comprovant",
|
||||
"AgentActivityChecked": "comprovat",
|
||||
"AgentActivityWaiting": "esperant",
|
||||
"AgentActivityWaited": "espera acabada",
|
||||
"FileMutationCreated": "Creat: {path}",
|
||||
"FileMutationUpdated": "Actualitzat: {path}",
|
||||
"FileMutationDeleted": "Suprimit: {path}",
|
||||
"FileMutationRenamed": "Reanomenat: {path}",
|
||||
"FileMutationCreatedCount": "creats: {count}",
|
||||
"FileMutationUpdatedCount": "actualitzats: {count}",
|
||||
"FileMutationDeletedCount": "suprimits: {count}",
|
||||
"FileMutationRenamedCount": "reanomenats: {count}",
|
||||
"FileMutationChangedFiles": "Fitxers modificats",
|
||||
"FileMutationMissingFile": "fitxer",
|
||||
"FileMutationNoText": "(sense canvis de text)",
|
||||
"FileMutationMoreDiff": "+{count} línies de diff · {hint}",
|
||||
"FileMutationChange": "canvi",
|
||||
"FileMutationSelectReceipt": "Selecciona el canvi corresponent; {hint}.",
|
||||
"FileMutationExactEvidence": "evidència exacta al registre corresponent",
|
||||
"FileMutationExactHeading": "Canvi exacte del fitxer",
|
||||
"FileActivityReadLabel": "Lectura",
|
||||
"FileActivityListLabel": "Llista",
|
||||
"FileActivitySearchLabel": "Cerca",
|
||||
"FileActivityWriteLabel": "Escriptura",
|
||||
"FileActivityDetailTitle": "Feina · activitat dels fitxers",
|
||||
"ToolReceiptQuiet": "en execució · sense sortida des de fa {duration}",
|
||||
"FileActivityEditedLabel": "Editats",
|
||||
"FileMutationAcrossFiles": " en {count} fitxers",
|
||||
"FileActivityNoDetail": "No s'han conservat detalls de la destinació que es puguin mostrar amb seguretat",
|
||||
"FileActivityPatterns": "patrons",
|
||||
"FileActivitySettled": "finalitzat"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "über der Grenze von {count} Regeldateien pro Verzeichnis",
|
||||
"InstructionDetailSymlinkedRules": "Regelverzeichnis als Symlink abgelehnt",
|
||||
"InstructionBudgetRefused": "Das Anweisungsbudget hat diese Anfrage abgelehnt: {reason}",
|
||||
"InstructionBudgetOverGuard": "statisches Präfix von ~{measured} geschätzten Tokens überschreitet die Grenze {guard}"
|
||||
"InstructionBudgetOverGuard": "statisches Präfix von ~{measured} geschätzten Tokens überschreitet die Grenze {guard}",
|
||||
"ActivityFileOne": "{count} Datei",
|
||||
"ActivityFileMany": "{count} Dateien",
|
||||
"ActivitySearchOne": "{count} Suchvorgang",
|
||||
"ActivitySearchMany": "{count} Suchvorgänge",
|
||||
"ActivityCommandOne": "{count} Befehl",
|
||||
"ActivityCommandMany": "{count} Befehle",
|
||||
"ActivityTaskOne": "{count} Aufgabe",
|
||||
"ActivityTaskMany": "{count} Aufgaben",
|
||||
"ActivityDirectoryOne": "{count} Verzeichnis",
|
||||
"ActivityDirectoryMany": "{count} Verzeichnisse",
|
||||
"ActivityPatternOne": "{count} Muster",
|
||||
"ActivityPatternMany": "{count} Muster",
|
||||
"ActivityExplored": "Untersucht: {items}",
|
||||
"ActivityRan": "ausgeführt: {items}",
|
||||
"ActivityEdited": "bearbeitet: {items}",
|
||||
"ActivityDelegated": "delegiert: {items}",
|
||||
"ActivityUpdatedMetadata": "Metadaten aktualisiert",
|
||||
"ActivityRead": "Gelesen: {items}",
|
||||
"ActivityListed": "Aufgelistet: {items}",
|
||||
"ActivitySearched": "Gesucht: {items}",
|
||||
"ActivityWrote": "Geschrieben: {items}",
|
||||
"ToolReceiptRunning": "läuft",
|
||||
"ToolReceiptHydrated": "geladen, nicht ausgeführt",
|
||||
"ToolReceiptStopped": "gestoppt",
|
||||
"ToolReceiptIssue": "Problem",
|
||||
"ToolReceiptFailed": "fehlgeschlagen",
|
||||
"AgentActivityChecking": "wird geprüft",
|
||||
"AgentActivityChecked": "geprüft",
|
||||
"AgentActivityWaiting": "wartet",
|
||||
"AgentActivityWaited": "Warten beendet",
|
||||
"FileMutationCreated": "Erstellt: {path}",
|
||||
"FileMutationUpdated": "Aktualisiert: {path}",
|
||||
"FileMutationDeleted": "Gelöscht: {path}",
|
||||
"FileMutationRenamed": "Umbenannt: {path}",
|
||||
"FileMutationCreatedCount": "{count} erstellt",
|
||||
"FileMutationUpdatedCount": "{count} aktualisiert",
|
||||
"FileMutationDeletedCount": "{count} gelöscht",
|
||||
"FileMutationRenamedCount": "{count} umbenannt",
|
||||
"FileMutationChangedFiles": "Geänderte Dateien",
|
||||
"FileMutationMissingFile": "Datei",
|
||||
"FileMutationNoText": "(keine Textänderungen)",
|
||||
"FileMutationMoreDiff": "+{count} Diff-Zeilen · {hint}",
|
||||
"FileMutationChange": "Änderung",
|
||||
"FileMutationSelectReceipt": "Wähle die zugehörige Änderung; {hint}.",
|
||||
"FileMutationExactEvidence": "genauer Änderungsnachweis im zugehörigen Beleg",
|
||||
"FileMutationExactHeading": "Genaue Dateiänderung",
|
||||
"FileActivityReadLabel": "Lesen",
|
||||
"FileActivityListLabel": "Auflisten",
|
||||
"FileActivitySearchLabel": "Suchen",
|
||||
"FileActivityWriteLabel": "Schreiben",
|
||||
"FileActivityDetailTitle": "Arbeit · Dateiaktivität",
|
||||
"ToolReceiptQuiet": "läuft · keine Ausgabe seit {duration}",
|
||||
"FileActivityEditedLabel": "Bearbeitet",
|
||||
"FileMutationAcrossFiles": " in {count} Dateien",
|
||||
"FileActivityNoDetail": "Keine sicher anzeigbaren Zieldetails gespeichert",
|
||||
"FileActivityPatterns": "Muster",
|
||||
"FileActivitySettled": "abgeschlossen"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "beyond the per-directory rules cap of {count} files",
|
||||
"InstructionDetailSymlinkedRules": "refusing symlinked rules directory",
|
||||
"InstructionBudgetRefused": "instruction budget refused this request: {reason}",
|
||||
"InstructionBudgetOverGuard": "static prefix ~{measured} estimated tokens exceeds guard {guard}"
|
||||
"InstructionBudgetOverGuard": "static prefix ~{measured} estimated tokens exceeds guard {guard}",
|
||||
"ActivityFileOne": "{count} file",
|
||||
"ActivityFileMany": "{count} files",
|
||||
"ActivitySearchOne": "{count} search",
|
||||
"ActivitySearchMany": "{count} searches",
|
||||
"ActivityCommandOne": "{count} command",
|
||||
"ActivityCommandMany": "{count} commands",
|
||||
"ActivityTaskOne": "{count} task",
|
||||
"ActivityTaskMany": "{count} tasks",
|
||||
"ActivityDirectoryOne": "{count} directory",
|
||||
"ActivityDirectoryMany": "{count} directories",
|
||||
"ActivityPatternOne": "{count} pattern",
|
||||
"ActivityPatternMany": "{count} patterns",
|
||||
"ActivityExplored": "Explored {items}",
|
||||
"ActivityRan": "ran {items}",
|
||||
"ActivityEdited": "edited {items}",
|
||||
"ActivityDelegated": "delegated {items}",
|
||||
"ActivityUpdatedMetadata": "updated metadata",
|
||||
"ActivityRead": "Read {items}",
|
||||
"ActivityListed": "Listed {items}",
|
||||
"ActivitySearched": "Searched {items}",
|
||||
"ActivityWrote": "Wrote {items}",
|
||||
"ToolReceiptRunning": "running",
|
||||
"ToolReceiptHydrated": "loaded, not run",
|
||||
"ToolReceiptStopped": "stopped",
|
||||
"ToolReceiptIssue": "issue",
|
||||
"ToolReceiptFailed": "failed",
|
||||
"AgentActivityChecking": "checking",
|
||||
"AgentActivityChecked": "checked",
|
||||
"AgentActivityWaiting": "waiting",
|
||||
"AgentActivityWaited": "waited",
|
||||
"FileMutationCreated": "Created {path}",
|
||||
"FileMutationUpdated": "Updated {path}",
|
||||
"FileMutationDeleted": "Deleted {path}",
|
||||
"FileMutationRenamed": "Renamed {path}",
|
||||
"FileMutationCreatedCount": "{count} created",
|
||||
"FileMutationUpdatedCount": "{count} updated",
|
||||
"FileMutationDeletedCount": "{count} deleted",
|
||||
"FileMutationRenamedCount": "{count} renamed",
|
||||
"FileMutationChangedFiles": "Changed files",
|
||||
"FileMutationMissingFile": "file",
|
||||
"FileMutationNoText": "(no textual changes)",
|
||||
"FileMutationMoreDiff": "+{count} diff lines · {hint}",
|
||||
"FileMutationChange": "change",
|
||||
"FileMutationSelectReceipt": "Select the matching File receipt; {hint}.",
|
||||
"FileMutationExactEvidence": "exact change evidence on the matching File receipt",
|
||||
"FileMutationExactHeading": "Exact File change",
|
||||
"FileActivityReadLabel": "Read",
|
||||
"FileActivityListLabel": "List",
|
||||
"FileActivitySearchLabel": "Search",
|
||||
"FileActivityWriteLabel": "Write",
|
||||
"FileActivityDetailTitle": "Work · file activity",
|
||||
"ToolReceiptQuiet": "running · no output for {duration}",
|
||||
"FileActivityEditedLabel": "Edited",
|
||||
"FileMutationAcrossFiles": " across {count} files",
|
||||
"FileActivityNoDetail": "No safe target detail retained",
|
||||
"FileActivityPatterns": "patterns",
|
||||
"FileActivitySettled": "settled"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "excede el tope de {count} archivos de reglas por directorio",
|
||||
"InstructionDetailSymlinkedRules": "se rechaza el directorio de reglas por ser un enlace simbólico",
|
||||
"InstructionBudgetRefused": "el presupuesto de instrucciones rechazó esta solicitud: {reason}",
|
||||
"InstructionBudgetOverGuard": "el prefijo estático de ~{measured} tokens estimados supera el límite {guard}"
|
||||
"InstructionBudgetOverGuard": "el prefijo estático de ~{measured} tokens estimados supera el límite {guard}",
|
||||
"ActivityFileOne": "{count} archivo",
|
||||
"ActivityFileMany": "{count} archivos",
|
||||
"ActivitySearchOne": "{count} búsqueda",
|
||||
"ActivitySearchMany": "{count} búsquedas",
|
||||
"ActivityCommandOne": "{count} comando",
|
||||
"ActivityCommandMany": "{count} comandos",
|
||||
"ActivityTaskOne": "{count} tarea",
|
||||
"ActivityTaskMany": "{count} tareas",
|
||||
"ActivityDirectoryOne": "{count} directorio",
|
||||
"ActivityDirectoryMany": "{count} directorios",
|
||||
"ActivityPatternOne": "{count} patrón",
|
||||
"ActivityPatternMany": "{count} patrones",
|
||||
"ActivityExplored": "Exploró {items}",
|
||||
"ActivityRan": "ejecutó {items}",
|
||||
"ActivityEdited": "editó {items}",
|
||||
"ActivityDelegated": "delegó {items}",
|
||||
"ActivityUpdatedMetadata": "actualizó los metadatos",
|
||||
"ActivityRead": "Leyó {items}",
|
||||
"ActivityListed": "Enumeró {items}",
|
||||
"ActivitySearched": "Buscó {items}",
|
||||
"ActivityWrote": "Escribió {items}",
|
||||
"ToolReceiptRunning": "en ejecución",
|
||||
"ToolReceiptHydrated": "cargado, sin ejecutar",
|
||||
"ToolReceiptStopped": "detenido",
|
||||
"ToolReceiptIssue": "problema",
|
||||
"ToolReceiptFailed": "falló",
|
||||
"AgentActivityChecking": "comprobando",
|
||||
"AgentActivityChecked": "comprobado",
|
||||
"AgentActivityWaiting": "esperando",
|
||||
"AgentActivityWaited": "espera terminada",
|
||||
"FileMutationCreated": "Creado: {path}",
|
||||
"FileMutationUpdated": "Actualizado: {path}",
|
||||
"FileMutationDeleted": "Eliminado: {path}",
|
||||
"FileMutationRenamed": "Renombrado: {path}",
|
||||
"FileMutationCreatedCount": "creados: {count}",
|
||||
"FileMutationUpdatedCount": "actualizados: {count}",
|
||||
"FileMutationDeletedCount": "eliminados: {count}",
|
||||
"FileMutationRenamedCount": "renombrados: {count}",
|
||||
"FileMutationChangedFiles": "Archivos modificados",
|
||||
"FileMutationMissingFile": "archivo",
|
||||
"FileMutationNoText": "(sin cambios de texto)",
|
||||
"FileMutationMoreDiff": "+{count} líneas de diff · {hint}",
|
||||
"FileMutationChange": "cambio",
|
||||
"FileMutationSelectReceipt": "Selecciona el cambio correspondiente; {hint}.",
|
||||
"FileMutationExactEvidence": "evidencia exacta en el registro correspondiente",
|
||||
"FileMutationExactHeading": "Cambio exacto del archivo",
|
||||
"FileActivityReadLabel": "Lectura",
|
||||
"FileActivityListLabel": "Listado",
|
||||
"FileActivitySearchLabel": "Búsqueda",
|
||||
"FileActivityWriteLabel": "Escritura",
|
||||
"FileActivityDetailTitle": "Trabajo · actividad de archivos",
|
||||
"ToolReceiptQuiet": "en ejecución · sin salida desde hace {duration}",
|
||||
"FileActivityEditedLabel": "Editados",
|
||||
"FileMutationAcrossFiles": " en {count} archivos",
|
||||
"FileActivityNoDetail": "No se conservaron detalles del destino que puedan mostrarse de forma segura",
|
||||
"FileActivityPatterns": "patrones",
|
||||
"FileActivitySettled": "finalizado"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "au-delà du plafond de {count} fichiers de règles par répertoire",
|
||||
"InstructionDetailSymlinkedRules": "refus : le répertoire de règles est un lien symbolique",
|
||||
"InstructionBudgetRefused": "le budget d'instructions a refusé cette requête : {reason}",
|
||||
"InstructionBudgetOverGuard": "le préfixe statique de ~{measured} tokens estimés dépasse la limite {guard}"
|
||||
"InstructionBudgetOverGuard": "le préfixe statique de ~{measured} tokens estimés dépasse la limite {guard}",
|
||||
"ActivityFileOne": "{count} fichier",
|
||||
"ActivityFileMany": "{count} fichiers",
|
||||
"ActivitySearchOne": "{count} recherche",
|
||||
"ActivitySearchMany": "{count} recherches",
|
||||
"ActivityCommandOne": "{count} commande",
|
||||
"ActivityCommandMany": "{count} commandes",
|
||||
"ActivityTaskOne": "{count} tâche",
|
||||
"ActivityTaskMany": "{count} tâches",
|
||||
"ActivityDirectoryOne": "{count} répertoire",
|
||||
"ActivityDirectoryMany": "{count} répertoires",
|
||||
"ActivityPatternOne": "{count} motif",
|
||||
"ActivityPatternMany": "{count} motifs",
|
||||
"ActivityExplored": "Exploration de {items}",
|
||||
"ActivityRan": "exécution de {items}",
|
||||
"ActivityEdited": "modification de {items}",
|
||||
"ActivityDelegated": "délégation de {items}",
|
||||
"ActivityUpdatedMetadata": "métadonnées mises à jour",
|
||||
"ActivityRead": "Lecture de {items}",
|
||||
"ActivityListed": "Liste de {items}",
|
||||
"ActivitySearched": "Recherche de {items}",
|
||||
"ActivityWrote": "Écriture de {items}",
|
||||
"ToolReceiptRunning": "en cours",
|
||||
"ToolReceiptHydrated": "chargé, non exécuté",
|
||||
"ToolReceiptStopped": "arrêté",
|
||||
"ToolReceiptIssue": "problème",
|
||||
"ToolReceiptFailed": "échec",
|
||||
"AgentActivityChecking": "vérification en cours",
|
||||
"AgentActivityChecked": "vérifié",
|
||||
"AgentActivityWaiting": "en attente",
|
||||
"AgentActivityWaited": "attente terminée",
|
||||
"FileMutationCreated": "Créé : {path}",
|
||||
"FileMutationUpdated": "Mis à jour : {path}",
|
||||
"FileMutationDeleted": "Supprimé : {path}",
|
||||
"FileMutationRenamed": "Renommé : {path}",
|
||||
"FileMutationCreatedCount": "créés : {count}",
|
||||
"FileMutationUpdatedCount": "mis à jour : {count}",
|
||||
"FileMutationDeletedCount": "supprimés : {count}",
|
||||
"FileMutationRenamedCount": "renommés : {count}",
|
||||
"FileMutationChangedFiles": "Fichiers modifiés",
|
||||
"FileMutationMissingFile": "fichier",
|
||||
"FileMutationNoText": "(aucune modification textuelle)",
|
||||
"FileMutationMoreDiff": "+{count} lignes de diff · {hint}",
|
||||
"FileMutationChange": "modification",
|
||||
"FileMutationSelectReceipt": "Sélectionnez la modification correspondante ; {hint}.",
|
||||
"FileMutationExactEvidence": "preuve exacte sur le reçu correspondant",
|
||||
"FileMutationExactHeading": "Modification exacte du fichier",
|
||||
"FileActivityReadLabel": "Lecture",
|
||||
"FileActivityListLabel": "Liste",
|
||||
"FileActivitySearchLabel": "Recherche",
|
||||
"FileActivityWriteLabel": "Écriture",
|
||||
"FileActivityDetailTitle": "Travail · activité des fichiers",
|
||||
"ToolReceiptQuiet": "en cours · aucune sortie depuis {duration}",
|
||||
"FileActivityEditedLabel": "Modifiés",
|
||||
"FileMutationAcrossFiles": " dans {count} fichiers",
|
||||
"FileActivityNoDetail": "Aucun détail de cible conservé pour un affichage sûr",
|
||||
"FileActivityPatterns": "motifs",
|
||||
"FileActivitySettled": "terminé"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "प्रति निर्देशिका {count} नियम फ़ाइलों की सीमा से अधिक",
|
||||
"InstructionDetailSymlinkedRules": "सिमलिंक वाली नियम निर्देशिका अस्वीकार की गई",
|
||||
"InstructionBudgetRefused": "निर्देश बजट ने इस अनुरोध को अस्वीकार कर दिया: {reason}",
|
||||
"InstructionBudgetOverGuard": "स्थिर प्रीफ़िक्स ~{measured} अनुमानित टोकन सीमा {guard} से अधिक है"
|
||||
"InstructionBudgetOverGuard": "स्थिर प्रीफ़िक्स ~{measured} अनुमानित टोकन सीमा {guard} से अधिक है",
|
||||
"ActivityFileOne": "{count} फ़ाइल",
|
||||
"ActivityFileMany": "{count} फ़ाइलें",
|
||||
"ActivitySearchOne": "{count} खोज",
|
||||
"ActivitySearchMany": "{count} खोजें",
|
||||
"ActivityCommandOne": "{count} कमांड",
|
||||
"ActivityCommandMany": "{count} कमांड",
|
||||
"ActivityTaskOne": "{count} कार्य",
|
||||
"ActivityTaskMany": "{count} कार्य",
|
||||
"ActivityDirectoryOne": "{count} डायरेक्टरी",
|
||||
"ActivityDirectoryMany": "{count} डायरेक्टरियाँ",
|
||||
"ActivityPatternOne": "{count} पैटर्न",
|
||||
"ActivityPatternMany": "{count} पैटर्न",
|
||||
"ActivityExplored": "जाँच: {items}",
|
||||
"ActivityRan": "निष्पादन: {items}",
|
||||
"ActivityEdited": "संपादन: {items}",
|
||||
"ActivityDelegated": "सौंपे गए: {items}",
|
||||
"ActivityUpdatedMetadata": "मेटाडेटा अपडेट किया",
|
||||
"ActivityRead": "पढ़ना: {items}",
|
||||
"ActivityListed": "सूची: {items}",
|
||||
"ActivitySearched": "खोज: {items}",
|
||||
"ActivityWrote": "लेखन: {items}",
|
||||
"ToolReceiptRunning": "चल रहा है",
|
||||
"ToolReceiptHydrated": "लोड हुआ, चलाया नहीं गया",
|
||||
"ToolReceiptStopped": "रोका गया",
|
||||
"ToolReceiptIssue": "समस्या",
|
||||
"ToolReceiptFailed": "विफल",
|
||||
"AgentActivityChecking": "जाँच जारी",
|
||||
"AgentActivityChecked": "जाँच पूरी",
|
||||
"AgentActivityWaiting": "प्रतीक्षा जारी",
|
||||
"AgentActivityWaited": "प्रतीक्षा पूरी",
|
||||
"FileMutationCreated": "बनाया: {path}",
|
||||
"FileMutationUpdated": "अपडेट किया: {path}",
|
||||
"FileMutationDeleted": "हटाया: {path}",
|
||||
"FileMutationRenamed": "नाम बदला: {path}",
|
||||
"FileMutationCreatedCount": "बनाए: {count}",
|
||||
"FileMutationUpdatedCount": "अपडेट किए: {count}",
|
||||
"FileMutationDeletedCount": "हटाए: {count}",
|
||||
"FileMutationRenamedCount": "नाम बदले: {count}",
|
||||
"FileMutationChangedFiles": "बदली गई फ़ाइलें",
|
||||
"FileMutationMissingFile": "फ़ाइल",
|
||||
"FileMutationNoText": "(पाठ में कोई बदलाव नहीं)",
|
||||
"FileMutationMoreDiff": "+{count} डिफ़ पंक्तियाँ · {hint}",
|
||||
"FileMutationChange": "बदलाव",
|
||||
"FileMutationSelectReceipt": "संबंधित बदलाव चुनें; {hint}.",
|
||||
"FileMutationExactEvidence": "संबंधित रिकॉर्ड में बदलाव का सटीक प्रमाण",
|
||||
"FileMutationExactHeading": "फ़ाइल का सटीक बदलाव",
|
||||
"FileActivityReadLabel": "पढ़ना",
|
||||
"FileActivityListLabel": "सूची",
|
||||
"FileActivitySearchLabel": "खोज",
|
||||
"FileActivityWriteLabel": "लेखन",
|
||||
"FileActivityDetailTitle": "कार्य · फ़ाइल गतिविधि",
|
||||
"ToolReceiptQuiet": "चल रहा है · {duration} से कोई आउटपुट नहीं",
|
||||
"FileActivityEditedLabel": "संपादित",
|
||||
"FileMutationAcrossFiles": " · {count} फ़ाइलों में",
|
||||
"FileActivityNoDetail": "सुरक्षित रूप से दिखाने योग्य लक्ष्य विवरण नहीं रखा गया",
|
||||
"FileActivityPatterns": "पैटर्न",
|
||||
"FileActivitySettled": "पूरा हुआ"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "melebihi batas {count} berkas aturan per direktori",
|
||||
"InstructionDetailSymlinkedRules": "menolak direktori aturan berupa symlink",
|
||||
"InstructionBudgetRefused": "anggaran instruksi menolak permintaan ini: {reason}",
|
||||
"InstructionBudgetOverGuard": "prefiks statis ~{measured} token perkiraan melebihi batas {guard}"
|
||||
"InstructionBudgetOverGuard": "prefiks statis ~{measured} token perkiraan melebihi batas {guard}",
|
||||
"ActivityFileOne": "{count} berkas",
|
||||
"ActivityFileMany": "{count} berkas",
|
||||
"ActivitySearchOne": "{count} pencarian",
|
||||
"ActivitySearchMany": "{count} pencarian",
|
||||
"ActivityCommandOne": "{count} perintah",
|
||||
"ActivityCommandMany": "{count} perintah",
|
||||
"ActivityTaskOne": "{count} tugas",
|
||||
"ActivityTaskMany": "{count} tugas",
|
||||
"ActivityDirectoryOne": "{count} direktori",
|
||||
"ActivityDirectoryMany": "{count} direktori",
|
||||
"ActivityPatternOne": "{count} pola",
|
||||
"ActivityPatternMany": "{count} pola",
|
||||
"ActivityExplored": "Menjelajahi {items}",
|
||||
"ActivityRan": "menjalankan {items}",
|
||||
"ActivityEdited": "mengedit {items}",
|
||||
"ActivityDelegated": "mendelegasikan {items}",
|
||||
"ActivityUpdatedMetadata": "memperbarui metadata",
|
||||
"ActivityRead": "Membaca {items}",
|
||||
"ActivityListed": "Mendaftar {items}",
|
||||
"ActivitySearched": "Mencari {items}",
|
||||
"ActivityWrote": "Menulis {items}",
|
||||
"ToolReceiptRunning": "berjalan",
|
||||
"ToolReceiptHydrated": "dimuat, belum dijalankan",
|
||||
"ToolReceiptStopped": "dihentikan",
|
||||
"ToolReceiptIssue": "masalah",
|
||||
"ToolReceiptFailed": "gagal",
|
||||
"AgentActivityChecking": "memeriksa",
|
||||
"AgentActivityChecked": "diperiksa",
|
||||
"AgentActivityWaiting": "menunggu",
|
||||
"AgentActivityWaited": "penantian selesai",
|
||||
"FileMutationCreated": "Dibuat: {path}",
|
||||
"FileMutationUpdated": "Diperbarui: {path}",
|
||||
"FileMutationDeleted": "Dihapus: {path}",
|
||||
"FileMutationRenamed": "Diubah namanya: {path}",
|
||||
"FileMutationCreatedCount": "{count} dibuat",
|
||||
"FileMutationUpdatedCount": "{count} diperbarui",
|
||||
"FileMutationDeletedCount": "{count} dihapus",
|
||||
"FileMutationRenamedCount": "{count} diubah namanya",
|
||||
"FileMutationChangedFiles": "Berkas yang diubah",
|
||||
"FileMutationMissingFile": "berkas",
|
||||
"FileMutationNoText": "(tidak ada perubahan teks)",
|
||||
"FileMutationMoreDiff": "+{count} baris diff · {hint}",
|
||||
"FileMutationChange": "perubahan",
|
||||
"FileMutationSelectReceipt": "Pilih perubahan yang sesuai; {hint}.",
|
||||
"FileMutationExactEvidence": "bukti perubahan persis pada catatan yang sesuai",
|
||||
"FileMutationExactHeading": "Perubahan berkas persis",
|
||||
"FileActivityReadLabel": "Baca",
|
||||
"FileActivityListLabel": "Daftar",
|
||||
"FileActivitySearchLabel": "Cari",
|
||||
"FileActivityWriteLabel": "Tulis",
|
||||
"FileActivityDetailTitle": "Kerja · aktivitas berkas",
|
||||
"ToolReceiptQuiet": "berjalan · tidak ada keluaran selama {duration}",
|
||||
"FileActivityEditedLabel": "Diedit",
|
||||
"FileMutationAcrossFiles": " di {count} berkas",
|
||||
"FileActivityNoDetail": "Tidak ada detail target yang aman untuk ditampilkan",
|
||||
"FileActivityPatterns": "pola",
|
||||
"FileActivitySettled": "selesai"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "ディレクトリごとのルール上限 {count} ファイルを超えています",
|
||||
"InstructionDetailSymlinkedRules": "シンボリックリンクのルールディレクトリは拒否します",
|
||||
"InstructionBudgetRefused": "指示の予算により、このリクエストは拒否されました: {reason}",
|
||||
"InstructionBudgetOverGuard": "静的プレフィックス ~{measured} 推定トークンが上限 {guard} を超えています"
|
||||
"InstructionBudgetOverGuard": "静的プレフィックス ~{measured} 推定トークンが上限 {guard} を超えています",
|
||||
"ActivityFileOne": "{count}ファイル",
|
||||
"ActivityFileMany": "{count}ファイル",
|
||||
"ActivitySearchOne": "{count}回の検索",
|
||||
"ActivitySearchMany": "{count}回の検索",
|
||||
"ActivityCommandOne": "{count}コマンド",
|
||||
"ActivityCommandMany": "{count}コマンド",
|
||||
"ActivityTaskOne": "{count}タスク",
|
||||
"ActivityTaskMany": "{count}タスク",
|
||||
"ActivityDirectoryOne": "{count}ディレクトリ",
|
||||
"ActivityDirectoryMany": "{count}ディレクトリ",
|
||||
"ActivityPatternOne": "{count}パターン",
|
||||
"ActivityPatternMany": "{count}パターン",
|
||||
"ActivityExplored": "{items}を確認",
|
||||
"ActivityRan": "{items}を実行",
|
||||
"ActivityEdited": "{items}を編集",
|
||||
"ActivityDelegated": "{items}を委任",
|
||||
"ActivityUpdatedMetadata": "メタデータを更新",
|
||||
"ActivityRead": "{items}を読み取り",
|
||||
"ActivityListed": "{items}を一覧表示",
|
||||
"ActivitySearched": "{items}を検索",
|
||||
"ActivityWrote": "{items}に書き込み",
|
||||
"ToolReceiptRunning": "実行中",
|
||||
"ToolReceiptHydrated": "読込済み、未実行",
|
||||
"ToolReceiptStopped": "停止",
|
||||
"ToolReceiptIssue": "問題あり",
|
||||
"ToolReceiptFailed": "失敗",
|
||||
"AgentActivityChecking": "確認中",
|
||||
"AgentActivityChecked": "確認済み",
|
||||
"AgentActivityWaiting": "待機中",
|
||||
"AgentActivityWaited": "待機終了",
|
||||
"FileMutationCreated": "作成: {path}",
|
||||
"FileMutationUpdated": "更新: {path}",
|
||||
"FileMutationDeleted": "削除: {path}",
|
||||
"FileMutationRenamed": "名前変更: {path}",
|
||||
"FileMutationCreatedCount": "作成 {count}件",
|
||||
"FileMutationUpdatedCount": "更新 {count}件",
|
||||
"FileMutationDeletedCount": "削除 {count}件",
|
||||
"FileMutationRenamedCount": "名前変更 {count}件",
|
||||
"FileMutationChangedFiles": "変更されたファイル",
|
||||
"FileMutationMissingFile": "ファイル",
|
||||
"FileMutationNoText": "(テキストの変更なし)",
|
||||
"FileMutationMoreDiff": "差分があと{count}行 · {hint}",
|
||||
"FileMutationChange": "変更",
|
||||
"FileMutationSelectReceipt": "該当する変更を選択してください。{hint}。",
|
||||
"FileMutationExactEvidence": "該当する記録の正確な変更内容",
|
||||
"FileMutationExactHeading": "正確なファイル変更",
|
||||
"FileActivityReadLabel": "読み取り",
|
||||
"FileActivityListLabel": "一覧",
|
||||
"FileActivitySearchLabel": "検索",
|
||||
"FileActivityWriteLabel": "書き込み",
|
||||
"FileActivityDetailTitle": "作業 · ファイル操作",
|
||||
"ToolReceiptQuiet": "実行中 · {duration}出力なし",
|
||||
"FileActivityEditedLabel": "編集済み",
|
||||
"FileMutationAcrossFiles": "({count}ファイル全体)",
|
||||
"FileActivityNoDetail": "安全に表示できる対象の詳細は保持されていません",
|
||||
"FileActivityPatterns": "パターン",
|
||||
"FileActivitySettled": "完了"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "디렉터리당 규칙 파일 한도 {count}개를 초과합니다",
|
||||
"InstructionDetailSymlinkedRules": "심볼릭 링크 규칙 디렉터리는 거부합니다",
|
||||
"InstructionBudgetRefused": "지침 예산이 이 요청을 거부했습니다: {reason}",
|
||||
"InstructionBudgetOverGuard": "정적 접두부 ~{measured} 추정 토큰이 한도 {guard}를 초과합니다"
|
||||
"InstructionBudgetOverGuard": "정적 접두부 ~{measured} 추정 토큰이 한도 {guard}를 초과합니다",
|
||||
"ActivityFileOne": "파일 {count}개",
|
||||
"ActivityFileMany": "파일 {count}개",
|
||||
"ActivitySearchOne": "검색 {count}회",
|
||||
"ActivitySearchMany": "검색 {count}회",
|
||||
"ActivityCommandOne": "명령 {count}개",
|
||||
"ActivityCommandMany": "명령 {count}개",
|
||||
"ActivityTaskOne": "작업 {count}개",
|
||||
"ActivityTaskMany": "작업 {count}개",
|
||||
"ActivityDirectoryOne": "디렉터리 {count}개",
|
||||
"ActivityDirectoryMany": "디렉터리 {count}개",
|
||||
"ActivityPatternOne": "패턴 {count}개",
|
||||
"ActivityPatternMany": "패턴 {count}개",
|
||||
"ActivityExplored": "{items} 확인",
|
||||
"ActivityRan": "{items} 실행",
|
||||
"ActivityEdited": "{items} 편집",
|
||||
"ActivityDelegated": "{items} 위임",
|
||||
"ActivityUpdatedMetadata": "메타데이터 업데이트",
|
||||
"ActivityRead": "{items} 읽음",
|
||||
"ActivityListed": "{items} 나열",
|
||||
"ActivitySearched": "{items} 검색",
|
||||
"ActivityWrote": "{items} 기록",
|
||||
"ToolReceiptRunning": "실행 중",
|
||||
"ToolReceiptHydrated": "불러옴, 실행하지 않음",
|
||||
"ToolReceiptStopped": "중지됨",
|
||||
"ToolReceiptIssue": "문제 있음",
|
||||
"ToolReceiptFailed": "실패",
|
||||
"AgentActivityChecking": "확인 중",
|
||||
"AgentActivityChecked": "확인됨",
|
||||
"AgentActivityWaiting": "대기 중",
|
||||
"AgentActivityWaited": "대기 완료",
|
||||
"FileMutationCreated": "생성: {path}",
|
||||
"FileMutationUpdated": "수정: {path}",
|
||||
"FileMutationDeleted": "삭제: {path}",
|
||||
"FileMutationRenamed": "이름 변경: {path}",
|
||||
"FileMutationCreatedCount": "생성 {count}개",
|
||||
"FileMutationUpdatedCount": "수정 {count}개",
|
||||
"FileMutationDeletedCount": "삭제 {count}개",
|
||||
"FileMutationRenamedCount": "이름 변경 {count}개",
|
||||
"FileMutationChangedFiles": "변경된 파일",
|
||||
"FileMutationMissingFile": "파일",
|
||||
"FileMutationNoText": "(텍스트 변경 없음)",
|
||||
"FileMutationMoreDiff": "차이 {count}줄 더 있음 · {hint}",
|
||||
"FileMutationChange": "변경",
|
||||
"FileMutationSelectReceipt": "해당 변경을 선택하세요. {hint}.",
|
||||
"FileMutationExactEvidence": "해당 기록의 정확한 변경 증거",
|
||||
"FileMutationExactHeading": "정확한 파일 변경",
|
||||
"FileActivityReadLabel": "읽기",
|
||||
"FileActivityListLabel": "목록",
|
||||
"FileActivitySearchLabel": "검색",
|
||||
"FileActivityWriteLabel": "쓰기",
|
||||
"FileActivityDetailTitle": "작업 · 파일 활동",
|
||||
"ToolReceiptQuiet": "실행 중 · {duration} 동안 출력 없음",
|
||||
"FileActivityEditedLabel": "편집됨",
|
||||
"FileMutationAcrossFiles": " · 파일 {count}개에 걸침",
|
||||
"FileActivityNoDetail": "안전하게 표시할 대상 세부 정보가 보관되지 않음",
|
||||
"FileActivityPatterns": "패턴",
|
||||
"FileActivitySettled": "완료됨"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "acima do limite de {count} arquivos de regras por diretório",
|
||||
"InstructionDetailSymlinkedRules": "recusando diretório de regras que é um link simbólico",
|
||||
"InstructionBudgetRefused": "o orçamento de instruções recusou esta solicitação: {reason}",
|
||||
"InstructionBudgetOverGuard": "o prefixo estático de ~{measured} tokens estimados excede o limite {guard}"
|
||||
"InstructionBudgetOverGuard": "o prefixo estático de ~{measured} tokens estimados excede o limite {guard}",
|
||||
"ActivityFileOne": "{count} arquivo",
|
||||
"ActivityFileMany": "{count} arquivos",
|
||||
"ActivitySearchOne": "{count} busca",
|
||||
"ActivitySearchMany": "{count} buscas",
|
||||
"ActivityCommandOne": "{count} comando",
|
||||
"ActivityCommandMany": "{count} comandos",
|
||||
"ActivityTaskOne": "{count} tarefa",
|
||||
"ActivityTaskMany": "{count} tarefas",
|
||||
"ActivityDirectoryOne": "{count} diretório",
|
||||
"ActivityDirectoryMany": "{count} diretórios",
|
||||
"ActivityPatternOne": "{count} padrão",
|
||||
"ActivityPatternMany": "{count} padrões",
|
||||
"ActivityExplored": "Explorou {items}",
|
||||
"ActivityRan": "executou {items}",
|
||||
"ActivityEdited": "editou {items}",
|
||||
"ActivityDelegated": "delegou {items}",
|
||||
"ActivityUpdatedMetadata": "atualizou os metadados",
|
||||
"ActivityRead": "Leu {items}",
|
||||
"ActivityListed": "Listou {items}",
|
||||
"ActivitySearched": "Pesquisou {items}",
|
||||
"ActivityWrote": "Gravou {items}",
|
||||
"ToolReceiptRunning": "em execução",
|
||||
"ToolReceiptHydrated": "carregado, não executado",
|
||||
"ToolReceiptStopped": "parado",
|
||||
"ToolReceiptIssue": "problema",
|
||||
"ToolReceiptFailed": "falhou",
|
||||
"AgentActivityChecking": "verificando",
|
||||
"AgentActivityChecked": "verificado",
|
||||
"AgentActivityWaiting": "aguardando",
|
||||
"AgentActivityWaited": "espera concluída",
|
||||
"FileMutationCreated": "Criado: {path}",
|
||||
"FileMutationUpdated": "Atualizado: {path}",
|
||||
"FileMutationDeleted": "Excluído: {path}",
|
||||
"FileMutationRenamed": "Renomeado: {path}",
|
||||
"FileMutationCreatedCount": "{count} criados",
|
||||
"FileMutationUpdatedCount": "{count} atualizados",
|
||||
"FileMutationDeletedCount": "{count} excluídos",
|
||||
"FileMutationRenamedCount": "{count} renomeados",
|
||||
"FileMutationChangedFiles": "Arquivos alterados",
|
||||
"FileMutationMissingFile": "arquivo",
|
||||
"FileMutationNoText": "(sem alterações de texto)",
|
||||
"FileMutationMoreDiff": "+{count} linhas de diff · {hint}",
|
||||
"FileMutationChange": "alteração",
|
||||
"FileMutationSelectReceipt": "Selecione a alteração correspondente; {hint}.",
|
||||
"FileMutationExactEvidence": "evidência exata no registro correspondente",
|
||||
"FileMutationExactHeading": "Alteração exata do arquivo",
|
||||
"FileActivityReadLabel": "Leitura",
|
||||
"FileActivityListLabel": "Listagem",
|
||||
"FileActivitySearchLabel": "Busca",
|
||||
"FileActivityWriteLabel": "Gravação",
|
||||
"FileActivityDetailTitle": "Trabalho · atividade de arquivos",
|
||||
"ToolReceiptQuiet": "em execução · sem saída há {duration}",
|
||||
"FileActivityEditedLabel": "Editados",
|
||||
"FileMutationAcrossFiles": " em {count} arquivos",
|
||||
"FileActivityNoDetail": "Nenhum detalhe do destino foi mantido para exibição segura",
|
||||
"FileActivityPatterns": "padrões",
|
||||
"FileActivitySettled": "concluído"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "сверх предела в {count} файлов правил на каталог",
|
||||
"InstructionDetailSymlinkedRules": "отклонено: каталог правил является символической ссылкой",
|
||||
"InstructionBudgetRefused": "бюджет инструкций отклонил этот запрос: {reason}",
|
||||
"InstructionBudgetOverGuard": "статический префикс ~{measured} оценочных токенов превышает предел {guard}"
|
||||
"InstructionBudgetOverGuard": "статический префикс ~{measured} оценочных токенов превышает предел {guard}",
|
||||
"ActivityFileOne": "файл — {count}",
|
||||
"ActivityFileMany": "файлы — {count}",
|
||||
"ActivitySearchOne": "поиск — {count}",
|
||||
"ActivitySearchMany": "поиски — {count}",
|
||||
"ActivityCommandOne": "команда — {count}",
|
||||
"ActivityCommandMany": "команды — {count}",
|
||||
"ActivityTaskOne": "задача — {count}",
|
||||
"ActivityTaskMany": "задачи — {count}",
|
||||
"ActivityDirectoryOne": "каталог — {count}",
|
||||
"ActivityDirectoryMany": "каталоги — {count}",
|
||||
"ActivityPatternOne": "шаблон — {count}",
|
||||
"ActivityPatternMany": "шаблоны — {count}",
|
||||
"ActivityExplored": "Просмотрено: {items}",
|
||||
"ActivityRan": "выполнено: {items}",
|
||||
"ActivityEdited": "изменено: {items}",
|
||||
"ActivityDelegated": "делегировано: {items}",
|
||||
"ActivityUpdatedMetadata": "метаданные обновлены",
|
||||
"ActivityRead": "Прочитано: {items}",
|
||||
"ActivityListed": "Перечислено: {items}",
|
||||
"ActivitySearched": "Найдено: {items}",
|
||||
"ActivityWrote": "Записано: {items}",
|
||||
"ToolReceiptRunning": "выполняется",
|
||||
"ToolReceiptHydrated": "загружено, не запущено",
|
||||
"ToolReceiptStopped": "остановлено",
|
||||
"ToolReceiptIssue": "проблема",
|
||||
"ToolReceiptFailed": "ошибка",
|
||||
"AgentActivityChecking": "проверка",
|
||||
"AgentActivityChecked": "проверено",
|
||||
"AgentActivityWaiting": "ожидание",
|
||||
"AgentActivityWaited": "ожидание завершено",
|
||||
"FileMutationCreated": "Создано: {path}",
|
||||
"FileMutationUpdated": "Обновлено: {path}",
|
||||
"FileMutationDeleted": "Удалено: {path}",
|
||||
"FileMutationRenamed": "Переименовано: {path}",
|
||||
"FileMutationCreatedCount": "создано: {count}",
|
||||
"FileMutationUpdatedCount": "обновлено: {count}",
|
||||
"FileMutationDeletedCount": "удалено: {count}",
|
||||
"FileMutationRenamedCount": "переименовано: {count}",
|
||||
"FileMutationChangedFiles": "Изменённые файлы",
|
||||
"FileMutationMissingFile": "файл",
|
||||
"FileMutationNoText": "(текстовых изменений нет)",
|
||||
"FileMutationMoreDiff": "ещё {count} строк diff · {hint}",
|
||||
"FileMutationChange": "изменение",
|
||||
"FileMutationSelectReceipt": "Выберите соответствующее изменение; {hint}.",
|
||||
"FileMutationExactEvidence": "точные данные изменения в соответствующей записи",
|
||||
"FileMutationExactHeading": "Точное изменение файла",
|
||||
"FileActivityReadLabel": "Чтение",
|
||||
"FileActivityListLabel": "Список",
|
||||
"FileActivitySearchLabel": "Поиск",
|
||||
"FileActivityWriteLabel": "Запись",
|
||||
"FileActivityDetailTitle": "Работа · действия с файлами",
|
||||
"ToolReceiptQuiet": "выполняется · нет вывода {duration}",
|
||||
"FileActivityEditedLabel": "Изменено",
|
||||
"FileMutationAcrossFiles": " · файлов: {count}",
|
||||
"FileActivityNoDetail": "Не сохранены сведения о цели, которые можно безопасно показать",
|
||||
"FileActivityPatterns": "шаблоны",
|
||||
"FileActivitySettled": "завершено"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "понад ліміт у {count} файлів правил на каталог",
|
||||
"InstructionDetailSymlinkedRules": "відхилено: каталог правил є символічним посиланням",
|
||||
"InstructionBudgetRefused": "бюджет інструкцій відхилив цей запит: {reason}",
|
||||
"InstructionBudgetOverGuard": "статичний префікс ~{measured} оціночних токенів перевищує ліміт {guard}"
|
||||
"InstructionBudgetOverGuard": "статичний префікс ~{measured} оціночних токенів перевищує ліміт {guard}",
|
||||
"ActivityFileOne": "файл — {count}",
|
||||
"ActivityFileMany": "файли — {count}",
|
||||
"ActivitySearchOne": "пошук — {count}",
|
||||
"ActivitySearchMany": "пошуки — {count}",
|
||||
"ActivityCommandOne": "команда — {count}",
|
||||
"ActivityCommandMany": "команди — {count}",
|
||||
"ActivityTaskOne": "завдання — {count}",
|
||||
"ActivityTaskMany": "завдання — {count}",
|
||||
"ActivityDirectoryOne": "каталог — {count}",
|
||||
"ActivityDirectoryMany": "каталоги — {count}",
|
||||
"ActivityPatternOne": "шаблон — {count}",
|
||||
"ActivityPatternMany": "шаблони — {count}",
|
||||
"ActivityExplored": "Переглянуто: {items}",
|
||||
"ActivityRan": "виконано: {items}",
|
||||
"ActivityEdited": "змінено: {items}",
|
||||
"ActivityDelegated": "делеговано: {items}",
|
||||
"ActivityUpdatedMetadata": "метадані оновлено",
|
||||
"ActivityRead": "Прочитано: {items}",
|
||||
"ActivityListed": "Перелічено: {items}",
|
||||
"ActivitySearched": "Знайдено: {items}",
|
||||
"ActivityWrote": "Записано: {items}",
|
||||
"ToolReceiptRunning": "виконується",
|
||||
"ToolReceiptHydrated": "завантажено, не запущено",
|
||||
"ToolReceiptStopped": "зупинено",
|
||||
"ToolReceiptIssue": "проблема",
|
||||
"ToolReceiptFailed": "помилка",
|
||||
"AgentActivityChecking": "перевірка",
|
||||
"AgentActivityChecked": "перевірено",
|
||||
"AgentActivityWaiting": "очікування",
|
||||
"AgentActivityWaited": "очікування завершено",
|
||||
"FileMutationCreated": "Створено: {path}",
|
||||
"FileMutationUpdated": "Оновлено: {path}",
|
||||
"FileMutationDeleted": "Видалено: {path}",
|
||||
"FileMutationRenamed": "Перейменовано: {path}",
|
||||
"FileMutationCreatedCount": "створено: {count}",
|
||||
"FileMutationUpdatedCount": "оновлено: {count}",
|
||||
"FileMutationDeletedCount": "видалено: {count}",
|
||||
"FileMutationRenamedCount": "перейменовано: {count}",
|
||||
"FileMutationChangedFiles": "Змінені файли",
|
||||
"FileMutationMissingFile": "файл",
|
||||
"FileMutationNoText": "(текстових змін немає)",
|
||||
"FileMutationMoreDiff": "ще {count} рядків diff · {hint}",
|
||||
"FileMutationChange": "зміна",
|
||||
"FileMutationSelectReceipt": "Виберіть відповідну зміну; {hint}.",
|
||||
"FileMutationExactEvidence": "точні дані зміни у відповідному записі",
|
||||
"FileMutationExactHeading": "Точна зміна файлу",
|
||||
"FileActivityReadLabel": "Читання",
|
||||
"FileActivityListLabel": "Список",
|
||||
"FileActivitySearchLabel": "Пошук",
|
||||
"FileActivityWriteLabel": "Запис",
|
||||
"FileActivityDetailTitle": "Робота · дії з файлами",
|
||||
"ToolReceiptQuiet": "виконується · немає виводу {duration}",
|
||||
"FileActivityEditedLabel": "Змінено",
|
||||
"FileMutationAcrossFiles": " · файлів: {count}",
|
||||
"FileActivityNoDetail": "Не збережено відомостей про ціль, які можна безпечно показати",
|
||||
"FileActivityPatterns": "шаблони",
|
||||
"FileActivitySettled": "завершено"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "vượt giới hạn {count} tệp quy tắc mỗi thư mục",
|
||||
"InstructionDetailSymlinkedRules": "từ chối thư mục quy tắc là liên kết tượng trưng",
|
||||
"InstructionBudgetRefused": "ngân sách chỉ dẫn đã từ chối yêu cầu này: {reason}",
|
||||
"InstructionBudgetOverGuard": "tiền tố tĩnh ~{measured} token ước tính vượt giới hạn {guard}"
|
||||
"InstructionBudgetOverGuard": "tiền tố tĩnh ~{measured} token ước tính vượt giới hạn {guard}",
|
||||
"ActivityFileOne": "{count} tệp",
|
||||
"ActivityFileMany": "{count} tệp",
|
||||
"ActivitySearchOne": "{count} lượt tìm kiếm",
|
||||
"ActivitySearchMany": "{count} lượt tìm kiếm",
|
||||
"ActivityCommandOne": "{count} lệnh",
|
||||
"ActivityCommandMany": "{count} lệnh",
|
||||
"ActivityTaskOne": "{count} tác vụ",
|
||||
"ActivityTaskMany": "{count} tác vụ",
|
||||
"ActivityDirectoryOne": "{count} thư mục",
|
||||
"ActivityDirectoryMany": "{count} thư mục",
|
||||
"ActivityPatternOne": "{count} mẫu",
|
||||
"ActivityPatternMany": "{count} mẫu",
|
||||
"ActivityExplored": "Đã xem {items}",
|
||||
"ActivityRan": "đã chạy {items}",
|
||||
"ActivityEdited": "đã sửa {items}",
|
||||
"ActivityDelegated": "đã giao {items}",
|
||||
"ActivityUpdatedMetadata": "đã cập nhật siêu dữ liệu",
|
||||
"ActivityRead": "Đã đọc {items}",
|
||||
"ActivityListed": "Đã liệt kê {items}",
|
||||
"ActivitySearched": "Đã tìm {items}",
|
||||
"ActivityWrote": "Đã ghi {items}",
|
||||
"ToolReceiptRunning": "đang chạy",
|
||||
"ToolReceiptHydrated": "đã tải, chưa chạy",
|
||||
"ToolReceiptStopped": "đã dừng",
|
||||
"ToolReceiptIssue": "có vấn đề",
|
||||
"ToolReceiptFailed": "thất bại",
|
||||
"AgentActivityChecking": "đang kiểm tra",
|
||||
"AgentActivityChecked": "đã kiểm tra",
|
||||
"AgentActivityWaiting": "đang chờ",
|
||||
"AgentActivityWaited": "đã chờ xong",
|
||||
"FileMutationCreated": "Đã tạo: {path}",
|
||||
"FileMutationUpdated": "Đã cập nhật: {path}",
|
||||
"FileMutationDeleted": "Đã xóa: {path}",
|
||||
"FileMutationRenamed": "Đã đổi tên: {path}",
|
||||
"FileMutationCreatedCount": "đã tạo: {count}",
|
||||
"FileMutationUpdatedCount": "đã cập nhật: {count}",
|
||||
"FileMutationDeletedCount": "đã xóa: {count}",
|
||||
"FileMutationRenamedCount": "đã đổi tên: {count}",
|
||||
"FileMutationChangedFiles": "Tệp đã thay đổi",
|
||||
"FileMutationMissingFile": "tệp",
|
||||
"FileMutationNoText": "(không có thay đổi văn bản)",
|
||||
"FileMutationMoreDiff": "+{count} dòng diff · {hint}",
|
||||
"FileMutationChange": "thay đổi",
|
||||
"FileMutationSelectReceipt": "Chọn thay đổi tương ứng; {hint}.",
|
||||
"FileMutationExactEvidence": "bằng chứng chính xác trong bản ghi tương ứng",
|
||||
"FileMutationExactHeading": "Thay đổi tệp chính xác",
|
||||
"FileActivityReadLabel": "Đọc",
|
||||
"FileActivityListLabel": "Liệt kê",
|
||||
"FileActivitySearchLabel": "Tìm kiếm",
|
||||
"FileActivityWriteLabel": "Ghi",
|
||||
"FileActivityDetailTitle": "Công việc · hoạt động tệp",
|
||||
"ToolReceiptQuiet": "đang chạy · không có đầu ra trong {duration}",
|
||||
"FileActivityEditedLabel": "Đã sửa",
|
||||
"FileMutationAcrossFiles": " trong {count} tệp",
|
||||
"FileActivityNoDetail": "Không giữ lại chi tiết mục tiêu có thể hiển thị an toàn",
|
||||
"FileActivityPatterns": "mẫu",
|
||||
"FileActivitySettled": "đã hoàn tất"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "超过每个目录 {count} 个规则文件的上限",
|
||||
"InstructionDetailSymlinkedRules": "拒绝符号链接的规则目录",
|
||||
"InstructionBudgetRefused": "指令预算拒绝了此请求:{reason}",
|
||||
"InstructionBudgetOverGuard": "静态前缀约 {measured} 估算 token 超出上限 {guard}"
|
||||
"InstructionBudgetOverGuard": "静态前缀约 {measured} 估算 token 超出上限 {guard}",
|
||||
"ActivityFileOne": "{count}个文件",
|
||||
"ActivityFileMany": "{count}个文件",
|
||||
"ActivitySearchOne": "{count}次搜索",
|
||||
"ActivitySearchMany": "{count}次搜索",
|
||||
"ActivityCommandOne": "{count}条命令",
|
||||
"ActivityCommandMany": "{count}条命令",
|
||||
"ActivityTaskOne": "{count}个任务",
|
||||
"ActivityTaskMany": "{count}个任务",
|
||||
"ActivityDirectoryOne": "{count}个目录",
|
||||
"ActivityDirectoryMany": "{count}个目录",
|
||||
"ActivityPatternOne": "{count}个模式",
|
||||
"ActivityPatternMany": "{count}个模式",
|
||||
"ActivityExplored": "浏览了{items}",
|
||||
"ActivityRan": "运行了{items}",
|
||||
"ActivityEdited": "编辑了{items}",
|
||||
"ActivityDelegated": "委派了{items}",
|
||||
"ActivityUpdatedMetadata": "更新了元数据",
|
||||
"ActivityRead": "读取了{items}",
|
||||
"ActivityListed": "列出了{items}",
|
||||
"ActivitySearched": "搜索了{items}",
|
||||
"ActivityWrote": "写入了{items}",
|
||||
"ToolReceiptRunning": "运行中",
|
||||
"ToolReceiptHydrated": "已加载,未运行",
|
||||
"ToolReceiptStopped": "已停止",
|
||||
"ToolReceiptIssue": "存在问题",
|
||||
"ToolReceiptFailed": "失败",
|
||||
"AgentActivityChecking": "检查中",
|
||||
"AgentActivityChecked": "已检查",
|
||||
"AgentActivityWaiting": "等待中",
|
||||
"AgentActivityWaited": "等待结束",
|
||||
"FileMutationCreated": "已创建{path}",
|
||||
"FileMutationUpdated": "已更新{path}",
|
||||
"FileMutationDeleted": "已删除{path}",
|
||||
"FileMutationRenamed": "已重命名{path}",
|
||||
"FileMutationCreatedCount": "创建了{count}个",
|
||||
"FileMutationUpdatedCount": "更新了{count}个",
|
||||
"FileMutationDeletedCount": "删除了{count}个",
|
||||
"FileMutationRenamedCount": "重命名了{count}个",
|
||||
"FileMutationChangedFiles": "已更改的文件",
|
||||
"FileMutationMissingFile": "文件",
|
||||
"FileMutationNoText": "(无文本更改)",
|
||||
"FileMutationMoreDiff": "还有{count}行差异 · {hint}",
|
||||
"FileMutationChange": "更改",
|
||||
"FileMutationSelectReceipt": "选择对应的更改;{hint}。",
|
||||
"FileMutationExactEvidence": "对应回执中的精确更改证据",
|
||||
"FileMutationExactHeading": "精确文件更改",
|
||||
"FileActivityReadLabel": "读取",
|
||||
"FileActivityListLabel": "列出",
|
||||
"FileActivitySearchLabel": "搜索",
|
||||
"FileActivityWriteLabel": "写入",
|
||||
"FileActivityDetailTitle": "工作 · 文件活动",
|
||||
"ToolReceiptQuiet": "运行中 · 已有{duration}没有输出",
|
||||
"FileActivityEditedLabel": "已编辑",
|
||||
"FileMutationAcrossFiles": "(涉及{count}个文件)",
|
||||
"FileActivityNoDetail": "未保留可安全显示的目标详情",
|
||||
"FileActivityPatterns": "模式",
|
||||
"FileActivitySettled": "已结束"
|
||||
}
|
||||
@@ -2673,5 +2673,62 @@
|
||||
"InstructionDetailRulesCap": "超過每個目錄 {count} 個規則檔案的上限",
|
||||
"InstructionDetailSymlinkedRules": "拒絕符號連結的規則目錄",
|
||||
"InstructionBudgetRefused": "指令預算拒絕了此請求:{reason}",
|
||||
"InstructionBudgetOverGuard": "靜態前綴約 {measured} 估算 token 超出上限 {guard}"
|
||||
"InstructionBudgetOverGuard": "靜態前綴約 {measured} 估算 token 超出上限 {guard}",
|
||||
"ActivityFileOne": "{count}個檔案",
|
||||
"ActivityFileMany": "{count}個檔案",
|
||||
"ActivitySearchOne": "{count}次搜尋",
|
||||
"ActivitySearchMany": "{count}次搜尋",
|
||||
"ActivityCommandOne": "{count}條命令",
|
||||
"ActivityCommandMany": "{count}條命令",
|
||||
"ActivityTaskOne": "{count}個工作",
|
||||
"ActivityTaskMany": "{count}個工作",
|
||||
"ActivityDirectoryOne": "{count}個目錄",
|
||||
"ActivityDirectoryMany": "{count}個目錄",
|
||||
"ActivityPatternOne": "{count}個模式",
|
||||
"ActivityPatternMany": "{count}個模式",
|
||||
"ActivityExplored": "瀏覽了{items}",
|
||||
"ActivityRan": "執行了{items}",
|
||||
"ActivityEdited": "編輯了{items}",
|
||||
"ActivityDelegated": "委派了{items}",
|
||||
"ActivityUpdatedMetadata": "更新了中繼資料",
|
||||
"ActivityRead": "讀取了{items}",
|
||||
"ActivityListed": "列出了{items}",
|
||||
"ActivitySearched": "搜尋了{items}",
|
||||
"ActivityWrote": "寫入了{items}",
|
||||
"ToolReceiptRunning": "執行中",
|
||||
"ToolReceiptHydrated": "已載入,未執行",
|
||||
"ToolReceiptStopped": "已停止",
|
||||
"ToolReceiptIssue": "發生問題",
|
||||
"ToolReceiptFailed": "失敗",
|
||||
"AgentActivityChecking": "檢查中",
|
||||
"AgentActivityChecked": "已檢查",
|
||||
"AgentActivityWaiting": "等待中",
|
||||
"AgentActivityWaited": "等待結束",
|
||||
"FileMutationCreated": "已建立{path}",
|
||||
"FileMutationUpdated": "已更新{path}",
|
||||
"FileMutationDeleted": "已刪除{path}",
|
||||
"FileMutationRenamed": "已重新命名{path}",
|
||||
"FileMutationCreatedCount": "建立了{count}個",
|
||||
"FileMutationUpdatedCount": "更新了{count}個",
|
||||
"FileMutationDeletedCount": "刪除了{count}個",
|
||||
"FileMutationRenamedCount": "重新命名了{count}個",
|
||||
"FileMutationChangedFiles": "已變更的檔案",
|
||||
"FileMutationMissingFile": "檔案",
|
||||
"FileMutationNoText": "(無文字變更)",
|
||||
"FileMutationMoreDiff": "還有{count}行差異 · {hint}",
|
||||
"FileMutationChange": "變更",
|
||||
"FileMutationSelectReceipt": "選取對應的變更;{hint}。",
|
||||
"FileMutationExactEvidence": "對應回執中的精確變更證據",
|
||||
"FileMutationExactHeading": "精確檔案變更",
|
||||
"FileActivityReadLabel": "讀取",
|
||||
"FileActivityListLabel": "列出",
|
||||
"FileActivitySearchLabel": "搜尋",
|
||||
"FileActivityWriteLabel": "寫入",
|
||||
"FileActivityDetailTitle": "工作 · 檔案活動",
|
||||
"ToolReceiptQuiet": "執行中 · 已有{duration}沒有輸出",
|
||||
"FileActivityEditedLabel": "已編輯",
|
||||
"FileMutationAcrossFiles": "(涉及{count}個檔案)",
|
||||
"FileActivityNoDetail": "未保留可安全顯示的目標詳細資訊",
|
||||
"FileActivityPatterns": "模式",
|
||||
"FileActivitySettled": "已結束"
|
||||
}
|
||||
@@ -2915,6 +2915,63 @@ pub enum MessageId {
|
||||
ExecAfterHelp,
|
||||
ExecPromptHelp,
|
||||
ExecFlagAfterPrompt,
|
||||
ActivityFileOne,
|
||||
ActivityFileMany,
|
||||
ActivitySearchOne,
|
||||
ActivitySearchMany,
|
||||
ActivityCommandOne,
|
||||
ActivityCommandMany,
|
||||
ActivityTaskOne,
|
||||
ActivityTaskMany,
|
||||
ActivityDirectoryOne,
|
||||
ActivityDirectoryMany,
|
||||
ActivityPatternOne,
|
||||
ActivityPatternMany,
|
||||
ActivityExplored,
|
||||
ActivityRan,
|
||||
ActivityEdited,
|
||||
ActivityDelegated,
|
||||
ActivityUpdatedMetadata,
|
||||
ActivityRead,
|
||||
ActivityListed,
|
||||
ActivitySearched,
|
||||
ActivityWrote,
|
||||
ToolReceiptRunning,
|
||||
ToolReceiptHydrated,
|
||||
ToolReceiptStopped,
|
||||
ToolReceiptIssue,
|
||||
ToolReceiptFailed,
|
||||
AgentActivityChecking,
|
||||
AgentActivityChecked,
|
||||
AgentActivityWaiting,
|
||||
AgentActivityWaited,
|
||||
FileMutationCreated,
|
||||
FileMutationUpdated,
|
||||
FileMutationDeleted,
|
||||
FileMutationRenamed,
|
||||
FileMutationCreatedCount,
|
||||
FileMutationUpdatedCount,
|
||||
FileMutationDeletedCount,
|
||||
FileMutationRenamedCount,
|
||||
FileMutationChangedFiles,
|
||||
FileMutationMissingFile,
|
||||
FileMutationNoText,
|
||||
FileMutationMoreDiff,
|
||||
FileMutationChange,
|
||||
FileMutationSelectReceipt,
|
||||
FileMutationExactEvidence,
|
||||
FileMutationExactHeading,
|
||||
FileActivityReadLabel,
|
||||
FileActivityListLabel,
|
||||
FileActivitySearchLabel,
|
||||
FileActivityWriteLabel,
|
||||
FileActivityDetailTitle,
|
||||
ToolReceiptQuiet,
|
||||
FileActivityEditedLabel,
|
||||
FileMutationAcrossFiles,
|
||||
FileActivityNoDetail,
|
||||
FileActivityPatterns,
|
||||
FileActivitySettled,
|
||||
}
|
||||
|
||||
pub const ALL_MESSAGE_IDS: &[MessageId] = &[
|
||||
@@ -5594,6 +5651,63 @@ pub const ALL_MESSAGE_IDS: &[MessageId] = &[
|
||||
MessageId::ExecAfterHelp,
|
||||
MessageId::ExecPromptHelp,
|
||||
MessageId::ExecFlagAfterPrompt,
|
||||
MessageId::ActivityFileOne,
|
||||
MessageId::ActivityFileMany,
|
||||
MessageId::ActivitySearchOne,
|
||||
MessageId::ActivitySearchMany,
|
||||
MessageId::ActivityCommandOne,
|
||||
MessageId::ActivityCommandMany,
|
||||
MessageId::ActivityTaskOne,
|
||||
MessageId::ActivityTaskMany,
|
||||
MessageId::ActivityDirectoryOne,
|
||||
MessageId::ActivityDirectoryMany,
|
||||
MessageId::ActivityPatternOne,
|
||||
MessageId::ActivityPatternMany,
|
||||
MessageId::ActivityExplored,
|
||||
MessageId::ActivityRan,
|
||||
MessageId::ActivityEdited,
|
||||
MessageId::ActivityDelegated,
|
||||
MessageId::ActivityUpdatedMetadata,
|
||||
MessageId::ActivityRead,
|
||||
MessageId::ActivityListed,
|
||||
MessageId::ActivitySearched,
|
||||
MessageId::ActivityWrote,
|
||||
MessageId::ToolReceiptRunning,
|
||||
MessageId::ToolReceiptHydrated,
|
||||
MessageId::ToolReceiptStopped,
|
||||
MessageId::ToolReceiptIssue,
|
||||
MessageId::ToolReceiptFailed,
|
||||
MessageId::AgentActivityChecking,
|
||||
MessageId::AgentActivityChecked,
|
||||
MessageId::AgentActivityWaiting,
|
||||
MessageId::AgentActivityWaited,
|
||||
MessageId::FileMutationCreated,
|
||||
MessageId::FileMutationUpdated,
|
||||
MessageId::FileMutationDeleted,
|
||||
MessageId::FileMutationRenamed,
|
||||
MessageId::FileMutationCreatedCount,
|
||||
MessageId::FileMutationUpdatedCount,
|
||||
MessageId::FileMutationDeletedCount,
|
||||
MessageId::FileMutationRenamedCount,
|
||||
MessageId::FileMutationChangedFiles,
|
||||
MessageId::FileMutationMissingFile,
|
||||
MessageId::FileMutationNoText,
|
||||
MessageId::FileMutationMoreDiff,
|
||||
MessageId::FileMutationChange,
|
||||
MessageId::FileMutationSelectReceipt,
|
||||
MessageId::FileMutationExactEvidence,
|
||||
MessageId::FileMutationExactHeading,
|
||||
MessageId::FileActivityReadLabel,
|
||||
MessageId::FileActivityListLabel,
|
||||
MessageId::FileActivitySearchLabel,
|
||||
MessageId::FileActivityWriteLabel,
|
||||
MessageId::FileActivityDetailTitle,
|
||||
MessageId::ToolReceiptQuiet,
|
||||
MessageId::FileActivityEditedLabel,
|
||||
MessageId::FileMutationAcrossFiles,
|
||||
MessageId::FileActivityNoDetail,
|
||||
MessageId::FileActivityPatterns,
|
||||
MessageId::FileActivitySettled,
|
||||
];
|
||||
|
||||
pub fn tr(locale: Locale, id: MessageId) -> Cow<'static, str> {
|
||||
|
||||
@@ -122,6 +122,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Localized activity and file receipts:** grouped tool activity, file counts, patch outcomes and tool status use the shared 15-language catalog. Language changes refresh cached summaries; filenames, diffs and execution receipts keep their original data.
|
||||
- **Automatic compaction stays off when disabled:** preflight and provider context-overflow recovery preserve the conversation and report the setting instead of forcing a summary. Manual compaction remains available, and a rejected recovery request retains its actual cause in both paths. (#7142)
|
||||
|
||||
- **VS Code packaging security:** use patched MSAL Node in the extension packager, addressing [GHSA-xcx6-jx8p-x3h5](https://github.com/AzureAD/microsoft-authentication-library-for-js/security/advisories/GHSA-xcx6-jx8p-x3h5).
|
||||
|
||||
@@ -2710,6 +2710,7 @@ pub(crate) struct ToolRunCache {
|
||||
pub(crate) threshold: usize,
|
||||
pub(crate) mode: ToolCollapseMode,
|
||||
pub(crate) calm_mode: bool,
|
||||
pub(crate) locale: Locale,
|
||||
#[cfg(test)]
|
||||
pub(crate) projection_builds: usize,
|
||||
pub(crate) history_len: usize,
|
||||
@@ -2778,6 +2779,7 @@ impl Default for ToolRunCache {
|
||||
threshold: usize::MAX,
|
||||
mode: ToolCollapseMode::Expanded,
|
||||
calm_mode: false,
|
||||
locale: Locale::En,
|
||||
#[cfg(test)]
|
||||
projection_builds: 0,
|
||||
history_len: usize::MAX,
|
||||
|
||||
+155
-100
@@ -13,7 +13,7 @@ use crate::tools::plan::PlanSnapshot;
|
||||
use crate::tools::review::ReviewOutput;
|
||||
use crate::tui::app::TranscriptSpacing;
|
||||
use crate::tui::motion::MotionMode;
|
||||
use codewhale_localization::Locale;
|
||||
use codewhale_localization::{Locale, MessageId, tr};
|
||||
use codewhale_models::{ContentBlock, Message};
|
||||
use codewhale_palette as palette;
|
||||
use codewhale_tui_kit::diff_render;
|
||||
@@ -1009,7 +1009,9 @@ impl ToolCell {
|
||||
match self {
|
||||
ToolCell::Exec(cell) => cell.render_with_locale(width, low_motion, mode, locale),
|
||||
ToolCell::Exploring(cell) => cell.render_with_locale(width, low_motion, mode, locale),
|
||||
ToolCell::PlanUpdate(cell) => cell.lines_with_motion(width, low_motion),
|
||||
ToolCell::PlanUpdate(cell) => {
|
||||
cell.lines_with_motion_and_locale(width, low_motion, locale)
|
||||
}
|
||||
ToolCell::PatchSummary(cell) => cell.render(
|
||||
width,
|
||||
low_motion,
|
||||
@@ -1019,8 +1021,12 @@ impl ToolCell {
|
||||
),
|
||||
ToolCell::Review(cell) => cell.render(width, low_motion, mode, locale),
|
||||
ToolCell::Mcp(cell) => cell.render(width, low_motion, mode, locale),
|
||||
ToolCell::ViewImage(cell) => cell.lines_with_motion(width, low_motion),
|
||||
ToolCell::WebSearch(cell) => cell.lines_with_motion(width, low_motion),
|
||||
ToolCell::ViewImage(cell) => {
|
||||
cell.lines_with_motion_and_locale(width, low_motion, locale)
|
||||
}
|
||||
ToolCell::WebSearch(cell) => {
|
||||
cell.lines_with_motion_and_locale(width, low_motion, locale)
|
||||
}
|
||||
ToolCell::Generic(cell) => {
|
||||
cell.lines_with_mode_and_locale(width, low_motion, mode, locale)
|
||||
}
|
||||
@@ -1118,10 +1124,15 @@ impl ExecCell {
|
||||
lines.push(render_tool_header_with_summary(
|
||||
"Shell",
|
||||
header_summary,
|
||||
status_text.as_ref(),
|
||||
if self.status == ToolStatus::Success || held_status.is_some() {
|
||||
Some(status_text.as_ref())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
self.status,
|
||||
self.started_at,
|
||||
low_motion || held_status.is_some(),
|
||||
locale,
|
||||
));
|
||||
|
||||
lines.extend(self.description_lines(width, locale));
|
||||
@@ -1397,10 +1408,10 @@ impl ExecCell {
|
||||
let state = held.or_else(|| match self.status {
|
||||
ToolStatus::Success => None,
|
||||
ToolStatus::Running => Some(self.started_at.map_or_else(
|
||||
|| tool_status_label(ToolStatus::Running).to_string(),
|
||||
|started| running_status_label_with_elapsed(started.elapsed().as_secs()),
|
||||
|| tool_status_label(ToolStatus::Running, locale).to_string(),
|
||||
|started| running_status_label_with_elapsed(started.elapsed().as_secs(), locale),
|
||||
)),
|
||||
status => Some(tool_status_label(status).to_string()),
|
||||
status => Some(tool_status_label(status, locale).to_string()),
|
||||
});
|
||||
let mut suffix = Vec::new();
|
||||
if let Some(state) = state {
|
||||
@@ -1462,7 +1473,7 @@ impl ExecCell {
|
||||
),
|
||||
_ => self
|
||||
.stale_elapsed_since_output_ms
|
||||
.map(stale_shell_status_label),
|
||||
.map(|ms| stale_shell_status_label(ms, locale)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1577,10 +1588,10 @@ impl ExploringCell {
|
||||
codewhale_localization::MessageId::ToolReceiptDone,
|
||||
)
|
||||
} else {
|
||||
Cow::Borrowed(tool_status_label(status))
|
||||
tool_status_label(status, locale)
|
||||
}
|
||||
} else {
|
||||
Cow::Borrowed("running")
|
||||
tr(locale, MessageId::ToolReceiptRunning)
|
||||
};
|
||||
// Search-only exploration cards read with the `find` verb so a
|
||||
// completed grep renders `find done · Searching for …` instead of the
|
||||
@@ -1590,10 +1601,15 @@ impl ExploringCell {
|
||||
let header = render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
header_summary.as_deref(),
|
||||
header_state.as_ref(),
|
||||
if status == ToolStatus::Success {
|
||||
Some(header_state.as_ref())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
);
|
||||
let header_shows_label = !multi_entry
|
||||
&& header_summary
|
||||
@@ -1743,24 +1759,25 @@ impl PatchSummaryCell {
|
||||
.as_ref()
|
||||
.map(|receipt| {
|
||||
if inline_diff_mode == codewhale_runtime::settings::InlineDiffMode::Off {
|
||||
receipt.outcome_label()
|
||||
receipt.outcome_label(locale)
|
||||
} else {
|
||||
receipt.semantic_summary()
|
||||
receipt.semantic_summary(locale)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| self.path.clone());
|
||||
lines.push(render_tool_header_with_summary(
|
||||
"File",
|
||||
Some(&header_summary),
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
if self.status == ToolStatus::Success
|
||||
&& let Some(receipt) = self.receipt.as_ref()
|
||||
{
|
||||
lines.extend(receipt.render_inline(width, inline_diff_mode));
|
||||
lines.extend(receipt.render_inline(width, inline_diff_mode, locale));
|
||||
} else {
|
||||
lines.extend(render_compact_kv(
|
||||
"file",
|
||||
@@ -1809,10 +1826,11 @@ impl ReviewCell {
|
||||
let mut lines = Vec::new();
|
||||
lines.push(render_tool_header(
|
||||
"Review",
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
|
||||
if !self.target.trim().is_empty() {
|
||||
@@ -1948,10 +1966,11 @@ impl McpToolCell {
|
||||
lines.push(render_tool_header_with_summary(
|
||||
"Tool",
|
||||
Some(&self.tool),
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
|
||||
if self.is_image {
|
||||
@@ -1992,15 +2011,21 @@ pub struct ViewImageCell {
|
||||
|
||||
impl ViewImageCell {
|
||||
/// Render the image view cell into lines.
|
||||
pub fn lines_with_motion(&self, width: u16, low_motion: bool) -> Vec<Line<'static>> {
|
||||
pub fn lines_with_motion_and_locale(
|
||||
&self,
|
||||
width: u16,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
let path = self.path.display().to_string();
|
||||
let header = render_tool_header_with_summary(
|
||||
"Image",
|
||||
Some(&path),
|
||||
"done",
|
||||
None,
|
||||
ToolStatus::Success,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
);
|
||||
let header_shows_path = header_shows_in_full(&header, &path, usize::from(width));
|
||||
let mut lines = vec![header];
|
||||
@@ -2024,15 +2049,26 @@ pub struct WebSearchCell {
|
||||
|
||||
impl WebSearchCell {
|
||||
/// Render the web search cell into lines.
|
||||
#[cfg(test)]
|
||||
pub fn lines_with_motion(&self, width: u16, low_motion: bool) -> Vec<Line<'static>> {
|
||||
self.lines_with_motion_and_locale(width, low_motion, Locale::En)
|
||||
}
|
||||
|
||||
pub fn lines_with_motion_and_locale(
|
||||
&self,
|
||||
width: u16,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
let mut lines = Vec::new();
|
||||
let header = render_tool_header_with_summary(
|
||||
"Search",
|
||||
Some(&self.query),
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
);
|
||||
let header_shows_query = header_shows_in_full(&header, &self.query, usize::from(width));
|
||||
lines.push(header);
|
||||
@@ -2139,7 +2175,7 @@ impl GenericToolCell {
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
if self.name == "activity_group" {
|
||||
return agent_activity::render_activity_group(self, width);
|
||||
return agent_activity::render_activity_group(self, width, locale);
|
||||
}
|
||||
|
||||
// Issue #241: when the underlying tool is a checklist/todo update and
|
||||
@@ -2162,7 +2198,7 @@ impl GenericToolCell {
|
||||
// wait) stay as a single compact line (#4112 dogfood A5).
|
||||
if self.name == "agent" {
|
||||
if agent_activity::is_agent_inspection(self) {
|
||||
return agent_activity::render_agent_compact(self, low_motion);
|
||||
return agent_activity::render_agent_compact(self, low_motion, locale);
|
||||
}
|
||||
// Spawn / start / run: suppress the generic tool card entirely.
|
||||
return Vec::new();
|
||||
@@ -2182,10 +2218,11 @@ impl GenericToolCell {
|
||||
vec![render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(summary.as_str()),
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
)],
|
||||
self.status,
|
||||
);
|
||||
@@ -2211,10 +2248,14 @@ impl GenericToolCell {
|
||||
let mut collapsed = vec![render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
header_summary.as_deref(),
|
||||
&tool_receipt_label(family, self.status, self.output.as_deref(), locale),
|
||||
Some(
|
||||
tool_receipt_label(family, self.status, self.output.as_deref(), locale)
|
||||
.as_ref(),
|
||||
),
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
)];
|
||||
if self.spillover_path.is_some() {
|
||||
collapsed.push(render_spillover_annotation(width));
|
||||
@@ -2236,10 +2277,11 @@ impl GenericToolCell {
|
||||
let header = render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
header_summary.as_deref(),
|
||||
&tool_receipt_label(family, self.status, self.output.as_deref(), locale),
|
||||
Some(tool_receipt_label(family, self.status, self.output.as_deref(), locale).as_ref()),
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
);
|
||||
let header_has_args = self.input_summary.as_deref().is_some_and(|args| {
|
||||
header_repeats_args(&header, header_summary.as_deref(), args, usize::from(width))
|
||||
@@ -2294,10 +2336,11 @@ impl GenericToolCell {
|
||||
lines.push(render_tool_header_with_summary(
|
||||
"Diff",
|
||||
diff_summary.as_deref(),
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
if matches!(mode, RenderMode::Live) {
|
||||
let limit = if self.status == ToolStatus::Failed {
|
||||
@@ -2380,6 +2423,7 @@ impl GenericToolCell {
|
||||
width,
|
||||
low_motion,
|
||||
mode,
|
||||
locale,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -2417,10 +2461,11 @@ impl GenericToolCell {
|
||||
let mut lines = vec![render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
None,
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
)];
|
||||
lines.extend(render_card_detail_line(
|
||||
None,
|
||||
@@ -2457,10 +2502,11 @@ impl GenericToolCell {
|
||||
lines.push(render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(header.as_str()),
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
if let Some(runs) = runs {
|
||||
for run in runs {
|
||||
@@ -2513,11 +2559,13 @@ impl GenericToolCell {
|
||||
lines.push(render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(name.as_str()),
|
||||
codewhale_localization::tr(
|
||||
locale,
|
||||
codewhale_localization::MessageId::WorkflowLineStarted,
|
||||
)
|
||||
.as_ref(),
|
||||
Some(
|
||||
codewhale_localization::tr(
|
||||
locale,
|
||||
codewhale_localization::MessageId::WorkflowLineStarted,
|
||||
)
|
||||
.as_ref(),
|
||||
),
|
||||
if owns_finish {
|
||||
ToolStatus::Success
|
||||
} else {
|
||||
@@ -2525,6 +2573,7 @@ impl GenericToolCell {
|
||||
},
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
}
|
||||
let finish = owns_finish.then(|| panel.finish_line()).flatten();
|
||||
@@ -2543,10 +2592,11 @@ impl GenericToolCell {
|
||||
lines.push(render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(facts.as_str()),
|
||||
&state,
|
||||
Some(&state),
|
||||
finish_status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
if let Some(detail) = detail {
|
||||
// Why a run fell short is the one fact worth reading: its first
|
||||
@@ -3046,64 +3096,58 @@ fn render_error_message(
|
||||
|
||||
fn render_tool_header(
|
||||
title: &str,
|
||||
state: &str,
|
||||
state: Option<&str>,
|
||||
status: ToolStatus,
|
||||
started_at: Option<Instant>,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Line<'static> {
|
||||
let family = crate::tui::widgets::tool_card::tool_family_for_title(title);
|
||||
render_tool_header_with_family(family, state, status, started_at, low_motion)
|
||||
render_tool_header_with_family_and_summary(
|
||||
family, None, state, status, started_at, low_motion, locale,
|
||||
)
|
||||
}
|
||||
|
||||
fn render_tool_header_with_summary(
|
||||
title: &str,
|
||||
summary: Option<&str>,
|
||||
state: &str,
|
||||
state: Option<&str>,
|
||||
status: ToolStatus,
|
||||
started_at: Option<Instant>,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Line<'static> {
|
||||
let family = crate::tui::widgets::tool_card::tool_family_for_title(title);
|
||||
render_tool_header_with_family_and_summary(
|
||||
family, summary, state, status, started_at, low_motion,
|
||||
family, summary, state, status, started_at, low_motion, locale,
|
||||
)
|
||||
}
|
||||
|
||||
/// Render a tool-card header with an explicit verb family. Lets callers
|
||||
/// (e.g. `GenericToolCell`) bypass the legacy title→family mapping when
|
||||
/// they already know the actual tool name.
|
||||
fn render_tool_header_with_family(
|
||||
family: crate::tui::widgets::tool_card::ToolFamily,
|
||||
state: &str,
|
||||
status: ToolStatus,
|
||||
started_at: Option<Instant>,
|
||||
low_motion: bool,
|
||||
) -> Line<'static> {
|
||||
render_tool_header_with_family_and_summary(family, None, state, status, started_at, low_motion)
|
||||
}
|
||||
|
||||
fn render_tool_header_with_family_and_summary(
|
||||
family: crate::tui::widgets::tool_card::ToolFamily,
|
||||
summary: Option<&str>,
|
||||
state: &str,
|
||||
state: Option<&str>,
|
||||
status: ToolStatus,
|
||||
started_at: Option<Instant>,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Line<'static> {
|
||||
// For long-running tools, append elapsed seconds so the user can see the
|
||||
// call isn't stuck. Threshold matches the eye's "did this hang?" reflex
|
||||
// — under 3s we stay quiet so quick reads/greps don't visually churn.
|
||||
let state_owned: String = if state == "running"
|
||||
&& status == ToolStatus::Running
|
||||
&& let Some(started) = started_at
|
||||
{
|
||||
running_status_label_with_elapsed(started.elapsed().as_secs())
|
||||
} else {
|
||||
state.to_string()
|
||||
let state_owned = match (state, status, started_at) {
|
||||
(Some(state), _, _) => state.to_string(),
|
||||
(None, ToolStatus::Running, Some(started)) => {
|
||||
running_status_label_with_elapsed(started.elapsed().as_secs(), locale)
|
||||
}
|
||||
_ => tool_status_label(status, locale).into_owned(),
|
||||
};
|
||||
|
||||
let glyph = crate::tui::widgets::tool_card::family_glyph(family);
|
||||
let verb = crate::tui::widgets::tool_card::family_label(family);
|
||||
let verb = crate::tui::widgets::tool_card::family_label(family, locale);
|
||||
|
||||
let glyph_style = Style::default().fg(tool_glyph_color(status, family));
|
||||
let mut spans = vec![
|
||||
@@ -3125,7 +3169,7 @@ fn render_tool_header_with_family_and_summary(
|
||||
// verb, the verb already carries the signal, so drop the redundant tail.
|
||||
if let Some(summary) = summary
|
||||
.and_then(normalize_header_summary)
|
||||
.filter(|summary| !summary.eq_ignore_ascii_case(verb))
|
||||
.filter(|summary| !summary.eq_ignore_ascii_case(verb.as_ref()))
|
||||
{
|
||||
spans.push(Span::styled(" · ", Style::default().fg(palette::TEXT_DIM)));
|
||||
spans.push(Span::styled(
|
||||
@@ -3178,18 +3222,19 @@ fn normalize_header_summary(summary: &str) -> Option<String> {
|
||||
/// tools. Below 3s the badge is suppressed to avoid visual churn for tools
|
||||
/// that resolve in milliseconds; at 3s and beyond the badge appears and ticks
|
||||
/// every second the tool stays in flight.
|
||||
pub(crate) fn running_status_label_with_elapsed(elapsed_secs: u64) -> String {
|
||||
pub(crate) fn running_status_label_with_elapsed(elapsed_secs: u64, locale: Locale) -> String {
|
||||
let state = tool_status_label(ToolStatus::Running, locale);
|
||||
if elapsed_secs < 3 {
|
||||
"running".to_string()
|
||||
state.into_owned()
|
||||
} else {
|
||||
format!("running ({elapsed_secs}s)")
|
||||
format!("{state} ({elapsed_secs}s)")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stale_shell_status_label(elapsed_since_output_ms: u64) -> String {
|
||||
format!(
|
||||
"running · no output for {}",
|
||||
codewhale_command_contract::elapsed::format_elapsed_ms(elapsed_since_output_ms)
|
||||
pub(crate) fn stale_shell_status_label(elapsed_since_output_ms: u64, locale: Locale) -> String {
|
||||
tr(locale, MessageId::ToolReceiptQuiet).replace(
|
||||
"{duration}",
|
||||
&codewhale_command_contract::elapsed::format_elapsed_ms(elapsed_since_output_ms),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3371,15 +3416,18 @@ fn tool_glyph_color(
|
||||
tool_rail_color(status)
|
||||
}
|
||||
|
||||
pub(crate) fn tool_status_label(status: ToolStatus) -> &'static str {
|
||||
match status {
|
||||
ToolStatus::Running => "running",
|
||||
ToolStatus::Success => "done",
|
||||
ToolStatus::Hydrated => "loaded, not run",
|
||||
ToolStatus::Stopped => "stopped",
|
||||
ToolStatus::Warning => "issue",
|
||||
ToolStatus::Failed => "failed",
|
||||
}
|
||||
pub(crate) fn tool_status_label(status: ToolStatus, locale: Locale) -> Cow<'static, str> {
|
||||
tr(
|
||||
locale,
|
||||
match status {
|
||||
ToolStatus::Running => MessageId::ToolReceiptRunning,
|
||||
ToolStatus::Success => MessageId::ToolReceiptDone,
|
||||
ToolStatus::Hydrated => MessageId::ToolReceiptHydrated,
|
||||
ToolStatus::Stopped => MessageId::ToolReceiptStopped,
|
||||
ToolStatus::Warning => MessageId::ToolReceiptIssue,
|
||||
ToolStatus::Failed => MessageId::ToolReceiptFailed,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// A finished read/find card can truthfully name how many rendered lines came
|
||||
@@ -3393,7 +3441,7 @@ pub(crate) fn tool_receipt_label(
|
||||
locale: Locale,
|
||||
) -> Cow<'static, str> {
|
||||
if status != ToolStatus::Success {
|
||||
return Cow::Borrowed(tool_status_label(status));
|
||||
return tool_status_label(status, locale);
|
||||
}
|
||||
use crate::tui::widgets::tool_card::ToolFamily;
|
||||
match family {
|
||||
@@ -3624,31 +3672,38 @@ impl FileActivitySummary {
|
||||
&& self.files_written == 0
|
||||
}
|
||||
|
||||
pub fn compact_display(&self) -> Vec<String> {
|
||||
pub fn compact_display(&self, locale: Locale) -> Vec<String> {
|
||||
let mut parts = Vec::new();
|
||||
if self.files_read > 0 {
|
||||
parts.push(format!(
|
||||
"Read {}",
|
||||
tool_run::counted(self.files_read as usize, "file", "files")
|
||||
));
|
||||
}
|
||||
if self.dirs_listed > 0 {
|
||||
parts.push(format!(
|
||||
"Listed {}",
|
||||
tool_run::counted(self.dirs_listed as usize, "directory", "directories")
|
||||
));
|
||||
}
|
||||
if self.patterns_searched > 0 {
|
||||
parts.push(format!(
|
||||
"Searched {}",
|
||||
tool_run::counted(self.patterns_searched as usize, "pattern", "patterns")
|
||||
));
|
||||
}
|
||||
if self.files_written > 0 {
|
||||
parts.push(format!(
|
||||
"Wrote {}",
|
||||
tool_run::counted(self.files_written as usize, "file", "files")
|
||||
));
|
||||
for (count, clause, singular, plural) in [
|
||||
(
|
||||
self.files_read,
|
||||
MessageId::ActivityRead,
|
||||
MessageId::ActivityFileOne,
|
||||
MessageId::ActivityFileMany,
|
||||
),
|
||||
(
|
||||
self.dirs_listed,
|
||||
MessageId::ActivityListed,
|
||||
MessageId::ActivityDirectoryOne,
|
||||
MessageId::ActivityDirectoryMany,
|
||||
),
|
||||
(
|
||||
self.patterns_searched,
|
||||
MessageId::ActivitySearched,
|
||||
MessageId::ActivityPatternOne,
|
||||
MessageId::ActivityPatternMany,
|
||||
),
|
||||
(
|
||||
self.files_written,
|
||||
MessageId::ActivityWrote,
|
||||
MessageId::ActivityFileOne,
|
||||
MessageId::ActivityFileMany,
|
||||
),
|
||||
] {
|
||||
if count > 0 {
|
||||
let count = tool_run::counted(count as usize, locale, singular, plural);
|
||||
parts.push(tr(locale, clause).replace("{items}", &count));
|
||||
}
|
||||
}
|
||||
parts
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
use ratatui::style::Style;
|
||||
use ratatui::text::{Line, Span};
|
||||
|
||||
use codewhale_localization::{Locale, MessageId, tr};
|
||||
use codewhale_palette as palette;
|
||||
|
||||
use super::{
|
||||
GenericToolCell, render_tool_header_with_family_and_summary, tool_status_label, truncate_text,
|
||||
};
|
||||
use super::{GenericToolCell, render_tool_header_with_family_and_summary, truncate_text};
|
||||
|
||||
pub(super) fn render_agent_compact(cell: &GenericToolCell, low_motion: bool) -> Vec<Line<'static>> {
|
||||
pub(super) fn render_agent_compact(
|
||||
cell: &GenericToolCell,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
let family = crate::tui::widgets::tool_card::ToolFamily::Delegate;
|
||||
let agent_id = cell
|
||||
.output
|
||||
@@ -21,24 +24,29 @@ pub(super) fn render_agent_compact(cell: &GenericToolCell, low_motion: bool) ->
|
||||
// spawn — during a fan-out session every peek/status/wait would otherwise
|
||||
// read as yet another completed delegate (#4112, dogfood A5). The action
|
||||
// is stamped at the front of the args summary by tool_routing.
|
||||
let state_label = match agent_inspection_action(cell) {
|
||||
Some(AgentCompactAction::Check) => match cell.status {
|
||||
super::ToolStatus::Running => "checking",
|
||||
_ => "checked",
|
||||
},
|
||||
Some(AgentCompactAction::Wait) => match cell.status {
|
||||
super::ToolStatus::Running => "waiting",
|
||||
_ => "waited",
|
||||
},
|
||||
None => tool_status_label(cell.status),
|
||||
let state_label = match (agent_inspection_action(cell), cell.status) {
|
||||
(Some(AgentCompactAction::Check), super::ToolStatus::Running) => {
|
||||
Some(tr(locale, MessageId::AgentActivityChecking))
|
||||
}
|
||||
(Some(AgentCompactAction::Check), super::ToolStatus::Success) => {
|
||||
Some(tr(locale, MessageId::AgentActivityChecked))
|
||||
}
|
||||
(Some(AgentCompactAction::Wait), super::ToolStatus::Running) => {
|
||||
Some(tr(locale, MessageId::AgentActivityWaiting))
|
||||
}
|
||||
(Some(AgentCompactAction::Wait), super::ToolStatus::Success) => {
|
||||
Some(tr(locale, MessageId::AgentActivityWaited))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
vec![render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(agent_id.as_str()),
|
||||
state_label,
|
||||
state_label.as_deref(),
|
||||
cell.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
)]
|
||||
}
|
||||
|
||||
@@ -66,8 +74,15 @@ fn agent_inspection_action(cell: &GenericToolCell) -> Option<AgentCompactAction>
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn render_activity_group(cell: &GenericToolCell, width: u16) -> Vec<Line<'static>> {
|
||||
let summary = cell.input_summary.as_deref().unwrap_or("Updated metadata");
|
||||
pub(super) fn render_activity_group(
|
||||
cell: &GenericToolCell,
|
||||
width: u16,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
let fallback = super::tool_run::sentence_case_activity(
|
||||
tr(locale, MessageId::ActivityUpdatedMetadata).into_owned(),
|
||||
);
|
||||
let summary = cell.input_summary.as_deref().unwrap_or(&fallback);
|
||||
let suffix = cell
|
||||
.output_summary
|
||||
.as_deref()
|
||||
|
||||
@@ -10,8 +10,7 @@ use codewhale_palette as palette;
|
||||
|
||||
use super::{
|
||||
RenderMode, TRANSCRIPT_RAIL, ToolStatus, render_card_detail_line_single,
|
||||
render_tool_header_with_family_and_summary, tool_status_label, tool_value_style, truncate_text,
|
||||
wrap_text,
|
||||
render_tool_header_with_family_and_summary, tool_value_style, truncate_text, wrap_text,
|
||||
};
|
||||
|
||||
pub(crate) fn is_checklist_tool_name(name: &str) -> bool {
|
||||
@@ -148,10 +147,11 @@ pub(super) fn render_checklist_change_card(
|
||||
lines.push(render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(&header_summary),
|
||||
tool_status_label(status),
|
||||
None,
|
||||
status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
|
||||
// Look up the title from the snapshot. `id` in tool input is
|
||||
@@ -240,6 +240,7 @@ pub(super) fn render_checklist_card(
|
||||
width: u16,
|
||||
low_motion: bool,
|
||||
mode: RenderMode,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
let mut lines = Vec::new();
|
||||
let header_summary = format!(
|
||||
@@ -250,10 +251,11 @@ pub(super) fn render_checklist_card(
|
||||
lines.push(render_tool_header_with_family_and_summary(
|
||||
family,
|
||||
Some(&header_summary),
|
||||
tool_status_label(status),
|
||||
None,
|
||||
status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
|
||||
let cap = match mode {
|
||||
|
||||
@@ -11,6 +11,7 @@ use ratatui::text::Line;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::tools::spec::ToolResult;
|
||||
use codewhale_localization::{Locale, MessageId, tr};
|
||||
use codewhale_palette as palette;
|
||||
use codewhale_runtime::settings::InlineDiffMode;
|
||||
use codewhale_tui_kit::diff_render;
|
||||
@@ -111,38 +112,74 @@ impl FileMutationReceipt {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn outcome_label(&self) -> String {
|
||||
pub fn outcome_label(&self, locale: Locale) -> String {
|
||||
let created = self.count(FileMutationOutcome::Created);
|
||||
let updated = self.count(FileMutationOutcome::Updated);
|
||||
let deleted = self.count(FileMutationOutcome::Deleted);
|
||||
let renamed = self.count(FileMutationOutcome::Renamed);
|
||||
let mut outcome_parts = Vec::new();
|
||||
push_count(&mut outcome_parts, created, "created");
|
||||
push_count(&mut outcome_parts, updated, "updated");
|
||||
push_count(&mut outcome_parts, deleted, "deleted");
|
||||
push_count(&mut outcome_parts, renamed, "renamed");
|
||||
push_count(
|
||||
&mut outcome_parts,
|
||||
created,
|
||||
locale,
|
||||
MessageId::FileMutationCreatedCount,
|
||||
);
|
||||
push_count(
|
||||
&mut outcome_parts,
|
||||
updated,
|
||||
locale,
|
||||
MessageId::FileMutationUpdatedCount,
|
||||
);
|
||||
push_count(
|
||||
&mut outcome_parts,
|
||||
deleted,
|
||||
locale,
|
||||
MessageId::FileMutationDeletedCount,
|
||||
);
|
||||
push_count(
|
||||
&mut outcome_parts,
|
||||
renamed,
|
||||
locale,
|
||||
MessageId::FileMutationRenamedCount,
|
||||
);
|
||||
|
||||
if self.files.is_empty() {
|
||||
"Changed files".to_string()
|
||||
tr(locale, MessageId::FileMutationChangedFiles).into_owned()
|
||||
} else if self.files.len() == 1 {
|
||||
let file = &self.files[0];
|
||||
match file.outcome {
|
||||
FileMutationOutcome::Created => format!("Created {}", file.path),
|
||||
FileMutationOutcome::Updated => format!("Updated {}", file.path),
|
||||
FileMutationOutcome::Deleted => format!("Deleted {}", file.path),
|
||||
FileMutationOutcome::Renamed => format!(
|
||||
"Renamed {} → {}",
|
||||
file.previous_path.as_deref().unwrap_or("file"),
|
||||
file.path
|
||||
),
|
||||
FileMutationOutcome::Created => {
|
||||
tr(locale, MessageId::FileMutationCreated).replace("{path}", &file.path)
|
||||
}
|
||||
FileMutationOutcome::Updated => {
|
||||
tr(locale, MessageId::FileMutationUpdated).replace("{path}", &file.path)
|
||||
}
|
||||
FileMutationOutcome::Deleted => {
|
||||
tr(locale, MessageId::FileMutationDeleted).replace("{path}", &file.path)
|
||||
}
|
||||
FileMutationOutcome::Renamed => {
|
||||
let fallback = tr(locale, MessageId::FileMutationMissingFile);
|
||||
let paths = format!(
|
||||
"{} → {}",
|
||||
file.previous_path.as_deref().unwrap_or(fallback.as_ref()),
|
||||
file.path
|
||||
);
|
||||
tr(locale, MessageId::FileMutationRenamed).replace("{path}", &paths)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
format!("{} files · {}", self.files.len(), outcome_parts.join(" · "))
|
||||
let count = super::tool_run::counted(
|
||||
self.files.len(),
|
||||
locale,
|
||||
MessageId::ActivityFileOne,
|
||||
MessageId::ActivityFileMany,
|
||||
);
|
||||
format!("{count} · {}", outcome_parts.join(" · "))
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn semantic_summary(&self) -> String {
|
||||
pub fn semantic_summary(&self, locale: Locale) -> String {
|
||||
let stats = format!("+{} -{}", self.added, self.deleted);
|
||||
let separator_chars = " · ".chars().count();
|
||||
let outcome_budget = MAX_SUMMARY_CHARS
|
||||
@@ -150,23 +187,28 @@ impl FileMutationReceipt {
|
||||
.saturating_sub(separator_chars);
|
||||
format!(
|
||||
"{} · {stats}",
|
||||
bounded_text(&self.outcome_label(), outcome_budget)
|
||||
bounded_text(&self.outcome_label(locale), outcome_budget)
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn inspect_text(&self) -> String {
|
||||
let summary = self.semantic_summary();
|
||||
pub fn inspect_text(&self, locale: Locale) -> String {
|
||||
let summary = self.semantic_summary(locale);
|
||||
if self.exact_diff.trim().is_empty() {
|
||||
format!("{summary}\n\n(no textual changes)")
|
||||
format!("{summary}\n\n{}", tr(locale, MessageId::FileMutationNoText))
|
||||
} else {
|
||||
format!("{summary}\n\n{}", self.exact_diff)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_inline(&self, width: u16, mode: InlineDiffMode) -> Vec<Line<'static>> {
|
||||
pub fn render_inline(
|
||||
&self,
|
||||
width: u16,
|
||||
mode: InlineDiffMode,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
match mode {
|
||||
InlineDiffMode::Off | InlineDiffMode::Summary => vec![exact_evidence_hint()],
|
||||
InlineDiffMode::Off | InlineDiffMode::Summary => vec![exact_evidence_hint(locale)],
|
||||
InlineDiffMode::Full => {
|
||||
let mut lines = Vec::new();
|
||||
if !self.display_diff.trim().is_empty() {
|
||||
@@ -178,16 +220,20 @@ impl FileMutationReceipt {
|
||||
lines.extend(rendered.lines);
|
||||
if rendered.omitted_rows > 0 {
|
||||
let detail_hint =
|
||||
crate::tui::key_shortcuts::tool_details_shortcut_action_hint("change");
|
||||
crate::tui::key_shortcuts::tool_details_shortcut_action_hint(
|
||||
tr(locale, MessageId::FileMutationChange).as_ref(),
|
||||
);
|
||||
lines.push(details_affordance_line(
|
||||
&format!("+{} diff lines · {detail_hint}", rendered.omitted_rows),
|
||||
&tr(locale, MessageId::FileMutationMoreDiff)
|
||||
.replace("{count}", &rendered.omitted_rows.to_string())
|
||||
.replace("{hint}", &detail_hint),
|
||||
Style::default().fg(palette::TEXT_MUTED).italic(),
|
||||
));
|
||||
} else {
|
||||
lines.push(exact_evidence_hint());
|
||||
lines.push(exact_evidence_hint(locale));
|
||||
}
|
||||
} else {
|
||||
lines.push(exact_evidence_hint());
|
||||
lines.push(exact_evidence_hint(locale));
|
||||
}
|
||||
lines
|
||||
}
|
||||
@@ -202,16 +248,18 @@ impl FileMutationReceipt {
|
||||
}
|
||||
}
|
||||
|
||||
fn exact_evidence_hint() -> Line<'static> {
|
||||
fn exact_evidence_hint(locale: Locale) -> Line<'static> {
|
||||
details_affordance_line(
|
||||
&crate::tui::key_shortcuts::tool_details_shortcut_action_hint("change"),
|
||||
&crate::tui::key_shortcuts::tool_details_shortcut_action_hint(
|
||||
tr(locale, MessageId::FileMutationChange).as_ref(),
|
||||
),
|
||||
Style::default().fg(palette::TEXT_MUTED).italic(),
|
||||
)
|
||||
}
|
||||
|
||||
fn push_count(parts: &mut Vec<String>, count: usize, label: &str) {
|
||||
fn push_count(parts: &mut Vec<String>, count: usize, locale: Locale, label: MessageId) {
|
||||
if count > 0 {
|
||||
parts.push(format!("{count} {label}"));
|
||||
parts.push(tr(locale, label).replace("{count}", &count.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +388,7 @@ mod tests {
|
||||
assert!(!receipt.display_diff.contains("alice"));
|
||||
assert!(
|
||||
receipt
|
||||
.semantic_summary()
|
||||
.semantic_summary(Locale::En)
|
||||
.contains("Created <external file>")
|
||||
);
|
||||
}
|
||||
@@ -358,9 +406,34 @@ mod tests {
|
||||
assert_eq!(receipt.added, 1);
|
||||
assert_eq!(receipt.deleted, 1);
|
||||
assert_eq!(
|
||||
receipt.semantic_summary(),
|
||||
receipt.semantic_summary(Locale::En),
|
||||
"2 files · 1 updated · 1 renamed · +1 -1"
|
||||
);
|
||||
let renamed = FileMutationReceipt {
|
||||
exact_diff: String::new(),
|
||||
display_diff: String::new(),
|
||||
files: vec![FileMutationFile {
|
||||
path: "new{path}.rs".into(),
|
||||
previous_path: Some("old{path}.rs".into()),
|
||||
outcome: FileMutationOutcome::Renamed,
|
||||
}],
|
||||
added: 0,
|
||||
deleted: 0,
|
||||
};
|
||||
assert_eq!(
|
||||
renamed.outcome_label(Locale::En),
|
||||
"Renamed old{path}.rs → new{path}.rs"
|
||||
);
|
||||
for (locale, verb) in [
|
||||
(Locale::ZhHans, "已重命名"),
|
||||
(Locale::ZhHant, "已重新命名"),
|
||||
(Locale::Ja, "名前変更"),
|
||||
] {
|
||||
let text = renamed.semantic_summary(locale);
|
||||
assert!(text.starts_with(verb), "{text}");
|
||||
assert!(text.contains("old{path}.rs → new{path}.rs"), "{text}");
|
||||
assert!(text.ends_with("+0 -0"), "{text}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -389,7 +462,7 @@ mod tests {
|
||||
let receipt =
|
||||
FileMutationReceipt::from_success(Path::new("/workspace"), &result).expect("receipt");
|
||||
|
||||
let full = receipt.render_inline(80, InlineDiffMode::Full);
|
||||
let full = receipt.render_inline(80, InlineDiffMode::Full, Locale::En);
|
||||
let full_text = plain(&full);
|
||||
assert!(full.len() <= MAX_INLINE_DIFF_LINES + 2, "{full_text}");
|
||||
assert!(full_text.contains("line 0"), "{full_text}");
|
||||
@@ -397,13 +470,13 @@ mod tests {
|
||||
assert!(full_text.contains(":change"), "{full_text}");
|
||||
assert!(!full_text.contains("summary:"), "{full_text}");
|
||||
|
||||
let summary = plain(&receipt.render_inline(80, InlineDiffMode::Summary));
|
||||
let summary = plain(&receipt.render_inline(80, InlineDiffMode::Summary, Locale::En));
|
||||
assert!(!summary.contains("Created src/lib.rs"), "{summary}");
|
||||
assert!(!summary.contains("+30 -0"), "{summary}");
|
||||
assert!(!summary.contains("line 0"), "{summary}");
|
||||
assert!(summary.contains(":change"), "{summary}");
|
||||
|
||||
let off = plain(&receipt.render_inline(80, InlineDiffMode::Off));
|
||||
let off = plain(&receipt.render_inline(80, InlineDiffMode::Off, Locale::En));
|
||||
assert!(!off.contains("line 0"), "{off}");
|
||||
assert!(!off.contains("+30 -0"), "{off}");
|
||||
assert!(off.contains(":change"), "{off}");
|
||||
@@ -418,7 +491,7 @@ mod tests {
|
||||
}));
|
||||
let receipt =
|
||||
FileMutationReceipt::from_success(Path::new("/workspace"), &result).expect("receipt");
|
||||
let full_text = plain(&receipt.render_inline(80, InlineDiffMode::Full));
|
||||
let full_text = plain(&receipt.render_inline(80, InlineDiffMode::Full, Locale::En));
|
||||
|
||||
assert!(full_text.contains("- old"), "{full_text}");
|
||||
assert!(full_text.contains("+ new"), "{full_text}");
|
||||
@@ -435,7 +508,7 @@ mod tests {
|
||||
}));
|
||||
let receipt =
|
||||
FileMutationReceipt::from_success(Path::new("/workspace"), &result).expect("receipt");
|
||||
let summary = receipt.semantic_summary();
|
||||
let summary = receipt.semantic_summary(Locale::En);
|
||||
assert!(summary.ends_with(" · +1 -1"), "{summary}");
|
||||
assert!(summary.chars().count() <= MAX_SUMMARY_CHARS);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! Rendering for plan-update transcript cells.
|
||||
|
||||
use codewhale_localization::Locale;
|
||||
use ratatui::text::Line;
|
||||
|
||||
use crate::tools::plan::{PlanSnapshot, StepStatus};
|
||||
|
||||
use super::{
|
||||
ToolStatus, render_compact_kv, render_tool_header, tool_status_label, tool_value_style,
|
||||
};
|
||||
use super::{ToolStatus, render_compact_kv, render_tool_header, tool_value_style};
|
||||
|
||||
/// Cell for plan updates emitted by the plan tool.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -17,14 +16,25 @@ pub struct PlanUpdateCell {
|
||||
|
||||
impl PlanUpdateCell {
|
||||
/// Render the plan update cell into lines.
|
||||
#[cfg(test)]
|
||||
pub fn lines_with_motion(&self, width: u16, low_motion: bool) -> Vec<Line<'static>> {
|
||||
self.lines_with_motion_and_locale(width, low_motion, Locale::En)
|
||||
}
|
||||
|
||||
pub fn lines_with_motion_and_locale(
|
||||
&self,
|
||||
width: u16,
|
||||
low_motion: bool,
|
||||
locale: Locale,
|
||||
) -> Vec<Line<'static>> {
|
||||
let mut lines = Vec::new();
|
||||
lines.push(render_tool_header(
|
||||
"Legacy plan",
|
||||
tool_status_label(self.status),
|
||||
None,
|
||||
self.status,
|
||||
None,
|
||||
low_motion,
|
||||
locale,
|
||||
));
|
||||
|
||||
render_plan_snapshot_lines(&self.snapshot, &mut lines, width);
|
||||
|
||||
@@ -1428,10 +1428,10 @@ fn a_card_verb_agrees_with_its_own_label_in_every_locale() {
|
||||
(
|
||||
"Searching for `TranscriptScroll`",
|
||||
"find done",
|
||||
"find 完成",
|
||||
"搜索 完成",
|
||||
"read done",
|
||||
),
|
||||
("Reading src/foo.rs", "read done", "read 完成", "find done"),
|
||||
("Reading src/foo.rs", "read done", "读取 完成", "find done"),
|
||||
] {
|
||||
let cell = super::ExploringCell {
|
||||
entries: vec![super::ExploringEntry {
|
||||
@@ -1534,6 +1534,18 @@ fn receipts_count_only_what_they_actually_counted() {
|
||||
"running",
|
||||
"an unfinished read has nothing to count yet"
|
||||
);
|
||||
for (status, text) in [
|
||||
(ToolStatus::Running, "运行中"),
|
||||
(ToolStatus::Failed, "失败"),
|
||||
(ToolStatus::Stopped, "已停止"),
|
||||
(ToolStatus::Hydrated, "已加载,未运行"),
|
||||
(ToolStatus::Warning, "存在问题"),
|
||||
] {
|
||||
assert_eq!(
|
||||
super::tool_receipt_label(ToolFamily::Run, status, None, Locale::ZhHans),
|
||||
text
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The same truthfulness contract through the real shell render path, where a
|
||||
@@ -1563,7 +1575,7 @@ fn shell_headers_stay_truthful_through_the_output_formatters() {
|
||||
|
||||
for (locale, done, unit) in [
|
||||
(Locale::En, "done", "lines"),
|
||||
(Locale::ZhHans, "完成", "行"),
|
||||
(Locale::ZhHans, "完成", " 行"),
|
||||
] {
|
||||
let header = line_text(&cell.render_with_locale(80, true, RenderMode::Live, locale)[0]);
|
||||
assert!(
|
||||
@@ -1654,6 +1666,16 @@ fn agent_cards_stay_one_line_and_spawn_cards_yield_to_the_delegate_card() {
|
||||
text.contains(" agent "),
|
||||
"the agent family label should appear once in the summary: {text:?}"
|
||||
);
|
||||
let mut failed = agent("action: peek agent_id: agent_scout_1", Some("failed"));
|
||||
failed.status = ToolStatus::Failed;
|
||||
let text = lines_text(&failed.lines_with_mode_and_locale(
|
||||
120,
|
||||
true,
|
||||
RenderMode::Live,
|
||||
codewhale_localization::Locale::ZhHans,
|
||||
));
|
||||
assert!(text.contains("失败"), "{text}");
|
||||
assert!(!text.contains("已检查"), "{text}");
|
||||
}
|
||||
|
||||
/// A tool the catalog does not have produces one useful sentence — the catalog
|
||||
@@ -2230,6 +2252,15 @@ fn an_activity_group_renders_as_a_single_metadata_line() {
|
||||
assert_eq!(lines.len(), 1);
|
||||
assert_eq!(lines_text(&lines), "Explored 2 files, 1 search ›");
|
||||
assert!(!lines_text(&lines).contains("activity_group"));
|
||||
cell.input_summary = None;
|
||||
let localized = cell.lines_with_mode_and_locale(
|
||||
120,
|
||||
true,
|
||||
RenderMode::Live,
|
||||
codewhale_localization::Locale::ZhHans,
|
||||
);
|
||||
assert!(lines_text(&localized).contains("更新了元数据"));
|
||||
assert!(!lines_text(&localized).contains("Updated metadata"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -2573,38 +2604,47 @@ fn a_collapsed_run_summary_names_what_actually_happened() {
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
super::tool_run_summary(&run(
|
||||
&["read_file", "list_dir"],
|
||||
super::ToolRunActivitySummary {
|
||||
files: 4,
|
||||
searches: 1,
|
||||
..Default::default()
|
||||
}
|
||||
)),
|
||||
super::tool_run_summary(
|
||||
&run(
|
||||
&["read_file", "list_dir"],
|
||||
super::ToolRunActivitySummary {
|
||||
files: 4,
|
||||
searches: 1,
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
codewhale_localization::Locale::En
|
||||
),
|
||||
"Explored 4 files, 1 search: read file, list dir"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
super::tool_run_summary(&run(
|
||||
&["read_file", "run_tests", "validate_data"],
|
||||
super::ToolRunActivitySummary {
|
||||
files: 2,
|
||||
commands: 2,
|
||||
..Default::default()
|
||||
}
|
||||
)),
|
||||
super::tool_run_summary(
|
||||
&run(
|
||||
&["read_file", "run_tests", "validate_data"],
|
||||
super::ToolRunActivitySummary {
|
||||
files: 2,
|
||||
commands: 2,
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
codewhale_localization::Locale::En
|
||||
),
|
||||
"Explored 2 files: read file, ran 2 commands: run tests, validate data",
|
||||
"each clause lists only its own families"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
super::tool_run_summary(&run(
|
||||
&["session_sync"],
|
||||
super::ToolRunActivitySummary {
|
||||
other: 2,
|
||||
..Default::default()
|
||||
}
|
||||
)),
|
||||
super::tool_run_summary(
|
||||
&run(
|
||||
&["session_sync"],
|
||||
super::ToolRunActivitySummary {
|
||||
other: 2,
|
||||
..Default::default()
|
||||
}
|
||||
),
|
||||
codewhale_localization::Locale::En
|
||||
),
|
||||
"Updated metadata",
|
||||
"a run of tools with no user-facing family falls back to a plain note"
|
||||
);
|
||||
@@ -2621,7 +2661,7 @@ fn a_collapsed_run_summary_names_what_actually_happened() {
|
||||
);
|
||||
assert_eq!(commands[0].activity.commands, 3);
|
||||
assert_eq!(
|
||||
super::tool_run_summary(&commands[0]),
|
||||
super::tool_run_summary(&commands[0], codewhale_localization::Locale::En),
|
||||
"Ran 3 commands: run tests, run verifiers, validate data"
|
||||
);
|
||||
|
||||
@@ -2635,9 +2675,29 @@ fn a_collapsed_run_summary_names_what_actually_happened() {
|
||||
);
|
||||
assert_eq!(git[0].activity.files, 3);
|
||||
assert_eq!(
|
||||
super::tool_run_summary(&git[0]),
|
||||
super::tool_run_summary(&git[0], codewhale_localization::Locale::En),
|
||||
"Explored 3 files: git log, git show, git blame"
|
||||
);
|
||||
let sample = run(
|
||||
&["read_file", "search"],
|
||||
super::ToolRunActivitySummary {
|
||||
files: 2,
|
||||
searches: 1,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
for (locale, expected) in [
|
||||
(codewhale_localization::Locale::ZhHans, "浏览了2个文件"),
|
||||
(codewhale_localization::Locale::ZhHant, "瀏覽了2個檔案"),
|
||||
(codewhale_localization::Locale::Ja, "2ファイル"),
|
||||
(codewhale_localization::Locale::Ru, "Просмотрено: файлы — 2"),
|
||||
(codewhale_localization::Locale::Uk, "Переглянуто: файли — 2"),
|
||||
] {
|
||||
let text = super::tool_run_summary(&sample, locale);
|
||||
assert!(text.contains(expected), "{locale:?}: {text}");
|
||||
assert!(!text.contains("Explored"), "{text}");
|
||||
assert!(text.contains("read file"), "{text}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The small pure helpers behind the cards, as one table each. Every row is a
|
||||
@@ -2700,11 +2760,14 @@ fn the_card_helpers_accept_their_documented_forms_and_decline_the_rest() {
|
||||
// The elapsed badge appears at three seconds and not before, so quick
|
||||
// reads and greps do not visually churn.
|
||||
for secs in [0, 1, 2] {
|
||||
assert_eq!(running_status_label_with_elapsed(secs), "running");
|
||||
assert_eq!(
|
||||
running_status_label_with_elapsed(secs, codewhale_localization::Locale::En),
|
||||
"running"
|
||||
);
|
||||
}
|
||||
for secs in [3u64, 7, 120] {
|
||||
assert_eq!(
|
||||
running_status_label_with_elapsed(secs),
|
||||
running_status_label_with_elapsed(secs, codewhale_localization::Locale::En),
|
||||
format!("running ({secs}s)")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Tool-run grouping for transcript collapse.
|
||||
|
||||
use super::{HistoryCell, ToolCell};
|
||||
use codewhale_localization::{Locale, MessageId, tr};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolRun {
|
||||
@@ -256,58 +257,74 @@ fn classify_tool_name_activity(name: &str) -> ToolRunActivity {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn tool_run_summary(run: &ToolRun) -> String {
|
||||
pub fn tool_run_summary(run: &ToolRun, locale: Locale) -> String {
|
||||
let activity = &run.activity;
|
||||
let mut parts = Vec::new();
|
||||
if activity.files > 0 {
|
||||
parts.push(counted(activity.files, "file", "files"));
|
||||
parts.push(counted(
|
||||
activity.files,
|
||||
locale,
|
||||
MessageId::ActivityFileOne,
|
||||
MessageId::ActivityFileMany,
|
||||
));
|
||||
}
|
||||
if activity.searches > 0 {
|
||||
parts.push(counted(activity.searches, "search", "searches"));
|
||||
parts.push(counted(
|
||||
activity.searches,
|
||||
locale,
|
||||
MessageId::ActivitySearchOne,
|
||||
MessageId::ActivitySearchMany,
|
||||
));
|
||||
}
|
||||
|
||||
let mut clauses = Vec::new();
|
||||
if !parts.is_empty() {
|
||||
let mut explore_clause = format!("Explored {}", parts.join(", "));
|
||||
let mut clause =
|
||||
tr(locale, MessageId::ActivityExplored).replace("{items}", &parts.join(", "));
|
||||
if let Some(families) =
|
||||
activity_family_summary(run, &[ToolRunActivity::File, ToolRunActivity::Search])
|
||||
{
|
||||
explore_clause.push_str(": ");
|
||||
explore_clause.push_str(&families);
|
||||
clause.push_str(": ");
|
||||
clause.push_str(&families);
|
||||
}
|
||||
clauses.push(explore_clause);
|
||||
clauses.push(clause);
|
||||
}
|
||||
if activity.commands > 0 {
|
||||
let mut command_clause =
|
||||
format!("ran {}", counted(activity.commands, "command", "commands"));
|
||||
let count = counted(
|
||||
activity.commands,
|
||||
locale,
|
||||
MessageId::ActivityCommandOne,
|
||||
MessageId::ActivityCommandMany,
|
||||
);
|
||||
let mut clause = tr(locale, MessageId::ActivityRan).replace("{items}", &count);
|
||||
if let Some(families) = activity_family_summary(run, &[ToolRunActivity::Command]) {
|
||||
command_clause.push_str(": ");
|
||||
command_clause.push_str(&families);
|
||||
clause.push_str(": ");
|
||||
clause.push_str(&families);
|
||||
}
|
||||
clauses.push(command_clause);
|
||||
clauses.push(clause);
|
||||
}
|
||||
if activity.edits > 0 {
|
||||
clauses.push(format!(
|
||||
"edited {}",
|
||||
counted(activity.edits, "file", "files")
|
||||
));
|
||||
let count = counted(
|
||||
activity.edits,
|
||||
locale,
|
||||
MessageId::ActivityFileOne,
|
||||
MessageId::ActivityFileMany,
|
||||
);
|
||||
clauses.push(tr(locale, MessageId::ActivityEdited).replace("{items}", &count));
|
||||
}
|
||||
if activity.delegates > 0 {
|
||||
clauses.push(format!(
|
||||
"delegated {}",
|
||||
counted(activity.delegates, "task", "tasks")
|
||||
));
|
||||
let count = counted(
|
||||
activity.delegates,
|
||||
locale,
|
||||
MessageId::ActivityTaskOne,
|
||||
MessageId::ActivityTaskMany,
|
||||
);
|
||||
clauses.push(tr(locale, MessageId::ActivityDelegated).replace("{items}", &count));
|
||||
}
|
||||
if activity.metadata > 0 || activity.other > 0 {
|
||||
clauses.push("updated metadata".to_string());
|
||||
if activity.metadata > 0 || activity.other > 0 || clauses.is_empty() {
|
||||
clauses.push(tr(locale, MessageId::ActivityUpdatedMetadata).into_owned());
|
||||
}
|
||||
|
||||
if clauses.is_empty() {
|
||||
return "Updated metadata".to_string();
|
||||
}
|
||||
|
||||
let summary = clauses.join(", ");
|
||||
sentence_case_activity(summary)
|
||||
sentence_case_activity(clauses.join(", "))
|
||||
}
|
||||
|
||||
fn activity_family_summary(run: &ToolRun, activities: &[ToolRunActivity]) -> Option<String> {
|
||||
@@ -322,12 +339,16 @@ fn activity_family_summary(run: &ToolRun, activities: &[ToolRunActivity]) -> Opt
|
||||
(!families.is_empty()).then(|| families.join(", "))
|
||||
}
|
||||
|
||||
pub(super) fn counted(count: usize, singular: &str, plural: &str) -> String {
|
||||
let noun = if count == 1 { singular } else { plural };
|
||||
format!("{count} {noun}")
|
||||
pub(super) fn counted(
|
||||
count: usize,
|
||||
locale: Locale,
|
||||
singular: MessageId,
|
||||
plural: MessageId,
|
||||
) -> String {
|
||||
tr(locale, if count == 1 { singular } else { plural }).replace("{count}", &count.to_string())
|
||||
}
|
||||
|
||||
fn sentence_case_activity(text: String) -> String {
|
||||
pub(super) fn sentence_case_activity(text: String) -> String {
|
||||
let mut chars = text.chars();
|
||||
let Some(first) = chars.next() else {
|
||||
return text;
|
||||
|
||||
@@ -230,7 +230,7 @@ fn detail_status_label(job: &ShellJobSnapshot) -> String {
|
||||
u64::try_from(crate::tools::shell::QUIET_NO_OUTPUT_AFTER.as_millis()).unwrap_or(u64::MAX);
|
||||
match job.elapsed_since_output_ms {
|
||||
Some(ms) if job.status == ShellStatus::Running && !job.stale && ms >= quiet => {
|
||||
crate::tui::history::stale_shell_status_label(ms)
|
||||
crate::tui::history::stale_shell_status_label(ms, codewhale_localization::Locale::En)
|
||||
}
|
||||
_ => status_label(&job.status, job.stale).to_string(),
|
||||
}
|
||||
|
||||
@@ -1567,7 +1567,7 @@ fn push_orphan_tool_completion(
|
||||
.files
|
||||
.first()
|
||||
.map_or_else(|| "<file>".to_string(), |file| file.path.clone());
|
||||
let summary = receipt.semantic_summary();
|
||||
let summary = receipt.semantic_summary(app.ui_locale);
|
||||
HistoryCell::Tool(ToolCell::PatchSummary(PatchSummaryCell {
|
||||
path,
|
||||
summary,
|
||||
|
||||
@@ -398,10 +398,18 @@ pub(crate) fn open_details_pager_for_cell(app: &mut App, cell_index: usize) -> b
|
||||
// model received against the full payload.
|
||||
let spillover_section = spillover_pager_section(app, cell_index);
|
||||
let mutation_section = match app.cell_at_virtual_index(cell_index) {
|
||||
Some(HistoryCell::Tool(ToolCell::PatchSummary(cell))) => cell
|
||||
.receipt
|
||||
.as_ref()
|
||||
.map(|receipt| format!("── Exact File change ──\n{}", receipt.inspect_text())),
|
||||
Some(HistoryCell::Tool(ToolCell::PatchSummary(cell))) => {
|
||||
cell.receipt.as_ref().map(|receipt| {
|
||||
format!(
|
||||
"── {} ──\n{}",
|
||||
codewhale_localization::tr(
|
||||
app.ui_locale,
|
||||
codewhale_localization::MessageId::FileMutationExactHeading
|
||||
),
|
||||
receipt.inspect_text(app.ui_locale)
|
||||
)
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -1173,12 +1181,14 @@ fn turn_timeline_lines(app: &App, start: usize, end: usize) -> Vec<String> {
|
||||
let (kind, summary) = timeline_tool_summary(app, idx, tool);
|
||||
let duration = tool_duration_for_activity(tool)
|
||||
.map(codewhale_command_contract::elapsed::format_elapsed_ms);
|
||||
let status = tool.status().map(tool_status_label);
|
||||
let status = tool
|
||||
.status()
|
||||
.map(|status| tool_status_label(status, app.ui_locale));
|
||||
let actions = timeline_cell_actions(app, idx, cell);
|
||||
rows.push(timeline_row(
|
||||
kind,
|
||||
&summary,
|
||||
status,
|
||||
status.as_deref(),
|
||||
duration.as_deref(),
|
||||
&actions,
|
||||
));
|
||||
@@ -1427,7 +1437,7 @@ fn turn_files_changed(app: &App, start: usize, end: usize) -> Vec<String> {
|
||||
lines.push(format!(
|
||||
"• {} — {}",
|
||||
truncate_line_to_width(&patch.path, 60),
|
||||
tool_status_label(patch.status)
|
||||
tool_status_label(patch.status, app.ui_locale)
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
@@ -1496,7 +1506,7 @@ fn turn_verifier_lines(app: &App, start: usize, end: usize) -> Vec<String> {
|
||||
lines.push(format!(
|
||||
"• {} — {}",
|
||||
truncate_line_to_width(&exec.command, 56),
|
||||
tool_status_label(exec.status)
|
||||
tool_status_label(exec.status, app.ui_locale)
|
||||
));
|
||||
}
|
||||
ToolCell::Review(review) => {
|
||||
@@ -1506,7 +1516,10 @@ fn turn_verifier_lines(app: &App, start: usize, end: usize) -> Vec<String> {
|
||||
} else {
|
||||
format!("review {target}")
|
||||
};
|
||||
lines.push(format!("• {target} — {}", tool_status_label(review.status)));
|
||||
lines.push(format!(
|
||||
"• {target} — {}",
|
||||
tool_status_label(review.status, app.ui_locale)
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1656,7 +1656,10 @@ fn canonical_file_mutations_attach_receipts_only_to_successful_outcomes() {
|
||||
);
|
||||
assert!(cell.receipt.is_some(), "{approval_mode:?} File.{action}");
|
||||
assert_eq!(
|
||||
cell.receipt.as_ref().unwrap().outcome_label(),
|
||||
cell.receipt
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.outcome_label(codewhale_localization::Locale::En),
|
||||
"Updated src/lib.rs",
|
||||
"{approval_mode:?} File.{action}"
|
||||
);
|
||||
@@ -26150,7 +26153,9 @@ fn orphan_success_with_mutation_metadata_keeps_the_file_receipt() {
|
||||
};
|
||||
assert_eq!(cell.status, ToolStatus::Success);
|
||||
assert_eq!(
|
||||
cell.receipt.as_ref().map(|receipt| receipt.outcome_label()),
|
||||
cell.receipt
|
||||
.as_ref()
|
||||
.map(|receipt| receipt.outcome_label(codewhale_localization::Locale::En)),
|
||||
Some("Updated src/lib.rs".to_string())
|
||||
);
|
||||
assert_eq!(app.tool_details_by_cell[&0].tool_name, "File");
|
||||
@@ -27455,7 +27460,9 @@ fn patch_approval_modal_does_not_displace_the_active_file_receipt() {
|
||||
};
|
||||
assert_eq!(cell.status, ToolStatus::Success);
|
||||
assert_eq!(
|
||||
cell.receipt.as_ref().map(|receipt| receipt.outcome_label()),
|
||||
cell.receipt
|
||||
.as_ref()
|
||||
.map(|receipt| receipt.outcome_label(codewhale_localization::Locale::En)),
|
||||
Some("Updated src/lib.rs".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -365,6 +365,7 @@ impl ChatWidget {
|
||||
&& app.tool_run_cache.threshold == app.tool_collapse_threshold
|
||||
&& app.tool_run_cache.mode == app.tool_collapse_mode
|
||||
&& app.tool_run_cache.calm_mode == app.calm_mode
|
||||
&& app.tool_run_cache.locale == app.ui_locale
|
||||
&& app.tool_run_cache.expanded_runs == app.expanded_tool_runs;
|
||||
if !cache_key_matches {
|
||||
let superseded_todos = superseded_todo_write_indices(&app.history, active_entries);
|
||||
@@ -396,7 +397,7 @@ impl ChatWidget {
|
||||
cache.summaries.insert(
|
||||
run.start,
|
||||
(
|
||||
tool_run_summary_cell(run),
|
||||
tool_run_summary_cell(run, app.ui_locale),
|
||||
tool_run_summary_revision(
|
||||
run,
|
||||
&app.history_revisions,
|
||||
@@ -417,6 +418,7 @@ impl ChatWidget {
|
||||
cache.threshold = app.tool_collapse_threshold;
|
||||
cache.mode = app.tool_collapse_mode;
|
||||
cache.calm_mode = app.calm_mode;
|
||||
cache.locale = app.ui_locale;
|
||||
cache.expanded_runs.clone_from(&app.expanded_tool_runs);
|
||||
}
|
||||
// v0.9.1: do not collapse concurrent sub-agent cards into an Enter-
|
||||
@@ -788,11 +790,11 @@ fn receipt_is_settling(receipt_order: usize, elapsed_ms: u128) -> bool {
|
||||
elapsed_ms < delay + 140
|
||||
}
|
||||
|
||||
fn tool_run_summary_cell(run: &ToolRun) -> HistoryCell {
|
||||
fn tool_run_summary_cell(run: &ToolRun, locale: codewhale_localization::Locale) -> HistoryCell {
|
||||
HistoryCell::Tool(ToolCell::Generic(GenericToolCell {
|
||||
name: "activity_group".to_string(),
|
||||
status: ToolStatus::Success,
|
||||
input_summary: Some(crate::tui::history::tool_run_summary(run)),
|
||||
input_summary: Some(crate::tui::history::tool_run_summary(run, locale)),
|
||||
output: None,
|
||||
prompts: None,
|
||||
spillover_path: None,
|
||||
@@ -4173,6 +4175,28 @@ mod tests {
|
||||
app.scroll_up(3);
|
||||
let _ = ChatWidget::new(&mut app, area);
|
||||
assert_eq!(app.tool_run_cache.projection_builds, 1);
|
||||
app.ui_locale = codewhale_localization::Locale::ZhHans;
|
||||
let mut localized = Buffer::empty(area);
|
||||
ChatWidget::new(&mut app, area).render(area, &mut localized);
|
||||
assert_eq!(app.tool_run_cache.projection_builds, 2);
|
||||
let cell = &app.tool_run_cache.summaries.get(&0).unwrap().0;
|
||||
let HistoryCell::Tool(ToolCell::Generic(cell)) = cell else {
|
||||
panic!("activity cell");
|
||||
};
|
||||
assert!(
|
||||
cell.input_summary
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.contains("浏览了2个文件")
|
||||
);
|
||||
assert!(!buffer_text(&localized, area).contains("Explored"));
|
||||
let _ = ChatWidget::new(&mut app, area);
|
||||
assert_eq!(app.tool_run_cache.projection_builds, 2);
|
||||
app.ui_locale = codewhale_localization::Locale::En;
|
||||
let mut english = Buffer::empty(area);
|
||||
ChatWidget::new(&mut app, area).render(area, &mut english);
|
||||
assert_eq!(app.tool_run_cache.projection_builds, 3);
|
||||
assert!(buffer_text(&english, area).contains("Explored 2 files"));
|
||||
app.expanded_tool_runs.insert(0);
|
||||
let _ = ChatWidget::new(&mut app, area);
|
||||
assert_eq!(app.collapsed_cell_map, vec![0, 1, 2]);
|
||||
|
||||
@@ -125,7 +125,7 @@ fn tool_display_label_for_name(name: &str) -> String {
|
||||
if matches!(family, ToolFamily::Generic) {
|
||||
name.to_string()
|
||||
} else {
|
||||
family_label(family).to_string()
|
||||
family_label(family, Locale::En).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,11 +149,11 @@ fn family_message_id(family: ToolFamily) -> codewhale_localization::MessageId {
|
||||
#[must_use]
|
||||
pub fn tool_activity_label_for_name(name: &str, locale: Locale) -> String {
|
||||
let family = tool_family_for_name(name);
|
||||
let mid = family_message_id(family);
|
||||
let label = family_label(family, locale);
|
||||
if matches!(family, ToolFamily::Generic) {
|
||||
format!("{} {name}", codewhale_localization::tr(locale, mid))
|
||||
format!("{label} {name}")
|
||||
} else {
|
||||
codewhale_localization::tr(locale, mid).to_string()
|
||||
label.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,23 +293,10 @@ pub fn family_glyph(family: ToolFamily) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// The short verb label for a family — appears in card headers next to the
|
||||
/// glyph. Lowercased on purpose; the verb-glyph + label is the new card
|
||||
/// title vocabulary.
|
||||
/// The catalog-owned verb label for a family, shown beside its header glyph.
|
||||
#[must_use]
|
||||
pub fn family_label(family: ToolFamily) -> &'static str {
|
||||
match family {
|
||||
ToolFamily::Read => "read",
|
||||
ToolFamily::Patch => "patch",
|
||||
ToolFamily::Run => "run",
|
||||
ToolFamily::Find => "find",
|
||||
ToolFamily::Delegate => "agent",
|
||||
ToolFamily::Fanout => "fanout",
|
||||
ToolFamily::Rlm => "rlm",
|
||||
ToolFamily::Verify => "verify",
|
||||
ToolFamily::Think => "think",
|
||||
ToolFamily::Generic => "tool",
|
||||
}
|
||||
pub fn family_label(family: ToolFamily, locale: Locale) -> std::borrow::Cow<'static, str> {
|
||||
codewhale_localization::tr(locale, family_message_id(family))
|
||||
}
|
||||
|
||||
/// Position of a line within a multi-line card — drives the left-rail
|
||||
@@ -507,7 +494,7 @@ mod tests {
|
||||
"family {family:?} has empty glyph",
|
||||
);
|
||||
assert!(
|
||||
!family_label(family).is_empty(),
|
||||
!family_label(family, Locale::En).is_empty(),
|
||||
"family {family:?} has empty label",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -303,6 +303,7 @@ impl ChatWidget {
|
||||
&& app.tool_run_cache.threshold == app.tool_collapse_threshold
|
||||
&& app.tool_run_cache.mode == app.tool_collapse_mode
|
||||
&& app.tool_run_cache.calm_mode == app.calm_mode
|
||||
&& app.tool_run_cache.locale == app.ui_locale
|
||||
&& app.tool_run_cache.expanded_runs == app.expanded_tool_runs;
|
||||
if !cache_key_matches {
|
||||
let superseded_todos = superseded_todo_write_indices(&app.history, active_entries);
|
||||
@@ -334,7 +335,7 @@ impl ChatWidget {
|
||||
cache.summaries.insert(
|
||||
run.start,
|
||||
(
|
||||
tool_run_summary_cell(run),
|
||||
tool_run_summary_cell(run, app.ui_locale),
|
||||
tool_run_summary_revision(
|
||||
run,
|
||||
&app.history_revisions,
|
||||
@@ -355,6 +356,7 @@ impl ChatWidget {
|
||||
cache.threshold = app.tool_collapse_threshold;
|
||||
cache.mode = app.tool_collapse_mode;
|
||||
cache.calm_mode = app.calm_mode;
|
||||
cache.locale = app.ui_locale;
|
||||
cache.expanded_runs.clone_from(&app.expanded_tool_runs);
|
||||
}
|
||||
// v0.9.1: do not collapse concurrent sub-agent cards into an Enter-
|
||||
@@ -679,11 +681,11 @@ fn receipt_is_settling(receipt_order: usize, elapsed_ms: u128) -> bool {
|
||||
elapsed_ms < delay + 140
|
||||
}
|
||||
|
||||
fn tool_run_summary_cell(run: &ToolRun) -> HistoryCell {
|
||||
fn tool_run_summary_cell(run: &ToolRun, locale: codewhale_localization::Locale) -> HistoryCell {
|
||||
HistoryCell::Tool(ToolCell::Generic(GenericToolCell {
|
||||
name: "activity_group".to_string(),
|
||||
status: ToolStatus::Success,
|
||||
input_summary: Some(crate::tui::history::tool_run_summary(run)),
|
||||
input_summary: Some(crate::tui::history::tool_run_summary(run, locale)),
|
||||
output: None,
|
||||
prompts: None,
|
||||
spillover_path: None,
|
||||
|
||||
@@ -1257,8 +1257,9 @@ struct RankedWorkRow {
|
||||
}
|
||||
|
||||
/// Complete unique targets; only the aggregate summary presentation is capped.
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub(super) struct SettledFileActivity {
|
||||
pub(super) locale: Locale,
|
||||
pub(super) summary: FileActivitySummary,
|
||||
pub(super) read: Vec<String>,
|
||||
list: Vec<String>,
|
||||
@@ -1268,6 +1269,21 @@ pub(super) struct SettledFileActivity {
|
||||
pub(super) inline_diff_mode: InlineDiffMode,
|
||||
}
|
||||
|
||||
impl Default for SettledFileActivity {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
locale: Locale::En,
|
||||
summary: FileActivitySummary::default(),
|
||||
read: Vec::new(),
|
||||
list: Vec::new(),
|
||||
search: Vec::new(),
|
||||
write: Vec::new(),
|
||||
mutations: Vec::new(),
|
||||
inline_diff_mode: InlineDiffMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SettledFileActivity {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SettledFileActivity")
|
||||
@@ -2313,6 +2329,7 @@ const fn agent_mark(bucket: WorkBucket) -> &'static str {
|
||||
|
||||
pub(super) fn settled_file_activity(app: &App) -> SettledFileActivity {
|
||||
let mut activity = SettledFileActivity {
|
||||
locale: app.ui_locale,
|
||||
inline_diff_mode: app.inline_diff_mode,
|
||||
..SettledFileActivity::default()
|
||||
};
|
||||
@@ -2372,7 +2389,8 @@ fn aggregate_activity_row(activity: &SettledFileActivity) -> Option<RankedWorkRo
|
||||
if activity.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let summaries = activity.summary.compact_display();
|
||||
let locale = activity.locale;
|
||||
let summaries = activity.summary.compact_display(locale);
|
||||
if summaries.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -2384,21 +2402,28 @@ fn aggregate_activity_row(activity: &SettledFileActivity) -> Option<RankedWorkRo
|
||||
};
|
||||
let mutation_detail = activity.mutations.last().map(|receipt| {
|
||||
if activity.inline_diff_mode == InlineDiffMode::Off {
|
||||
receipt.outcome_label()
|
||||
receipt.outcome_label(locale)
|
||||
} else {
|
||||
receipt.semantic_summary()
|
||||
receipt.semantic_summary(locale)
|
||||
}
|
||||
});
|
||||
let mutation_body = settled_mutation_body(&activity.mutations, activity.inline_diff_mode);
|
||||
let mutation_body =
|
||||
settled_mutation_body(&activity.mutations, activity.inline_diff_mode, locale);
|
||||
let mut body_parts = Vec::new();
|
||||
if !mutation_body.is_empty() {
|
||||
body_parts.push(mutation_body);
|
||||
}
|
||||
for (kind, details) in [
|
||||
("Read", &activity.read),
|
||||
("List", &activity.list),
|
||||
("Search", &activity.search),
|
||||
("Write", &activity.write),
|
||||
(tr(locale, MessageId::FileActivityReadLabel), &activity.read),
|
||||
(tr(locale, MessageId::FileActivityListLabel), &activity.list),
|
||||
(
|
||||
tr(locale, MessageId::FileActivitySearchLabel),
|
||||
&activity.search,
|
||||
),
|
||||
(
|
||||
tr(locale, MessageId::FileActivityWriteLabel),
|
||||
&activity.write,
|
||||
),
|
||||
] {
|
||||
if details.is_empty() {
|
||||
continue;
|
||||
@@ -2409,7 +2434,7 @@ fn aggregate_activity_row(activity: &SettledFileActivity) -> Option<RankedWorkRo
|
||||
));
|
||||
}
|
||||
if body_parts.is_empty() {
|
||||
body_parts.push("No safe target detail retained".to_string());
|
||||
body_parts.push(tr(locale, MessageId::FileActivityNoDetail).into_owned());
|
||||
}
|
||||
let detail = mutation_detail
|
||||
.or_else(|| {
|
||||
@@ -2418,10 +2443,15 @@ fn aggregate_activity_row(activity: &SettledFileActivity) -> Option<RankedWorkRo
|
||||
.first()
|
||||
.cloned()
|
||||
.or_else(|| activity.read.first().cloned())
|
||||
.or_else(|| activity.search.first().map(|_| "patterns".to_string()))
|
||||
.or_else(|| {
|
||||
activity
|
||||
.search
|
||||
.first()
|
||||
.map(|_| tr(locale, MessageId::FileActivityPatterns).into_owned())
|
||||
})
|
||||
.or_else(|| activity.list.first().cloned())
|
||||
})
|
||||
.unwrap_or_else(|| "settled".to_string());
|
||||
.unwrap_or_else(|| tr(locale, MessageId::FileActivitySettled).into_owned());
|
||||
let detail = sanitize_summary_title(&detail);
|
||||
Some(RankedWorkRow {
|
||||
bucket: WorkBucket::Recent,
|
||||
@@ -2435,7 +2465,7 @@ fn aggregate_activity_row(activity: &SettledFileActivity) -> Option<RankedWorkRo
|
||||
tone: WorkTone::Success,
|
||||
selectable: true,
|
||||
primary_action: Some(SidebarRowAction::InspectWork {
|
||||
title: "Work · file activity".to_string(),
|
||||
title: tr(locale, MessageId::FileActivityDetailTitle).into_owned(),
|
||||
body: body_parts.join("\n\n"),
|
||||
stop_action: None,
|
||||
}),
|
||||
@@ -2452,17 +2482,18 @@ fn activity_rows(activity: SettledFileActivity) -> Vec<RankedWorkRow> {
|
||||
pub(super) fn settled_mutation_body(
|
||||
receipts: &[FileMutationReceipt],
|
||||
mode: InlineDiffMode,
|
||||
locale: Locale,
|
||||
) -> String {
|
||||
let Some(receipt) = receipts.last() else {
|
||||
return String::new();
|
||||
};
|
||||
let details = crate::tui::key_shortcuts::tool_details_shortcut_action_hint(
|
||||
"exact change evidence on the matching File receipt",
|
||||
tr(locale, MessageId::FileMutationExactEvidence).as_ref(),
|
||||
);
|
||||
let hint = format!("Select the matching File receipt; {details}.");
|
||||
let hint = tr(locale, MessageId::FileMutationSelectReceipt).replace("{hint}", &details);
|
||||
match mode {
|
||||
InlineDiffMode::Off => format!("{}\n\n{hint}", receipt.outcome_label()),
|
||||
InlineDiffMode::Summary => format!("{}\n\n{hint}", receipt.semantic_summary()),
|
||||
InlineDiffMode::Off => format!("{}\n\n{hint}", receipt.outcome_label(locale)),
|
||||
InlineDiffMode::Summary => format!("{}\n\n{hint}", receipt.semantic_summary(locale)),
|
||||
InlineDiffMode::Full => {
|
||||
let diff = receipt
|
||||
.display_diff
|
||||
@@ -2471,9 +2502,9 @@ pub(super) fn settled_mutation_body(
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
if diff.trim().is_empty() {
|
||||
format!("{}\n\n{hint}", receipt.semantic_summary())
|
||||
format!("{}\n\n{hint}", receipt.semantic_summary(locale))
|
||||
} else {
|
||||
format!("{}\n\n{diff}\n\n{hint}", receipt.semantic_summary())
|
||||
format!("{}\n\n{diff}\n\n{hint}", receipt.semantic_summary(locale))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4237,6 +4268,17 @@ mod tests {
|
||||
assert!(body.contains(&format!("{action}-11.rs")));
|
||||
assert!(!body.contains(&format!("{action}-12.rs")));
|
||||
}
|
||||
app.ui_locale = Locale::ZhHans;
|
||||
app.work_surface.file_activity = settled_file_activity(&app);
|
||||
let rows = files_rows(&mut app);
|
||||
assert!(rows.iter().any(|row| row.label == "读取 13"));
|
||||
assert!(rows.iter().any(|row| row.label == "已编辑 13"));
|
||||
let summary = aggregate_activity_row(&app.work_surface.file_activity).unwrap();
|
||||
assert!(
|
||||
summary.row.label.contains("读取了14个文件"),
|
||||
"{}",
|
||||
summary.row.label
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
use crate::agent_roster::format_tokens;
|
||||
use crate::tui::app::{App, SidebarRowAction};
|
||||
use codewhale_localization::{MessageId, tr};
|
||||
|
||||
use super::model::{RailPanel, WorkRow, WorkRowId, WorkTone};
|
||||
|
||||
@@ -57,7 +58,14 @@ pub(super) fn files_rows(app: &mut App) -> Vec<WorkRow> {
|
||||
let mut out = Vec::new();
|
||||
let edited = edited_files(&activity);
|
||||
if !edited.is_empty() {
|
||||
out.push(heading("files:edited", format!("Edited {}", edited.len())));
|
||||
out.push(heading(
|
||||
"files:edited",
|
||||
format!(
|
||||
"{} {}",
|
||||
tr(activity.locale, MessageId::FileActivityEditedLabel),
|
||||
edited.len()
|
||||
),
|
||||
));
|
||||
for (path, receipt) in &edited {
|
||||
let Some(receipt) = receipt else {
|
||||
out.push(WorkRow {
|
||||
@@ -68,7 +76,8 @@ pub(super) fn files_rows(app: &mut App) -> Vec<WorkRow> {
|
||||
continue;
|
||||
};
|
||||
let across = if receipt.files.len() > 1 {
|
||||
format!(" across {} files", receipt.files.len())
|
||||
tr(activity.locale, MessageId::FileMutationAcrossFiles)
|
||||
.replace("{count}", &receipt.files.len().to_string())
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
@@ -80,10 +89,14 @@ pub(super) fn files_rows(app: &mut App) -> Vec<WorkRow> {
|
||||
tone: WorkTone::Success,
|
||||
selectable: true,
|
||||
primary_action: Some(SidebarRowAction::InspectWork {
|
||||
title: format!("File · {path}"),
|
||||
title: format!(
|
||||
"{} · {path}",
|
||||
tr(activity.locale, MessageId::ApprovalLabelFile)
|
||||
),
|
||||
body: super::model::settled_mutation_body(
|
||||
std::slice::from_ref(*receipt),
|
||||
activity.inline_diff_mode,
|
||||
activity.locale,
|
||||
),
|
||||
stop_action: None,
|
||||
}),
|
||||
@@ -94,7 +107,11 @@ pub(super) fn files_rows(app: &mut App) -> Vec<WorkRow> {
|
||||
if !activity.read.is_empty() {
|
||||
out.push(heading(
|
||||
"files:read",
|
||||
format!("Read {}", activity.read.len()),
|
||||
format!(
|
||||
"{} {}",
|
||||
tr(activity.locale, MessageId::FileActivityReadLabel),
|
||||
activity.read.len()
|
||||
),
|
||||
));
|
||||
for path in &activity.read {
|
||||
out.push(WorkRow {
|
||||
|
||||
+1
-1
@@ -651,7 +651,7 @@ owner = "crates/tui/src/commands/groups/session/color.rs"
|
||||
[[feature]]
|
||||
id = "localization"
|
||||
name = "Localization"
|
||||
summary = "Interface text in 15 locales."
|
||||
summary = "Interface text in 15 locales, including tool activity, file receipts and language-aware transcript caches."
|
||||
status = "stable"
|
||||
surfaces = { tui = "stable" }
|
||||
docs = "docs/LOCALIZATION.md"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"format": "codewhale.features.v1",
|
||||
"source": {
|
||||
"path": "docs/features.toml",
|
||||
"sha256": "0b7740404421a766df45a7e5787b2e2dbf6f9ee6361ad3a479044d9c972ea3d5"
|
||||
"sha256": "5e18921ce90014e26473bc496fc80a29abf1a288144f59c25fcea5b6939d38e1"
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
@@ -844,7 +844,7 @@
|
||||
{
|
||||
"id": "localization",
|
||||
"name": "Localization",
|
||||
"summary": "Interface text in 15 locales.",
|
||||
"summary": "Interface text in 15 locales, including tool activity, file receipts and language-aware transcript caches.",
|
||||
"status": "stable",
|
||||
"surfaces": {
|
||||
"tui": "stable"
|
||||
|
||||
Reference in new issue
Block a user