All files / src/lib i18n.ts

100% Statements 17/17
100% Branches 9/9
100% Functions 3/3
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958          3x           3x     8x 8x 5x       4x             3x                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     3x                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   3x                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   3x       107x 107x 107x 4x   107x    
// ─── Kronn i18n — lightweight translation system ────────────────────────────
// UI language (stored in localStorage) is separate from agent output language (stored in backend config).
 
export type UILocale = 'fr' | 'en' | 'es';
 
export const UI_LOCALES: { code: UILocale; label: string; flag: string }[] = [
  { code: 'fr', label: 'Français', flag: '🇫🇷' },
  { code: 'en', label: 'English', flag: '🇬🇧' },
  { code: 'es', label: 'Español', flag: '🇪🇸' },
];
 
const STORAGE_KEY = 'kronn:ui-locale';
 
export function getUILocale(): UILocale {
  const stored = localStorage.getItem(STORAGE_KEY);
  if (stored && ['fr', 'en', 'es'].includes(stored)) return stored as UILocale;
  return 'fr';
}
 
export function setUILocale(locale: UILocale) {
  localStorage.setItem(STORAGE_KEY, locale);
}
 
// ─── Translation dictionary ─────────────────────────────────────────────────
 
type TranslationDict = Record<string, string>;
 
const fr: TranslationDict = {
  // ── Nav & Tabs ──
  'nav.projects': 'Projets',
  'nav.discussions': 'Discussions',
  'nav.mcps': 'MCPs',
  'nav.workflows': 'Workflows',
  'nav.config': 'Config',
  'nav.scan': 'Scanner',
 
  // ── Projects page ──
  'projects.title': 'Projets',
  'projects.aiReady': 'AI-ready',
  'projects.hidden': 'cache',
  'projects.hiddenPlural': 'caches',
  'projects.hideHidden': 'Masquer les caches',
  'projects.showHidden': 'Voir les caches',
  'projects.search': 'Rechercher un projet...',
  'projects.showMore': 'Voir plus ({0} projet{1} restant{2})',
  'projects.collapse': 'Reduire',
  'projects.empty': 'Aucun projet. Cliquez sur "Scanner" pour detecter vos depots.',
  'projects.emptySearch': 'Aucun projet correspondant.',
  'projects.manageMcps': 'Gerer dans MCPs',
  'projects.noMcp': 'Aucun MCP — configurer',
  'projects.delete': 'Supprimer',
  'projects.deleteConfirm': 'Supprimer ce projet de Kronn ?',
  'projects.status.valid': 'valide',
  'projects.status.validating': 'validation...',
  'projects.status.auditOk': 'audit OK',
  'projects.status.template': 'template',
  'projects.status.none': 'aucun',
 
  // ── AI Audit ──
  'audit.noTemplate': 'Aucun template AI installe. Installez le template pour structurer la documentation AI du projet.',
  'audit.installTemplate': 'Installer le template AI',
  'audit.description': "L'audit AI analyse l'integralite du projet en 10 etapes et remplit la doc ai/.",
  'audit.warning': 'Duree estimee : ~20 minutes. Consommation elevee de tokens.',
  'audit.launch': "Lancer l'audit AI",
  'audit.step': 'Etape {0}/{1} — {2}',
  'audit.validationInProgress': 'Validation en cours — {0} messages echanges.',
  'audit.validationHint': "La validation se termine depuis la discussion, quand l'IA confirme que tout est resolu.",
  'audit.resumeValidation': 'Reprendre la validation',
  'audit.readyToValidate': "Documentation AI generee. Validez l'audit pour resoudre les zones d'ambiguite avec l'IA.",
  'audit.validate': "Valider l'audit",
  'audit.done': 'Audit valide. Documentation AI prete dans ai/.',
  'audit.markValid': "Marquer l'audit comme valide",
  'audit.validationComplete': "L'IA confirme que la validation est terminee.",
  'audit.validationTitle': 'Validation audit AI',
 
  // ── Discussions ──
  'disc.new': 'Nouvelle',
  'disc.newTitle': 'Nouvelle discussion',
  'disc.project': 'Projet (optionnel)',
  'disc.noProject': 'Aucun projet',
  'disc.agent': 'Agent',
  'disc.noAgent': 'Aucun agent installe',
  'disc.title': 'Titre (optionnel)',
  'disc.titlePlaceholder': 'Auto-genere depuis le prompt...',
  'disc.prompt': 'Prompt initial',
  'disc.promptPlaceholder': 'Decrivez ce que vous souhaitez...',
  'disc.start': 'Demarrer la discussion',
  'disc.auditWarn': "L'IA va analyser l'ensemble du contexte AI et du projet. Cette premiere analyse peut prendre quelques minutes et consommer des tokens.",
  'disc.auditHint': "Il peut y avoir plusieurs questions. La conversation peut etre reprise a tout moment depuis l'onglet Discussions.",
  'disc.empty': 'Aucune discussion.\nCliquez "Nouvelle" pour commencer.',
  'disc.selectOrCreate': 'Selectionnez ou creez une discussion',
  'disc.mentionHint': '{0} pour cibler un agent...',
  'disc.messagePlaceholder': 'Votre message...',
  'disc.general': 'General',
  'disc.system': 'Systeme',
  'disc.thinking': '{0} reflechit...',
  'disc.running': "Agent en cours d'execution...",
  'disc.synthesis': 'Synthese',
  'disc.cancel': 'Annuler',
  'disc.resend': 'Renvoyer',
  'disc.editResend': 'Editer et relancer',
  'disc.retryResponse': 'Relancer la reponse',
  'disc.overrideKey': 'Activer ou remplacer la clé API',
  'disc.orCheckAgent': 'ou verifiez que votre agent local est connecte',
  'disc.stopThinking': 'Arreter la reflexion',
 
  // ── Debate ──
  'debate.title': 'Debat multi-agents',
  'debate.header': 'Debat inter-agents',
  'debate.instructions': 'Selectionnez au moins 2 agents. Ils debattront sur 3 rounds puis l\'agent principal fera la synthese.',
  'debate.main': 'principal',
  'debate.launch': 'Lancer le debat ({0} agents)',
  'debate.rounds': 'Rounds',
 
  // ── Config ──
  'config.subtitle': 'Parametres generaux de Kronn',
  'config.uiLanguage': "Langue de l'interface",
  'config.uiLanguageHint': "Langue d'affichage de l'interface Kronn.",
  'config.outputLanguage': 'Langue de sortie',
  'config.outputLanguageHint': 'Langue dans laquelle les agents repondront par defaut.',
  'config.agents': 'Agents',
  'config.installed': 'installe',
  'config.installedPlural': 'installes',
  'config.refresh': 'Rafraichir',
  'config.fullAccess': 'Autorise l\'acces web, la modification de fichiers et l\'execution de commandes.',
  'config.autoApply': 'Applique automatiquement les modifications sans confirmation.',
  'config.uninstallConfirm': 'Desinstaller {0} ?',
  'config.uninstall': 'Desinstaller',
  'config.hostManaged': 'Installe sur l\'hote',
  'config.uninstallFailed': 'Echec desinstallation — desinstallez manuellement sur l\'hote',
  'config.getKey': 'Obtenir une cle API',
  'config.wslWarning': 'WSL detecte — preferez une installation directement sur WSL, Kronn detectera automatiquement l\'agent. Une installation dans Docker sera perdue lors d\'un rebuild.',
  'config.enabled': 'Actif',
  'config.disabled': 'En pause',
  'config.toggleEnable': 'Reactiver',
  'config.toggleDisable': 'Mettre en pause',
  'config.apiKeys': 'Cles API (optionnel)',
  'config.apiKeysHint1': 'Vos agents locaux utilisent leur propre authentification (abonnements).',
  'config.apiKeysHint2': 'Si vous souhaitez utiliser une cle API specifique a la place, vous pouvez la renseigner ici en override.',
  'config.localAuth': 'auth locale',
  'config.tokenUsage': 'Consommation tokens',
  'config.enableOverride': 'Activer l\'override API key',
  'config.disableOverride': 'Desactiver l\'override (utiliser auth locale)',
  'config.syncTokensConfirm': 'Mettre a jour la cle API dans la config de tous les agents installes ?',
  'config.syncTokensDone': 'Cle synchronisee : {0}',
  'config.syncTokensNone': 'Aucun agent a synchroniser',
  'config.addKey': 'Ajouter une cle',
  'config.keyName': 'Nom de la cle',
  'config.defaultKeyName': 'Cle API personnelle',
  'config.deleteKey': 'Supprimer cette cle',
  'config.deleteKeyConfirm': 'Supprimer la cle "{0}" ?',
  'config.activateKey': 'Activer cette cle',
  'config.overrideActive': 'override actif',
  'config.overrideDisabled': 'override desactive',
  'config.save': 'Sauvegarder',
  'config.saved': 'Sauvegarde !',
  'config.database': 'Base de donnees',
  'config.dbProjects': 'Projets',
  'config.dbDiscussions': 'Discussions',
  'config.dbMessages': 'Messages',
  'config.dbMcps': 'MCPs',
  'config.dbTasks': 'Taches',
  'config.export': 'Exporter',
  'config.import': 'Importer',
  'config.importInvalid': 'Fichier invalide',
  'config.importConfirm': 'Cela remplacera toutes les donnees actuelles. Continuer ?',
  'config.importError': "Erreur lors de l'import",
  'config.resetHint': "Remettre a zero la configuration et relancer l'assistant de setup.",
  'config.reset': 'Reset configuration',
  'config.configFile': 'Fichier de config',
 
  // ── MCPs ──
  'mcp.title': 'MCP Servers',
  'mcp.add': 'Ajouter',
  'mcp.detect': 'Detecter',
  'mcp.search': 'Rechercher un MCP ou un projet...',
  'mcp.searchRegistry': 'Rechercher un MCP...',
  'mcp.configure': 'Configurer {0}',
  'mcp.addTitle': 'Ajouter un MCP',
  'mcp.addAnother': 'Ajouter une autre config {0}',
  'mcp.label': 'Nom / Label',
  'mcp.envVars': "Variables d'environnement",
  'mcp.getToken': 'Obtenir un token',
  'mcp.value': 'valeur...',
  'mcp.globalAll': 'Global (tous les projets)',
  'mcp.addBtn': 'Ajouter',
  'mcp.back': 'Retour',
  'mcp.alreadyAdded': 'deja present',
  'mcp.keys': 'cle',
  'mcp.keysPlural': 'cles',
  'mcp.clickToRename': 'Cliquer pour renommer',
  'mcp.editKeys': 'Modifier les cles',
  'mcp.close': 'Fermer',
  'mcp.show': 'Afficher',
  'mcp.hide': 'Masquer',
  'mcp.saving': 'Sauvegarde...',
  'mcp.save': 'Sauvegarder',
  'mcp.cancel': 'Annuler',
  'mcp.disableGlobal': 'Desactiver global — garder uniquement les projets coches',
  'mcp.enableGlobal': 'Activer sur tous les projets',
  'mcp.editContext': 'Editer le contexte de {0} pour {1}',
  'mcp.customized': '(personnalise)',
  'mcp.default': '(par defaut)',
  'mcp.moreProjects': '+{0} projets',
  'mcp.lessProjects': 'Reduire',
  'mcp.deleteConfig': 'Supprimer cette config',
  'mcp.empty': 'Aucun MCP configure. Cliquez "Ajouter" pour en installer un ou "Detecter" pour scanner vos projets.',
  'mcp.contextTitle': 'Contexte MCP — {0}',
  'mcp.contextInfo': 'Projet: {0} · Fichier: ai/operations/mcp-servers/{1}.md',
  'mcp.contextPlaceholder': 'Instructions pour les agents utilisant ce MCP dans ce projet...',
  'mcp.config': 'config',
  'mcp.configPlural': 'configs',
  'mcp.server': 'serveur',
  'mcp.serverPlural': 'serveurs',
  'mcp.global': 'globale',
  'mcp.globalPlural': 'globales',
  'mcp.project': 'projet',
  'mcp.projectPlural': 'projets',
  // Categories
  'mcp.cat.gitCode': 'Git & Code',
  'mcp.cat.databases': 'Bases de donnees',
  'mcp.cat.cloud': 'Cloud & Infra',
  'mcp.cat.search': 'Recherche & Web',
  'mcp.cat.monitoring': 'Monitoring',
  'mcp.cat.communication': 'Communication',
  'mcp.cat.projectMgmt': 'Gestion de projet',
  'mcp.cat.design': 'Design',
  'mcp.cat.utilities': 'Utilitaires',
  'mcp.cat.other': 'Autres',
 
  // ── Workflows ──
  'wf.title': 'Workflows',
  'wf.subtitle': 'Automatisation: triggers, steps',
  'wf.new': 'Nouveau workflow',
  'wf.empty': 'Aucun workflow configure',
  'wf.emptyHint': 'Creez un workflow pour automatiser vos taches',
  'wf.active': 'Actif',
  'wf.inactive': 'Inactif',
  'wf.trigger': 'Lancer',
  'wf.delete': 'Suppr.',
  'wf.selectOne': 'Selectionnez un workflow',
  'wf.unknown': 'Inconnu',
  'wf.edit': 'Editer',
  'wf.refresh': 'Rafraichir',
  'wf.launch': 'Lancer',
  'wf.running': 'Execution en cours',
  'wf.runDone': 'Run termine — {0}',
  'wf.stepsExecuted': '{0} steps executes',
  'wf.deleteAll': 'Tout supprimer',
  'wf.noRuns': 'Aucun run',
  'wf.skipNext': 'Skip step suivant',
  'wf.manual': 'Manuel',
  'wf.inProgress': 'en cours...',
  'wf.pending': 'en attente',
  'wf.deleteRun': 'Supprimer ce run',
  'wf.noOutput': '(aucune sortie)',
  'wf.status': 'Statut',
  'wf.duration': 'Duree',
 
  // ── Workflow wizard ──
  'wiz.infos': 'Infos',
  'wiz.trigger': 'Trigger',
  'wiz.steps': 'Steps',
  'wiz.config': 'Config',
  'wiz.summary': 'Resume',
  'wiz.name': 'Nom du workflow',
  'wiz.namePlaceholder': 'ex: Auto-fix 5xx errors',
  'wiz.project': 'Projet',
  'wiz.projectOptional': 'Projet (optionnel)',
  'wiz.noProject': 'Aucun projet',
  'wiz.triggerType': 'Type de trigger',
  'wiz.frequency': 'Frequence',
  'wiz.every': 'Tous les',
  'wiz.minutes': 'minutes',
  'wiz.hours': 'heures',
  'wiz.days': 'jours',
  'wiz.weeks': 'semaines',
  'wiz.months': 'mois',
  'wiz.at': 'a',
  'wiz.labels': 'Labels (virgule)',
  'wiz.labelsPlaceholder': 'bug-5xx, auto-fix',
  'wiz.pollInterval': 'Intervalle de poll (cron)',
  'wiz.availableVars': 'Variables disponibles',
  'wiz.triggerVars': 'Variables de trigger (Tracker)',
  'wiz.issueTitle': "Titre de l'issue",
  'wiz.issueBody': "Contenu de l'issue",
  'wiz.issueNumber': 'Numero (#42)',
  'wiz.issueUrl': "Lien vers l'issue",
  'wiz.issueLabels': 'Labels (virgule)',
  'wiz.stepChaining': 'Chainage entre steps',
  'wiz.prevOutput': 'Sortie du step precedent',
  'wiz.namedOutput': "Sortie d'un step par nom",
  'wiz.example': 'Exemple : workflow multi-step',
  'wiz.stepName': 'Nom du step',
  'wiz.advanced': 'Avance',
  'wiz.mode': 'Mode',
  'wiz.debateAgents': 'Agents du debat',
  'wiz.maxRounds': 'Rounds max',
  'wiz.agentSettings': 'Agent settings (optionnel)',
  'wiz.model': 'Model',
  'wiz.modelPlaceholder': 'ex: o3',
  'wiz.stallTimeout': 'Stall timeout (sec)',
  'wiz.delayAfter': 'Delai apres (sec)',
  'wiz.retry': 'Retry',
  'wiz.conditions': 'Conditions (on_result)',
  'wiz.ifContains': 'si contient',
  'wiz.noResultsPlaceholder': 'NO_RESULTS (obligatoire)',
  'wiz.noResultsStop': 'Aucun resultat → Stop',
  'wiz.addStep': 'Ajouter un step',
  'wiz.security': 'Securite',
  'wiz.sandbox': 'Sandbox (Docker)',
  'wiz.requireApproval': 'Approbation requise',
  'wiz.maxFiles': 'Max fichiers modifies',
  'wiz.maxLines': 'Max lignes modifiees',
  'wiz.concurrency': 'Limite de concurrence (runs simultanes)',
  'wiz.hooks': 'Hooks de workspace (optionnel)',
  'wiz.hooksHint': 'Commandes shell executees a chaque phase du worktree git',
  'wiz.hookAfterCreate': 'Apres creation',
  'wiz.hookBeforeRun': 'Avant execution',
  'wiz.hookAfterRun': 'Apres execution',
  'wiz.hookBeforeRemove': 'Avant suppression',
  'wiz.previous': 'Precedent',
  'wiz.next': 'Suivant',
  'wiz.create': 'Creer',
  'wiz.save': 'Enregistrer',
 
  // ── Common ──
  'common.cancel': 'Annuler',
  'common.save': 'Sauvegarder',
  'common.delete': 'Supprimer',
  'common.close': 'Fermer',
  'common.loading': 'Chargement...',
};
 
const en: TranslationDict = {
  // ── Nav & Tabs ──
  'nav.projects': 'Projects',
  'nav.discussions': 'Discussions',
  'nav.mcps': 'MCPs',
  'nav.workflows': 'Workflows',
  'nav.config': 'Config',
  'nav.scan': 'Scan',
 
  // ── Projects page ──
  'projects.title': 'Projects',
  'projects.aiReady': 'AI-ready',
  'projects.hidden': 'hidden',
  'projects.hiddenPlural': 'hidden',
  'projects.hideHidden': 'Hide hidden projects',
  'projects.showHidden': 'Show hidden projects',
  'projects.search': 'Search a project...',
  'projects.showMore': 'Show more ({0} project{1} remaining)',
  'projects.collapse': 'Collapse',
  'projects.empty': 'No projects. Click "Scan" to detect your repositories.',
  'projects.emptySearch': 'No matching project.',
  'projects.manageMcps': 'Manage in MCPs',
  'projects.noMcp': 'No MCP — configure',
  'projects.delete': 'Delete',
  'projects.deleteConfirm': 'Remove this project from Kronn?',
  'projects.status.valid': 'valid',
  'projects.status.validating': 'validating...',
  'projects.status.auditOk': 'audit OK',
  'projects.status.template': 'template',
  'projects.status.none': 'none',
 
  // ── AI Audit ──
  'audit.noTemplate': 'No AI template installed. Install the template to structure the AI documentation for this project.',
  'audit.installTemplate': 'Install AI template',
  'audit.description': 'The AI audit analyzes the entire project in 10 steps and fills the ai/ documentation.',
  'audit.warning': 'Estimated duration: ~20 minutes. High token usage.',
  'audit.launch': 'Launch AI audit',
  'audit.step': 'Step {0}/{1} — {2}',
  'audit.validationInProgress': 'Validation in progress — {0} messages exchanged.',
  'audit.validationHint': 'Validation completes from the discussion when the AI confirms all items are resolved.',
  'audit.resumeValidation': 'Resume validation',
  'audit.readyToValidate': 'AI documentation generated. Validate the audit to resolve ambiguities with the AI.',
  'audit.validate': 'Validate audit',
  'audit.done': 'Audit validated. AI documentation ready in ai/.',
  'audit.markValid': 'Mark audit as validated',
  'audit.validationComplete': 'The AI confirms that validation is complete.',
  'audit.validationTitle': 'AI Audit Validation',
 
  // ── Discussions ──
  'disc.new': 'New',
  'disc.newTitle': 'New discussion',
  'disc.project': 'Project (optional)',
  'disc.noProject': 'No project',
  'disc.agent': 'Agent',
  'disc.noAgent': 'No agent installed',
  'disc.title': 'Title (optional)',
  'disc.titlePlaceholder': 'Auto-generated from prompt...',
  'disc.prompt': 'Initial prompt',
  'disc.promptPlaceholder': 'Describe what you want...',
  'disc.start': 'Start discussion',
  'disc.auditWarn': 'The AI will analyze all AI context and the project. This first analysis may take a few minutes and consume tokens.',
  'disc.auditHint': 'There may be several questions. The conversation can be resumed at any time from the Discussions tab.',
  'disc.empty': 'No discussions.\nClick "New" to start.',
  'disc.selectOrCreate': 'Select or create a discussion',
  'disc.mentionHint': '{0} to target an agent...',
  'disc.messagePlaceholder': 'Your message...',
  'disc.general': 'General',
  'disc.system': 'System',
  'disc.thinking': '{0} is thinking...',
  'disc.running': 'Agent running...',
  'disc.synthesis': 'Synthesis',
  'disc.cancel': 'Cancel',
  'disc.resend': 'Resend',
  'disc.editResend': 'Edit and resend',
  'disc.retryResponse': 'Retry response',
  'disc.overrideKey': 'Activate or Override API Key',
  'disc.orCheckAgent': 'or check that your local agent is connected',
  'disc.stopThinking': 'Stop thinking',
 
  // ── Debate ──
  'debate.title': 'Multi-agent debate',
  'debate.header': 'Inter-agent debate',
  'debate.instructions': 'Select at least 2 agents. They will debate for 3 rounds, then the main agent will synthesize.',
  'debate.main': 'main',
  'debate.launch': 'Launch debate ({0} agents)',
  'debate.rounds': 'Rounds',
 
  // ── Config ──
  'config.subtitle': 'General Kronn settings',
  'config.uiLanguage': 'Interface language',
  'config.uiLanguageHint': 'Display language for the Kronn interface.',
  'config.outputLanguage': 'Output language',
  'config.outputLanguageHint': 'Language in which agents will respond by default.',
  'config.agents': 'Agents',
  'config.installed': 'installed',
  'config.installedPlural': 'installed',
  'config.refresh': 'Refresh',
  'config.fullAccess': 'Allows web access, file modifications and command execution.',
  'config.autoApply': 'Automatically applies changes without confirmation.',
  'config.uninstallConfirm': 'Uninstall {0}?',
  'config.uninstall': 'Uninstall',
  'config.hostManaged': 'Installed on host',
  'config.uninstallFailed': 'Uninstall failed — remove manually on host',
  'config.getKey': 'Get an API key',
  'config.wslWarning': 'WSL detected — prefer installing agents directly on WSL, Kronn will detect them automatically. Docker-installed agents are lost on rebuild.',
  'config.enabled': 'Active',
  'config.disabled': 'Paused',
  'config.toggleEnable': 'Re-enable',
  'config.toggleDisable': 'Pause',
  'config.apiKeys': 'API Keys (optional)',
  'config.apiKeysHint1': 'Your local agents use their own authentication (subscriptions).',
  'config.apiKeysHint2': 'If you want to use a specific API key instead, you can enter it here as an override.',
  'config.localAuth': 'local auth',
  'config.tokenUsage': 'Token usage',
  'config.enableOverride': 'Enable API key override',
  'config.disableOverride': 'Disable override (use local auth)',
  'config.syncTokensConfirm': 'Update the API key in all installed agent configs?',
  'config.syncTokensDone': 'Key synced: {0}',
  'config.syncTokensNone': 'No agents to sync',
  'config.addKey': 'Add a key',
  'config.keyName': 'Key name',
  'config.defaultKeyName': 'Personal API Key',
  'config.deleteKey': 'Delete this key',
  'config.deleteKeyConfirm': 'Delete key "{0}"?',
  'config.activateKey': 'Activate this key',
  'config.overrideActive': 'override active',
  'config.overrideDisabled': 'override disabled',
  'config.save': 'Save',
  'config.saved': 'Saved!',
  'config.database': 'Database',
  'config.dbProjects': 'Projects',
  'config.dbDiscussions': 'Discussions',
  'config.dbMessages': 'Messages',
  'config.dbMcps': 'MCPs',
  'config.dbTasks': 'Tasks',
  'config.export': 'Export',
  'config.import': 'Import',
  'config.importInvalid': 'Invalid file',
  'config.importConfirm': 'This will replace all current data. Continue?',
  'config.importError': 'Error during import',
  'config.resetHint': 'Reset configuration and relaunch the setup wizard.',
  'config.reset': 'Reset configuration',
  'config.configFile': 'Config file',
 
  // ── MCPs ──
  'mcp.title': 'MCP Servers',
  'mcp.add': 'Add',
  'mcp.detect': 'Detect',
  'mcp.search': 'Search MCP or project...',
  'mcp.searchRegistry': 'Search MCP...',
  'mcp.configure': 'Configure {0}',
  'mcp.addTitle': 'Add an MCP',
  'mcp.addAnother': 'Add another {0} config',
  'mcp.label': 'Name / Label',
  'mcp.envVars': 'Environment variables',
  'mcp.getToken': 'Get a token',
  'mcp.value': 'value...',
  'mcp.globalAll': 'Global (all projects)',
  'mcp.addBtn': 'Add',
  'mcp.back': 'Back',
  'mcp.alreadyAdded': 'already added',
  'mcp.keys': 'key',
  'mcp.keysPlural': 'keys',
  'mcp.clickToRename': 'Click to rename',
  'mcp.editKeys': 'Edit keys',
  'mcp.close': 'Close',
  'mcp.show': 'Show',
  'mcp.hide': 'Hide',
  'mcp.saving': 'Saving...',
  'mcp.save': 'Save',
  'mcp.cancel': 'Cancel',
  'mcp.disableGlobal': 'Disable global — keep only checked projects',
  'mcp.enableGlobal': 'Enable on all projects',
  'mcp.editContext': 'Edit context of {0} for {1}',
  'mcp.customized': '(customized)',
  'mcp.default': '(default)',
  'mcp.moreProjects': '+{0} projects',
  'mcp.lessProjects': 'Collapse',
  'mcp.deleteConfig': 'Delete this config',
  'mcp.empty': 'No MCP configured. Click "Add" to install one or "Detect" to scan your projects.',
  'mcp.contextTitle': 'MCP Context — {0}',
  'mcp.contextInfo': 'Project: {0} · File: ai/operations/mcp-servers/{1}.md',
  'mcp.contextPlaceholder': 'Instructions for agents using this MCP in this project...',
  'mcp.config': 'config',
  'mcp.configPlural': 'configs',
  'mcp.server': 'server',
  'mcp.serverPlural': 'servers',
  'mcp.global': 'global',
  'mcp.globalPlural': 'global',
  'mcp.project': 'project',
  'mcp.projectPlural': 'projects',
  'mcp.cat.gitCode': 'Git & Code',
  'mcp.cat.databases': 'Databases',
  'mcp.cat.cloud': 'Cloud & Infra',
  'mcp.cat.search': 'Search & Web',
  'mcp.cat.monitoring': 'Monitoring',
  'mcp.cat.communication': 'Communication',
  'mcp.cat.projectMgmt': 'Project Management',
  'mcp.cat.design': 'Design',
  'mcp.cat.utilities': 'Utilities',
  'mcp.cat.other': 'Other',
 
  // ── Workflows ──
  'wf.title': 'Workflows',
  'wf.subtitle': 'Automation: triggers, steps',
  'wf.new': 'New workflow',
  'wf.empty': 'No workflow configured',
  'wf.emptyHint': 'Create a workflow to automate your tasks',
  'wf.active': 'Active',
  'wf.inactive': 'Inactive',
  'wf.trigger': 'Trigger',
  'wf.delete': 'Delete',
  'wf.selectOne': 'Select a workflow',
  'wf.unknown': 'Unknown',
  'wf.edit': 'Edit',
  'wf.refresh': 'Refresh',
  'wf.launch': 'Trigger',
  'wf.running': 'Running',
  'wf.runDone': 'Run finished — {0}',
  'wf.stepsExecuted': '{0} steps executed',
  'wf.deleteAll': 'Delete all',
  'wf.noRuns': 'No runs',
  'wf.skipNext': 'Skip next step',
  'wf.manual': 'Manual',
  'wf.inProgress': 'in progress...',
  'wf.pending': 'pending',
  'wf.deleteRun': 'Delete this run',
  'wf.noOutput': '(no output)',
  'wf.status': 'Status',
  'wf.duration': 'Duration',
 
  // ── Workflow wizard ──
  'wiz.infos': 'Info',
  'wiz.trigger': 'Trigger',
  'wiz.steps': 'Steps',
  'wiz.config': 'Config',
  'wiz.summary': 'Summary',
  'wiz.name': 'Workflow name',
  'wiz.namePlaceholder': 'e.g.: Auto-fix 5xx errors',
  'wiz.project': 'Project',
  'wiz.projectOptional': 'Project (optional)',
  'wiz.noProject': 'No project',
  'wiz.triggerType': 'Trigger type',
  'wiz.frequency': 'Frequency',
  'wiz.every': 'Every',
  'wiz.minutes': 'minutes',
  'wiz.hours': 'hours',
  'wiz.days': 'days',
  'wiz.weeks': 'weeks',
  'wiz.months': 'months',
  'wiz.at': 'at',
  'wiz.labels': 'Labels (comma-separated)',
  'wiz.labelsPlaceholder': 'bug-5xx, auto-fix',
  'wiz.pollInterval': 'Poll interval (cron)',
  'wiz.availableVars': 'Available variables',
  'wiz.triggerVars': 'Trigger variables (Tracker)',
  'wiz.issueTitle': 'Issue title',
  'wiz.issueBody': 'Issue body',
  'wiz.issueNumber': 'Number (#42)',
  'wiz.issueUrl': 'Link to issue',
  'wiz.issueLabels': 'Labels (comma-separated)',
  'wiz.stepChaining': 'Step chaining',
  'wiz.prevOutput': 'Previous step output',
  'wiz.namedOutput': 'Named step output',
  'wiz.example': 'Example: multi-step workflow',
  'wiz.stepName': 'Step name',
  'wiz.advanced': 'Advanced',
  'wiz.mode': 'Mode',
  'wiz.debateAgents': 'Debate agents',
  'wiz.maxRounds': 'Max rounds',
  'wiz.agentSettings': 'Agent settings (optional)',
  'wiz.model': 'Model',
  'wiz.modelPlaceholder': 'e.g.: o3',
  'wiz.stallTimeout': 'Stall timeout (sec)',
  'wiz.delayAfter': 'Delay after (sec)',
  'wiz.retry': 'Retry',
  'wiz.conditions': 'Conditions (on_result)',
  'wiz.ifContains': 'if contains',
  'wiz.noResultsPlaceholder': 'NO_RESULTS (required)',
  'wiz.noResultsStop': 'No results → Stop',
  'wiz.addStep': 'Add a step',
  'wiz.security': 'Security',
  'wiz.sandbox': 'Sandbox (Docker)',
  'wiz.requireApproval': 'Approval required',
  'wiz.maxFiles': 'Max files modified',
  'wiz.maxLines': 'Max lines modified',
  'wiz.concurrency': 'Concurrency limit (simultaneous runs)',
  'wiz.hooks': 'Workspace hooks (optional)',
  'wiz.hooksHint': 'Shell commands executed at each phase of the git worktree',
  'wiz.hookAfterCreate': 'After creation',
  'wiz.hookBeforeRun': 'Before execution',
  'wiz.hookAfterRun': 'After execution',
  'wiz.hookBeforeRemove': 'Before removal',
  'wiz.previous': 'Previous',
  'wiz.next': 'Next',
  'wiz.create': 'Create',
  'wiz.save': 'Save',
 
  // ── Common ──
  'common.cancel': 'Cancel',
  'common.save': 'Save',
  'common.delete': 'Delete',
  'common.close': 'Close',
  'common.loading': 'Loading...',
};
 
const es: TranslationDict = {
  // ── Nav & Tabs ──
  'nav.projects': 'Proyectos',
  'nav.discussions': 'Discusiones',
  'nav.mcps': 'MCPs',
  'nav.workflows': 'Workflows',
  'nav.config': 'Config',
  'nav.scan': 'Escanear',
 
  // ── Projects page ──
  'projects.title': 'Proyectos',
  'projects.aiReady': 'AI-ready',
  'projects.hidden': 'oculto',
  'projects.hiddenPlural': 'ocultos',
  'projects.hideHidden': 'Ocultar proyectos ocultos',
  'projects.showHidden': 'Mostrar proyectos ocultos',
  'projects.search': 'Buscar un proyecto...',
  'projects.showMore': 'Ver mas ({0} proyecto{1} restante{2})',
  'projects.collapse': 'Reducir',
  'projects.empty': 'Sin proyectos. Haga clic en "Escanear" para detectar sus repositorios.',
  'projects.emptySearch': 'Ningun proyecto encontrado.',
  'projects.manageMcps': 'Gestionar en MCPs',
  'projects.noMcp': 'Sin MCP — configurar',
  'projects.delete': 'Eliminar',
  'projects.deleteConfirm': 'Eliminar este proyecto de Kronn?',
  'projects.status.valid': 'validado',
  'projects.status.validating': 'validando...',
  'projects.status.auditOk': 'audit OK',
  'projects.status.template': 'template',
  'projects.status.none': 'ninguno',
 
  // ── AI Audit ──
  'audit.noTemplate': 'No hay template AI instalado. Instale el template para estructurar la documentacion AI del proyecto.',
  'audit.installTemplate': 'Instalar template AI',
  'audit.description': 'La auditoria AI analiza todo el proyecto en 10 pasos y completa la doc ai/.',
  'audit.warning': 'Duracion estimada: ~20 minutos. Alto consumo de tokens.',
  'audit.launch': 'Lanzar auditoria AI',
  'audit.step': 'Paso {0}/{1} — {2}',
  'audit.validationInProgress': 'Validacion en curso — {0} mensajes intercambiados.',
  'audit.validationHint': 'La validacion se completa desde la discusion cuando la IA confirma que todo esta resuelto.',
  'audit.resumeValidation': 'Reanudar validacion',
  'audit.readyToValidate': 'Documentacion AI generada. Valide la auditoria para resolver ambiguedades con la IA.',
  'audit.validate': 'Validar auditoria',
  'audit.done': 'Auditoria validada. Documentacion AI lista en ai/.',
  'audit.markValid': 'Marcar auditoria como validada',
  'audit.validationComplete': 'La IA confirma que la validacion esta completa.',
  'audit.validationTitle': 'Validacion auditoria AI',
 
  // ── Discussions ──
  'disc.new': 'Nueva',
  'disc.newTitle': 'Nueva discusion',
  'disc.project': 'Proyecto (opcional)',
  'disc.noProject': 'Sin proyecto',
  'disc.agent': 'Agente',
  'disc.noAgent': 'Ningun agente instalado',
  'disc.title': 'Titulo (opcional)',
  'disc.titlePlaceholder': 'Auto-generado desde el prompt...',
  'disc.prompt': 'Prompt inicial',
  'disc.promptPlaceholder': 'Describa lo que desea...',
  'disc.start': 'Iniciar discusion',
  'disc.auditWarn': 'La IA analizara todo el contexto AI y el proyecto. Este primer analisis puede tardar unos minutos y consumir tokens.',
  'disc.auditHint': 'Puede haber varias preguntas. La conversacion se puede reanudar en cualquier momento desde la pestana Discusiones.',
  'disc.empty': 'Sin discusiones.\nHaga clic en "Nueva" para comenzar.',
  'disc.selectOrCreate': 'Seleccione o cree una discusion',
  'disc.mentionHint': '{0} para dirigirse a un agente...',
  'disc.messagePlaceholder': 'Su mensaje...',
  'disc.general': 'General',
  'disc.system': 'Sistema',
  'disc.thinking': '{0} esta pensando...',
  'disc.running': 'Agente en ejecucion...',
  'disc.synthesis': 'Sintesis',
  'disc.cancel': 'Cancelar',
  'disc.resend': 'Reenviar',
  'disc.editResend': 'Editar y reenviar',
  'disc.retryResponse': 'Reintentar respuesta',
  'disc.overrideKey': 'Activar o reemplazar clave API',
  'disc.orCheckAgent': 'o verifique que su agente local esta conectado',
  'disc.stopThinking': 'Detener pensamiento',
 
  // ── Debate ──
  'debate.title': 'Debate multi-agente',
  'debate.header': 'Debate inter-agentes',
  'debate.instructions': 'Seleccione al menos 2 agentes. Debatiran durante 3 rondas y luego el agente principal hara la sintesis.',
  'debate.main': 'principal',
  'debate.launch': 'Lanzar debate ({0} agentes)',
  'debate.rounds': 'Rondas',
 
  // ── Config ──
  'config.subtitle': 'Configuracion general de Kronn',
  'config.uiLanguage': 'Idioma de la interfaz',
  'config.uiLanguageHint': 'Idioma de visualizacion de la interfaz Kronn.',
  'config.outputLanguage': 'Idioma de salida',
  'config.outputLanguageHint': 'Idioma en el que los agentes responderan por defecto.',
  'config.agents': 'Agentes',
  'config.installed': 'instalado',
  'config.installedPlural': 'instalados',
  'config.refresh': 'Actualizar',
  'config.fullAccess': 'Permite acceso web, modificacion de archivos y ejecucion de comandos.',
  'config.autoApply': 'Aplica automaticamente los cambios sin confirmacion.',
  'config.uninstallConfirm': 'Desinstalar {0}?',
  'config.uninstall': 'Desinstalar',
  'config.hostManaged': 'Instalado en el host',
  'config.uninstallFailed': 'Error al desinstalar — eliminar manualmente en el host',
  'config.getKey': 'Obtener una clave API',
  'config.wslWarning': 'WSL detectado — instale los agentes directamente en WSL, Kronn los detectara automaticamente. Los agentes instalados en Docker se pierden al reconstruir.',
  'config.enabled': 'Activo',
  'config.disabled': 'En pausa',
  'config.toggleEnable': 'Reactivar',
  'config.toggleDisable': 'Pausar',
  'config.apiKeys': 'Claves API (opcional)',
  'config.apiKeysHint1': 'Sus agentes locales usan su propia autenticacion (suscripciones).',
  'config.apiKeysHint2': 'Si desea usar una clave API especifica, puede ingresarla aqui como override.',
  'config.localAuth': 'auth local',
  'config.tokenUsage': 'Consumo de tokens',
  'config.enableOverride': 'Activar override de clave API',
  'config.disableOverride': 'Desactivar override (usar auth local)',
  'config.syncTokensConfirm': 'Actualizar la clave API en la config de todos los agentes instalados?',
  'config.syncTokensDone': 'Clave sincronizada: {0}',
  'config.syncTokensNone': 'Ningun agente para sincronizar',
  'config.addKey': 'Agregar una clave',
  'config.keyName': 'Nombre de la clave',
  'config.defaultKeyName': 'Clave API personal',
  'config.deleteKey': 'Eliminar esta clave',
  'config.deleteKeyConfirm': 'Eliminar clave "{0}"?',
  'config.activateKey': 'Activar esta clave',
  'config.overrideActive': 'override activo',
  'config.overrideDisabled': 'override desactivado',
  'config.save': 'Guardar',
  'config.saved': 'Guardado!',
  'config.database': 'Base de datos',
  'config.dbProjects': 'Proyectos',
  'config.dbDiscussions': 'Discusiones',
  'config.dbMessages': 'Mensajes',
  'config.dbMcps': 'MCPs',
  'config.dbTasks': 'Tareas',
  'config.export': 'Exportar',
  'config.import': 'Importar',
  'config.importInvalid': 'Archivo invalido',
  'config.importConfirm': 'Esto reemplazara todos los datos actuales. Continuar?',
  'config.importError': 'Error durante la importacion',
  'config.resetHint': 'Restablecer la configuracion y relanzar el asistente de setup.',
  'config.reset': 'Restablecer configuracion',
  'config.configFile': 'Archivo de config',
 
  // ── MCPs ──
  'mcp.title': 'MCP Servers',
  'mcp.add': 'Agregar',
  'mcp.detect': 'Detectar',
  'mcp.search': 'Buscar MCP o proyecto...',
  'mcp.searchRegistry': 'Buscar MCP...',
  'mcp.configure': 'Configurar {0}',
  'mcp.addTitle': 'Agregar un MCP',
  'mcp.addAnother': 'Agregar otra config {0}',
  'mcp.label': 'Nombre / Label',
  'mcp.envVars': 'Variables de entorno',
  'mcp.getToken': 'Obtener un token',
  'mcp.value': 'valor...',
  'mcp.globalAll': 'Global (todos los proyectos)',
  'mcp.addBtn': 'Agregar',
  'mcp.back': 'Volver',
  'mcp.alreadyAdded': 'ya presente',
  'mcp.keys': 'clave',
  'mcp.keysPlural': 'claves',
  'mcp.clickToRename': 'Clic para renombrar',
  'mcp.editKeys': 'Editar claves',
  'mcp.close': 'Cerrar',
  'mcp.show': 'Mostrar',
  'mcp.hide': 'Ocultar',
  'mcp.saving': 'Guardando...',
  'mcp.save': 'Guardar',
  'mcp.cancel': 'Cancelar',
  'mcp.disableGlobal': 'Desactivar global — mantener solo proyectos marcados',
  'mcp.enableGlobal': 'Activar en todos los proyectos',
  'mcp.editContext': 'Editar contexto de {0} para {1}',
  'mcp.customized': '(personalizado)',
  'mcp.default': '(por defecto)',
  'mcp.moreProjects': '+{0} proyectos',
  'mcp.lessProjects': 'Reducir',
  'mcp.deleteConfig': 'Eliminar esta config',
  'mcp.empty': 'Ningun MCP configurado. Haga clic en "Agregar" para instalar uno o "Detectar" para escanear sus proyectos.',
  'mcp.contextTitle': 'Contexto MCP — {0}',
  'mcp.contextInfo': 'Proyecto: {0} · Archivo: ai/operations/mcp-servers/{1}.md',
  'mcp.contextPlaceholder': 'Instrucciones para agentes que usan este MCP en este proyecto...',
  'mcp.config': 'config',
  'mcp.configPlural': 'configs',
  'mcp.server': 'servidor',
  'mcp.serverPlural': 'servidores',
  'mcp.global': 'global',
  'mcp.globalPlural': 'globales',
  'mcp.project': 'proyecto',
  'mcp.projectPlural': 'proyectos',
  'mcp.cat.gitCode': 'Git & Code',
  'mcp.cat.databases': 'Bases de datos',
  'mcp.cat.cloud': 'Cloud & Infra',
  'mcp.cat.search': 'Busqueda & Web',
  'mcp.cat.monitoring': 'Monitoreo',
  'mcp.cat.communication': 'Comunicacion',
  'mcp.cat.projectMgmt': 'Gestion de proyectos',
  'mcp.cat.design': 'Diseno',
  'mcp.cat.utilities': 'Utilidades',
  'mcp.cat.other': 'Otros',
 
  // ── Workflows ──
  'wf.title': 'Workflows',
  'wf.subtitle': 'Automatizacion: triggers, steps',
  'wf.new': 'Nuevo workflow',
  'wf.empty': 'Ningun workflow configurado',
  'wf.emptyHint': 'Cree un workflow para automatizar sus tareas',
  'wf.active': 'Activo',
  'wf.inactive': 'Inactivo',
  'wf.trigger': 'Ejecutar',
  'wf.delete': 'Eliminar',
  'wf.selectOne': 'Seleccione un workflow',
  'wf.unknown': 'Desconocido',
  'wf.edit': 'Editar',
  'wf.refresh': 'Actualizar',
  'wf.launch': 'Ejecutar',
  'wf.running': 'En ejecucion',
  'wf.runDone': 'Run terminado — {0}',
  'wf.stepsExecuted': '{0} steps ejecutados',
  'wf.deleteAll': 'Eliminar todo',
  'wf.noRuns': 'Sin ejecuciones',
  'wf.skipNext': 'Saltar siguiente step',
  'wf.manual': 'Manual',
  'wf.inProgress': 'en curso...',
  'wf.pending': 'pendiente',
  'wf.deleteRun': 'Eliminar este run',
  'wf.noOutput': '(sin salida)',
  'wf.status': 'Estado',
  'wf.duration': 'Duracion',
 
  // ── Workflow wizard ──
  'wiz.infos': 'Info',
  'wiz.trigger': 'Trigger',
  'wiz.steps': 'Steps',
  'wiz.config': 'Config',
  'wiz.summary': 'Resumen',
  'wiz.name': 'Nombre del workflow',
  'wiz.namePlaceholder': 'ej: Auto-fix 5xx errors',
  'wiz.project': 'Proyecto',
  'wiz.projectOptional': 'Proyecto (opcional)',
  'wiz.noProject': 'Sin proyecto',
  'wiz.triggerType': 'Tipo de trigger',
  'wiz.frequency': 'Frecuencia',
  'wiz.every': 'Cada',
  'wiz.minutes': 'minutos',
  'wiz.hours': 'horas',
  'wiz.days': 'dias',
  'wiz.weeks': 'semanas',
  'wiz.months': 'meses',
  'wiz.at': 'a las',
  'wiz.labels': 'Labels (coma)',
  'wiz.labelsPlaceholder': 'bug-5xx, auto-fix',
  'wiz.pollInterval': 'Intervalo de poll (cron)',
  'wiz.availableVars': 'Variables disponibles',
  'wiz.triggerVars': 'Variables de trigger (Tracker)',
  'wiz.issueTitle': 'Titulo del issue',
  'wiz.issueBody': 'Contenido del issue',
  'wiz.issueNumber': 'Numero (#42)',
  'wiz.issueUrl': 'Enlace al issue',
  'wiz.issueLabels': 'Labels (coma)',
  'wiz.stepChaining': 'Encadenamiento entre steps',
  'wiz.prevOutput': 'Salida del step anterior',
  'wiz.namedOutput': 'Salida de un step por nombre',
  'wiz.example': 'Ejemplo: workflow multi-step',
  'wiz.stepName': 'Nombre del step',
  'wiz.advanced': 'Avanzado',
  'wiz.mode': 'Modo',
  'wiz.debateAgents': 'Agentes del debate',
  'wiz.maxRounds': 'Rondas max',
  'wiz.agentSettings': 'Agent settings (opcional)',
  'wiz.model': 'Modelo',
  'wiz.modelPlaceholder': 'ej: o3',
  'wiz.stallTimeout': 'Stall timeout (seg)',
  'wiz.delayAfter': 'Retraso despues (seg)',
  'wiz.retry': 'Reintentar',
  'wiz.conditions': 'Condiciones (on_result)',
  'wiz.ifContains': 'si contiene',
  'wiz.noResultsPlaceholder': 'NO_RESULTS (obligatorio)',
  'wiz.noResultsStop': 'Sin resultados → Stop',
  'wiz.addStep': 'Agregar un step',
  'wiz.security': 'Seguridad',
  'wiz.sandbox': 'Sandbox (Docker)',
  'wiz.requireApproval': 'Aprobacion requerida',
  'wiz.maxFiles': 'Max archivos modificados',
  'wiz.maxLines': 'Max lineas modificadas',
  'wiz.concurrency': 'Limite de concurrencia (ejecuciones simultaneas)',
  'wiz.hooks': 'Hooks de workspace (opcional)',
  'wiz.hooksHint': 'Comandos shell ejecutados en cada fase del worktree git',
  'wiz.hookAfterCreate': 'Despues de creacion',
  'wiz.hookBeforeRun': 'Antes de ejecucion',
  'wiz.hookAfterRun': 'Despues de ejecucion',
  'wiz.hookBeforeRemove': 'Antes de eliminacion',
  'wiz.previous': 'Anterior',
  'wiz.next': 'Siguiente',
  'wiz.create': 'Crear',
  'wiz.save': 'Guardar',
 
  // ── Common ──
  'common.cancel': 'Cancelar',
  'common.save': 'Guardar',
  'common.delete': 'Eliminar',
  'common.close': 'Cerrar',
  'common.loading': 'Cargando...',
};
 
const dictionaries: Record<UILocale, TranslationDict> = { fr, en, es };
 
/** Get a translated string with optional {0}, {1}... interpolation */
export function t(locale: UILocale, key: string, ...args: (string | number)[]): string {
  const dict = dictionaries[locale] ?? dictionaries.fr;
  let str = dict[key] ?? dictionaries.fr[key] ?? key;
  for (let i = 0; i < args.length; i++) {
    str = str.replace(`{${i}}`, String(args[i]));
  }
  return str;
}