hermes-agent/web/src/i18n/fr.ts
Teknium c39168453d
feat(i18n): localize all gateway commands + web dashboard, add 8 new locales (16 total) (#22914)
* feat(i18n): localize /model command output

Reported by @tianma8888: when Chinese users run /model, the labels
("Provider:", "Context:", "_session only_", etc.) are still English.
This routes the static prose through the existing i18n catalog so it
follows display.language / HERMES_LANGUAGE.

Changes:
- locales/{en,zh,ja,de,es,fr,tr,uk}.yaml: add 17 keys under
  gateway.model.* covering switched/provider/context/max_output/cost/
  capabilities/prompt_caching/warning/saved_global/session_only_hint/
  current_label/current_tag/more_models_suffix/usage_*.
- gateway/run.py _handle_model_command: replace hardcoded f-strings in
  the picker callback, the text-list fallback, and the direct-switch
  confirmation block with t("gateway.model.<key>", ...).

What stays English:
- model IDs, provider slugs, capability strings, cost figures, and the
  "[Note: model was just switched...]" prepended to the model's next
  prompt (LLM-facing, not user-facing).
- The two slightly-different session-only hints unify on a single key
  with the em-dash phrasing.

Validation: tests/agent/test_i18n.py 27/27 passing (parity contract
holds), tests/gateway/ -k 'model or i18n' 74/74 passing.

* feat(i18n): localize all gateway slash command outputs

Expands the i18n catalog from 7 strings to 234 keys across 35 gateway
slash command handlers, so non-English users see localized output for
\`/profile\`, \`/status\`, \`/help\`, \`/personality\`, \`/voice\`, \`/reset\`,
\`/agents\`, \`/restart\`, \`/commands\`, \`/goal\`, \`/retry\`, \`/undo\`,
\`/sethome\`, \`/title\`, \`/yolo\`, \`/background\`, \`/approve\`, \`/deny\`,
\`/insights\`, \`/debug\`, \`/rollback\`, \`/reasoning\`, \`/fast\`,
\`/verbose\`, \`/footer\`, \`/compress\`, \`/topic\`, \`/kanban\`,
\`/resume\`, \`/branch\`, \`/usage\`, \`/reload-mcp\`, \`/reload-skills\`,
\`/update\`, \`/stop\` (plus the \`/model\` block already added in the
previous commit).

Reported by @tianma8888 — Chinese users want command output prose in
their language, not just the labels we already had.

Translations are hand-written for all 8 supported locales (en, zh, ja,
de, es, fr, tr, uk), matching each catalog's existing style: full-width
punctuation in zh, em-dashes in zh/ja/uk, French spaced colons,
German noun capitalization, etc.

What stays English (unchanged):
- Identifiers/values: model IDs, file paths, profile names, session IDs,
  command flag names like --global, URLs, config keys.
- Backtick code spans: \`/foo\`, \`config.yaml\`.
- Log messages (logger.info/warning/error).
- LLM-facing system notes prepended to next prompt (e.g. [Note: model
  was just switched...]).
- Strings produced by external modules (gateway_help_lines,
  format_gateway, manual_compression_feedback) — those have their
  own surfaces.

New shared keys for cross-handler boilerplate:
- gateway.shared.session_db_unavailable (5 call sites: branch, title,
  resume, topic, _disable_telegram_topic_mode_for_chat)
- gateway.shared.session_not_found (1 site)
- gateway.shared.warn_passthrough (2 sites in /title's f"⚠️ {e}" pattern)

YAML gotcha fixed: \`yolo.on\` and \`yolo.off\` were originally written
unquoted, which YAML 1.1 parses as boolean True/False keys. Renamed to
\`yolo.enabled\` / \`yolo.disabled\` for both safety and clarity.

Test fix: tests/agent/test_i18n.py::test_t_missing_key_in_non_english_falls_back_to_english
now resets the catalog cache on teardown, so the fake "foo: English Foo"
locale doesn't poison the module-level cache for subsequent tests in
the same xdist worker. (Without this, every gateway slash command test
that shares a worker with the i18n suite would see the fake catalog.)

Validation:
- tests/agent/test_i18n.py: 27/27 (parity contract — every key in every
  locale, matching placeholder tokens).
- tests/gateway/: 5077 passed, 0 failed (full gateway suite).
- 180 t() call sites added across 35 handlers; 1872 catalog entries
  total (234 keys × 8 locales).

* feat(i18n): add 8 new locales — af, ko, it, ga, zh-hant, pt, ru, hu

Expands the static-message catalog from 8 → 16 languages, each with full
270-key parity against the English source-of-truth.  Every locale now
covers the same surface PR #22914 added: approval prompts plus all 35
gateway slash command outputs.

New locales:
- af  Afrikaans      (community ask in #21961 by @GodsBoy; PRs #21962, #21970)
- ko  Korean         (PRs #20297 by @tmdgusya, #22285 by @project820)
- it  Italian        (PR #20371 by @leprincep35700)
- ga  Irish/Gaeilge  (PR #20962 by @ryanmcc09-dot)
- zh-hant Traditional Chinese (PRs #20523 by @jackey8616, #13140 by @anomixer)
- pt  Portuguese     (PRs #20443 by @pedroborges, #15737 by @carloshenriquecarniatto, #22063 by @Magaav)
- ru  Russian        (PR #22770 by @DrMaks22)
- hu  Hungarian      (PR #22336 by @lunasec007)

Each locale uses native-quality translations matching the existing tone
and conventions of the older 8 locales:
- zh-hant uses 繁體 characters with TW/HK technical vocabulary (軟體
  not 软件, 連線 not 连接, 設定 not 设置, 訊息 not 消息, 工作階段 not 会话, 程式
  not 程序, 預設 not 默认, 伺服器 not 服务器), full-width punctuation 「:()」.
- ko uses formal 합니다체 (습니다/합니다) register throughout.
- pt uses European Portuguese as baseline with neutral PT/BR vocabulary
  where possible.
- ga uses standard An Caighdeán Oifigiúil; English loanwords retained
  for tech terms without good Irish equivalents (gateway, API, JSON).
- All preserve {placeholder} tokens, backtick code spans, slash commands,
  brand names (Hermes, MCP, TTS, YOLO, OpenAI, Telegram, etc.), and emoji.

Aliases added in agent/i18n.py:
- af-za, Afrikaans → af
- ko-kr, Korean, 한국어 → ko
- it-it, italiano → it
- ga-ie, Irish, Gaeilge → ga
- zh-tw, zh-hk, zh-mo, traditional-chinese → zh-hant (note: zh-tw used to
  alias to zh; now aliases to its own zh-hant catalog)
- zh-cn, zh-hans, zh-sg → zh (unchanged from before)
- pt-pt, pt-br, brazilian, portuguese → pt
- ru-ru, Russian, русский → ru
- hu-hu, Magyar → hu

The zh-tw alias re-routing is intentional: previously typing 'zh-TW' got
the Simplified Chinese catalog (wrong vocabulary for Taiwan/HK users).
Now those users get the proper Traditional Chinese catalog.

Validation:
- tests/agent/test_i18n.py: 43/43 (parity contract holds for all 16
  languages × 270 keys = 4320 catalog entries, with matching placeholder
  tokens).
- E2E alias resolution verified for all 19 alias inputs (Afrikaans, ko-KR,
  한국어, italiano, Gaeilge, zh-TW, zh-HK, traditional-chinese, pt-BR,
  brazilian, Magyar, etc.).
- tests/gateway/: 5198 passed (3 pre-existing TTS routing failures
  unrelated to i18n).

Credit to all contributors whose PRs surfaced these language requests.
Their original PRs may now be closed as superseded with credit.

* feat(dashboard-i18n): add 14 web dashboard locales matching the static catalog

Brings the React dashboard (web/src/) up to the same 16-language
coverage the static catalog already has after the previous commits in
this PR. The Translations interface is TypeScript-typed, so every new
locale must provide every key — tsc -b is the parity guard.

Languages added (each is a complete 429-line locale file):
- af  Afrikaans
- ja  Japanese        (PR #22513 by @snuffxxx surfaced this)
- de  German          (PR #21749 by @mag1art)
- es  Spanish         (PR #21749)
- fr  French          (PRs #21749, #10310 by @foXaCe)
- tr  Turkish
- uk  Ukrainian
- ko  Korean          (PRs #21749, #18894 by @ovstng, #22285 by @project820)
- it  Italian
- ga  Irish (Gaeilge)
- zh-hant Traditional Chinese (PR #13140 by @anomixer)
- pt  Portuguese      (PRs #22063 by @Magaav, #22182 by @wesleysimplicio, #15737 by @carloshenriquecarniatto)
- ru  Russian         (PRs #21749, #22770 by @DrMaks22)
- hu  Hungarian       (PR #22336 by @lunasec007)

Each translation covers all 15 namespaces with full key parity vs en.ts,
preserves every {placeholder} token verbatim, keeps identifiers
untranslated (brand names, file paths, cron expressions, code spans),
translates the language.switchTo tooltip into the target language, and
matches existing tone conventions (zh-hant uses TW/HK vocab; ja uses
formal desu/masu; ko uses formal seumnida register; ga uses An
Caighdean Oifigiuil with English loanwords for tech vocab without good
Irish equivalents).

Plumbing:
- web/src/i18n/types.ts: Locale union expanded to all 16 codes.
- web/src/i18n/context.tsx: imports all 16 catalogs; exports
  LOCALE_META (endonym + flag per locale); isLocale() type guard.
- web/src/i18n/index.ts: re-export LOCALE_META.
- web/src/components/LanguageSwitcher.tsx: replaced two-state EN-ZH
  toggle with a click-to-open dropdown listing all 16 languages.

Note: zh-hant.ts exports zhHant (camelCase) since hyphen is invalid in
a JS identifier; the canonical 'zh-hant' string keys it in TRANSLATIONS.

Validation:
- npx tsc -b: 0 errors. Every locale satisfies Translations.
- npm run build (tsc + vite production): green, 2062 modules.
- Each locale file is exactly 429 lines.

Out of scope: plugin dashboards (kanban/achievements ship as prebuilt
bundles with no source in repo); Docusaurus docs (separate surface);
TUI (no i18n yet).

* feat(plugin-i18n): localize achievements + kanban plugin dashboards across all 16 locales

Brings the two shipped plugin dashboards (hermes-achievements, kanban)
under the same i18n umbrella as the core dashboard PR #22914 just
established.  Both bundles now read user-facing strings from the host's
i18n catalog via SDK.useI18n() instead of hardcoded English.

## Approach

Plugin dashboards ship as prebuilt IIFE bundles in
plugins/<name>/dashboard/dist/index.js — no build step, no source in
repo (upstream-authored, vendored as compiled JS).  Earlier contributor
PRs (#22594, #22595, #18747) tried direct edits but didn't actually
wire the bundles to read translations.

This change does the wiring properly:

1.  Each bundle gets a useI18n shim at IIFE scope:
        const useI18n = SDK.useI18n
          || function () { return { t: { kanban: null }, locale: "en" }; };
    Older host SDKs without useI18n still load the bundle and render
    English fallbacks.

2.  A small tx(t, path, fallback, vars) helper resolves dotted keys
    under the plugin's namespace (t.kanban.* or t.achievements.*) and
    interpolates {placeholder} tokens.

3.  Every React component starts with const { t } = useI18n() and
    each user-visible string is wrapped in tx(t, "key", "English fallback").
    Helpers called outside React components (window.prompt callers,
    constants used during init) take t as a parameter.

4.  Top-level constants that were English dictionaries (COLUMN_LABEL,
    COLUMN_HELP, DESTRUCTIVE_TRANSITIONS, DIAGNOSTIC_EVENT_LABELS in
    kanban) become getColumnLabel(t, status)-style functions backed by
    FALLBACK_* dictionaries.

## Translations added

Two new top-level namespaces added to the dashboard's TypeScript-typed
Translations interface:

- achievements: ~70 keys covering the hero, scan banner, achievement
  card, share dialog, stats, filters, and empty states.
- kanban: ~145 keys covering the board, columns (with nested
  columnLabels and columnHelp sub-dicts), card detail panel,
  bulk-actions toolbar, dependency editor, board switcher, and
  diagnostic callouts.

Each key is provided across all 16 supported locales:
en, zh, zh-hant, ja, de, es, fr, tr, uk, af, ko, it, ga, pt, ru, hu.

Total new translation entries: ~3,440 (215 keys × 16 locales).

## What stays English (deliberate)

- API paths, CSS class names, data-* attributes, JSON keys, regex
  strings, URLs, file paths (~/.hermes/kanban.db, boards/_archived/).
- State identifier strings used as lookup keys (triage / todo / ready /
  running / blocked / done / archived) — labels translate, key strings
  don't.
- The PNG share-card text rendered to canvas in the achievements
  ShareDialog (HERMES AGENT watermark, UNLOCKED stamp, tier names) —
  these become part of a globally-shared image and stay English.
- localStorage keys (hermes.kanban.selectedBoard).
- Brand names (Kanban, Hermes, WebSocket, Nous Research).

## Contributor credit

PR #22594 by @02356abc and PR #22595 by @02356abc supplied the
en + zh kanban namespace skeleton (145 keys); used as the en source-
of-truth in this commit and translated to the other 14 locales.

PR #18747 by @laolaoshiren first surfaced the achievements
localization request.

## Validation

- npx tsc -b: 0 errors. All 16 locale .ts files satisfy the
  Translations type with full key parity.
- npm run build (tsc + vite production build): green, 2062 modules,
  1.56MB JS / 95KB CSS, ~2.5s build.
- node --check on both plugin bundles: parse cleanly.
- 126 tx() call sites in kanban, 46 in achievements.

## Out of scope

- TUI (ui-tui/) has no i18n infrastructure yet.
- Docusaurus docs (website/i18n/) — already had zh-Hans; expanding
  is a separate translation workstream (Thai / Korean / Hindi PRs).
2026-05-10 07:14:14 -07:00

695 lines
28 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { Translations } from "./types";
export const fr: Translations = {
common: {
save: "Enregistrer",
saving: "Enregistrement...",
cancel: "Annuler",
close: "Fermer",
confirm: "Confirmer",
delete: "Supprimer",
refresh: "Actualiser",
retry: "Réessayer",
search: "Rechercher...",
loading: "Chargement...",
create: "Créer",
creating: "Création...",
set: "Définir",
replace: "Remplacer",
clear: "Effacer",
live: "En direct",
off: "Désactivé",
enabled: "activé",
disabled: "désactivé",
active: "actif",
inactive: "inactif",
unknown: "inconnu",
untitled: "Sans titre",
none: "Aucun",
form: "Formulaire",
noResults: "Aucun résultat",
of: "sur",
page: "Page",
msgs: "msgs",
tools: "outils",
match: "correspondance",
other: "Autre",
configured: "configuré",
removed: "supprimé",
failedToToggle: "Échec du basculement",
failedToRemove: "Échec de la suppression",
failedToReveal: "Échec de l'affichage",
collapse: "Réduire",
expand: "Développer",
general: "Général",
messaging: "Messagerie",
pluginLoadFailed:
"Impossible de charger le script de ce plugin. Vérifiez l'onglet Réseau (dashboard-plugins/…) et le chemin des plugins du serveur.",
pluginNotRegistered:
"Le script du plugin n'a pas appelé register(), ou le script a échoué. Ouvrez la console du navigateur pour plus de détails.",
},
app: {
brand: "Hermes Agent",
brandShort: "HA",
closeNavigation: "Fermer la navigation",
closeModelTools: "Fermer modèle et outils",
footer: {
org: "Nous Research",
},
activeSessionsLabel: "Sessions actives:",
gatewayStatusLabel: "État de la passerelle:",
gatewayStrip: {
failed: "Échec du démarrage",
off: "Désactivé",
running: "En cours",
starting: "Démarrage",
stopped: "Arrêté",
},
nav: {
analytics: "Analyses",
chat: "Chat",
config: "Configuration",
cron: "Cron",
documentation: "Documentation",
keys: "Clés",
logs: "Journaux",
models: "Modèles",
profiles: "profils : multi agents",
plugins: "Plugins",
sessions: "Sessions",
skills: "Compétences",
},
modelToolsSheetSubtitle: "& outils",
modelToolsSheetTitle: "Modèle",
navigation: "Navigation",
openDocumentation: "Ouvrir la documentation dans un nouvel onglet",
openNavigation: "Ouvrir la navigation",
pluginNavSection: "Plugins",
sessionsActiveCount: "{count} actives",
statusOverview: "Aperçu de l'état",
system: "Système",
webUi: "Web UI",
},
status: {
actionFailed: "Action échouée",
actionFinished: "Terminé",
actions: "Actions",
agent: "Agent",
activeSessions: "Sessions actives",
connected: "Connecté",
connectedPlatforms: "Plateformes connectées",
disconnected: "Déconnecté",
error: "Erreur",
failed: "Échec",
gateway: "Passerelle",
gatewayFailedToStart: "Le démarrage de la passerelle a échoué",
lastUpdate: "Dernière mise à jour",
noneRunning: "Aucun",
notRunning: "Non lancé",
pid: "PID",
platformDisconnected: "déconnecté",
platformError: "erreur",
recentSessions: "Sessions récentes",
restartGateway: "Redémarrer la passerelle",
restartingGateway: "Redémarrage de la passerelle…",
running: "En cours",
runningRemote: "En cours (distant)",
startFailed: "Échec du démarrage",
starting: "Démarrage",
startedInBackground: "Démarré en arrière-plan — consultez les journaux pour la progression",
stopped: "Arrêté",
updateHermes: "Mettre à jour Hermes",
updatingHermes: "Mise à jour de Hermes…",
waitingForOutput: "En attente de la sortie…",
},
sessions: {
title: "Sessions",
searchPlaceholder: "Rechercher dans les messages...",
noSessions: "Aucune session pour l'instant",
noMatch: "Aucune session ne correspond à votre recherche",
startConversation: "Démarrez une conversation pour la voir ici",
noMessages: "Aucun message",
untitledSession: "Session sans titre",
deleteSession: "Supprimer la session",
confirmDeleteTitle: "Supprimer la session ?",
confirmDeleteMessage:
"Cela supprime définitivement la conversation et tous ses messages. Cette action est irréversible.",
sessionDeleted: "Session supprimée",
failedToDelete: "Échec de la suppression de la session",
resumeInChat: "Reprendre dans le chat",
previousPage: "Page précédente",
nextPage: "Page suivante",
roles: {
user: "Utilisateur",
assistant: "Assistant",
system: "Système",
tool: "Outil",
},
},
analytics: {
period: "Période:",
totalTokens: "Tokens totaux",
totalSessions: "Sessions totales",
apiCalls: "Appels API",
dailyTokenUsage: "Utilisation quotidienne des tokens",
dailyBreakdown: "Détail quotidien",
perModelBreakdown: "Détail par modèle",
topSkills: "Compétences les plus utilisées",
skill: "Compétence",
loads: "Agent chargé",
edits: "Agent géré",
lastUsed: "Dernière utilisation",
input: "Entrée",
output: "Sortie",
total: "Total",
noUsageData: "Aucune donnée d'utilisation pour cette période",
startSession: "Démarrez une session pour voir les analyses ici",
date: "Date",
model: "Modèle",
tokens: "Tokens",
perDayAvg: "/jour moy",
acrossModels: "sur {count} modèles",
inOut: "{input} entrée / {output} sortie",
},
models: {
modelsUsed: "Modèles utilisés",
estimatedCost: "Coût est.",
tokens: "tokens",
sessions: "sessions",
avgPerSession: "moy/session",
apiCalls: "appels API",
toolCalls: "appels d'outil",
noModelsData: "Aucune donnée de modèle pour cette période",
startSession: "Démarrez une session pour voir les données de modèle ici",
},
logs: {
title: "Journaux",
autoRefresh: "Actualisation auto",
file: "Fichier",
level: "Niveau",
component: "Composant",
lines: "Lignes",
noLogLines: "Aucune ligne de journal trouvée",
},
cron: {
confirmDeleteMessage:
"Cela supprime la tâche du planning. Cette action est irréversible.",
confirmDeleteTitle: "Supprimer la tâche planifiée ?",
newJob: "Nouvelle tâche cron",
nameOptional: "Nom (facultatif)",
namePlaceholder: "ex. Résumé quotidien",
prompt: "Invite",
promptPlaceholder: "Que doit faire l'agent à chaque exécution ?",
schedule: "Planning (expression cron)",
schedulePlaceholder: "0 9 * * *",
deliverTo: "Livrer à",
scheduledJobs: "Tâches planifiées",
noJobs: "Aucune tâche cron configurée. Créez-en une ci-dessus.",
last: "Dernière",
next: "Prochaine",
pause: "Pause",
resume: "Reprendre",
triggerNow: "Déclencher maintenant",
delivery: {
local: "Local",
telegram: "Telegram",
discord: "Discord",
slack: "Slack",
email: "Email",
},
},
profiles: {
newProfile: "Nouveau profil",
name: "Nom",
namePlaceholder: "ex. coder, writer, etc.",
nameRequired: "Le nom est requis",
nameRule:
"Lettres minuscules, chiffres, _ et - uniquement ; doit commencer par une lettre ou un chiffre ; jusqu'à 64 caractères.",
invalidName: "Nom de profil invalide",
cloneFromDefault: "Cloner la configuration du profil par défaut",
allProfiles: "Profils",
noProfiles: "Aucun profil trouvé.",
defaultBadge: "défaut",
hasEnv: "env",
model: "Modèle",
skills: "Compétences",
rename: "Renommer",
editSoul: "Modifier SOUL.md",
soulSection: "SOUL.md (personnalité / invite système)",
soulPlaceholder: "# Comment cet agent doit se comporter…",
saveSoul: "Enregistrer SOUL",
soulSaved: "SOUL.md enregistré",
openInTerminal: "Copier la commande CLI",
commandCopied: "Copié dans le presse-papiers",
copyFailed: "Impossible de copier",
confirmDeleteTitle: "Supprimer le profil ?",
confirmDeleteMessage:
"Cela supprime définitivement le profil '{name}' — configuration, clés, mémoires, sessions, compétences, tâches cron. Action irréversible.",
created: "Créé",
deleted: "Supprimé",
renamed: "Renommé",
},
pluginsPage: {
contextEngineLabel: "Moteur de contexte",
dashboardSlots: "Emplacements du tableau de bord",
disableRuntime: "Désactiver",
enableAfterInstall: "Activer après l'installation",
enableRuntime: "Activer",
forceReinstall: "Forcer la réinstallation (supprimer d'abord le dossier existant)",
headline:
"Découvrez, installez, activez et mettez à jour les plugins Hermes (parité avec `hermes plugins`).",
identifierLabel: "URL Git ou owner/repo",
inactive: "inactif",
installBtn: "Installer depuis Git",
installHeading: "Installer depuis GitHub / URL Git",
installHint: "Utilisez le raccourci owner/repo ou une URL de clonage complète https:// ou git@.",
memoryProviderLabel: "Fournisseur de mémoire",
missingEnvWarn: "Définissez ces variables dans Clés avant que le plugin puisse s'exécuter:",
noDashboardTab: "Aucun onglet de tableau de bord",
openTab: "Ouvrir",
orphanHeading: "Extensions du tableau de bord uniquement (aucune correspondance plugin.yaml d'agent)",
pluginListHeading: "Plugins installés",
providerDefaults: "intégré / par défaut",
providersHeading: "Plugins fournisseurs d'exécution",
providersHint:
"Écrit memory.provider (vide = intégré) et context.engine dans config.yaml. Prend effet à la prochaine session.",
refreshDashboard: "Re-scanner les extensions du tableau de bord",
removeConfirm: "Retirer ce plugin de ~/.hermes/plugins/ ?",
removeHint: "Seuls les plugins installés par l'utilisateur sous ~/.hermes/plugins peuvent être supprimés.",
rescanHeading: "Registre des plugins SPA",
rescanHint: "Re-scannez après avoir ajouté des fichiers sur le disque pour que la barre latérale prenne en compte les nouveaux manifestes.",
runtimeHeading: "Exécution de la passerelle (plugins YAML)",
saveProviders: "Enregistrer les paramètres de fournisseur",
savedProviders: "Paramètres de fournisseur enregistrés.",
sourceBadge: "Source",
authRequired: "Authentification requise",
authRequiredHint: "Exécutez cette commande pour vous authentifier:",
updateGit: "Git pull",
versionBadge: "Version",
showInSidebar: "Afficher dans la barre latérale",
hideFromSidebar: "Masquer de la barre latérale",
},
skills: {
title: "Compétences",
searchPlaceholder: "Rechercher des compétences et des outils...",
enabledOf: "{enabled}/{total} activées",
all: "Toutes",
categories: "Catégories",
filters: "Filtres",
noSkills: "Aucune compétence trouvée. Les compétences sont chargées depuis ~/.hermes/skills/",
noSkillsMatch: "Aucune compétence ne correspond à votre recherche ou filtre.",
skillCount: "{count} compétence{s}",
resultCount: "{count} résultat{s}",
noDescription: "Aucune description disponible.",
toolsets: "Ensembles d'outils",
toolsetLabel: "Ensemble d'outils {name}",
noToolsetsMatch: "Aucun ensemble d'outils ne correspond à la recherche.",
setupNeeded: "Configuration nécessaire",
disabledForCli: "Désactivé pour CLI",
more: "+{count} de plus",
},
config: {
configPath: "~/.hermes/config.yaml",
filters: "Filtres",
sections: "Sections",
exportConfig: "Exporter la configuration en JSON",
importConfig: "Importer la configuration depuis JSON",
resetDefaults: "Réinitialiser aux valeurs par défaut",
resetScopeTooltip: "Réinitialiser {scope} aux valeurs par défaut",
confirmResetScope: "Réinitialiser tous les paramètres de {scope} aux valeurs par défaut ? Cela ne met à jour que le formulaire — les modifications ne sont écrites dans config.yaml qu'après avoir appuyé sur Enregistrer.",
resetScopeToast: "{scope} réinitialisé aux valeurs par défaut — vérifiez et enregistrez pour conserver",
rawYaml: "Configuration YAML brute",
searchResults: "Résultats de recherche",
fields: "champ{s}",
noFieldsMatch: 'Aucun champ ne correspond à "{query}"',
configSaved: "Configuration enregistrée",
yamlConfigSaved: "Configuration YAML enregistrée",
failedToSave: "Échec de l'enregistrement",
failedToSaveYaml: "Échec de l'enregistrement YAML",
failedToLoadRaw: "Échec du chargement de la configuration brute",
configImported: "Configuration importée — vérifiez et enregistrez",
invalidJson: "Fichier JSON invalide",
categories: {
general: "Général",
agent: "Agent",
terminal: "Terminal",
display: "Affichage",
delegation: "Délégation",
memory: "Mémoire",
compression: "Compression",
security: "Sécurité",
browser: "Navigateur",
voice: "Voix",
tts: "Synthèse vocale",
stt: "Reconnaissance vocale",
logging: "Journalisation",
discord: "Discord",
auxiliary: "Auxiliaire",
},
},
env: {
changesNote: "Les modifications sont enregistrées sur le disque immédiatement. Les sessions actives récupèrent les nouvelles clés automatiquement.",
confirmClearMessage:
"La valeur stockée pour cette variable sera supprimée de votre fichier .env. Cette action ne peut pas être annulée depuis l'interface.",
confirmClearTitle: "Effacer cette clé ?",
description: "Gérer les clés API et les secrets stockés dans",
hideAdvanced: "Masquer les options avancées",
showAdvanced: "Afficher les options avancées",
llmProviders: "Fournisseurs LLM",
providersConfigured: "{configured} sur {total} fournisseurs configurés",
getKey: "Obtenir la clé",
notConfigured: "{count} non configuré",
notSet: "Non défini",
keysCount: "{count} clé{s}",
enterValue: "Saisir une valeur...",
replaceCurrentValue: "Remplacer la valeur actuelle ({preview})",
showValue: "Afficher la valeur réelle",
hideValue: "Masquer la valeur",
},
oauth: {
title: "Connexions fournisseurs (OAuth)",
providerLogins: "Connexions fournisseurs (OAuth)",
description: "{connected} sur {total} fournisseurs OAuth connectés. Les flux de connexion s'exécutent actuellement via le CLI ; cliquez sur Copier la commande et collez-la dans un terminal pour configurer.",
connected: "Connecté",
expired: "Expiré",
notConnected: "Non connecté. Exécutez {command} dans un terminal.",
runInTerminal: "dans un terminal.",
noProviders: "Aucun fournisseur compatible OAuth détecté.",
login: "Connexion",
disconnect: "Déconnecter",
managedExternally: "Géré en externe",
copied: "Copié ✓",
cli: "CLI",
copyCliCommand: "Copier la commande CLI (pour externe / repli)",
connect: "Connecter",
sessionExpires: "La session expire dans {time}",
initiatingLogin: "Lancement du flux de connexion…",
exchangingCode: "Échange du code contre des jetons…",
connectedClosing: "Connecté ! Fermeture…",
loginFailed: "Échec de la connexion.",
sessionExpired: "Session expirée. Cliquez sur Réessayer pour démarrer une nouvelle connexion.",
reOpenAuth: "Rouvrir la page d'authentification",
reOpenVerification: "Rouvrir la page de vérification",
submitCode: "Soumettre le code",
pasteCode: "Collez le code d'autorisation (avec suffixe #state accepté)",
waitingAuth: "En attente de votre autorisation dans le navigateur…",
enterCodePrompt: "Un nouvel onglet s'est ouvert. Saisissez ce code si demandé:",
pkceStep1: "Un nouvel onglet s'est ouvert vers claude.ai. Connectez-vous et cliquez sur Autoriser.",
pkceStep2: "Copiez le code d'autorisation affiché après autorisation.",
pkceStep3: "Collez-le ci-dessous et soumettez.",
flowLabels: {
pkce: "Connexion navigateur (PKCE)",
device_code: "Code d'appareil",
external: "CLI externe",
},
expiresIn: "expire dans {time}",
},
language: {
switchTo: "Passer à l'anglais",
},
theme: {
title: "Thème",
switchTheme: "Changer de thème",
},
achievements: {
hero: {
kicker: "Agentic Gamerscore",
title: "Hermes Achievements",
subtitle:
"Badges Hermes à collectionner, gagnés à partir de l'historique réel des sessions. Les succès connus non terminés sont affichés comme Découverts ; les succès secrets restent cachés jusqu'à l'apparition du premier comportement correspondant.",
scan_subtitle:
"Analyse de l'historique des sessions Hermes en cours. Le premier scan peut prendre 5 à 10 secondes sur les historiques volumineux.",
},
actions: {
rescan: "Relancer le scan",
},
stats: {
unlocked: "Débloqués",
unlocked_hint: "badges obtenus",
discovered: "Découverts",
discovered_hint: "connus, pas encore obtenus",
secrets: "Secrets",
secrets_hint: "cachés jusqu'au premier signal",
highest_tier: "Niveau le plus élevé",
highest_tier_hint: "Copper → Silver → Gold → Diamond → Olympian",
latest: "Dernier",
latest_hint_empty: "utilisez Hermes davantage",
none_yet: "Aucun pour l'instant",
},
state: {
unlocked: "Débloqué",
discovered: "Découvert",
secret: "Secret",
},
tier: {
target: "Cible {tier}",
hidden: "Caché",
complete: "Terminé",
objective: "Objectif",
},
progress: {
hidden: "caché",
},
scan: {
building_headline: "Création du profil de succès…",
building_detail:
"Lecture des sessions, des appels d'outils, des métadonnées du modèle et de l'état de déblocage.",
starting_headline: "Démarrage du scan des succès…",
progress_detail:
"{scanned} sessions analysées sur {total} · {pct}%. Les badges se débloquent à mesure que l'historique est traité.",
idle_detail:
"Lecture des sessions, des appels d'outils, des métadonnées du modèle et de l'état de déblocage. Les badges apparaissent ici à mesure qu'ils se débloquent.",
},
guide: {
tiers_header: "Niveaux",
secret_header: "Succès secrets",
secret_body:
"Les secrets cachent leur déclencheur exact. Dès qu'Hermes détecte un signal lié, la carte passe à Découvert et affiche son exigence.",
scan_status_header: "État du scan",
scan_status_body:
"Hermes analyse l'historique local une seule fois, puis les cartes apparaîtront automatiquement. Rien n'est bloqué si cela prend quelques secondes.",
what_scanned_header: "Ce qui est analysé",
what_scanned_body:
"Sessions, appels d'outils, métadonnées du modèle, erreurs, succès et état de déblocage local.",
},
card: {
share_title: "Partager ce succès",
share_label: "Partager {name}",
share_text: "Partager",
how_to_reveal: "Comment le révéler",
what_counts: "Ce qui compte",
evidence_label: "Preuve",
evidence_session_fallback: "session",
no_evidence: "Pas encore de preuve",
},
latest: {
header: "Déblocages récents",
},
empty: {
no_secrets_header: "Plus aucun secret caché dans ce scan.",
no_secrets_body:
"Indice: les secrets démarrent généralement à partir d'échecs inhabituels ou de schémas d'utilisateurs avancés — conflits de ports, murs de permissions, variables d'environnement manquantes, erreurs YAML, collisions Docker, utilisation de rollback/checkpoint, succès de cache ou petits correctifs après beaucoup de texte rouge.",
},
filters: {
all_categories: "Tous",
visibility_all: "tous",
visibility_unlocked: "débloqués",
visibility_discovered: "découverts",
visibility_secret: "secrets",
},
share: {
dialog_label: "Partager le succès",
header: "Partager: {name}",
close: "Fermer",
rendering: "Rendu en cours…",
card_alt: "Carte de partage {name}",
error_generic: "Une erreur s'est produite.",
x_title: "Ouvre X avec une publication préremplie",
x_button: "Partager sur X",
copy_title: "Copiez l'image pour la coller dans votre publication",
copy_button: "Copier l'image",
copied: "Copié ✓",
download_button: "Télécharger le PNG",
hint:
"Partager sur X ouvre une publication préremplie dans un nouvel onglet. Cliquez d'abord sur Copier l'image si vous voulez joindre le badge 1200×630 — X vous laisse le coller directement dans l'éditeur de tweet. Télécharger le PNG enregistre le fichier pour l'utiliser n'importe où.",
clipboard_unsupported:
"La copie d'image dans le presse-papiers n'est pas prise en charge par ce navigateur — utilisez Télécharger à la place.",
tweet_text: "Just unlocked {tier_part}\"{name}\" in Hermes Agent ☤",
},
},
kanban: {
loading: "Chargement du tableau Kanban…",
loadFailed: "Échec du chargement du tableau Kanban: ",
loadFailedHint:
"Le backend crée automatiquement kanban.db à la première lecture. Si le problème persiste, consultez les logs du dashboard.",
board: "Tableau",
newBoard: "+ Nouveau tableau",
newBoardTitle: "Nouveau tableau",
newBoardDescription:
"Les tableaux vous permettent de séparer des flux de travail indépendants — un par projet, dépôt ou domaine. Les workers d'un tableau ne voient jamais les tâches d'un autre.",
slug: "Slug",
slugHint: "— minuscules, tirets, par ex. atm10-server",
displayName: "Nom affiché",
displayNameHint: "(facultatif)",
description: "Description",
descriptionHint: "(facultatif)",
icon: "Icône",
iconHint: "(un seul caractère ou emoji)",
switchAfterCreate: "Basculer sur ce tableau après l'avoir créé",
cancel: "Annuler",
creating: "Création…",
createBoard: "Créer le tableau",
search: "Rechercher",
filterCards: "Filtrer les cartes…",
tenant: "Tenant",
allTenants: "Tous les tenants",
assignee: "Assigné à",
allProfiles: "Tous les profils",
showArchived: "Afficher les archivés",
lanesByProfile: "Couloirs par profil",
nudgeDispatcher: "Solliciter le dispatcher",
refresh: "Rafraîchir",
selected: "sélectionné(s)",
complete: "Terminer",
archive: "Archiver",
apply: "Appliquer",
clear: "Effacer",
createTask: "Créer une tâche dans cette colonne",
noTasks: "— aucune tâche —",
unassigned: "non assigné",
untitled: "(sans titre)",
loadingDetail: "Chargement…",
addComment: "Ajouter un commentaire… (Enter pour envoyer)",
comment: "Commentaire",
status: "Statut",
workspace: "Workspace",
skills: "Compétences",
createdBy: "Créé par",
result: "Result",
comments: "Commentaires",
events: "Événements",
runHistory: "Historique d'exécution",
workerLog: "Log du worker",
loadingLog: "Chargement du log…",
noWorkerLog:
"— pas encore de log du worker (la tâche n'a pas démarré ou le log a été effacé par rotation) —",
noDescription: "— aucune description —",
noComments: "— aucun commentaire —",
edit: "modifier",
save: "Enregistrer",
dependencies: "Dépendances",
parents: "Parents:",
children: "Enfants:",
none: "aucun",
addParent: "— ajouter un parent —",
addChild: "— ajouter un enfant —",
removeDependency: "Supprimer la dépendance",
block: "Bloquer",
unblock: "Débloquer",
notifyHomeChannels: "Notifier les canaux home",
diagnostics: "Diagnostics",
hide: "Masquer",
show: "Afficher",
attention: "Attention",
tasksNeedAttention: "tâches nécessitent une attention",
taskNeedsAttention: "1 tâche nécessite une attention",
diagnostic: "diagnostic",
open: "Ouvrir",
close: "Fermer (Esc)",
reassignTo: "Réassigner à:",
copied: "Copié",
copyCommand: "Copier la commande dans le presse-papiers",
reclaim: "Récupérer",
reassign: "Réassigner",
renderingError: "L'onglet Kanban a rencontré une erreur de rendu",
reloadView: "Recharger la vue",
wsAuthFailed:
"Échec d'authentification WebSocket — rechargez la page pour rafraîchir le jeton de session.",
markDone: "Marquer {n} tâche(s) comme terminée(s) ?",
markArchived: "Archiver {n} tâche(s) ?",
warning: "Avertissement",
phantomIds: "IDs fantômes:",
active: "actif",
ended: "terminé",
noProfile: "(aucun profil)",
showAllAttempts: "Afficher toutes les tentatives",
sendingUpdates: "Envoi des mises à jour à",
sendNotifications: "Envoyer les notifications completed / blocked / gave_up à",
archiveBoardConfirm:
"Archiver le tableau '{name}' ? Il sera déplacé vers boards/_archived/ pour pouvoir être récupéré plus tard. Les tâches de ce tableau n'apparaîtront plus nulle part dans l'UI.",
archiveBoardTitle: "Archiver ce tableau",
boardSwitcherHint: "Les tableaux vous permettent de séparer des flux de travail indépendants",
taskCreatedWarning: "Tâche créée, mais: ",
moveFailed: "Échec du déplacement: ",
bulkFailed: "Lot: ",
completionBlockedHallucination: "⚠ Achèvement bloqué — IDs de carte fantômes",
suspectedHallucinatedReferences: "⚠ Le texte a référencé des IDs de carte fantômes",
pickProfileFirst: "Choisissez d'abord un profil.",
unblockedMessage: "Débloqué {id}. La tâche est prête pour le prochain tick.",
unblockFailed: "Échec du déblocage: ",
reclaimedMessage: "Récupéré {id}. La tâche est de nouveau prête.",
reclaimFailed: "Échec de la récupération: ",
reassignedMessage: "Réassigné {id} à {profile}.",
reassignFailed: "Échec de la réassignation: ",
selectForBulk: "Sélectionner pour des actions groupées",
clickToEdit: "Cliquez pour modifier",
clickToEditAssignee: "Cliquez pour modifier l'assigné",
emptyAssignee: "(vide = désassigner)",
columnLabels: {
triage: "Triage",
todo: "À faire",
ready: "Prêt",
running: "En cours",
blocked: "Bloqué",
done: "Terminé",
archived: "Archivé",
},
columnHelp: {
triage: "Idées brutes — un specifier rédigera la spécification",
todo: "En attente de dépendances ou non assigné",
ready: "Assigné et en attente d'un tick du dispatcher",
running: "Réclamé par un worker — en cours d'exécution",
blocked: "Le worker a demandé une intervention humaine",
done: "Terminé",
archived: "Archivé",
},
confirmDone:
"Marquer cette tâche comme terminée ? La revendication du worker est libérée et les enfants dépendants deviennent prêts.",
confirmArchive:
"Archiver cette tâche ? Elle disparaîtra de la vue par défaut du tableau.",
confirmBlocked:
"Marquer cette tâche comme bloquée ? La revendication du worker est libérée.",
completionSummary:
"Résumé d'achèvement pour {label}. Stocké comme result de la tâche.",
completionSummaryRequired:
"Un résumé d'achèvement est requis avant de marquer une tâche comme terminée.",
triagePlaceholder: "Idée approximative — l'IA la spécifiera…",
taskTitlePlaceholder: "Titre de la nouvelle tâche…",
specifier: "specifier",
assigneePlaceholder: "assigné",
priority: "Priorité",
skillsPlaceholder:
"compétences (facultatif, séparées par virgules): translation, github-code-review",
noParent: "— aucun parent —",
workspacePathDir: "chemin du workspace (requis, par ex. ~/projects/my-app)",
workspacePathOptional:
"chemin du workspace (facultatif, dérivé de l'assigné si vide)",
logTruncated: "(affichage des derniers 100 KB — log complet à ",
logAt: ")",
},
};