@sumdaelive/cli 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +1176 -417
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -44,7 +44,7 @@ import { useEffect as useEffect9 } from "react";
44
44
  // src/app.tsx
45
45
  import { Box as Box16, Text as Text19, useApp as useApp3, useInput as useInput5 } from "ink";
46
46
  import open from "open";
47
- import { useEffect as useEffect7, useState as useState9 } from "react";
47
+ import { useEffect as useEffect7, useRef as useRef4, useState as useState9 } from "react";
48
48
 
49
49
  // src/config.ts
50
50
  import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
@@ -73,6 +73,718 @@ function saveConfig(cfg) {
73
73
  // src/credentials.ts
74
74
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
75
75
  import { join as join2 } from "path";
76
+
77
+ // src/i18n-catalog.ts
78
+ var CATALOG = {
79
+ fr: {
80
+ "agentFeed.agent.subagent": "sous-agent {n}",
81
+ "agentFeed.agent.sumdae": "agent sumdae",
82
+ "agentFeed.diff.moreLines": "\u2026 ({n} lignes de plus)",
83
+ "agentFeed.lagged": "\u2026 (flux rattrap\xE9)",
84
+ "agentFeed.result.failed": "\xE9chec",
85
+ "agentFeed.result.ok": "ok",
86
+ "agentFeed.shell": "shell",
87
+ "api.authRequired": "session expir\xE9e ou absente \u2014 lancez `sumdae login`",
88
+ "api.permissionDenied": "Vous n'avez pas la permission d'effectuer cette action.",
89
+ "api.quotaInsufficient": "quota de build insuffisant : {remaining} restant(s), {needed} requis",
90
+ "api.quotaInvalidResponse": "r\xE9ponse quota invalide \u2014 v\xE9rification impossible, refus par prudence",
91
+ "api.tokenExchangeFailed": "\xE9chec de l'\xE9change de token",
92
+ "banner.tagline": "la plateforme sumdae, depuis le terminal",
93
+ "buildCmd.buildLaunched": "build {buildId} lanc\xE9 \u2014 quota consomm\xE9",
94
+ "buildCmd.cancelRequested": "annulation demand\xE9e \u2014 suivi jusqu\u2019\xE0 l\u2019arr\xEAt complet :",
95
+ "buildCmd.commandSent": "commande \xAB {action} \xBB envoy\xE9e \xE0 {agent}",
96
+ "buildCmd.invalidAction": "action invalide \u2014 'kill' ou 'message'",
97
+ "buildCmd.noActiveOrgForCancel": "aucune organisation active",
98
+ "buildCmd.simEmpty": 't\xE2che vide \u2014 donne une instruction au master, ex. : sumdae simulate "cr\xE9e /workspace/hello.txt et liste le dossier"',
99
+ "buildCmd.simLaunched": "simulation {buildId} lanc\xE9e \u2014 quota consomm\xE9 \xB7 pod lou\xE9, agents au travail",
100
+ "buildSummary.heading": "r\xE9sum\xE9 du job",
101
+ "buildSummary.rewatch": "Revoir le flux complet",
102
+ "buildSummary.rewatch.hint": "rejouer les events",
103
+ "buildSummary.watchLive": "Voir le job en cours",
104
+ "buildWatch.connectionLost": "connexion au build perdue (30 tentatives) \u2014 relancez `sumdae watch`",
105
+ "buildWatch.refused": "watch refus\xE9 (HTTP {status})",
106
+ "cli.agent.description": "piloter les agents d\u2019un build",
107
+ "cli.agent.kill.description": "arr\xEAter un agent (d\xE9faut : master)",
108
+ "cli.agent.msg.description": "envoyer un message \xE0 un agent en cours",
109
+ "cli.agent.opt.agent": "master | subagent-N | all",
110
+ "cli.build.description": "lancer un build Docker (consomme 1 quota) et suivre en live",
111
+ "cli.build.opt.pipeline": "artifact pipeline \xE0 int\xE9grer",
112
+ "cli.cancel.description": "annuler un build (environnement lib\xE9r\xE9, quota rembours\xE9 si rien commenc\xE9)",
113
+ "cli.login.description": "se connecter \xE0 sumdae (OAuth + PKCE)",
114
+ "cli.login.opt.noBrowser": "afficher l'URL au lieu d'ouvrir le navigateur",
115
+ "cli.login.opt.withKey": "coller une cl\xE9 API lab_\u2026 (headless)",
116
+ "cli.logout.description": "se d\xE9connecter",
117
+ "cli.org.description": "g\xE9rer les organisations",
118
+ "cli.org.list.description": "lister vos organisations",
119
+ "cli.org.switch.description": "changer d'organisation active",
120
+ "cli.push.description": "uploader des poids ou un pipeline vers l'organisation active",
121
+ "cli.push.opt.kind": "weights | pipeline (d\xE9duit de l\u2019extension sinon)",
122
+ "cli.quota.description": "builds restants de l'organisation active",
123
+ "cli.root.description": "CLI de la plateforme sumdae",
124
+ "cli.simulate.description": "lancer un build SIMULATION \u2014 pas besoin de mod\xE8le/pipeline (consomme 1 quota)",
125
+ "cli.uninstall.description": "supprimer proprement sumdae de cette machine (tous canaux)",
126
+ "cli.uninstall.opt.yes": "ne pas demander de confirmation (headless)",
127
+ "cli.watch.description": "suivre un build en cours (WS temps r\xE9el)",
128
+ "cli.whoami.description": "profil + organisation active",
129
+ "common.activeOrgLabel": "organisation active :",
130
+ "common.activeOrgSet": "organisation active : {name}",
131
+ "common.back": "Retourner en arri\xE8re",
132
+ "common.backToHome": "Retourner \xE0 l'accueil",
133
+ "common.connectedAs": "connect\xE9 : {email}",
134
+ "common.creatingJob": "cr\xE9ation du job\u2026",
135
+ "common.jobImpossible": "job impossible",
136
+ "common.loginFailed": "connexion \xE9chou\xE9e",
137
+ "common.noActiveOrgRunSwitch": "aucune organisation active \u2014 lancez `sumdae org switch`",
138
+ "common.noOrgCreateOnLab": "aucune organisation \u2014 cr\xE9ez-en une sur lab.sumdae.fr",
139
+ "common.orEscape": "ou \xC9chap",
140
+ "common.quit": "Quitter",
141
+ "common.signingOut": "d\xE9connexion\u2026",
142
+ "common.terminalCurrent": "(actuel : {cols}\xD7{rows})",
143
+ "common.terminalEnlarge": "Agrandissez \xE0 {cols}\xD7{rows} minimum",
144
+ "common.terminalTooSmall": "Terminal trop petit",
145
+ "common.truncated": "\u2026 (tronqu\xE9)",
146
+ "common.waitingBrowserLogin": "en attente de la connexion dans le navigateur\u2026",
147
+ "credentials.keychainFallback": "\u26A0 trousseau syst\xE8me indisponible \u2014 credentials stock\xE9s dans {path} (chmod 600)",
148
+ "credentials.keyringUnavailable": "\u26A0 trousseau syst\xE8me indisponible \u2014 credentials stock\xE9s dans {filePath} (chmod 600)",
149
+ "directUpload.err.refused": "upload refus\xE9 par l'environnement (HTTP {status})",
150
+ "help.build": "lancer un build + suivi live",
151
+ "help.cancel": "annuler un build proprement",
152
+ "help.heading": "COMMANDES \xB7 v{version}",
153
+ "help.login": "se connecter (OAuth + PKCE)",
154
+ "help.logout": "se d\xE9connecter",
155
+ "help.push": "uploader poids/pipeline (R2)",
156
+ "help.quota": "builds restants de l'org active",
157
+ "help.watch": "suivre un build en cours",
158
+ "home.actions": "actions",
159
+ "home.job.kind.build": "build",
160
+ "home.job.kind.sim": "sim",
161
+ "home.loadingHistory": "chargement de l\u2019historique\u2026",
162
+ "home.menu.job": "Lancer un job",
163
+ "home.menu.job.hint": "int\xE9grer un mod\xE8le (Hugging Face ou fichier local)",
164
+ "home.menu.lang.hint": "\u2190/\u2192 pour changer",
165
+ "home.menu.logout": "Se d\xE9connecter",
166
+ "home.menu.orgs": "Changer d'organisation",
167
+ "home.menu.quota": "Quota de build",
168
+ "home.menu.quota.hint": "builds restants",
169
+ "home.menu.refresh": "Rafra\xEEchir l\u2019historique",
170
+ "home.menu.simulate": "Lancer une simulation",
171
+ "home.menu.simulate.hint": "t\xE2che libre \u2014 dry-run du pipeline complet",
172
+ "home.menu.uninstall": "D\xE9sinstaller sumdae",
173
+ "home.menu.uninstall.hint": "supprimer la CLI de cette machine",
174
+ "home.menu.whoami": "Qui suis-je",
175
+ "home.menu.whoami.hint": "profil complet",
176
+ "home.noActiveOrg": "\u2014 aucune organisation",
177
+ "home.noJobsYet": "aucun job pour le moment \u2014 lancez une simulation pour commencer",
178
+ "home.recentJobs": "derniers jobs",
179
+ "home.sessionLabel": "session : {session}",
180
+ "home.static.orgLine": "organisation : {name}",
181
+ "home.static.orgNone": "organisation : \u2014 (sumdae org switch)",
182
+ "home.static.sessionApiKey": "session : cl\xE9 API",
183
+ "home.static.sessionOauth": "session : oauth",
184
+ "job.fetching": "r\xE9cup\xE9ration du job\u2026",
185
+ "job.file.err.empty.title": "fichier vide",
186
+ "job.file.err.notFile.detail": "{path} est un dossier \u2014 fournis un fichier de poids ou une archive.",
187
+ "job.file.err.notFile.title": "pas un fichier",
188
+ "job.file.err.notFound.title": "fichier introuvable",
189
+ "job.file.placeholder": "ex. : ~/models/mistral-7b.safetensors ou ./weights.tar.gz",
190
+ "job.file.title": "Fichier de poids / archive locale",
191
+ "job.fileConfirm.changeFile": "Changer de fichier",
192
+ "job.fileConfirm.fileLabel": "fichier : ",
193
+ "job.fileConfirm.gotItLaunch": "J'ai compris \u2014 lancer le job",
194
+ "job.fileConfirm.title": "envoi direct vers l'environnement",
195
+ "job.fileConfirm.warnDirect": "\u26A0 le fichier est stream\xE9 DIRECTEMENT de cette machine vers l'environnement (il ne transite par aucun serveur interm\xE9diaire).",
196
+ "job.fileConfirm.warnKeepAlive": "garde ta machine allum\xE9e et ta connexion stable pendant tout l'envoi : si la connexion est interrompue, le job est coup\xE9 automatiquement.",
197
+ "job.hf.checking": "v\xE9rification de l'acc\xE8s Hugging Face\u2026",
198
+ "job.hf.err.checkFailed.title": "v\xE9rification Hugging Face \xE9chou\xE9e",
199
+ "job.hf.err.gated.detail": "Le repo est gated : accepte ses conditions sur huggingface.co avec le compte du token, ou v\xE9rifie que le token a bien l'acc\xE8s en lecture.",
200
+ "job.hf.err.gated.title": "acc\xE8s refus\xE9 \xE0 ce mod\xE8le",
201
+ "job.hf.err.invalidToken.detail": "Le token configur\xE9 pour ton organisation est invalide ou r\xE9voqu\xE9 \u2014 mets-le \xE0 jour dans lab.sumdae.fr \u2192 \xAB Param\xE8tres org. \xBB.",
202
+ "job.hf.err.invalidToken.title": "token Hugging Face invalide",
203
+ "job.hf.err.noToken.detail": "Ton organisation n'a pas de token Hugging Face configur\xE9.\nAjoute un token READ : lab.sumdae.fr \u2192 menu du compte (en bas de la sidebar) \u2192 \xAB Param\xE8tres org. \xBB \u2192 Hugging Face token.\nCr\xE9e le token sur huggingface.co \u2192 Settings \u2192 Access Tokens (Read).",
204
+ "job.hf.err.noToken.title": "token Hugging Face manquant",
205
+ "job.hf.err.notFound.detail": "Repo ou r\xE9vision inexistants sur Hugging Face \u2014 ou repo priv\xE9 invisible pour ce token. V\xE9rifie le lien.",
206
+ "job.hf.err.notFound.title": "mod\xE8le introuvable",
207
+ "job.hf.invalidRef.detail": "Formats accept\xE9s : owner/repo, owner/repo@revision, ou une URL huggingface.co compl\xE8te.",
208
+ "job.hf.invalidRef.title": "r\xE9f\xE9rence invalide",
209
+ "job.hf.noToken": "aucun token HF configur\xE9 \u2014 ajoutez-en un dans Param\xE8tres org. sur lab.sumdae.fr",
210
+ "job.hf.placeholder": "ex. : mistralai/Mistral-7B-v0.1, owner/repo@rev, ou URL huggingface.co",
211
+ "job.hf.title": "Mod\xE8le Hugging Face",
212
+ "job.hf.tokenLabel": "token HF : ",
213
+ "job.hf.tokenMasked": "({masked})",
214
+ "job.hfConfirm.editLink": "Modifier le lien",
215
+ "job.hfConfirm.explain": "le mod\xE8le sera t\xE9l\xE9charg\xE9 sur l'environnement, int\xE9gr\xE9 dans la template, test\xE9, puis l'image Docker sera publi\xE9e. 1 build de quota sera consomm\xE9.",
216
+ "job.hfConfirm.gated": "\xB7 repo gated (acc\xE8s accord\xE9)",
217
+ "job.hfConfirm.launch": "Lancer le job",
218
+ "job.hfConfirm.repo": "repo : ",
219
+ "job.hfConfirm.sizeEstimate": "taille estim\xE9e : ",
220
+ "job.hfConfirm.sizeUnknown": "inconnue",
221
+ "job.hfConfirm.title": "mod\xE8le v\xE9rifi\xE9 \u2014 acc\xE8s OK",
222
+ "job.noUploadToken": "r\xE9ponse serveur sans upload_token",
223
+ "job.notFound": "job introuvable",
224
+ "job.source.file": "Fichier local",
225
+ "job.source.file.hint": "stream\xE9 depuis cette machine vers l\u2019environnement",
226
+ "job.source.hf": "Lien Hugging Face",
227
+ "job.source.hf.hint": "t\xE9l\xE9charg\xE9 directement sur l\u2019environnement (token org requis)",
228
+ "job.source.question": "d'o\xF9 viennent les poids du mod\xE8le \xE0 int\xE9grer ?",
229
+ "job.source.title": "lancer un job",
230
+ "jobLine.relative.daysAgo": "il y a {n} j",
231
+ "jobLine.relative.hoursAgo": "il y a {n} h",
232
+ "jobLine.relative.minutesAgo": "il y a {n} min",
233
+ "jobLine.relative.secondsAgo": "il y a {n} s",
234
+ "jobSummary.costAgentValue": "{cost} \xB7 {calls} appel(s)",
235
+ "jobSummary.row.costAgent": "co\xFBt agent",
236
+ "jobSummary.row.costEnv": "co\xFBt environnement",
237
+ "jobSummary.row.costTotal": "co\xFBt total",
238
+ "jobSummary.row.duration": "dur\xE9e",
239
+ "jobSummary.row.environment": "environnement",
240
+ "jobSummary.row.error": "erreur",
241
+ "jobSummary.row.status": "statut",
242
+ "jobSummary.row.task": "t\xE2che",
243
+ "jobSummary.row.type": "type",
244
+ "jobSummary.row.weights": "poids",
245
+ "jobSummary.type.build": "build",
246
+ "jobSummary.type.simulation": "simulation",
247
+ "keycaps.back": "retour",
248
+ "keycaps.cancel": "annuler",
249
+ "keycaps.checkAccess": "v\xE9rifier l\u2019acc\xE8s",
250
+ "keycaps.escBack": "retour",
251
+ "keycaps.navigate": "naviguer",
252
+ "keycaps.newline": "nouvelle ligne",
253
+ "keycaps.send": "envoyer",
254
+ "keycaps.switchZone": "basculer zone",
255
+ "keycaps.validate": "valider",
256
+ "landing.howConnect": "comment voulez-vous vous connecter ?",
257
+ "landing.menu.browser": "Se connecter via le navigateur",
258
+ "landing.menu.browser.hint": "OAuth + PKCE",
259
+ "landing.menu.key": "Coller une cl\xE9 API",
260
+ "landing.menu.key.hint": "lab_\u2026 \xB7 headless",
261
+ "landing.menu.later": "Plus tard",
262
+ "landing.menu.later.hint": "`sumdae login` quand vous voulez",
263
+ "login.browser.copyUrl": "si rien ne s'ouvre, copiez cette URL :",
264
+ "login.browser.openUrl": "ouvrez cette URL pour vous connecter :",
265
+ "login.connectedBody": "Vous pouvez fermer cet onglet et revenir au terminal.",
266
+ "login.connectedTitle": "\u2713 Connect\xE9",
267
+ "login.err.invalidState": "state invalide (possible CSRF) \u2014 flow abandonn\xE9",
268
+ "login.err.missingCode": "code manquant dans le callback",
269
+ "login.err.noBrowserResponse": "aucune r\xE9ponse du navigateur apr\xE8s {min} min",
270
+ "login.err.serverRefused": "refus\xE9 par le serveur d'identit\xE9 : {error}",
271
+ "login.failedTitle": "\u2717 \xC9chec de la connexion",
272
+ "login.invalidKey": "cl\xE9 API invalide",
273
+ "login.key.placeholder": "lab_\u2026",
274
+ "login.key.prompt": "cl\xE9 API (lab_\u2026) : ",
275
+ "login.key.title": "Cl\xE9 API",
276
+ "login.noKeyProvided": "aucune cl\xE9 fournie",
277
+ "login.noOrgCreateThenSwitch": "choisissez une organisation avec `sumdae org switch` (TTY)",
278
+ "login.nonInteractiveTty": "terminal non interactif \u2014 utilisez `sumdae login --no-browser` dans un TTY, ou `--with-key`",
279
+ "login.validatingKey": "validation de la cl\xE9\u2026",
280
+ "logout.done": "d\xE9connect\xE9",
281
+ "memory.duplicate": "LE\xC7ON D\xC9J\xC0 CONNUE",
282
+ "memory.query": "M\xC9MOIRE",
283
+ "memory.resultHit": "LE\xC7ON TROUV\xC9E",
284
+ "memory.resultMiss": "RIEN EN M\xC9MOIRE",
285
+ "memory.saved": "LE\xC7ON ENREGISTR\xC9E",
286
+ "memory.scorePrefix": "score {score} ",
287
+ "noOrg.continue": "Continuer sans organisation",
288
+ "noOrg.createThenReturn": "cr\xE9ez-en une sur lab.sumdae.fr puis revenez ici.",
289
+ "noOrg.none": "aucune organisation associ\xE9e \xE0 ce compte.",
290
+ "noOrg.retry": "R\xE9essayer",
291
+ "noOrg.retry.hint": "recharger vos organisations",
292
+ "orgs.fetchFailed": "impossible de r\xE9cup\xE9rer les organisations",
293
+ "orgs.loading": "chargement des organisations\u2026",
294
+ "orgs.nonInteractiveTty": "terminal non interactif \u2014 impossible de choisir une organisation",
295
+ "orgs.role.member": "member",
296
+ "progress.build": "BUILD",
297
+ "progress.download": "T\xC9L\xC9CHARGEMENT",
298
+ "progress.elapsed": " \xB7 {duration} \xE9coul\xE9",
299
+ "progress.generic": "\xC9TAPE",
300
+ "progress.inProgress": "en cours",
301
+ "progress.install": "INSTALLATION",
302
+ "progress.push": "PUSH IMAGE",
303
+ "progress.transferred": " transf\xE9r\xE9s",
304
+ "progress.waitingNetwork": " \xB7 en attente du r\xE9seau\u2026 {duration}",
305
+ "promptBox.hint": "entr\xE9e = envoyer \xB7 \\ + entr\xE9e = nouvelle ligne \xB7 esc = retour",
306
+ "push.failed.title": "push \xE9chou\xE9",
307
+ "push.hashing": "sha256 de {filePath}\u2026",
308
+ "push.inferKindFailed": "impossible de d\xE9duire le type \u2014 pr\xE9cisez --kind weights ou --kind pipeline",
309
+ "push.uploaded": "artifact upload\xE9",
310
+ "quota.exhausted": "quota \xE9puis\xE9 \u2014 demandez \xE0 l'admin d'augmenter build_quota",
311
+ "quota.loading": "quota de build\u2026",
312
+ "quota.row.note": "note",
313
+ "quota.row.remaining": "builds restants",
314
+ "quota.unreachable": "quota de build inaccessible",
315
+ "selectList.hint": "\u2191\u2193 naviguer \xB7 entr\xE9e valider",
316
+ "simulate.creating": "cr\xE9ation de la simulation\u2026",
317
+ "simulate.impossible": "simulation impossible",
318
+ "simulate.placeholder": "ex. : cr\xE9e /workspace/hello.txt avec \xAB bonjour \xBB et liste le dossier",
319
+ "simulate.title": "T\xE2che pour le master \xB7 mode SIMULATION",
320
+ "uninstall.cancel": "Annuler",
321
+ "uninstall.cancel.hint": "ne rien supprimer",
322
+ "uninstall.channel.binary": "binaire",
323
+ "uninstall.channel.npm": "npm (global)",
324
+ "uninstall.channel.selfhosted": "auto-h\xE9berg\xE9",
325
+ "uninstall.cli.cancelled": "annul\xE9.",
326
+ "uninstall.cli.channel": " canal : {channel}",
327
+ "uninstall.cli.cleaned": "config et identifiants nettoy\xE9s ; l\u2019install sera effac\xE9e \xE0 la fermeture.",
328
+ "uninstall.cli.config": " config : {configDir}",
329
+ "uninstall.cli.credentials": " credentials : trousseau + fichier",
330
+ "uninstall.cli.heading": "D\xE9sinstallation de sumdae :",
331
+ "uninstall.cli.installGlobal": "npm uninstall -g (global)",
332
+ "uninstall.cli.installLine": " install : {install}",
333
+ "uninstall.cli.installManual": " install : manuel \u2192 {hint}",
334
+ "uninstall.cli.launcher": " launcher : {launcher}",
335
+ "uninstall.cli.manualFallback": "\xE0 faire manuellement",
336
+ "uninstall.cli.stepFailed": "\xE9chec",
337
+ "uninstall.close": "Fermer",
338
+ "uninstall.config.now": "{configDir} (maintenant)",
339
+ "uninstall.confirm.title": "D\xE9sinstaller sumdae",
340
+ "uninstall.confirm.warning": "ceci supprimera d\xE9finitivement la CLI et toutes ses donn\xE9es de cette machine :",
341
+ "uninstall.confirmDelete": "Oui, tout supprimer",
342
+ "uninstall.credentials.keychainFileNow": "trousseau + fichier (maintenant)",
343
+ "uninstall.install.atClose": "{root} (\xE0 la fermeture)",
344
+ "uninstall.install.manual": "\u26A0 manuel \u2192 {hint}",
345
+ "uninstall.install.npmAtClose": "npm uninstall -g @sumdaelive/cli (\xE0 la fermeture)",
346
+ "uninstall.report.done": "config et identifiants supprim\xE9s. sumdae sera effac\xE9 \xE0 la fermeture de ce terminal.",
347
+ "uninstall.report.someFailed": "certaines suppressions ont \xE9chou\xE9 \u2014 voir ci-dessus.",
348
+ "uninstall.report.title": "D\xE9sinstallation",
349
+ "uninstall.row.channel": "canal",
350
+ "uninstall.row.config": "config",
351
+ "uninstall.row.credentials": "credentials",
352
+ "uninstall.row.install": "install",
353
+ "uninstall.scheduleFailed": "planification \xE9chou\xE9e ({err}) \u2014 manuel : {manual}",
354
+ "uninstall.working": "suppression en cours\u2026",
355
+ "update.doneRestarting": "\u2713 install\xE9 \u2014 red\xE9marrage\u2026",
356
+ "update.downloading": "t\xE9l\xE9chargement\u2026",
357
+ "update.err.extractFailed": "extraction tar \xE9chou\xE9e",
358
+ "update.err.invalidSignature": "signature invalide \u2014 mise \xE0 jour refus\xE9e",
359
+ "update.err.npmFailed": "npm install a \xE9chou\xE9",
360
+ "update.err.sha256Mismatch": "sha256 du tarball ne correspond pas",
361
+ "update.failed": "\xE9chec de la mise \xE0 jour",
362
+ "update.forcedFailed": "Cette version ({current}) n'est plus support\xE9e et la mise \xE0 jour a \xE9chou\xE9 : {err}\nR\xE9essayez, ou r\xE9installez : https://lab.sumdae.fr/cli",
363
+ "update.installing": "installation\u2026",
364
+ "update.screen.title": "Mise \xE0 jour",
365
+ "update.verifyingSignature": "v\xE9rification de la signature\u2026",
366
+ "upload.bytesSent": "({bytes} envoy\xE9s)",
367
+ "upload.cancelledBeforeStart": "annul\xE9 avant le d\xE9but de l\u2019upload \u2014 le job sera coup\xE9 automatiquement c\xF4t\xE9 serveur (quota rembours\xE9).",
368
+ "upload.connInterrupted": "connexion interrompue pendant l'upload \u2014 le job va \xEAtre coup\xE9 c\xF4t\xE9 serveur ({detail})",
369
+ "upload.envNotReady": "l'environnement n'a pas \xE9t\xE9 pr\xEAt \xE0 temps pour recevoir le fichier",
370
+ "upload.envRate": "d\xE9bit environnement : ",
371
+ "upload.err.cancelled": "upload annul\xE9",
372
+ "upload.err.missingEtag": "ETag manquant dans la r\xE9ponse R2",
373
+ "upload.err.missingPresigned": "URL pr\xE9sign\xE9e manquante pour la part {n}",
374
+ "upload.err.partFailed": "part {n}/{total} en \xE9chec apr\xE8s {tries} tentatives : {msg}",
375
+ "upload.etaLabel": "temps restant estim\xE9 : ",
376
+ "upload.interrupted.title": "upload interrompu",
377
+ "upload.jobCancelled": "job annul\xE9",
378
+ "upload.jobFailedDuring": "le job a \xE9chou\xE9 pendant l'upload{suffix}",
379
+ "upload.localRate": "d\xE9bit local : ",
380
+ "upload.title": "envoi direct vers l'environnement",
381
+ "upload.verifying": "v\xE9rification du fichier sur l'environnement\u2026",
382
+ "upload.waiting": "r\xE9servation de l'environnement et ouverture du canal\u2026",
383
+ "upload.warnKeepAlive": "\u26A0 garde ta machine allum\xE9e et ta connexion stable \u2014 si l'envoi est interrompu, le job est coup\xE9.",
384
+ "watch.buildPrefix": "build ",
385
+ "watch.cancel.confirm": "annuler ce job pour de bon ?",
386
+ "watch.cancel.confirmKeys": "x/y confirmer \xB7 n ou \xE9chap annuler",
387
+ "watch.cancel.error": "\xE9chec de la demande d\u2019annulation \u2014 r\xE9essayez avec x",
388
+ "watch.cancel.sent": "annulation demand\xE9e \u2014 arr\xEAt de l'environnement en cours\u2026",
389
+ "watch.eventStream": "flux d'\xE9v\xE9nements",
390
+ "watch.keycap.cancelBuild": "annuler le build",
391
+ "watch.status.bootstrapping": "pr\xE9paration de l'environnement",
392
+ "watch.status.cancelled": "annul\xE9",
393
+ "watch.status.created": "cr\xE9\xE9",
394
+ "watch.status.failed": "\xE9chou\xE9",
395
+ "watch.status.provisioning": "r\xE9servation de l'environnement",
396
+ "watch.status.running": "agent sumdae au travail",
397
+ "watch.status.starting": "d\xE9marrage de l'environnement",
398
+ "watch.status.succeeded": "termin\xE9 \u2713",
399
+ "watch.status.terminating": "arr\xEAt en cours",
400
+ "whoami.loading": "profil\u2026",
401
+ "whoami.machine.unknownIp": "ip inconnue",
402
+ "whoami.org.none": "\u2014 aucune",
403
+ "whoami.org.noneSwitch": "\u2014 aucune (sumdae org switch)",
404
+ "whoami.row.email": "email",
405
+ "whoami.row.machine": "machine",
406
+ "whoami.row.org": "org",
407
+ "whoami.row.session": "session",
408
+ "whoami.row.user": "user",
409
+ "whoami.session.apiKey": "cl\xE9 API",
410
+ "whoami.session.oauthExpires": "oauth \xB7 access token expire \xE0 {time}",
411
+ "whoami.unreachable": "profil inaccessible"
412
+ },
413
+ en: {
414
+ "agentFeed.agent.subagent": "subagent {n}",
415
+ "agentFeed.agent.sumdae": "sumdae agent",
416
+ "agentFeed.diff.moreLines": "\u2026 ({n} more lines)",
417
+ "agentFeed.lagged": "\u2026 (stream caught up)",
418
+ "agentFeed.result.failed": "failed",
419
+ "agentFeed.result.ok": "ok",
420
+ "agentFeed.shell": "shell",
421
+ "api.authRequired": "session expired or missing \u2014 run `sumdae login`",
422
+ "api.permissionDenied": "You don't have permission to perform this action.",
423
+ "api.quotaInsufficient": "insufficient build quota: {remaining} remaining, {needed} required",
424
+ "api.quotaInvalidResponse": "invalid quota response \u2014 can't verify, refused as a precaution",
425
+ "api.tokenExchangeFailed": "token exchange failed",
426
+ "banner.tagline": "the sumdae platform, from the terminal",
427
+ "buildCmd.buildLaunched": "build {buildId} started \u2014 quota consumed",
428
+ "buildCmd.cancelRequested": "cancellation requested \u2014 watching until full shutdown:",
429
+ "buildCmd.commandSent": "command \u201C{action}\u201D sent to {agent}",
430
+ "buildCmd.invalidAction": "invalid action \u2014 'kill' or 'message'",
431
+ "buildCmd.noActiveOrgForCancel": "no active organization",
432
+ "buildCmd.simEmpty": 'empty task \u2014 give the master an instruction, e.g. sumdae simulate "create /workspace/hello.txt and list the folder"',
433
+ "buildCmd.simLaunched": "simulation {buildId} started \u2014 quota consumed \xB7 pod rented, agents at work",
434
+ "buildSummary.heading": "job summary",
435
+ "buildSummary.rewatch": "Replay the full stream",
436
+ "buildSummary.rewatch.hint": "replay the events",
437
+ "buildSummary.watchLive": "View the running job",
438
+ "buildWatch.connectionLost": "lost connection to the build (30 attempts) \u2014 re-run `sumdae watch`",
439
+ "buildWatch.refused": "watch refused (HTTP {status})",
440
+ "cli.agent.description": "control a build's agents",
441
+ "cli.agent.kill.description": "stop an agent (default: master)",
442
+ "cli.agent.msg.description": "send a message to a running agent",
443
+ "cli.agent.opt.agent": "master | subagent-N | all",
444
+ "cli.build.description": "start a Docker build (consumes 1 quota) and follow live",
445
+ "cli.build.opt.pipeline": "pipeline artifact to integrate",
446
+ "cli.cancel.description": "cancel a build (environment released, quota refunded if nothing started)",
447
+ "cli.login.description": "sign in to sumdae (OAuth + PKCE)",
448
+ "cli.login.opt.noBrowser": "print the URL instead of opening the browser",
449
+ "cli.login.opt.withKey": "paste an API key lab_\u2026 (headless)",
450
+ "cli.logout.description": "sign out",
451
+ "cli.org.description": "manage organizations",
452
+ "cli.org.list.description": "list your organizations",
453
+ "cli.org.switch.description": "switch the active organization",
454
+ "cli.push.description": "upload weights or a pipeline to the active organization",
455
+ "cli.push.opt.kind": "weights | pipeline (inferred from the extension otherwise)",
456
+ "cli.quota.description": "remaining builds of the active organization",
457
+ "cli.root.description": "sumdae platform CLI",
458
+ "cli.simulate.description": "start a SIMULATION build \u2014 no model/pipeline needed (consumes 1 quota)",
459
+ "cli.uninstall.description": "cleanly remove sumdae from this machine (all channels)",
460
+ "cli.uninstall.opt.yes": "don't ask for confirmation (headless)",
461
+ "cli.watch.description": "follow a running build (real-time WS)",
462
+ "cli.whoami.description": "profile + active organization",
463
+ "common.activeOrgLabel": "active organization:",
464
+ "common.activeOrgSet": "active organization: {name}",
465
+ "common.back": "Go back",
466
+ "common.backToHome": "Back to home",
467
+ "common.connectedAs": "signed in: {email}",
468
+ "common.creatingJob": "creating job\u2026",
469
+ "common.jobImpossible": "job failed",
470
+ "common.loginFailed": "sign-in failed",
471
+ "common.noActiveOrgRunSwitch": "no active organization \u2014 run `sumdae org switch`",
472
+ "common.noOrgCreateOnLab": "no organization \u2014 create one on lab.sumdae.fr",
473
+ "common.orEscape": "or Esc",
474
+ "common.quit": "Quit",
475
+ "common.signingOut": "signing out\u2026",
476
+ "common.terminalCurrent": "(current: {cols}\xD7{rows})",
477
+ "common.terminalEnlarge": "Enlarge to at least {cols}\xD7{rows}",
478
+ "common.terminalTooSmall": "Terminal too small",
479
+ "common.truncated": "\u2026 (truncated)",
480
+ "common.waitingBrowserLogin": "waiting for browser sign-in\u2026",
481
+ "credentials.keychainFallback": "\u26A0 system keychain unavailable \u2014 credentials stored in {path} (chmod 600)",
482
+ "credentials.keyringUnavailable": "\u26A0 system keychain unavailable \u2014 credentials stored in {filePath} (chmod 600)",
483
+ "directUpload.err.refused": "upload refused by the environment (HTTP {status})",
484
+ "help.build": "start a build + live tracking",
485
+ "help.cancel": "cancel a build cleanly",
486
+ "help.heading": "COMMANDS \xB7 v{version}",
487
+ "help.login": "sign in (OAuth + PKCE)",
488
+ "help.logout": "sign out",
489
+ "help.push": "upload weights/pipeline (R2)",
490
+ "help.quota": "remaining builds of the active org",
491
+ "help.watch": "follow a running build",
492
+ "home.actions": "actions",
493
+ "home.job.kind.build": "build",
494
+ "home.job.kind.sim": "sim",
495
+ "home.loadingHistory": "loading history\u2026",
496
+ "home.menu.job": "Start a job",
497
+ "home.menu.job.hint": "integrate a model (Hugging Face or local file)",
498
+ "home.menu.lang.hint": "\u2190/\u2192 to switch",
499
+ "home.menu.logout": "Sign out",
500
+ "home.menu.orgs": "Switch organization",
501
+ "home.menu.quota": "Build quota",
502
+ "home.menu.quota.hint": "builds remaining",
503
+ "home.menu.refresh": "Refresh history",
504
+ "home.menu.simulate": "Start a simulation",
505
+ "home.menu.simulate.hint": "free task \u2014 dry-run of the full pipeline",
506
+ "home.menu.uninstall": "Uninstall sumdae",
507
+ "home.menu.uninstall.hint": "remove the CLI from this machine",
508
+ "home.menu.whoami": "Who am I",
509
+ "home.menu.whoami.hint": "full profile",
510
+ "home.noActiveOrg": "\u2014 no organization",
511
+ "home.noJobsYet": "no jobs yet \u2014 start a simulation to begin",
512
+ "home.recentJobs": "recent jobs",
513
+ "home.sessionLabel": "session: {session}",
514
+ "home.static.orgLine": "organization: {name}",
515
+ "home.static.orgNone": "organization: \u2014 (sumdae org switch)",
516
+ "home.static.sessionApiKey": "session: API key",
517
+ "home.static.sessionOauth": "session: oauth",
518
+ "job.fetching": "fetching job\u2026",
519
+ "job.file.err.empty.title": "empty file",
520
+ "job.file.err.notFile.detail": "{path} is a folder \u2014 provide a weights file or an archive.",
521
+ "job.file.err.notFile.title": "not a file",
522
+ "job.file.err.notFound.title": "file not found",
523
+ "job.file.placeholder": "e.g. ~/models/mistral-7b.safetensors or ./weights.tar.gz",
524
+ "job.file.title": "Weights file / local archive",
525
+ "job.fileConfirm.changeFile": "Change file",
526
+ "job.fileConfirm.fileLabel": "file: ",
527
+ "job.fileConfirm.gotItLaunch": "Got it \u2014 start the job",
528
+ "job.fileConfirm.title": "direct upload to the environment",
529
+ "job.fileConfirm.warnDirect": "\u26A0 the file is streamed DIRECTLY from this machine to the environment (it passes through no intermediate server).",
530
+ "job.fileConfirm.warnKeepAlive": "keep your machine on and your connection stable for the whole upload: if the connection drops, the job is cut automatically.",
531
+ "job.hf.checking": "checking Hugging Face access\u2026",
532
+ "job.hf.err.checkFailed.title": "Hugging Face check failed",
533
+ "job.hf.err.gated.detail": "The repo is gated: accept its terms on huggingface.co with the token's account, or check that the token has read access.",
534
+ "job.hf.err.gated.title": "access denied to this model",
535
+ "job.hf.err.invalidToken.detail": "The token configured for your organization is invalid or revoked \u2014 update it in lab.sumdae.fr \u2192 \u201COrg. settings\u201D.",
536
+ "job.hf.err.invalidToken.title": "Hugging Face token invalid",
537
+ "job.hf.err.noToken.detail": "Your organization has no Hugging Face token configured.\nAdd a READ token: lab.sumdae.fr \u2192 account menu (bottom of the sidebar) \u2192 \u201COrg. settings\u201D \u2192 Hugging Face token.\nCreate the token on huggingface.co \u2192 Settings \u2192 Access Tokens (Read).",
538
+ "job.hf.err.noToken.title": "Hugging Face token missing",
539
+ "job.hf.err.notFound.detail": "Repo or revision doesn't exist on Hugging Face \u2014 or a private repo invisible to this token. Check the link.",
540
+ "job.hf.err.notFound.title": "model not found",
541
+ "job.hf.invalidRef.detail": "Accepted formats: owner/repo, owner/repo@revision, or a full huggingface.co URL.",
542
+ "job.hf.invalidRef.title": "invalid reference",
543
+ "job.hf.noToken": "no HF token configured \u2014 add one in Org. settings on lab.sumdae.fr",
544
+ "job.hf.placeholder": "e.g. mistralai/Mistral-7B-v0.1, owner/repo@rev, or a huggingface.co URL",
545
+ "job.hf.title": "Hugging Face model",
546
+ "job.hf.tokenLabel": "HF token: ",
547
+ "job.hf.tokenMasked": "({masked})",
548
+ "job.hfConfirm.editLink": "Edit the link",
549
+ "job.hfConfirm.explain": "the model will be downloaded on the environment, integrated into the template, tested, then the Docker image will be published. 1 build of quota will be consumed.",
550
+ "job.hfConfirm.gated": "\xB7 gated repo (access granted)",
551
+ "job.hfConfirm.launch": "Start the job",
552
+ "job.hfConfirm.repo": "repo: ",
553
+ "job.hfConfirm.sizeEstimate": "estimated size: ",
554
+ "job.hfConfirm.sizeUnknown": "unknown",
555
+ "job.hfConfirm.title": "model verified \u2014 access OK",
556
+ "job.noUploadToken": "server response without upload_token",
557
+ "job.notFound": "job not found",
558
+ "job.source.file": "Local file",
559
+ "job.source.file.hint": "streamed from this machine to the environment",
560
+ "job.source.hf": "Hugging Face link",
561
+ "job.source.hf.hint": "downloaded directly on the environment (org token required)",
562
+ "job.source.question": "where do the model weights to integrate come from?",
563
+ "job.source.title": "start a job",
564
+ "jobLine.relative.daysAgo": "{n} d ago",
565
+ "jobLine.relative.hoursAgo": "{n} h ago",
566
+ "jobLine.relative.minutesAgo": "{n} min ago",
567
+ "jobLine.relative.secondsAgo": "{n} s ago",
568
+ "jobSummary.costAgentValue": "{cost} \xB7 {calls} call(s)",
569
+ "jobSummary.row.costAgent": "agent cost",
570
+ "jobSummary.row.costEnv": "environment cost",
571
+ "jobSummary.row.costTotal": "total cost",
572
+ "jobSummary.row.duration": "duration",
573
+ "jobSummary.row.environment": "environment",
574
+ "jobSummary.row.error": "error",
575
+ "jobSummary.row.status": "status",
576
+ "jobSummary.row.task": "task",
577
+ "jobSummary.row.type": "type",
578
+ "jobSummary.row.weights": "weights",
579
+ "jobSummary.type.build": "build",
580
+ "jobSummary.type.simulation": "simulation",
581
+ "keycaps.back": "back",
582
+ "keycaps.cancel": "cancel",
583
+ "keycaps.checkAccess": "check access",
584
+ "keycaps.escBack": "back",
585
+ "keycaps.navigate": "navigate",
586
+ "keycaps.newline": "new line",
587
+ "keycaps.send": "send",
588
+ "keycaps.switchZone": "switch zone",
589
+ "keycaps.validate": "confirm",
590
+ "landing.howConnect": "how do you want to sign in?",
591
+ "landing.menu.browser": "Sign in via browser",
592
+ "landing.menu.browser.hint": "OAuth + PKCE",
593
+ "landing.menu.key": "Paste an API key",
594
+ "landing.menu.key.hint": "lab_\u2026 \xB7 headless",
595
+ "landing.menu.later": "Later",
596
+ "landing.menu.later.hint": "`sumdae login` whenever you want",
597
+ "login.browser.copyUrl": "if nothing opens, copy this URL:",
598
+ "login.browser.openUrl": "open this URL to sign in:",
599
+ "login.connectedBody": "You can close this tab and return to the terminal.",
600
+ "login.connectedTitle": "\u2713 Signed in",
601
+ "login.err.invalidState": "invalid state (possible CSRF) \u2014 flow aborted",
602
+ "login.err.missingCode": "missing code in the callback",
603
+ "login.err.noBrowserResponse": "no response from the browser after {min} min",
604
+ "login.err.serverRefused": "refused by the identity server: {error}",
605
+ "login.failedTitle": "\u2717 Sign-in failed",
606
+ "login.invalidKey": "invalid API key",
607
+ "login.key.placeholder": "lab_\u2026",
608
+ "login.key.prompt": "API key (lab_\u2026): ",
609
+ "login.key.title": "API key",
610
+ "login.noKeyProvided": "no key provided",
611
+ "login.noOrgCreateThenSwitch": "pick an organization with `sumdae org switch` (TTY)",
612
+ "login.nonInteractiveTty": "non-interactive terminal \u2014 use `sumdae login --no-browser` in a TTY, or `--with-key`",
613
+ "login.validatingKey": "validating key\u2026",
614
+ "logout.done": "signed out",
615
+ "memory.duplicate": "LESSON ALREADY KNOWN",
616
+ "memory.query": "MEMORY",
617
+ "memory.resultHit": "LESSON FOUND",
618
+ "memory.resultMiss": "NOTHING IN MEMORY",
619
+ "memory.saved": "LESSON SAVED",
620
+ "memory.scorePrefix": "score {score} ",
621
+ "noOrg.continue": "Continue without organization",
622
+ "noOrg.createThenReturn": "create one on lab.sumdae.fr then come back here.",
623
+ "noOrg.none": "no organization linked to this account.",
624
+ "noOrg.retry": "Retry",
625
+ "noOrg.retry.hint": "reload your organizations",
626
+ "orgs.fetchFailed": "could not fetch organizations",
627
+ "orgs.loading": "loading organizations\u2026",
628
+ "orgs.nonInteractiveTty": "non-interactive terminal \u2014 can't pick an organization",
629
+ "orgs.role.member": "member",
630
+ "progress.build": "BUILD",
631
+ "progress.download": "DOWNLOAD",
632
+ "progress.elapsed": " \xB7 {duration} elapsed",
633
+ "progress.generic": "STEP",
634
+ "progress.inProgress": "in progress",
635
+ "progress.install": "INSTALL",
636
+ "progress.push": "PUSH IMAGE",
637
+ "progress.transferred": " transferred",
638
+ "progress.waitingNetwork": " \xB7 waiting for network\u2026 {duration}",
639
+ "promptBox.hint": "enter = send \xB7 \\ + enter = new line \xB7 esc = back",
640
+ "push.failed.title": "push failed",
641
+ "push.hashing": "sha256 of {filePath}\u2026",
642
+ "push.inferKindFailed": "can't infer the type \u2014 specify --kind weights or --kind pipeline",
643
+ "push.uploaded": "artifact uploaded",
644
+ "quota.exhausted": "quota exhausted \u2014 ask the admin to raise build_quota",
645
+ "quota.loading": "build quota\u2026",
646
+ "quota.row.note": "note",
647
+ "quota.row.remaining": "builds remaining",
648
+ "quota.unreachable": "build quota unavailable",
649
+ "selectList.hint": "\u2191\u2193 navigate \xB7 enter confirm",
650
+ "simulate.creating": "creating simulation\u2026",
651
+ "simulate.impossible": "simulation failed",
652
+ "simulate.placeholder": "e.g. create /workspace/hello.txt with \u201Chello\u201D and list the folder",
653
+ "simulate.title": "Task for the master \xB7 SIMULATION mode",
654
+ "uninstall.cancel": "Cancel",
655
+ "uninstall.cancel.hint": "delete nothing",
656
+ "uninstall.channel.binary": "binary",
657
+ "uninstall.channel.npm": "npm (global)",
658
+ "uninstall.channel.selfhosted": "self-hosted",
659
+ "uninstall.cli.cancelled": "cancelled.",
660
+ "uninstall.cli.channel": " channel : {channel}",
661
+ "uninstall.cli.cleaned": "config and credentials cleaned; the install will be erased on close.",
662
+ "uninstall.cli.config": " config : {configDir}",
663
+ "uninstall.cli.credentials": " credentials : keychain + file",
664
+ "uninstall.cli.heading": "Uninstalling sumdae:",
665
+ "uninstall.cli.installGlobal": "npm uninstall -g (global)",
666
+ "uninstall.cli.installLine": " install : {install}",
667
+ "uninstall.cli.installManual": " install : manual \u2192 {hint}",
668
+ "uninstall.cli.launcher": " launcher : {launcher}",
669
+ "uninstall.cli.manualFallback": "to do manually",
670
+ "uninstall.cli.stepFailed": "failed",
671
+ "uninstall.close": "Close",
672
+ "uninstall.config.now": "{configDir} (now)",
673
+ "uninstall.confirm.title": "Uninstall sumdae",
674
+ "uninstall.confirm.warning": "this will permanently remove the CLI and all its data from this machine:",
675
+ "uninstall.confirmDelete": "Yes, delete everything",
676
+ "uninstall.credentials.keychainFileNow": "keychain + file (now)",
677
+ "uninstall.install.atClose": "{root} (on close)",
678
+ "uninstall.install.manual": "\u26A0 manual \u2192 {hint}",
679
+ "uninstall.install.npmAtClose": "npm uninstall -g @sumdaelive/cli (on close)",
680
+ "uninstall.report.done": "config and credentials removed. sumdae will be erased when you close this terminal.",
681
+ "uninstall.report.someFailed": "some deletions failed \u2014 see above.",
682
+ "uninstall.report.title": "Uninstall",
683
+ "uninstall.row.channel": "channel",
684
+ "uninstall.row.config": "config",
685
+ "uninstall.row.credentials": "credentials",
686
+ "uninstall.row.install": "install",
687
+ "uninstall.scheduleFailed": "scheduling failed ({err}) \u2014 manual: {manual}",
688
+ "uninstall.working": "deleting\u2026",
689
+ "update.doneRestarting": "\u2713 installed \u2014 restarting\u2026",
690
+ "update.downloading": "downloading\u2026",
691
+ "update.err.extractFailed": "tar extraction failed",
692
+ "update.err.invalidSignature": "invalid signature \u2014 update refused",
693
+ "update.err.npmFailed": "npm install failed",
694
+ "update.err.sha256Mismatch": "tarball sha256 doesn't match",
695
+ "update.failed": "update failed",
696
+ "update.forcedFailed": "This version ({current}) is no longer supported and the update failed: {err}\nRetry, or reinstall: https://lab.sumdae.fr/cli",
697
+ "update.installing": "installing\u2026",
698
+ "update.screen.title": "Update",
699
+ "update.verifyingSignature": "verifying signature\u2026",
700
+ "upload.bytesSent": "({bytes} sent)",
701
+ "upload.cancelledBeforeStart": "cancelled before the upload started \u2014 the job will be cut automatically server-side (quota refunded).",
702
+ "upload.connInterrupted": "connection interrupted during the upload \u2014 the job will be cut server-side ({detail})",
703
+ "upload.envNotReady": "the environment wasn't ready in time to receive the file",
704
+ "upload.envRate": "environment rate: ",
705
+ "upload.err.cancelled": "upload cancelled",
706
+ "upload.err.missingEtag": "missing ETag in R2 response",
707
+ "upload.err.missingPresigned": "missing presigned URL for part {n}",
708
+ "upload.err.partFailed": "part {n}/{total} failed after {tries} attempts: {msg}",
709
+ "upload.etaLabel": "estimated time remaining: ",
710
+ "upload.interrupted.title": "upload interrupted",
711
+ "upload.jobCancelled": "job cancelled",
712
+ "upload.jobFailedDuring": "the job failed during the upload{suffix}",
713
+ "upload.localRate": "local rate: ",
714
+ "upload.title": "direct upload to the environment",
715
+ "upload.verifying": "verifying the file on the environment\u2026",
716
+ "upload.waiting": "reserving the environment and opening the channel\u2026",
717
+ "upload.warnKeepAlive": "\u26A0 keep your machine on and your connection stable \u2014 if the upload is interrupted, the job is cut.",
718
+ "watch.buildPrefix": "build ",
719
+ "watch.cancel.confirm": "cancel this job for good?",
720
+ "watch.cancel.confirmKeys": "x/y confirm \xB7 n or esc cancel",
721
+ "watch.cancel.error": "cancellation request failed \u2014 retry with x",
722
+ "watch.cancel.sent": "cancellation requested \u2014 shutting down the environment\u2026",
723
+ "watch.eventStream": "event stream",
724
+ "watch.keycap.cancelBuild": "cancel the build",
725
+ "watch.status.bootstrapping": "preparing the environment",
726
+ "watch.status.cancelled": "cancelled",
727
+ "watch.status.created": "created",
728
+ "watch.status.failed": "failed",
729
+ "watch.status.provisioning": "reserving the environment",
730
+ "watch.status.running": "sumdae agent at work",
731
+ "watch.status.starting": "starting the environment",
732
+ "watch.status.succeeded": "done \u2713",
733
+ "watch.status.terminating": "shutting down",
734
+ "whoami.loading": "profile\u2026",
735
+ "whoami.machine.unknownIp": "unknown ip",
736
+ "whoami.org.none": "\u2014 none",
737
+ "whoami.org.noneSwitch": "\u2014 none (sumdae org switch)",
738
+ "whoami.row.email": "email",
739
+ "whoami.row.machine": "machine",
740
+ "whoami.row.org": "org",
741
+ "whoami.row.session": "session",
742
+ "whoami.row.user": "user",
743
+ "whoami.session.apiKey": "API key",
744
+ "whoami.session.oauthExpires": "oauth \xB7 access token expires at {time}",
745
+ "whoami.unreachable": "profile unavailable"
746
+ }
747
+ };
748
+
749
+ // src/i18n.ts
750
+ function detectSystemLocale(env = process.env) {
751
+ const raw = env.LC_ALL || env.LC_MESSAGES || env.LANG || "";
752
+ return /^fr/i.test(raw.trim()) ? "fr" : "en";
753
+ }
754
+ var cache = null;
755
+ function getLocale() {
756
+ if (cache) return cache;
757
+ const cfg = loadConfig();
758
+ if (cfg.locale === "fr" || cfg.locale === "en") {
759
+ cache = cfg.locale;
760
+ return cache;
761
+ }
762
+ const detected = detectSystemLocale();
763
+ cache = detected;
764
+ try {
765
+ saveConfig({ ...loadConfig(), locale: detected });
766
+ } catch {
767
+ }
768
+ return cache;
769
+ }
770
+ function setLocale(l) {
771
+ cache = l;
772
+ try {
773
+ saveConfig({ ...loadConfig(), locale: l });
774
+ } catch {
775
+ }
776
+ }
777
+ function t(key, vars) {
778
+ const loc = getLocale();
779
+ const raw = CATALOG[loc]?.[key] ?? CATALOG.fr[key] ?? key;
780
+ if (!vars) return raw;
781
+ return raw.replace(
782
+ /\{(\w+)\}/g,
783
+ (m, name) => name in vars ? String(vars[name]) : m
784
+ );
785
+ }
786
+
787
+ // src/credentials.ts
76
788
  var SERVICE = "sumdae-cli";
77
789
  var ACCOUNT = "default";
78
790
  var filePath = () => join2(configDir(), "credentials.json");
@@ -80,7 +792,7 @@ var warnedFallback = false;
80
792
  function warnFallbackOnce() {
81
793
  if (warnedFallback) return;
82
794
  warnedFallback = true;
83
- console.error(`\u26A0 trousseau syst\xE8me indisponible \u2014 credentials stock\xE9s dans ${filePath()} (chmod 600)`);
795
+ console.error(t("credentials.keyringUnavailable", { filePath: filePath() }));
84
796
  }
85
797
  async function keyringEntry() {
86
798
  if (process.env.SUMDAE_NO_KEYRING === "1") return null;
@@ -146,7 +858,7 @@ import { randomUUID } from "crypto";
146
858
  import { arch as osArch, hostname, networkInterfaces, release, userInfo } from "os";
147
859
 
148
860
  // src/version.ts
149
- var CLI_VERSION = "0.1.3";
861
+ var CLI_VERSION = "0.1.5";
150
862
 
151
863
  // src/system.ts
152
864
  function getDeviceId() {
@@ -210,7 +922,7 @@ var ApiError = class extends Error {
210
922
  function humanizeError(e) {
211
923
  if (e instanceof ApiError) {
212
924
  if (e.status === 403 || e.code === "missing_permission" || e.code === "not_member" || e.code === "forbidden") {
213
- return "Vous n'avez pas la permission d'effectuer cette action.";
925
+ return t("api.permissionDenied");
214
926
  }
215
927
  return e.message;
216
928
  }
@@ -218,7 +930,7 @@ function humanizeError(e) {
218
930
  }
219
931
  var AuthRequiredError = class extends Error {
220
932
  constructor() {
221
- super("session expir\xE9e ou absente \u2014 lancez `sumdae login`");
933
+ super(t("api.authRequired"));
222
934
  this.name = "AuthRequiredError";
223
935
  }
224
936
  };
@@ -241,24 +953,24 @@ async function exchangeToken(form) {
241
953
  throw new ApiError(
242
954
  res.status,
243
955
  String(data.error ?? "unknown"),
244
- String(data.error_description ?? "\xE9chec de l'\xE9change de token")
956
+ String(data.error_description ?? t("api.tokenExchangeFailed"))
245
957
  );
246
958
  }
247
959
  return data;
248
960
  }
249
- function tokenSetFromResponse(t) {
961
+ function tokenSetFromResponse(t2) {
250
962
  return {
251
963
  kind: "oauth",
252
- accessToken: t.access_token,
253
- refreshToken: t.refresh_token,
254
- expiresAt: Date.now() + t.expires_in * 1e3
964
+ accessToken: t2.access_token,
965
+ refreshToken: t2.refresh_token,
966
+ expiresAt: Date.now() + t2.expires_in * 1e3
255
967
  };
256
968
  }
257
969
  async function refresh(creds) {
258
970
  if (!creds.refreshToken) throw new AuthRequiredError();
259
- let t;
971
+ let t2;
260
972
  try {
261
- t = await exchangeToken({
973
+ t2 = await exchangeToken({
262
974
  grant_type: "refresh_token",
263
975
  refresh_token: creds.refreshToken,
264
976
  client_id: "sumdae-cli"
@@ -267,7 +979,7 @@ async function refresh(creds) {
267
979
  await clearCredentials();
268
980
  throw new AuthRequiredError();
269
981
  }
270
- const next = tokenSetFromResponse(t);
982
+ const next = tokenSetFromResponse(t2);
271
983
  await saveCredentials(next);
272
984
  return next;
273
985
  }
@@ -318,7 +1030,7 @@ function parseQuota(data) {
318
1030
  throw new ApiError(
319
1031
  502,
320
1032
  "invalid_quota_response",
321
- "r\xE9ponse quota invalide \u2014 v\xE9rification impossible, refus par prudence",
1033
+ t("api.quotaInvalidResponse"),
322
1034
  data
323
1035
  );
324
1036
  }
@@ -383,7 +1095,7 @@ function connectOnce(url, token, cursor, onEvent, signal) {
383
1095
  };
384
1096
  signal?.addEventListener("abort", close, { once: true });
385
1097
  ws.on("unexpected-response", (_req, res) => {
386
- reject(new Error(`watch refus\xE9 (HTTP ${res.statusCode})`));
1098
+ reject(new Error(t("buildWatch.refused", { status: res.statusCode ?? "?" })));
387
1099
  close();
388
1100
  });
389
1101
  ws.on("error", () => {
@@ -429,16 +1141,16 @@ async function watchBuild(buildId, onEvent, opts = {}) {
429
1141
  if (opts.signal?.aborted) return "cancelled-local";
430
1142
  failures = cursor.last > before ? 0 : failures + 1;
431
1143
  if (failures > 30) {
432
- throw new Error("connexion au build perdue (30 tentatives) \u2014 relancez `sumdae watch`");
1144
+ throw new Error(t("buildWatch.connectionLost"));
433
1145
  }
434
1146
  await new Promise((resolve3) => {
435
1147
  const ms = Math.min(1e3 * (failures + 1), 1e4);
436
- const t = setTimeout(() => {
1148
+ const t2 = setTimeout(() => {
437
1149
  opts.signal?.removeEventListener("abort", onAbort);
438
1150
  resolve3();
439
1151
  }, ms);
440
1152
  const onAbort = () => {
441
- clearTimeout(t);
1153
+ clearTimeout(t2);
442
1154
  resolve3();
443
1155
  };
444
1156
  opts.signal?.addEventListener("abort", onAbort, { once: true });
@@ -595,7 +1307,7 @@ function AgentEventRow({ ev }) {
595
1307
  ] });
596
1308
  case "agent": {
597
1309
  const raw = String(data.agent ?? "agent");
598
- const displayName = raw === "master" ? "agent sumdae" : raw.startsWith("subagent-") ? `sous-agent ${raw.slice("subagent-".length)}` : raw;
1310
+ const displayName = raw === "master" ? t("agentFeed.agent.sumdae") : raw.startsWith("subagent-") ? `sous-agent ${raw.slice("subagent-".length)}` : raw;
599
1311
  return /* @__PURE__ */ jsxs(Box, { children: [
600
1312
  /* @__PURE__ */ jsx(Text, { color: COLORS.zinc500, children: "\u25CF" }),
601
1313
  /* @__PURE__ */ jsx(Text, { children: " " }),
@@ -611,7 +1323,7 @@ function AgentEventRow({ ev }) {
611
1323
  return /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
612
1324
  /* @__PURE__ */ jsx(Text, { color: isCommand ? COLORS.turquoise : COLORS.emerald, children: isCommand ? "\u25B8" : "\u25CF" }),
613
1325
  /* @__PURE__ */ jsx(Text, { children: " " }),
614
- /* @__PURE__ */ jsx(Text, { bold: true, color: COLORS.zinc100, children: isCommand ? "shell" : tool }),
1326
+ /* @__PURE__ */ jsx(Text, { bold: true, color: COLORS.zinc100, children: isCommand ? t("agentFeed.shell") : tool }),
615
1327
  summary ? isCommand ? /* @__PURE__ */ jsxs(Text, { color: COLORS.turquoise, children: [
616
1328
  " $ ",
617
1329
  summary
@@ -624,7 +1336,7 @@ function AgentEventRow({ ev }) {
624
1336
  }
625
1337
  case "tool_result": {
626
1338
  const ok2 = data.ok === true;
627
- const msg = ev.message ?? (ok2 ? "ok" : "\xE9chec");
1339
+ const msg = ev.message ?? (ok2 ? t("agentFeed.result.ok") : t("agentFeed.result.failed"));
628
1340
  return /* @__PURE__ */ jsxs(Box, { marginLeft: 2, children: [
629
1341
  /* @__PURE__ */ jsxs(Box, { flexShrink: 0, children: [
630
1342
  /* @__PURE__ */ jsx(Text, { color: COLORS.zinc700, children: "\u23BF " }),
@@ -663,7 +1375,7 @@ function AgentEventRow({ ev }) {
663
1375
  ] })
664
1376
  ] });
665
1377
  case "lagged":
666
- return /* @__PURE__ */ jsx(Text, { color: COLORS.zinc700, dimColor: true, children: "\u2026 (flux rattrap\xE9)" });
1378
+ return /* @__PURE__ */ jsx(Text, { color: COLORS.zinc700, dimColor: true, children: t("agentFeed.lagged") });
667
1379
  default: {
668
1380
  const level = String(data.level ?? "info");
669
1381
  const color = level === "error" ? COLORS.red : level === "warn" ? COLORS.amber : COLORS.zinc500;
@@ -679,7 +1391,7 @@ function AgentEventRow({ ev }) {
679
1391
  import { Box as Box2, Text as Text2 } from "ink";
680
1392
  import { useEffect, useMemo, useRef, useState } from "react";
681
1393
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
682
- var VERBS = [
1394
+ var VERBS_FR = [
683
1395
  // espace / planète
684
1396
  "Orbite",
685
1397
  "Gravite",
@@ -726,6 +1438,53 @@ var VERBS = [
726
1438
  "Cogite",
727
1439
  "Trame"
728
1440
  ];
1441
+ var VERBS_EN = [
1442
+ // space / planet
1443
+ "Orbiting",
1444
+ "Gravitating",
1445
+ "Propelling",
1446
+ "Lifting off",
1447
+ "Landing",
1448
+ "Docking",
1449
+ "Mooring",
1450
+ "Mapping",
1451
+ "Constellating",
1452
+ "Satelliting",
1453
+ "Hyperspacing",
1454
+ "Aligning the stars",
1455
+ "Terraforming",
1456
+ // docker / build
1457
+ "Containerizing",
1458
+ "Dockerizing",
1459
+ "Stacking layers",
1460
+ "Compiling",
1461
+ "Assembling",
1462
+ "Welding",
1463
+ "Forging",
1464
+ "Tagging",
1465
+ "Pushing to registry",
1466
+ "Smoke-testing",
1467
+ "Scaffolding",
1468
+ "Setting the Dockerfile",
1469
+ // models / pipelines
1470
+ "Tensing the tensors",
1471
+ "Vectorizing",
1472
+ "Quantizing",
1473
+ "Distilling",
1474
+ "Waking the weights",
1475
+ "Warming the pipeline",
1476
+ "Calibrating",
1477
+ "Inferring",
1478
+ "Taming the model",
1479
+ "Wrangling the agents",
1480
+ // agent kitchen
1481
+ "Simmering",
1482
+ "Concocting",
1483
+ "Brewing",
1484
+ "Orchestrating",
1485
+ "Cogitating",
1486
+ "Weaving"
1487
+ ];
729
1488
  var TWINKLE = ["\xB7", "\u2722", "\u2733", "\u2736", "\u273B", "\u273D"];
730
1489
  var TWINKLE_CYCLE = [...TWINKLE, ...[...TWINKLE].reverse()];
731
1490
  var FRAME_MS = 120;
@@ -757,14 +1516,15 @@ var PLANET_MAP = [
757
1516
  ];
758
1517
  var PLANET_PERIOD = 6;
759
1518
  function pickVerb() {
760
- return VERBS[Math.floor(Math.random() * VERBS.length)];
1519
+ const verbs = getLocale() === "en" ? VERBS_EN : VERBS_FR;
1520
+ return verbs[Math.floor(Math.random() * verbs.length)];
761
1521
  }
762
1522
  function PlanetScene({ frame }) {
763
1523
  const starPhase = Math.floor(frame / 2) % STAR_GLYPHS.length;
764
1524
  const star = (i) => i === starPhase ? /* @__PURE__ */ jsx2(Text2, { color: COLORS.emerald, children: STAR_GLYPHS[i] }) : /* @__PURE__ */ jsx2(Text2, { color: COLORS.zinc700, children: "\xB7" });
765
1525
  const rot = Math.floor(frame / PLANET_PERIOD);
766
1526
  const mapW = PLANET_MAP[0].length;
767
- const pixelColor = (t) => t === 0 ? COLORS.oceanDeep : t === 1 ? COLORS.oceanShallow : t === 2 ? COLORS.land : COLORS.emeraldDim;
1527
+ const pixelColor = (t2) => t2 === 0 ? COLORS.oceanDeep : t2 === 1 ? COLORS.oceanShallow : t2 === 2 ? COLORS.land : COLORS.emeraldDim;
768
1528
  const pixel = (x, y) => {
769
1529
  const { start, end } = PLANET_ROWS[y];
770
1530
  if (x < start || x >= end) return null;
@@ -774,14 +1534,14 @@ function PlanetScene({ frame }) {
774
1534
  const buildRow = (yTop) => {
775
1535
  const cells = [];
776
1536
  for (let x = 0; x < PLANET_GRID_W; x++) {
777
- const t = pixel(x, yTop);
1537
+ const t2 = pixel(x, yTop);
778
1538
  const b = pixel(x, yTop + 1);
779
- if (!t && !b) {
1539
+ if (!t2 && !b) {
780
1540
  cells.push(/* @__PURE__ */ jsx2(Text2, { children: " " }, x));
781
1541
  continue;
782
1542
  }
783
- const glyph = t && b ? "\u2588" : t ? "\u2580" : "\u2584";
784
- const color = rank(t) >= rank(b) ? t ?? b : b;
1543
+ const glyph = t2 && b ? "\u2588" : t2 ? "\u2580" : "\u2584";
1544
+ const color = rank(t2) >= rank(b) ? t2 ?? b : b;
785
1545
  cells.push(
786
1546
  /* @__PURE__ */ jsx2(Text2, { color, children: glyph }, x)
787
1547
  );
@@ -822,13 +1582,13 @@ function BusyIndicator({
822
1582
  const verb = useMemo(() => forcedVerb ?? pickVerb(), [forcedVerb, verbKey, label]);
823
1583
  const withShimmer = !label;
824
1584
  useEffect(() => {
825
- const t = setInterval(() => setFrame((f) => (f + 1) % TWINKLE_CYCLE.length), FRAME_MS);
826
- return () => clearInterval(t);
1585
+ const t2 = setInterval(() => setFrame((f) => (f + 1) % TWINKLE_CYCLE.length), FRAME_MS);
1586
+ return () => clearInterval(t2);
827
1587
  }, []);
828
1588
  useEffect(() => {
829
1589
  if (!withShimmer) return;
830
- const t = setInterval(() => setShimmer((s) => s + 1), SHIMMER_MS);
831
- return () => clearInterval(t);
1590
+ const t2 = setInterval(() => setShimmer((s) => s + 1), SHIMMER_MS);
1591
+ return () => clearInterval(t2);
832
1592
  }, [withShimmer]);
833
1593
  const glyph = TWINKLE_CYCLE[frame];
834
1594
  const text = label ?? `${verb}\u2026`;
@@ -902,26 +1662,26 @@ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
902
1662
  var MEMORY_ITEM_TTL_MS = 6e3;
903
1663
  function agentLabel(raw) {
904
1664
  if (!raw) return "";
905
- if (raw === "master") return "agent sumdae";
1665
+ if (raw === "master") return t("agentFeed.agent.sumdae");
906
1666
  if (raw.startsWith("subagent-")) return `sous-agent ${raw.slice("subagent-".length)}`;
907
1667
  return raw;
908
1668
  }
909
1669
  function clip(s, max) {
910
- const t = s.replace(/\s+/g, " ").trim();
911
- return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
1670
+ const t2 = s.replace(/\s+/g, " ").trim();
1671
+ return t2.length > max ? `${t2.slice(0, max - 1)}\u2026` : t2;
912
1672
  }
913
1673
  function describe(op) {
914
1674
  switch (op) {
915
1675
  case "query":
916
- return { icon: "\u{1F50E}", verb: "M\xC9MOIRE", color: COLORS.sky };
1676
+ return { icon: "\u{1F50E}", verb: t("memory.query"), color: COLORS.sky };
917
1677
  case "result_hit":
918
- return { icon: "\u{1F4A1}", verb: "LE\xC7ON TROUV\xC9E", color: COLORS.amber };
1678
+ return { icon: "\u{1F4A1}", verb: t("memory.resultHit"), color: COLORS.amber };
919
1679
  case "result_miss":
920
- return { icon: "\u2205", verb: "RIEN EN M\xC9MOIRE", color: COLORS.zinc500 };
1680
+ return { icon: "\u2205", verb: t("memory.resultMiss"), color: COLORS.zinc500 };
921
1681
  case "saved":
922
- return { icon: "\u{1F4BE}", verb: "LE\xC7ON ENREGISTR\xC9E", color: COLORS.emerald };
1682
+ return { icon: "\u{1F4BE}", verb: t("memory.saved"), color: COLORS.emerald };
923
1683
  case "duplicate":
924
- return { icon: "\u2248", verb: "LE\xC7ON D\xC9J\xC0 CONNUE", color: COLORS.zinc500 };
1684
+ return { icon: "\u2248", verb: t("memory.duplicate"), color: COLORS.zinc500 };
925
1685
  }
926
1686
  }
927
1687
  function MemoryRow({ item }) {
@@ -987,14 +1747,14 @@ function fmtDuration(secs) {
987
1747
  function describe2(label) {
988
1748
  const l = label.toLowerCase();
989
1749
  if (l.includes("push") || l.includes("upload"))
990
- return { icon: "\u2B06", verb: "PUSH IMAGE", color: COLORS.opPush };
1750
+ return { icon: "\u2B06", verb: t("progress.push"), color: COLORS.opPush };
991
1751
  if (l.includes("pull") || l.includes("download") || l.includes("wget") || l.includes("curl") || l.includes("clone") || l.includes("fetch"))
992
- return { icon: "\u2B07", verb: "T\xC9L\xC9CHARGEMENT", color: COLORS.opDownload };
1752
+ return { icon: "\u2B07", verb: t("progress.download"), color: COLORS.opDownload };
993
1753
  if (l.includes("pip") || l.includes("apt") || l.includes("install"))
994
- return { icon: "\u{1F4E6}", verb: "INSTALLATION", color: COLORS.opInstall };
1754
+ return { icon: "\u{1F4E6}", verb: t("progress.install"), color: COLORS.opInstall };
995
1755
  if (l.includes("bazel") || l.includes("build") || l.includes("make") || l.includes("compile"))
996
- return { icon: "\u2699", verb: "BUILD", color: COLORS.opBuild };
997
- return { icon: "\u25B8", verb: "\xC9TAPE", color: COLORS.opGeneric };
1756
+ return { icon: "\u2699", verb: t("progress.build"), color: COLORS.opBuild };
1757
+ return { icon: "\u25B8", verb: t("progress.generic"), color: COLORS.opGeneric };
998
1758
  }
999
1759
  function rateSuffix(item) {
1000
1760
  const parts = [];
@@ -1050,15 +1810,15 @@ function ProgressRow({ item, tick }) {
1050
1810
  " "
1051
1811
  ] }),
1052
1812
  /* @__PURE__ */ jsx7(Text7, { color: COLORS.zinc100, bold: true, children: fmtBytes(item.done) }),
1053
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.zinc400, children: " transf\xE9r\xE9s" }),
1813
+ /* @__PURE__ */ jsxs6(Text7, { color: COLORS.zinc400, children: [
1814
+ " ",
1815
+ t("progress.transferred")
1816
+ ] }),
1054
1817
  item.rate > 0 ? /* @__PURE__ */ jsxs6(Text7, { color: COLORS.zinc500, children: [
1055
1818
  " \xB7 ",
1056
1819
  fmtBytes(item.rate),
1057
1820
  "/s"
1058
- ] }) : stalled ? /* @__PURE__ */ jsxs6(Text7, { color: COLORS.amber, children: [
1059
- " \xB7 en attente du r\xE9seau\u2026 ",
1060
- fmtDuration(item.stalledFor ?? 0)
1061
- ] }) : null
1821
+ ] }) : stalled ? /* @__PURE__ */ jsx7(Text7, { color: COLORS.amber, children: t("progress.waitingNetwork", { duration: fmtDuration(item.stalledFor ?? 0) }) }) : null
1062
1822
  ] })
1063
1823
  ) : /* @__PURE__ */ jsxs6(Text7, { children: [
1064
1824
  /* @__PURE__ */ jsxs6(Text7, { color, children: [
@@ -1066,12 +1826,8 @@ function ProgressRow({ item, tick }) {
1066
1826
  SPINNER[tick % SPINNER.length],
1067
1827
  " "
1068
1828
  ] }),
1069
- /* @__PURE__ */ jsx7(Text7, { color: COLORS.zinc300, children: "en cours" }),
1070
- /* @__PURE__ */ jsxs6(Text7, { color: COLORS.zinc600, children: [
1071
- " \xB7 ",
1072
- fmtDuration(item.done),
1073
- " \xE9coul\xE9"
1074
- ] })
1829
+ /* @__PURE__ */ jsx7(Text7, { color: COLORS.zinc300, children: t("progress.inProgress") }),
1830
+ /* @__PURE__ */ jsx7(Text7, { color: COLORS.zinc600, children: t("progress.elapsed", { duration: fmtDuration(item.done) }) })
1075
1831
  ] })
1076
1832
  ] });
1077
1833
  }
@@ -1225,19 +1981,19 @@ function TimelineDock({ groups, tick }) {
1225
1981
  // src/commands/build.tsx
1226
1982
  import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1227
1983
  var STATUS_LABEL = {
1228
- created: "cr\xE9\xE9",
1229
- provisioning: "r\xE9servation de l'environnement",
1230
- starting: "d\xE9marrage de l'environnement",
1231
- bootstrapping: "pr\xE9paration de l'environnement",
1232
- running: "agent sumdae au travail",
1233
- terminating: "arr\xEAt en cours",
1234
- succeeded: "termin\xE9 \u2713",
1235
- failed: "\xE9chou\xE9",
1236
- cancelled: "annul\xE9"
1984
+ created: t("watch.status.created"),
1985
+ provisioning: t("watch.status.provisioning"),
1986
+ starting: t("watch.status.starting"),
1987
+ bootstrapping: t("watch.status.bootstrapping"),
1988
+ running: t("watch.status.running"),
1989
+ terminating: t("watch.status.terminating"),
1990
+ succeeded: t("watch.status.succeeded"),
1991
+ failed: t("watch.status.failed"),
1992
+ cancelled: t("watch.status.cancelled")
1237
1993
  };
1238
1994
  async function requestCancel(buildId) {
1239
1995
  const active = await resolveActiveOrg().catch(() => null);
1240
- if (!active) throw new Error("aucune organisation active");
1996
+ if (!active) throw new Error(t("buildCmd.noActiveOrgForCancel"));
1241
1997
  await labApi(
1242
1998
  `/orgs/${encodeURIComponent(active.slug)}/builds/${encodeURIComponent(buildId)}/cancel`,
1243
1999
  { method: "POST" }
@@ -1259,12 +2015,12 @@ function normalizeTimelineStatus(s, fallback) {
1259
2015
  return s === "pending" || s === "active" || s === "done" || s === "failed" ? s : fallback;
1260
2016
  }
1261
2017
  function truncateEvent(ev) {
1262
- const message = ev.message && ev.message.length > 4e3 ? `${ev.message.slice(0, 4e3)} \u2026 (tronqu\xE9)` : ev.message;
2018
+ const message = ev.message && ev.message.length > 4e3 ? `${ev.message.slice(0, 4e3)} ${t("common.truncated")}` : ev.message;
1263
2019
  let data = ev.data;
1264
2020
  const patch = data?.patch;
1265
2021
  if (typeof patch === "string" && patch.length > 8e3) {
1266
2022
  data = { ...data, patch: `${patch.slice(0, 8e3)}
1267
- \u2026 (tronqu\xE9)` };
2023
+ ${t("common.truncated")}` };
1268
2024
  }
1269
2025
  return message === ev.message && data === ev.data ? ev : { ...ev, message, data };
1270
2026
  }
@@ -1337,10 +2093,10 @@ function BuildWatcher({
1337
2093
  (ev) => {
1338
2094
  if (!mounted) return;
1339
2095
  if (!startPinned.current && ev.at) {
1340
- const t = Date.parse(ev.at);
1341
- if (Number.isFinite(t)) {
2096
+ const t2 = Date.parse(ev.at);
2097
+ if (Number.isFinite(t2)) {
1342
2098
  startPinned.current = true;
1343
- setStartedAt(t);
2099
+ setStartedAt(t2);
1344
2100
  }
1345
2101
  }
1346
2102
  const s = ev.data?.status;
@@ -1507,8 +2263,8 @@ function BuildWatcher({
1507
2263
  const hasSpinners = Object.keys(progress).length > 0 || memory.length > 0 || timelineGroups.some((g) => g.status === "active");
1508
2264
  useEffect2(() => {
1509
2265
  if (!hasSpinners) return;
1510
- const t = setInterval(() => setSpinTick((n) => n + 1), 120);
1511
- return () => clearInterval(t);
2266
+ const t2 = setInterval(() => setSpinTick((n) => n + 1), 120);
2267
+ return () => clearInterval(t2);
1512
2268
  }, [hasSpinners]);
1513
2269
  useEffect2(() => {
1514
2270
  if (!stdout) return;
@@ -1535,7 +2291,7 @@ function BuildWatcher({
1535
2291
  /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc500, children: "build " }),
1536
2292
  /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc100, bold: true, children: buildId.slice(0, 8) }),
1537
2293
  /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc700, children: " \xB7 " }),
1538
- /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc400, children: orgName ?? "flux d'\xE9v\xE9nements" })
2294
+ /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc400, children: orgName ?? t("watch.eventStream") })
1539
2295
  ] });
1540
2296
  const orgBlock = /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", flexShrink: 0, marginLeft: 4, alignItems: "flex-end", children: [
1541
2297
  buildHeader,
@@ -1563,16 +2319,16 @@ function BuildWatcher({
1563
2319
  // s'affichent sur la ligne JUSTE sous le chrono, dans la même
1564
2320
  // colonne (même padding que le verbe et le timer).
1565
2321
  (() => {
1566
- const footer = cancelState === "sent" ? /* @__PURE__ */ jsx9(Text9, { color: COLORS.amber, dimColor: true, children: "annulation demand\xE9e \u2014 arr\xEAt de l'environnement en cours\u2026" }) : cancelState === "error" ? /* @__PURE__ */ jsx9(Text9, { color: COLORS.red, dimColor: true, children: "\xE9chec de la demande d\u2019annulation \u2014 r\xE9essayez avec x" }) : confirmKill ? /* @__PURE__ */ jsxs8(Text9, { color: COLORS.amber, bold: true, children: [
1567
- "annuler ce job pour de bon ?",
2322
+ const footer = cancelState === "sent" ? /* @__PURE__ */ jsx9(Text9, { color: COLORS.amber, dimColor: true, children: t("watch.cancel.sent") }) : cancelState === "error" ? /* @__PURE__ */ jsx9(Text9, { color: COLORS.red, dimColor: true, children: t("watch.cancel.error") }) : confirmKill ? /* @__PURE__ */ jsxs8(Text9, { color: COLORS.amber, bold: true, children: [
2323
+ t("watch.cancel.confirm"),
1568
2324
  " ",
1569
- /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc500, children: "x/y confirmer \xB7 n ou \xE9chap annuler" })
2325
+ /* @__PURE__ */ jsx9(Text9, { color: COLORS.zinc500, children: t("watch.cancel.confirmKeys") })
1570
2326
  ] }) : /* @__PURE__ */ jsx9(
1571
2327
  Keycaps,
1572
2328
  {
1573
2329
  items: [
1574
- { key: "x", desc: "annuler le build" },
1575
- { key: "\xE9chap", desc: "retour" }
2330
+ { key: "x", desc: t("watch.keycap.cancelBuild") },
2331
+ { key: "\xE9chap", desc: t("keycaps.escBack") }
1576
2332
  ]
1577
2333
  }
1578
2334
  );
@@ -1613,18 +2369,18 @@ async function cancelAction(buildId) {
1613
2369
  process.exitCode = 1;
1614
2370
  return;
1615
2371
  }
1616
- console.log("annulation demand\xE9e \u2014 suivi jusqu\u2019\xE0 l\u2019arr\xEAt complet :");
2372
+ console.log(t("buildCmd.cancelRequested"));
1617
2373
  await watchAction(buildId);
1618
2374
  }
1619
2375
  async function agentCommandAction(buildId, action, opts) {
1620
2376
  if (action !== "kill" && action !== "message") {
1621
- console.error("action invalide \u2014 'kill' ou 'message'");
2377
+ console.error(t("buildCmd.invalidAction"));
1622
2378
  process.exitCode = 1;
1623
2379
  return;
1624
2380
  }
1625
2381
  const active = await resolveActiveOrg().catch(() => null);
1626
2382
  if (!active) {
1627
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
2383
+ console.error(t("common.noActiveOrgRunSwitch"));
1628
2384
  process.exitCode = 1;
1629
2385
  return;
1630
2386
  }
@@ -1634,7 +2390,7 @@ async function agentCommandAction(buildId, action, opts) {
1634
2390
  headers: { "content-type": "application/json" },
1635
2391
  body: JSON.stringify({ action, agent: opts.agent ?? "master", message: opts.message })
1636
2392
  });
1637
- console.log(`commande \xAB ${action} \xBB envoy\xE9e \xE0 ${opts.agent ?? "master"}`);
2393
+ console.log(t("buildCmd.commandSent", { action, agent: opts.agent ?? "master" }));
1638
2394
  } catch (e) {
1639
2395
  console.error(e instanceof Error ? e.message : String(e));
1640
2396
  process.exitCode = 1;
@@ -1643,7 +2399,7 @@ async function agentCommandAction(buildId, action, opts) {
1643
2399
  async function buildAction(weightsArtifactId, opts) {
1644
2400
  const active = await resolveActiveOrg().catch(() => null);
1645
2401
  if (!active) {
1646
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
2402
+ console.error(t("common.noActiveOrgRunSwitch"));
1647
2403
  process.exitCode = 1;
1648
2404
  return;
1649
2405
  }
@@ -1666,19 +2422,19 @@ async function buildAction(weightsArtifactId, opts) {
1666
2422
  process.exitCode = 1;
1667
2423
  return;
1668
2424
  }
1669
- console.log(`build ${buildId} lanc\xE9 \u2014 quota consomm\xE9`);
2425
+ console.log(t("buildCmd.buildLaunched", { buildId }));
1670
2426
  await watchAction(buildId);
1671
2427
  }
1672
2428
  async function simulateAction(task) {
1673
2429
  const trimmed = task.trim();
1674
2430
  if (!trimmed) {
1675
- console.error('t\xE2che vide \u2014 donne une instruction au master, ex. : sumdae simulate "cr\xE9e /workspace/hello.txt et liste le dossier"');
2431
+ console.error(t("buildCmd.simEmpty"));
1676
2432
  process.exitCode = 1;
1677
2433
  return;
1678
2434
  }
1679
2435
  const active = await resolveActiveOrg().catch(() => null);
1680
2436
  if (!active) {
1681
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
2437
+ console.error(t("common.noActiveOrgRunSwitch"));
1682
2438
  process.exitCode = 1;
1683
2439
  return;
1684
2440
  }
@@ -1698,7 +2454,7 @@ async function simulateAction(task) {
1698
2454
  process.exitCode = 1;
1699
2455
  return;
1700
2456
  }
1701
- console.log(`simulation ${buildId} lanc\xE9e \u2014 quota consomm\xE9 \xB7 pod lou\xE9, agents au travail`);
2457
+ console.log(t("buildCmd.simLaunched", { buildId }));
1702
2458
  await watchAction(buildId);
1703
2459
  }
1704
2460
 
@@ -1810,7 +2566,7 @@ async function uploadFileToPod(opts) {
1810
2566
  } else {
1811
2567
  reject(
1812
2568
  new Error(
1813
- `upload refus\xE9 par l'environnement (HTTP ${res.statusCode ?? "?"})${body ? ` : ${body.slice(0, 200)}` : ""}`
2569
+ t("directUpload.err.refused", { status: res.statusCode ?? "?" }) + (body ? ` : ${body.slice(0, 200)}` : "")
1814
2570
  )
1815
2571
  );
1816
2572
  }
@@ -1818,7 +2574,7 @@ async function uploadFileToPod(opts) {
1818
2574
  }
1819
2575
  );
1820
2576
  const abort = () => {
1821
- req.destroy(new Error("upload annul\xE9"));
2577
+ req.destroy(new Error(t("upload.err.cancelled")));
1822
2578
  };
1823
2579
  signal?.addEventListener("abort", abort, { once: true });
1824
2580
  req.on("error", (e) => {
@@ -1944,7 +2700,7 @@ function JobUpload({
1944
2700
  const d = e.data;
1945
2701
  if (d?.status === "failed" || d?.status === "cancelled") {
1946
2702
  fail(
1947
- d.status === "failed" ? `le job a \xE9chou\xE9 pendant l'upload${e.message ? ` : ${e.message}` : ""}` : "job annul\xE9"
2703
+ d.status === "failed" ? t("upload.jobFailedDuring", { suffix: e.message ? ` : ${e.message}` : "" }) : t("upload.jobCancelled")
1948
2704
  );
1949
2705
  }
1950
2706
  }
@@ -1968,7 +2724,7 @@ function JobUpload({
1968
2724
  }
1969
2725
  if (done.current) return;
1970
2726
  if (!url) {
1971
- fail("l'environnement n'a pas \xE9t\xE9 pr\xEAt \xE0 temps pour recevoir le fichier");
2727
+ fail(t("upload.envNotReady"));
1972
2728
  return;
1973
2729
  }
1974
2730
  uploadStarted = true;
@@ -1988,7 +2744,9 @@ function JobUpload({
1988
2744
  setTimeout(() => succeed(), 1e4);
1989
2745
  } catch (e) {
1990
2746
  fail(
1991
- `connexion interrompue pendant l'upload \u2014 le job va \xEAtre coup\xE9 c\xF4t\xE9 serveur (${e instanceof Error ? e.message : String(e)})`
2747
+ t("upload.connInterrupted", {
2748
+ detail: e instanceof Error ? e.message : String(e)
2749
+ })
1992
2750
  );
1993
2751
  }
1994
2752
  };
@@ -2001,23 +2759,21 @@ function JobUpload({
2001
2759
  useInput2((_input, key) => {
2002
2760
  if (key.escape && phase === "waiting" && !done.current) {
2003
2761
  done.current = true;
2004
- finish.current.onError(
2005
- "annul\xE9 avant le d\xE9but de l\u2019upload \u2014 le job sera coup\xE9 automatiquement c\xF4t\xE9 serveur (quota rembours\xE9)."
2006
- );
2762
+ finish.current.onError(t("upload.cancelledBeforeStart"));
2007
2763
  }
2008
2764
  });
2009
2765
  const remaining = Math.max(0, totalBytes - podBytes);
2010
2766
  const effectiveRate = podRate > 0 ? podRate : localRate;
2011
2767
  return /* @__PURE__ */ jsxs10(Box9, { flexDirection: "column", children: [
2012
- /* @__PURE__ */ jsx11(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc100, bold: true, children: "envoi direct vers l'environnement" }) }),
2768
+ /* @__PURE__ */ jsx11(Box9, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc100, bold: true, children: t("upload.title") }) }),
2013
2769
  /* @__PURE__ */ jsxs10(Text11, { color: COLORS.zinc500, children: [
2014
2770
  fileName,
2015
2771
  " \xB7 ",
2016
2772
  formatBytes(totalBytes)
2017
2773
  ] }),
2018
2774
  phase === "waiting" ? /* @__PURE__ */ jsxs10(Box9, { marginTop: 1, flexDirection: "column", children: [
2019
- /* @__PURE__ */ jsx11(BusyIndicator, { label: "r\xE9servation de l'environnement et ouverture du canal\u2026" }),
2020
- /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Keycaps, { items: [{ key: "esc", desc: "annuler" }] }) })
2775
+ /* @__PURE__ */ jsx11(BusyIndicator, { label: t("upload.waiting") }),
2776
+ /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Keycaps, { items: [{ key: "esc", desc: t("keycaps.cancel") }] }) })
2021
2777
  ] }) : /* @__PURE__ */ jsxs10(Box9, { marginTop: 1, flexDirection: "column", children: [
2022
2778
  /* @__PURE__ */ jsx11(
2023
2779
  ProgressBar,
@@ -2029,24 +2785,23 @@ function JobUpload({
2029
2785
  ),
2030
2786
  /* @__PURE__ */ jsxs10(Box9, { marginTop: 1, flexDirection: "column", children: [
2031
2787
  /* @__PURE__ */ jsxs10(Text11, { children: [
2032
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc500, children: "d\xE9bit local : " }),
2788
+ /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc500, children: t("upload.localRate") }),
2033
2789
  /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc100, children: formatRate(localRate) }),
2034
2790
  /* @__PURE__ */ jsxs10(Text11, { color: COLORS.zinc600, children: [
2035
- " (",
2036
- formatBytes(sentBytes),
2037
- " envoy\xE9s)"
2791
+ " ",
2792
+ t("upload.bytesSent", { bytes: formatBytes(sentBytes) })
2038
2793
  ] })
2039
2794
  ] }),
2040
2795
  /* @__PURE__ */ jsxs10(Text11, { children: [
2041
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc500, children: "d\xE9bit environnement : " }),
2796
+ /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc500, children: t("upload.envRate") }),
2042
2797
  /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc100, children: formatRate(podRate) })
2043
2798
  ] }),
2044
2799
  /* @__PURE__ */ jsxs10(Text11, { children: [
2045
- /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc500, children: "temps restant estim\xE9 : " }),
2800
+ /* @__PURE__ */ jsx11(Text11, { color: COLORS.zinc500, children: t("upload.etaLabel") }),
2046
2801
  /* @__PURE__ */ jsx11(Text11, { color: COLORS.emerald, children: formatEta(remaining, effectiveRate) })
2047
2802
  ] })
2048
2803
  ] }),
2049
- phase === "finalizing" ? /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(BusyIndicator, { label: "v\xE9rification du fichier sur l'environnement\u2026", compact: true }) }) : /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: COLORS.amber, children: "\u26A0 garde ta machine allum\xE9e et ta connexion stable \u2014 si l'envoi est interrompu, le job est coup\xE9." }) })
2804
+ phase === "finalizing" ? /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(BusyIndicator, { label: t("upload.verifying"), compact: true }) }) : /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: COLORS.amber, children: t("upload.warnKeepAlive") }) })
2050
2805
  ] })
2051
2806
  ] });
2052
2807
  }
@@ -2471,7 +3226,7 @@ async function runLoginFlow(opts = {}) {
2471
3226
  const code = await new Promise((resolve3, reject) => {
2472
3227
  const timer = setTimeout(
2473
3228
  () => reject(
2474
- new Error(`aucune r\xE9ponse du navigateur apr\xE8s ${Math.round(timeoutMs / 6e4) || 1} min`)
3229
+ new Error(t("login.err.noBrowserResponse", { min: Math.round(timeoutMs / 6e4) || 1 }))
2475
3230
  ),
2476
3231
  timeoutMs
2477
3232
  );
@@ -2482,18 +3237,18 @@ async function runLoginFlow(opts = {}) {
2482
3237
  return;
2483
3238
  }
2484
3239
  const fail = (msg) => {
2485
- res.writeHead(400, { "content-type": "text/html; charset=utf-8" }).end(page("\u2717 \xC9chec de la connexion", msg, false));
3240
+ res.writeHead(400, { "content-type": "text/html; charset=utf-8" }).end(page(t("login.failedTitle"), msg, false));
2486
3241
  clearTimeout(timer);
2487
3242
  reject(new Error(msg));
2488
3243
  };
2489
3244
  const err = url.searchParams.get("error");
2490
- if (err) return fail(`refus\xE9 par le serveur d'identit\xE9 : ${err}`);
3245
+ if (err) return fail(t("login.err.serverRefused", { error: err }));
2491
3246
  if (url.searchParams.get("state") !== state) {
2492
- return fail("state invalide (possible CSRF) \u2014 flow abandonn\xE9");
3247
+ return fail(t("login.err.invalidState"));
2493
3248
  }
2494
3249
  const code2 = url.searchParams.get("code");
2495
- if (!code2) return fail("code manquant dans le callback");
2496
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page("\u2713 Connect\xE9", "Vous pouvez fermer cet onglet et revenir au terminal.", true));
3250
+ if (!code2) return fail(t("login.err.missingCode"));
3251
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page(t("login.connectedTitle"), t("login.connectedBody"), true));
2497
3252
  clearTimeout(timer);
2498
3253
  resolve3(code2);
2499
3254
  });
@@ -2593,15 +3348,15 @@ function KeyValue({ rows }) {
2593
3348
  // src/ui/JobSummary.tsx
2594
3349
  import { jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
2595
3350
  var STATUS_LABEL2 = {
2596
- created: "cr\xE9\xE9",
2597
- provisioning: "r\xE9servation de l'environnement",
2598
- starting: "d\xE9marrage de l'environnement",
2599
- bootstrapping: "pr\xE9paration de l'environnement",
2600
- running: "agent sumdae au travail",
2601
- terminating: "arr\xEAt en cours",
2602
- succeeded: "termin\xE9 \u2713",
2603
- failed: "\xE9chou\xE9",
2604
- cancelled: "annul\xE9"
3351
+ created: t("watch.status.created"),
3352
+ provisioning: t("watch.status.provisioning"),
3353
+ starting: t("watch.status.starting"),
3354
+ bootstrapping: t("watch.status.bootstrapping"),
3355
+ running: t("watch.status.running"),
3356
+ terminating: t("watch.status.terminating"),
3357
+ succeeded: t("watch.status.succeeded"),
3358
+ failed: t("watch.status.failed"),
3359
+ cancelled: t("watch.status.cancelled")
2605
3360
  };
2606
3361
  var STATUS_COLOR = {
2607
3362
  succeeded: COLORS.emerald,
@@ -2611,8 +3366,8 @@ var STATUS_COLOR = {
2611
3366
  var fmtCost = (n) => n != null ? `${n.toFixed(4)} $` : "\u2014";
2612
3367
  var parseIso = (iso) => {
2613
3368
  if (!iso) return null;
2614
- const t = new Date(iso).getTime();
2615
- return Number.isFinite(t) ? t : null;
3369
+ const t2 = new Date(iso).getTime();
3370
+ return Number.isFinite(t2) ? t2 : null;
2616
3371
  };
2617
3372
  var fmtDuration2 = (b) => {
2618
3373
  const start = parseIso(b.created_at);
@@ -2626,29 +3381,29 @@ function JobSummary({ build }) {
2626
3381
  const color = STATUS_COLOR[build.status] ?? COLORS.zinc400;
2627
3382
  const totalCost = (build.cost_runpod_usd ?? 0) + (build.cost_deepseek_usd ?? 0);
2628
3383
  const rows = [
2629
- ["statut", STATUS_LABEL2[build.status] ?? build.status],
2630
- ["type", build.kind === "simulation" ? "simulation" : "build"]
3384
+ [t("jobSummary.row.status"), STATUS_LABEL2[build.status] ?? build.status],
3385
+ [t("jobSummary.row.type"), build.kind === "simulation" ? t("jobSummary.type.simulation") : t("jobSummary.type.build")]
2631
3386
  ];
2632
3387
  if (build.kind === "simulation" && build.simulation_task) {
2633
3388
  const task = build.simulation_task;
2634
- rows.push(["t\xE2che", task.length > 80 ? task.slice(0, 77) + "\u2026" : task]);
3389
+ rows.push([t("jobSummary.row.task"), task.length > 80 ? task.slice(0, 77) + "\u2026" : task]);
2635
3390
  }
2636
3391
  if (build.weights_artifact_id) {
2637
- rows.push(["poids", build.weights_artifact_id.slice(0, 8)]);
3392
+ rows.push([t("jobSummary.row.weights"), build.weights_artifact_id.slice(0, 8)]);
2638
3393
  }
2639
3394
  rows.push(
2640
- ["dur\xE9e", fmtDuration2(build)],
2641
- ["environnement", build.pod?.machine ?? "\u2014"],
2642
- ["co\xFBt environnement", fmtCost(build.cost_runpod_usd)],
2643
- ["co\xFBt agent", `${fmtCost(build.cost_deepseek_usd)} \xB7 ${build.agent_calls ?? 0} appel(s)`],
2644
- ["co\xFBt total", fmtCost(totalCost)]
3395
+ [t("jobSummary.row.duration"), fmtDuration2(build)],
3396
+ [t("jobSummary.row.environment"), build.pod?.machine ?? "\u2014"],
3397
+ [t("jobSummary.row.costEnv"), fmtCost(build.cost_runpod_usd)],
3398
+ [t("jobSummary.row.costAgent"), `${fmtCost(build.cost_deepseek_usd)} \xB7 ${build.agent_calls ?? 0} appel(s)`],
3399
+ [t("jobSummary.row.costTotal"), fmtCost(totalCost)]
2645
3400
  );
2646
3401
  if (build.error) {
2647
- rows.push(["erreur", build.error.length > 80 ? build.error.slice(0, 77) + "\u2026" : build.error]);
3402
+ rows.push([t("jobSummary.row.error"), build.error.length > 80 ? build.error.slice(0, 77) + "\u2026" : build.error]);
2648
3403
  }
2649
3404
  return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
2650
3405
  /* @__PURE__ */ jsxs13(Text14, { color, bold: true, children: [
2651
- build.kind === "simulation" ? "simulation" : "build",
3406
+ build.kind === "simulation" ? t("jobSummary.type.simulation") : t("jobSummary.type.build"),
2652
3407
  " ",
2653
3408
  build.id.slice(0, 8)
2654
3409
  ] }),
@@ -2665,12 +3420,16 @@ function SelectList({
2665
3420
  onSelect,
2666
3421
  isActive = true,
2667
3422
  initialIndex = 0,
2668
- showHint = true
3423
+ showHint = true,
3424
+ onIndexChange
2669
3425
  }) {
2670
3426
  const [index, setIndex] = useState4(initialIndex);
2671
3427
  useEffect4(() => {
2672
3428
  if (index >= items.length) setIndex(Math.max(0, items.length - 1));
2673
3429
  }, [items.length, index]);
3430
+ useEffect4(() => {
3431
+ onIndexChange?.(index);
3432
+ }, [index]);
2674
3433
  useInput3(
2675
3434
  (input, key) => {
2676
3435
  if (key.upArrow || input === "k") {
@@ -2707,7 +3466,7 @@ function SelectList({
2707
3466
  ] }) : null
2708
3467
  ] }, item.value);
2709
3468
  }),
2710
- showHint && isActive ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: COLORS.zinc700, dimColor: true, children: "\u2191\u2193 naviguer \xB7 entr\xE9e valider" }) }) : null
3469
+ showHint && isActive ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text15, { color: COLORS.zinc700, dimColor: true, children: t("selectList.hint") }) }) : null
2711
3470
  ] });
2712
3471
  }
2713
3472
 
@@ -2907,12 +3666,12 @@ function semverCmp(a, b) {
2907
3666
  async function fetchLatest() {
2908
3667
  try {
2909
3668
  const ctrl = new AbortController();
2910
- const t = setTimeout(() => ctrl.abort(), CHECK_TIMEOUT_MS);
3669
+ const t2 = setTimeout(() => ctrl.abort(), CHECK_TIMEOUT_MS);
2911
3670
  const res = await fetch(`${labUrl()}/cli/latest`, {
2912
3671
  signal: ctrl.signal,
2913
3672
  headers: { "x-cli-version": CLI_VERSION }
2914
3673
  });
2915
- clearTimeout(t);
3674
+ clearTimeout(t2);
2916
3675
  if (!res.ok) return null;
2917
3676
  const j = await res.json();
2918
3677
  if (!j.version || !j.tarball_url || !j.signature || !j.sha256) return null;
@@ -3008,7 +3767,7 @@ function installTarball(tarPath, opts = {}) {
3008
3767
  const r = spawnSync("tar", ["-xzf", tarPath, "-C", staging], { stdio: "ignore" });
3009
3768
  if (r.status !== 0) {
3010
3769
  rmSync2(staging, { recursive: true, force: true });
3011
- throw new Error("extraction tar \xE9chou\xE9e");
3770
+ throw new Error(t("update.err.extractFailed"));
3012
3771
  }
3013
3772
  const newDist = join4(staging, "dist");
3014
3773
  if (!existsSync(join4(newDist, "main.js"))) {
@@ -3063,9 +3822,9 @@ function realNorm(p) {
3063
3822
  }
3064
3823
  }
3065
3824
  function isDangerousRoot(root) {
3066
- const t = root.trim();
3067
- if (t === "") return true;
3068
- const n = norm(t);
3825
+ const t2 = root.trim();
3826
+ if (t2 === "") return true;
3827
+ const n = norm(t2);
3069
3828
  if (n === sep) return true;
3070
3829
  if (n === norm(homedir2())) return true;
3071
3830
  return false;
@@ -3221,7 +3980,7 @@ function scheduleInstallRemoval(plan, opts = {}) {
3221
3980
  return {
3222
3981
  step: "install",
3223
3982
  ok: false,
3224
- error: `planification \xE9chou\xE9e (${errMsg(e)}) \u2014 manuel : ${plan.manualHint ?? plan.installRoot}`
3983
+ error: t("uninstall.scheduleFailed", { err: errMsg(e), manual: plan.manualHint ?? plan.installRoot })
3225
3984
  };
3226
3985
  }
3227
3986
  }
@@ -3247,20 +4006,22 @@ async function performUninstall(plan) {
3247
4006
  // src/ui/UninstallScreen.tsx
3248
4007
  import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
3249
4008
  function channelLabel(c) {
3250
- return c === "npm" ? "npm (global)" : c === "selfhosted" ? "auto-h\xE9berg\xE9" : "binaire";
4009
+ return c === "npm" ? t("uninstall.channel.npm") : c === "selfhosted" ? t("uninstall.channel.selfhosted") : t("uninstall.channel.binary");
3251
4010
  }
3252
4011
  function planRows(plan) {
3253
- const rows = [["canal", channelLabel(plan.channel)]];
4012
+ const rows = [[t("uninstall.row.channel"), channelLabel(plan.channel)]];
4013
+ const install = t("uninstall.row.install");
3254
4014
  if (plan.channel === "npm") {
3255
- rows.push(["install", "npm uninstall -g @sumdae/cli (\xE0 la fermeture)"]);
4015
+ rows.push([install, t("uninstall.install.atClose", { root: plan.manualHint ?? "npm" })]);
3256
4016
  } else if (plan.installBlockedReason) {
3257
- rows.push(["install", `\u26A0 manuel \u2192 ${plan.manualHint ?? plan.installRoot}`]);
4017
+ rows.push([install, t("uninstall.install.manual", { hint: plan.manualHint ?? plan.installRoot })]);
3258
4018
  } else {
3259
- rows.push(["install", `${plan.installRoot} (\xE0 la fermeture)`]);
3260
- if (plan.launcherPath) rows.push(["launcher", `${plan.launcherPath} (\xE0 la fermeture)`]);
4019
+ rows.push([install, t("uninstall.install.atClose", { root: plan.installRoot })]);
4020
+ if (plan.launcherPath)
4021
+ rows.push(["launcher", t("uninstall.install.atClose", { root: plan.launcherPath })]);
3261
4022
  }
3262
- rows.push(["config", `${plan.configDir} (maintenant)`]);
3263
- rows.push(["credentials", "trousseau + fichier (maintenant)"]);
4023
+ rows.push([t("uninstall.row.config"), t("uninstall.config.now", { configDir: plan.configDir })]);
4024
+ rows.push([t("uninstall.row.credentials"), t("uninstall.credentials.keychainFileNow")]);
3264
4025
  return rows;
3265
4026
  }
3266
4027
  function UninstallView({
@@ -3277,13 +4038,13 @@ function UninstallView({
3277
4038
  if (view.kind === "working") {
3278
4039
  return /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Text17, { color: COLORS.zinc400, children: [
3279
4040
  /* @__PURE__ */ jsx17(Text17, { color: COLORS.emerald, children: "\xB7 " }),
3280
- "suppression en cours\u2026"
4041
+ t("uninstall.working")
3281
4042
  ] }) });
3282
4043
  }
3283
4044
  if (view.kind === "report") {
3284
4045
  const dataFailed = view.report.some((s) => s.step !== "install" && !s.ok);
3285
4046
  return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
3286
- /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc100, bold: true, children: "D\xE9sinstallation" }),
4047
+ /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc100, bold: true, children: t("uninstall.report.title") }),
3287
4048
  /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", marginTop: 1, children: view.report.map((s) => /* @__PURE__ */ jsxs16(Box15, { children: [
3288
4049
  /* @__PURE__ */ jsx17(Text17, { color: s.ok ? COLORS.emerald : COLORS.red, children: s.ok ? "\u2713 " : "\u2717 " }),
3289
4050
  /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc300, children: s.step }),
@@ -3292,26 +4053,26 @@ function UninstallView({
3292
4053
  s.error
3293
4054
  ] }) : null
3294
4055
  ] }, s.step)) }),
3295
- /* @__PURE__ */ jsx17(Box15, { marginTop: 1, flexDirection: "column", children: !dataFailed ? /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc400, children: "config et identifiants supprim\xE9s. sumdae sera effac\xE9 \xE0 la fermeture de ce terminal." }) : /* @__PURE__ */ jsx17(Text17, { color: COLORS.amber, children: "certaines suppressions ont \xE9chou\xE9 \u2014 voir ci-dessus." }) }),
4056
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, flexDirection: "column", children: !dataFailed ? /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc400, children: t("uninstall.report.done") }) : /* @__PURE__ */ jsx17(Text17, { color: COLORS.amber, children: t("uninstall.report.someFailed") }) }),
3296
4057
  /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(
3297
4058
  SelectList,
3298
4059
  {
3299
- items: [{ label: "Fermer", value: "close" }],
4060
+ items: [{ label: t("uninstall.close"), value: "close" }],
3300
4061
  onSelect: () => onDone(view.report)
3301
4062
  }
3302
4063
  ) })
3303
4064
  ] });
3304
4065
  }
3305
4066
  return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
3306
- /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc100, bold: true, children: "D\xE9sinstaller sumdae" }),
3307
- /* @__PURE__ */ jsx17(Box15, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc500, children: "ceci supprimera d\xE9finitivement la CLI et toutes ses donn\xE9es de cette machine :" }) }),
4067
+ /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc100, bold: true, children: t("uninstall.confirm.title") }),
4068
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc500, children: t("uninstall.confirm.warning") }) }),
3308
4069
  /* @__PURE__ */ jsx17(KeyValue, { rows: planRows(plan) }),
3309
4070
  /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(
3310
4071
  SelectList,
3311
4072
  {
3312
4073
  items: [
3313
- { label: "Annuler", value: "cancel", hint: "ne rien supprimer" },
3314
- { label: "Oui, tout supprimer", value: "go", labelColor: COLORS.red }
4074
+ { label: t("uninstall.cancel"), value: "cancel", hint: t("uninstall.cancel.hint") },
4075
+ { label: t("uninstall.confirmDelete"), value: "go", labelColor: COLORS.red }
3315
4076
  ],
3316
4077
  onSelect: (i) => {
3317
4078
  if (i.value === "go") void run();
@@ -3355,8 +4116,8 @@ var FRAME_MS2 = 120;
3355
4116
  function Twinkle({ color = COLORS.emerald }) {
3356
4117
  const [frame, setFrame] = useState7(0);
3357
4118
  useEffect5(() => {
3358
- const t = setInterval(() => setFrame((f) => (f + 1) % TWINKLE_CYCLE2.length), FRAME_MS2);
3359
- return () => clearInterval(t);
4119
+ const t2 = setInterval(() => setFrame((f) => (f + 1) % TWINKLE_CYCLE2.length), FRAME_MS2);
4120
+ return () => clearInterval(t2);
3360
4121
  }, []);
3361
4122
  return /* @__PURE__ */ jsx18(Text18, { color, children: TWINKLE_CYCLE2[frame] });
3362
4123
  }
@@ -3428,15 +4189,15 @@ function NoOrgPage({
3428
4189
  onQuit
3429
4190
  }) {
3430
4191
  return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3431
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: "aucune organisation associ\xE9e \xE0 ce compte." }),
3432
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "cr\xE9ez-en une sur lab.sumdae.fr puis revenez ici." }),
4192
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: t("noOrg.none") }),
4193
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("noOrg.createThenReturn") }),
3433
4194
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
3434
4195
  SelectList,
3435
4196
  {
3436
4197
  items: [
3437
- { label: "R\xE9essayer", value: "retry", hint: "recharger vos organisations" },
3438
- { label: "Continuer sans organisation", value: "continue" },
3439
- { label: "Quitter", value: "quit" }
4198
+ { label: t("noOrg.retry"), value: "retry", hint: t("noOrg.retry.hint") },
4199
+ { label: t("noOrg.continue"), value: "continue" },
4200
+ { label: t("common.quit"), value: "quit" }
3440
4201
  ],
3441
4202
  onSelect: (i) => {
3442
4203
  if (i.value === "retry") onRetry();
@@ -3456,6 +4217,12 @@ function App({
3456
4217
  const { exit } = useApp3();
3457
4218
  const { columns, rows } = useTerminalSize();
3458
4219
  const [session, setSession] = useState9(initialSession);
4220
+ const [locale, setLocaleState] = useState9(getLocale());
4221
+ const switchLocale = (l) => {
4222
+ setLocale(l);
4223
+ setLocaleState(l);
4224
+ };
4225
+ void locale;
3459
4226
  const [page2, setPageState] = useState9(
3460
4227
  initialSession ? { name: "home" } : { name: "landing" }
3461
4228
  );
@@ -3484,10 +4251,10 @@ function App({
3484
4251
  setPageState(p);
3485
4252
  };
3486
4253
  const back = () => setPage(parentOf(page2));
3487
- const backItem = (label = "Retourner en arri\xE8re") => ({
4254
+ const backItem = (label = t("common.back")) => ({
3488
4255
  label,
3489
4256
  value: "__back__",
3490
- hint: "ou \xC9chap"
4257
+ hint: t("common.orEscape")
3491
4258
  });
3492
4259
  const [recentBuilds, setRecentBuilds] = useState9(null);
3493
4260
  const [recentError, setRecentError] = useState9(null);
@@ -3501,6 +4268,17 @@ function App({
3501
4268
  },
3502
4269
  { isActive: page2.name === "home" }
3503
4270
  );
4271
+ const [homeMenuIndex, setHomeMenuIndex] = useState9(0);
4272
+ const homeLangIndexRef = useRef4(0);
4273
+ useInput5(
4274
+ (_input, key) => {
4275
+ if (!key.leftArrow && !key.rightArrow) return;
4276
+ if (homeFocus !== "menu") return;
4277
+ if (homeMenuIndex !== homeLangIndexRef.current) return;
4278
+ switchLocale(getLocale() === "fr" ? "en" : "fr");
4279
+ },
4280
+ { isActive: page2.name === "home" }
4281
+ );
3504
4282
  const ESCAPE_OWNED = /* @__PURE__ */ new Set([
3505
4283
  "home",
3506
4284
  "landing",
@@ -3557,7 +4335,7 @@ function App({
3557
4335
  };
3558
4336
  }, [page2.name]);
3559
4337
  const startSimulation = async (task) => {
3560
- setPage({ name: "loading", label: "cr\xE9ation de la simulation\u2026" });
4338
+ setPage({ name: "loading", label: t("simulate.creating") });
3561
4339
  try {
3562
4340
  const active = await resolveActiveOrg();
3563
4341
  if (!active) {
@@ -3567,7 +4345,7 @@ function App({
3567
4345
  const buildId = await createSimulation(active.slug, task);
3568
4346
  setPage({ name: "watch", buildId });
3569
4347
  } catch (e) {
3570
- toError(e, "simulation impossible", { name: "home" });
4348
+ toError(e, t("simulate.impossible"), { name: "home" });
3571
4349
  }
3572
4350
  };
3573
4351
  const hfErrorPage = (e) => {
@@ -3577,38 +4355,38 @@ function App({
3577
4355
  case "no_token":
3578
4356
  return {
3579
4357
  name: "error",
3580
- title: "token Hugging Face manquant",
3581
- detail: "Ton organisation n'a pas de token Hugging Face configur\xE9.\nAjoute un token READ : lab.sumdae.fr \u2192 menu du compte (en bas de la sidebar) \u2192 \xAB Param\xE8tres org. \xBB \u2192 Hugging Face token.\nCr\xE9e le token sur huggingface.co \u2192 Settings \u2192 Access Tokens (Read).",
4358
+ title: t("job.hf.err.noToken.title"),
4359
+ detail: t("job.hf.err.noToken.detail"),
3582
4360
  back: back2
3583
4361
  };
3584
4362
  case "invalid_token":
3585
4363
  return {
3586
4364
  name: "error",
3587
- title: "token Hugging Face invalide",
3588
- detail: "Le token configur\xE9 pour ton organisation est invalide ou r\xE9voqu\xE9 \u2014 mets-le \xE0 jour dans lab.sumdae.fr \u2192 \xAB Param\xE8tres org. \xBB.",
4365
+ title: t("job.hf.err.invalidToken.title"),
4366
+ detail: t("job.hf.err.invalidToken.detail"),
3589
4367
  back: back2
3590
4368
  };
3591
4369
  case "gated_or_forbidden":
3592
4370
  return {
3593
4371
  name: "error",
3594
- title: "acc\xE8s refus\xE9 \xE0 ce mod\xE8le",
3595
- detail: "Le repo est gated : accepte ses conditions sur huggingface.co avec le compte du token, ou v\xE9rifie que le token a bien l'acc\xE8s en lecture.",
4372
+ title: t("job.hf.err.gated.title"),
4373
+ detail: t("job.hf.err.gated.detail"),
3596
4374
  back: back2
3597
4375
  };
3598
4376
  case "not_found":
3599
4377
  return {
3600
4378
  name: "error",
3601
- title: "mod\xE8le introuvable",
3602
- detail: "Repo ou r\xE9vision inexistants sur Hugging Face \u2014 ou repo priv\xE9 invisible pour ce token. V\xE9rifie le lien.",
4379
+ title: t("job.hf.err.notFound.title"),
4380
+ detail: t("job.hf.err.notFound.detail"),
3603
4381
  back: back2
3604
4382
  };
3605
4383
  default:
3606
- return { name: "error", title: "v\xE9rification Hugging Face \xE9chou\xE9e", detail: e.message, back: back2 };
4384
+ return { name: "error", title: t("job.hf.err.checkFailed.title"), detail: e.message, back: back2 };
3607
4385
  }
3608
4386
  }
3609
4387
  return {
3610
4388
  name: "error",
3611
- title: "v\xE9rification Hugging Face \xE9chou\xE9e",
4389
+ title: t("job.hf.err.checkFailed.title"),
3612
4390
  detail: e instanceof Error ? e.message : String(e),
3613
4391
  back: back2
3614
4392
  };
@@ -3618,13 +4396,13 @@ function App({
3618
4396
  if (!parsed) {
3619
4397
  setPage({
3620
4398
  name: "error",
3621
- title: "r\xE9f\xE9rence invalide",
3622
- detail: "Formats accept\xE9s : owner/repo, owner/repo@revision, ou une URL huggingface.co compl\xE8te.",
4399
+ title: t("job.hf.invalidRef.title"),
4400
+ detail: t("job.hf.invalidRef.detail"),
3623
4401
  back: { name: "job-hf" }
3624
4402
  });
3625
4403
  return;
3626
4404
  }
3627
- setPage({ name: "loading", label: "v\xE9rification de l'acc\xE8s Hugging Face\u2026" });
4405
+ setPage({ name: "loading", label: t("job.hf.checking") });
3628
4406
  try {
3629
4407
  const active = await resolveActiveOrg();
3630
4408
  if (!active) {
@@ -3644,7 +4422,7 @@ function App({
3644
4422
  }
3645
4423
  };
3646
4424
  const startHfJob = async (repo, revision) => {
3647
- setPage({ name: "loading", label: "cr\xE9ation du job\u2026" });
4425
+ setPage({ name: "loading", label: t("common.creatingJob") });
3648
4426
  try {
3649
4427
  const active = await resolveActiveOrg();
3650
4428
  if (!active) {
@@ -3657,7 +4435,7 @@ function App({
3657
4435
  if (e instanceof ApiError && ["no_token", "invalid_token", "gated_or_forbidden", "not_found"].includes(e.code)) {
3658
4436
  setPage(hfErrorPage(e));
3659
4437
  } else {
3660
- toError(e, "job impossible", { name: "home" });
4438
+ toError(e, t("common.jobImpossible"), { name: "home" });
3661
4439
  }
3662
4440
  }
3663
4441
  };
@@ -3677,8 +4455,8 @@ function App({
3677
4455
  if (!st.isFile()) {
3678
4456
  setPage({
3679
4457
  name: "error",
3680
- title: "pas un fichier",
3681
- detail: `${p} est un dossier \u2014 fournis un fichier de poids ou une archive.`,
4458
+ title: t("job.file.err.notFile.title"),
4459
+ detail: t("job.file.err.notFile.detail", { path: p }),
3682
4460
  back: { name: "job-file" }
3683
4461
  });
3684
4462
  return;
@@ -3687,7 +4465,7 @@ function App({
3687
4465
  } catch {
3688
4466
  setPage({
3689
4467
  name: "error",
3690
- title: "fichier introuvable",
4468
+ title: t("job.file.err.notFound.title"),
3691
4469
  detail: p,
3692
4470
  back: { name: "job-file" }
3693
4471
  });
@@ -3696,7 +4474,7 @@ function App({
3696
4474
  if (size <= 0) {
3697
4475
  setPage({
3698
4476
  name: "error",
3699
- title: "fichier vide",
4477
+ title: t("job.file.err.empty.title"),
3700
4478
  detail: p,
3701
4479
  back: { name: "job-file" }
3702
4480
  });
@@ -3705,7 +4483,7 @@ function App({
3705
4483
  setPage({ name: "job-file-confirm", filePath: p, fileName: path.basename(p), sizeBytes: size });
3706
4484
  };
3707
4485
  const startDirectJob = async (filePath2, fileName, sizeBytes) => {
3708
- setPage({ name: "loading", label: "cr\xE9ation du job\u2026" });
4486
+ setPage({ name: "loading", label: t("common.creatingJob") });
3709
4487
  try {
3710
4488
  const active = await resolveActiveOrg();
3711
4489
  if (!active) {
@@ -3719,8 +4497,8 @@ function App({
3719
4497
  });
3720
4498
  if (!created.upload_token) {
3721
4499
  toError(
3722
- new Error("r\xE9ponse serveur sans upload_token"),
3723
- "job impossible",
4500
+ new Error(t("job.noUploadToken")),
4501
+ t("common.jobImpossible"),
3724
4502
  { name: "home" }
3725
4503
  );
3726
4504
  return;
@@ -3734,11 +4512,11 @@ function App({
3734
4512
  sizeBytes
3735
4513
  });
3736
4514
  } catch (e) {
3737
- toError(e, "job impossible", { name: "home" });
4515
+ toError(e, t("common.jobImpossible"), { name: "home" });
3738
4516
  }
3739
4517
  };
3740
4518
  const openBuildSummary = async (buildId, opts = {}) => {
3741
- if (!opts.quiet) setPage({ name: "loading", label: "r\xE9cup\xE9ration du job\u2026" });
4519
+ if (!opts.quiet) setPage({ name: "loading", label: t("job.fetching") });
3742
4520
  try {
3743
4521
  const active = await resolveActiveOrg();
3744
4522
  if (!active) {
@@ -3748,18 +4526,18 @@ function App({
3748
4526
  const build = await getBuild(active.slug, buildId);
3749
4527
  setPage({ name: "build-summary", build });
3750
4528
  } catch (e) {
3751
- toError(e, "job introuvable", { name: "home" });
4529
+ toError(e, t("job.notFound"), { name: "home" });
3752
4530
  }
3753
4531
  };
3754
4532
  const loadOrgs = async (back2) => {
3755
- setPage({ name: "loading", label: "chargement des organisations\u2026" });
4533
+ setPage({ name: "loading", label: t("orgs.loading") });
3756
4534
  try {
3757
4535
  const me = await labApi("/me");
3758
4536
  const orgs = await labApi("/orgs");
3759
4537
  if (orgs.length === 0) setPage({ name: "no-org" });
3760
4538
  else setPage({ name: "orgs", me, orgs });
3761
4539
  } catch (e) {
3762
- toError(e, "impossible de r\xE9cup\xE9rer les organisations", back2);
4540
+ toError(e, t("orgs.fetchFailed"), back2);
3763
4541
  }
3764
4542
  };
3765
4543
  const startBrowserLogin = async () => {
@@ -3774,19 +4552,19 @@ function App({
3774
4552
  setSession("oauth");
3775
4553
  await loadOrgs({ name: "landing" });
3776
4554
  } catch (e) {
3777
- toError(e, "connexion \xE9chou\xE9e", { name: "landing" });
4555
+ toError(e, t("common.loginFailed"), { name: "landing" });
3778
4556
  }
3779
4557
  };
3780
4558
  const submitKey = async (key) => {
3781
4559
  const trimmed = key.trim();
3782
4560
  if (!trimmed) return;
3783
- setPage({ name: "loading", label: "validation de la cl\xE9\u2026" });
4561
+ setPage({ name: "loading", label: t("login.validatingKey") });
3784
4562
  try {
3785
4563
  await saveCredentials({ kind: "api-key", apiKey: trimmed });
3786
4564
  await labApi("/me");
3787
4565
  } catch (e) {
3788
4566
  await clearCredentials();
3789
- toError(e, "cl\xE9 API invalide", { name: "login-key" });
4567
+ toError(e, t("login.invalidKey"), { name: "login-key" });
3790
4568
  return;
3791
4569
  }
3792
4570
  setSession("api-key");
@@ -3797,7 +4575,7 @@ function App({
3797
4575
  setPage({ name: "home" });
3798
4576
  };
3799
4577
  const showQuota = async () => {
3800
- setPage({ name: "loading", label: "quota de build\u2026" });
4578
+ setPage({ name: "loading", label: t("quota.loading") });
3801
4579
  try {
3802
4580
  const active = await resolveActiveOrg();
3803
4581
  if (!active) {
@@ -3807,20 +4585,20 @@ function App({
3807
4585
  const q = await getOrgQuota(active.slug);
3808
4586
  setPage({ name: "quota", orgName: active.name, remaining: q.remaining });
3809
4587
  } catch (e) {
3810
- toError(e, "quota de build inaccessible", { name: "home" });
4588
+ toError(e, t("quota.unreachable"), { name: "home" });
3811
4589
  }
3812
4590
  };
3813
4591
  const showWhoami = async () => {
3814
- setPage({ name: "loading", label: "profil\u2026" });
4592
+ setPage({ name: "loading", label: t("whoami.loading") });
3815
4593
  try {
3816
4594
  const me = await labApi("/me");
3817
4595
  setPage({ name: "whoami", me });
3818
4596
  } catch (e) {
3819
- toError(e, "profil inaccessible", { name: "home" });
4597
+ toError(e, t("whoami.unreachable"), { name: "home" });
3820
4598
  }
3821
4599
  };
3822
4600
  const logout = async () => {
3823
- setPage({ name: "loading", label: "d\xE9connexion\u2026" });
4601
+ setPage({ name: "loading", label: t("common.signingOut") });
3824
4602
  await clearCredentials();
3825
4603
  const cfg = loadConfig();
3826
4604
  delete cfg.activeOrgId;
@@ -3833,14 +4611,14 @@ function App({
3833
4611
  switch (page2.name) {
3834
4612
  case "landing":
3835
4613
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3836
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "comment voulez-vous vous connecter ?" }),
4614
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("landing.howConnect") }),
3837
4615
  /* @__PURE__ */ jsx19(
3838
4616
  SelectList,
3839
4617
  {
3840
4618
  items: [
3841
- { label: "Se connecter via le navigateur", value: "browser", hint: "OAuth + PKCE" },
3842
- { label: "Coller une cl\xE9 API", value: "key", hint: "lab_\u2026 \xB7 headless" },
3843
- { label: "Plus tard", value: "later", hint: "`sumdae login` quand vous voulez" }
4619
+ { label: t("landing.menu.browser"), value: "browser", hint: t("landing.menu.browser.hint") },
4620
+ { label: t("landing.menu.key"), value: "key", hint: t("landing.menu.key.hint") },
4621
+ { label: t("landing.menu.later"), value: "later", hint: t("landing.menu.later.hint") }
3844
4622
  ],
3845
4623
  onSelect: (i) => {
3846
4624
  if (i.value === "browser") void startBrowserLogin();
@@ -3853,9 +4631,9 @@ function App({
3853
4631
  break;
3854
4632
  case "login-browser":
3855
4633
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3856
- /* @__PURE__ */ jsx19(BusyIndicator, { label: "en attente de la connexion dans le navigateur\u2026", compact: true }),
4634
+ /* @__PURE__ */ jsx19(BusyIndicator, { label: t("common.waitingBrowserLogin"), compact: true }),
3857
4635
  page2.url ? /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", children: [
3858
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: "si rien ne s'ouvre, copiez cette URL :" }),
4636
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: t("login.browser.copyUrl") }),
3859
4637
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc400, children: page2.url })
3860
4638
  ] }) : null
3861
4639
  ] });
@@ -3865,13 +4643,13 @@ function App({
3865
4643
  /* @__PURE__ */ jsx19(
3866
4644
  TextField,
3867
4645
  {
3868
- title: "Cl\xE9 API",
3869
- placeholder: "lab_\u2026",
4646
+ title: t("login.key.title"),
4647
+ placeholder: t("login.key.placeholder"),
3870
4648
  onSubmit: (v) => void submitKey(v),
3871
4649
  onCancel: () => back()
3872
4650
  }
3873
4651
  ),
3874
- /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(Keycaps, { items: [{ key: "enter", desc: "valider" }, { key: "esc", desc: "retour" }] }) })
4652
+ /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(Keycaps, { items: [{ key: "enter", desc: t("keycaps.validate") }, { key: "esc", desc: t("keycaps.back") }] }) })
3875
4653
  ] });
3876
4654
  break;
3877
4655
  case "loading":
@@ -3882,19 +4660,16 @@ function App({
3882
4660
  break;
3883
4661
  case "orgs":
3884
4662
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3885
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc400, children: [
3886
- "connect\xE9 : ",
3887
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.me.email })
3888
- ] }),
4663
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc400, children: t("common.connectedAs", { email: page2.me.email }) }),
3889
4664
  /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", children: [
3890
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "organisation active :" }),
4665
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("common.activeOrgLabel") }),
3891
4666
  /* @__PURE__ */ jsx19(
3892
4667
  SelectList,
3893
4668
  {
3894
4669
  items: page2.orgs.map((o) => ({
3895
4670
  label: o.name,
3896
4671
  value: o._id,
3897
- hint: `${o.slug} \xB7 ${o.members.find((m) => m.user_id === page2.me.id)?.role ?? "member"}`
4672
+ hint: `${o.slug} \xB7 ${o.members.find((m) => m.user_id === page2.me.id)?.role ?? t("orgs.role.member")}`
3898
4673
  })),
3899
4674
  onSelect: (item) => {
3900
4675
  const org2 = page2.orgs.find((o) => o._id === item.value);
@@ -3917,24 +4692,27 @@ function App({
3917
4692
  break;
3918
4693
  case "home": {
3919
4694
  const cfg = loadConfig();
4695
+ const langLabel = `Langue / Language : ${getLocale() === "fr" ? "Fran\xE7ais" : "English"} \u203A`;
3920
4696
  const menuItems = [
3921
- { label: "Lancer un job", value: "job", hint: "int\xE9grer un mod\xE8le (Hugging Face ou fichier local)" },
3922
- { label: "Lancer une simulation", value: "simulate", hint: "t\xE2che libre \u2014 dry-run du pipeline complet" },
3923
- { label: "Quota de build", value: "quota", hint: "builds restants" },
3924
- { label: "Changer d'organisation", value: "orgs" },
3925
- { label: "Qui suis-je", value: "whoami", hint: "profil complet" },
3926
- { label: "Rafra\xEEchir l\u2019historique", value: "refresh" },
3927
- { label: "Se d\xE9connecter", value: "logout" },
3928
- { label: "D\xE9sinstaller sumdae", value: "uninstall", hint: "supprimer la CLI de cette machine" },
3929
- { label: "Quitter", value: "quit" }
4697
+ { label: t("home.menu.job"), value: "job", hint: t("home.menu.job.hint") },
4698
+ { label: t("home.menu.simulate"), value: "simulate", hint: t("home.menu.simulate.hint") },
4699
+ { label: t("home.menu.quota"), value: "quota", hint: t("home.menu.quota.hint") },
4700
+ { label: t("home.menu.orgs"), value: "orgs" },
4701
+ { label: t("home.menu.whoami"), value: "whoami", hint: t("home.menu.whoami.hint") },
4702
+ { label: t("home.menu.refresh"), value: "refresh" },
4703
+ { label: langLabel, value: "lang", hint: t("home.menu.lang.hint") },
4704
+ { label: t("home.menu.logout"), value: "logout" },
4705
+ { label: t("home.menu.uninstall"), value: "uninstall", hint: t("home.menu.uninstall.hint") },
4706
+ { label: t("common.quit"), value: "quit" }
3930
4707
  ];
4708
+ homeLangIndexRef.current = menuItems.findIndex((i) => i.value === "lang");
3931
4709
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3932
4710
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", marginBottom: 1, children: [
3933
4711
  /* @__PURE__ */ jsx19(Box16, { children: /* @__PURE__ */ jsxs17(Text19, { color: homeFocus === "history" ? COLORS.emerald : COLORS.zinc500, children: [
3934
4712
  homeFocus === "history" ? "\u25B8 " : " ",
3935
- "derniers jobs"
4713
+ t("home.recentJobs")
3936
4714
  ] }) }),
3937
- recentBuilds === null ? /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(BusyIndicator, { label: "chargement de l\u2019historique\u2026", compact: true }) }) : recentBuilds.length === 0 ? /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: recentError ?? "aucun job pour le moment \u2014 lancez une simulation pour commencer" }) }) : /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(
4715
+ recentBuilds === null ? /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(BusyIndicator, { label: t("home.loadingHistory"), compact: true }) }) : recentBuilds.length === 0 ? /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: recentError ?? t("home.noJobsYet") }) }) : /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(
3938
4716
  SelectList,
3939
4717
  {
3940
4718
  isActive: homeFocus === "history",
@@ -3942,7 +4720,7 @@ function App({
3942
4720
  items: recentBuilds.map((b) => {
3943
4721
  const running = !isTerminalStatus(b.status);
3944
4722
  return {
3945
- label: `${b.id.slice(0, 8)} \xB7 ${b.kind === "simulation" ? "sim" : "build"} \xB7 ${b.status}`,
4723
+ label: `${b.id.slice(0, 8)} \xB7 ${b.kind === "simulation" ? t("home.job.kind.sim") : t("home.job.kind.build")} \xB7 ${b.status}`,
3946
4724
  value: `job:${b.id}`,
3947
4725
  hint: b.simulation_task ? b.simulation_task.slice(0, 50) : b.weights_artifact_id?.slice(0, 8) ?? void 0,
3948
4726
  ...running ? { prefix: /* @__PURE__ */ jsx19(Twinkle, {}), labelColor: COLORS.emerald } : {}
@@ -3957,20 +4735,17 @@ function App({
3957
4735
  ] }),
3958
4736
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "row", children: [
3959
4737
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", flexShrink: 0, marginRight: 3, children: [
3960
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: cfg.activeOrgName ?? "\u2014 aucune organisation" }),
4738
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: cfg.activeOrgName ?? t("home.noActiveOrg") }),
3961
4739
  /* @__PURE__ */ jsxs17(Text19, { children: [
3962
4740
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.emerald, children: "\u2713 " }),
3963
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
3964
- "session : ",
3965
- session ?? "\u2014"
3966
- ] })
4741
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("home.sessionLabel", { session: session ?? "\u2014" }) })
3967
4742
  ] }),
3968
4743
  logoCells ? /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(OrgLogo, { cells: logoCells }) }) : null
3969
4744
  ] }),
3970
4745
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", flexGrow: 1, marginTop: logoCells ? 3 : 0, children: [
3971
4746
  /* @__PURE__ */ jsx19(Box16, { children: /* @__PURE__ */ jsxs17(Text19, { color: homeFocus === "menu" ? COLORS.emerald : COLORS.zinc500, children: [
3972
4747
  homeFocus === "menu" ? "\u25B8 " : " ",
3973
- "actions"
4748
+ t("home.actions")
3974
4749
  ] }) }),
3975
4750
  /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(
3976
4751
  SelectList,
@@ -3978,6 +4753,7 @@ function App({
3978
4753
  isActive: homeFocus === "menu",
3979
4754
  showHint: false,
3980
4755
  items: menuItems,
4756
+ onIndexChange: setHomeMenuIndex,
3981
4757
  onSelect: (i) => {
3982
4758
  if (i.value === "job") setPage({ name: "job-source" });
3983
4759
  else if (i.value === "simulate") setPage({ name: "simulate-prompt" });
@@ -3985,6 +4761,7 @@ function App({
3985
4761
  else if (i.value === "orgs") void loadOrgs({ name: "home" });
3986
4762
  else if (i.value === "whoami") void showWhoami();
3987
4763
  else if (i.value === "refresh") void reloadRecentBuilds();
4764
+ else if (i.value === "lang") switchLocale(getLocale() === "fr" ? "en" : "fr");
3988
4765
  else if (i.value === "logout") void logout();
3989
4766
  else if (i.value === "uninstall") setPage({ name: "uninstall-confirm" });
3990
4767
  else quit();
@@ -3997,9 +4774,9 @@ function App({
3997
4774
  Keycaps,
3998
4775
  {
3999
4776
  items: [
4000
- { key: "\u2191\u2193", desc: "naviguer" },
4001
- { key: "tab", desc: "basculer zone" },
4002
- { key: "enter", desc: "valider" }
4777
+ { key: "\u2191\u2193", desc: t("keycaps.navigate") },
4778
+ { key: "tab", desc: t("keycaps.switchZone") },
4779
+ { key: "enter", desc: t("keycaps.validate") }
4003
4780
  ]
4004
4781
  }
4005
4782
  ) })
@@ -4014,17 +4791,17 @@ function App({
4014
4791
  KeyValue,
4015
4792
  {
4016
4793
  rows: [
4017
- ["user", name || page2.me.email],
4018
- ["email", page2.me.email],
4019
- ["org", cfg.activeOrgName ? `${cfg.activeOrgName} (${cfg.activeOrgId})` : "\u2014 aucune"],
4020
- ["session", session ?? "\u2014"]
4794
+ [t("whoami.row.user"), name || page2.me.email],
4795
+ [t("whoami.row.email"), page2.me.email],
4796
+ [t("whoami.row.org"), cfg.activeOrgName ? `${cfg.activeOrgName} (${cfg.activeOrgId})` : t("whoami.org.none")],
4797
+ [t("whoami.row.session"), session ?? "\u2014"]
4021
4798
  ]
4022
4799
  }
4023
4800
  ),
4024
4801
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4025
4802
  SelectList,
4026
4803
  {
4027
- items: [backItem("Retourner \xE0 l'accueil")],
4804
+ items: [backItem(t("common.backToHome"))],
4028
4805
  onSelect: () => back()
4029
4806
  }
4030
4807
  ) })
@@ -4033,18 +4810,18 @@ function App({
4033
4810
  }
4034
4811
  case "quota": {
4035
4812
  const rows2 = [
4036
- ["org", page2.orgName],
4037
- ["builds restants", String(page2.remaining)]
4813
+ [t("whoami.row.org"), page2.orgName],
4814
+ [t("quota.row.remaining"), String(page2.remaining)]
4038
4815
  ];
4039
4816
  if (page2.remaining === 0) {
4040
- rows2.push(["note", "quota \xE9puis\xE9 \u2014 demandez \xE0 l'admin d'augmenter build_quota"]);
4817
+ rows2.push([t("quota.row.note"), t("quota.exhausted")]);
4041
4818
  }
4042
4819
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4043
4820
  /* @__PURE__ */ jsx19(KeyValue, { rows: rows2 }),
4044
4821
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4045
4822
  SelectList,
4046
4823
  {
4047
- items: [backItem("Retourner \xE0 l'accueil")],
4824
+ items: [backItem(t("common.backToHome"))],
4048
4825
  onSelect: () => back()
4049
4826
  }
4050
4827
  ) })
@@ -4056,8 +4833,8 @@ function App({
4056
4833
  /* @__PURE__ */ jsx19(
4057
4834
  TextField,
4058
4835
  {
4059
- title: "T\xE2che pour le master \xB7 mode SIMULATION",
4060
- placeholder: "ex. : cr\xE9e /workspace/hello.txt avec \xAB bonjour \xBB et liste le dossier",
4836
+ title: t("simulate.title"),
4837
+ placeholder: t("simulate.placeholder"),
4061
4838
  onCancel: () => back(),
4062
4839
  onSubmit: (task) => void startSimulation(task)
4063
4840
  }
@@ -4066,10 +4843,10 @@ function App({
4066
4843
  Keycaps,
4067
4844
  {
4068
4845
  items: [
4069
- { key: "enter", desc: "envoyer" },
4070
- { key: "\\ + enter", desc: "nouvelle ligne" },
4071
- { key: "\u2190\u2192\u2191\u2193", desc: "naviguer" },
4072
- { key: "esc", desc: "retour" }
4846
+ { key: "enter", desc: t("keycaps.send") },
4847
+ { key: "\\ + enter", desc: t("keycaps.newline") },
4848
+ { key: "\u2190\u2192\u2191\u2193", desc: t("keycaps.navigate") },
4849
+ { key: "esc", desc: t("keycaps.back") }
4073
4850
  ]
4074
4851
  }
4075
4852
  ) })
@@ -4077,21 +4854,21 @@ function App({
4077
4854
  break;
4078
4855
  case "job-source":
4079
4856
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4080
- /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: "lancer un job" }) }),
4081
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "d'o\xF9 viennent les poids du mod\xE8le \xE0 int\xE9grer ?" }),
4857
+ /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: t("job.source.title") }) }),
4858
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.source.question") }),
4082
4859
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4083
4860
  SelectList,
4084
4861
  {
4085
4862
  items: [
4086
4863
  {
4087
- label: "Lien Hugging Face",
4864
+ label: t("job.source.hf"),
4088
4865
  value: "hf",
4089
- hint: "t\xE9l\xE9charg\xE9 directement sur l\u2019environnement (token org requis)"
4866
+ hint: t("job.source.hf.hint")
4090
4867
  },
4091
4868
  {
4092
- label: "Fichier local",
4869
+ label: t("job.source.file"),
4093
4870
  value: "file",
4094
- hint: "stream\xE9 depuis cette machine vers l\u2019environnement"
4871
+ hint: t("job.source.file.hint")
4095
4872
  },
4096
4873
  backItem()
4097
4874
  ],
@@ -4109,30 +4886,26 @@ function App({
4109
4886
  /* @__PURE__ */ jsx19(
4110
4887
  TextField,
4111
4888
  {
4112
- title: "Mod\xE8le Hugging Face",
4113
- placeholder: "ex. : mistralai/Mistral-7B-v0.1, owner/repo@rev, ou URL huggingface.co",
4889
+ title: t("job.hf.title"),
4890
+ placeholder: t("job.hf.placeholder"),
4114
4891
  onCancel: () => back(),
4115
4892
  onSubmit: (v) => void submitHfRepo(v)
4116
4893
  }
4117
4894
  ),
4118
4895
  hfTokenHint ? /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, children: [
4119
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: "token HF : " }),
4896
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: t("job.hf.tokenLabel") }),
4120
4897
  hfTokenHint.account ? /* @__PURE__ */ jsxs17(Text19, { color: COLORS.emerald, children: [
4121
4898
  hfTokenHint.account,
4122
4899
  " "
4123
4900
  ] }) : null,
4124
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
4125
- "(",
4126
- hfTokenHint.masked,
4127
- ")"
4128
- ] })
4129
- ] }) : /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: "aucun token HF configur\xE9 \u2014 ajoutez-en un dans Param\xE8tres org. sur lab.sumdae.fr" }) }),
4901
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.hf.tokenMasked", { masked: hfTokenHint.masked }) })
4902
+ ] }) : /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: t("job.hf.noToken") }) }),
4130
4903
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4131
4904
  Keycaps,
4132
4905
  {
4133
4906
  items: [
4134
- { key: "enter", desc: "v\xE9rifier l\u2019acc\xE8s" },
4135
- { key: "esc", desc: "retour" }
4907
+ { key: "enter", desc: t("keycaps.checkAccess") },
4908
+ { key: "esc", desc: t("keycaps.back") }
4136
4909
  ]
4137
4910
  }
4138
4911
  ) })
@@ -4140,9 +4913,9 @@ function App({
4140
4913
  break;
4141
4914
  case "job-hf-confirm":
4142
4915
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4143
- /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: "mod\xE8le v\xE9rifi\xE9 \u2014 acc\xE8s OK" }) }),
4916
+ /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: t("job.hfConfirm.title") }) }),
4144
4917
  /* @__PURE__ */ jsxs17(Text19, { children: [
4145
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "repo : " }),
4918
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.hfConfirm.repo") }),
4146
4919
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.repo }),
4147
4920
  page2.revision ? /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
4148
4921
  " @ ",
@@ -4150,17 +4923,17 @@ function App({
4150
4923
  ] }) : null
4151
4924
  ] }),
4152
4925
  /* @__PURE__ */ jsxs17(Text19, { children: [
4153
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "taille estim\xE9e : " }),
4154
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.sizeBytes !== null ? formatBytes(page2.sizeBytes) : "inconnue" }),
4155
- page2.gated ? /* @__PURE__ */ jsx19(Text19, { color: COLORS.amber, children: " \xB7 repo gated (acc\xE8s accord\xE9)" }) : null
4926
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.hfConfirm.sizeEstimate") }),
4927
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.sizeBytes !== null ? formatBytes(page2.sizeBytes) : t("job.hfConfirm.sizeUnknown") }),
4928
+ page2.gated ? /* @__PURE__ */ jsx19(Text19, { color: COLORS.amber, children: t("job.hfConfirm.gated") }) : null
4156
4929
  ] }),
4157
- /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "le mod\xE8le sera t\xE9l\xE9charg\xE9 sur l'environnement, int\xE9gr\xE9 dans la template, test\xE9, puis l'image Docker sera publi\xE9e. 1 build de quota sera consomm\xE9." }) }),
4930
+ /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.hfConfirm.explain") }) }),
4158
4931
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4159
4932
  SelectList,
4160
4933
  {
4161
4934
  items: [
4162
- { label: "Lancer le job", value: "go" },
4163
- { label: "Modifier le lien", value: "edit" },
4935
+ { label: t("job.hfConfirm.launch"), value: "go" },
4936
+ { label: t("job.hfConfirm.editLink"), value: "edit" },
4164
4937
  backItem()
4165
4938
  ],
4166
4939
  onSelect: (i) => {
@@ -4177,8 +4950,8 @@ function App({
4177
4950
  /* @__PURE__ */ jsx19(
4178
4951
  TextField,
4179
4952
  {
4180
- title: "Fichier de poids / archive locale",
4181
- placeholder: "ex. : ~/models/mistral-7b.safetensors ou ./weights.tar.gz",
4953
+ title: t("job.file.title"),
4954
+ placeholder: t("job.file.placeholder"),
4182
4955
  onCancel: () => back(),
4183
4956
  onSubmit: (v) => void submitFilePath(v)
4184
4957
  }
@@ -4187,8 +4960,8 @@ function App({
4187
4960
  Keycaps,
4188
4961
  {
4189
4962
  items: [
4190
- { key: "enter", desc: "valider" },
4191
- { key: "esc", desc: "retour" }
4963
+ { key: "enter", desc: t("keycaps.validate") },
4964
+ { key: "esc", desc: t("keycaps.back") }
4192
4965
  ]
4193
4966
  }
4194
4967
  ) })
@@ -4196,9 +4969,9 @@ function App({
4196
4969
  break;
4197
4970
  case "job-file-confirm":
4198
4971
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4199
- /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: "envoi direct vers l'environnement" }) }),
4972
+ /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: t("job.fileConfirm.title") }) }),
4200
4973
  /* @__PURE__ */ jsxs17(Text19, { children: [
4201
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "fichier : " }),
4974
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.fileConfirm.fileLabel") }),
4202
4975
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.fileName }),
4203
4976
  /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
4204
4977
  " (",
@@ -4207,15 +4980,15 @@ function App({
4207
4980
  ] })
4208
4981
  ] }),
4209
4982
  /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", children: [
4210
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.amber, children: "\u26A0 le fichier est stream\xE9 DIRECTEMENT de cette machine vers l'environnement (il ne transite par aucun serveur interm\xE9diaire)." }),
4211
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.amber, children: "garde ta machine allum\xE9e et ta connexion stable pendant tout l'envoi : si la connexion est interrompue, le job est coup\xE9 automatiquement." })
4983
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.amber, children: t("job.fileConfirm.warnDirect") }),
4984
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.amber, children: t("job.fileConfirm.warnKeepAlive") })
4212
4985
  ] }),
4213
4986
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4214
4987
  SelectList,
4215
4988
  {
4216
4989
  items: [
4217
- { label: "J'ai compris \u2014 lancer le job", value: "go" },
4218
- { label: "Changer de fichier", value: "edit" },
4990
+ { label: t("job.fileConfirm.gotItLaunch"), value: "go" },
4991
+ { label: t("job.fileConfirm.changeFile"), value: "edit" },
4219
4992
  backItem()
4220
4993
  ],
4221
4994
  onSelect: (i) => {
@@ -4241,7 +5014,7 @@ function App({
4241
5014
  onDone: () => setPage({ name: "watch", buildId: page2.buildId }),
4242
5015
  onError: (message) => setPage({
4243
5016
  name: "error",
4244
- title: "upload interrompu",
5017
+ title: t("upload.interrupted.title"),
4245
5018
  detail: message,
4246
5019
  back: { name: "home" }
4247
5020
  })
@@ -4268,20 +5041,20 @@ function App({
4268
5041
  const active = !isTerminalStatus(page2.build.status);
4269
5042
  const createdAtMs = page2.build.created_at ? Date.parse(page2.build.created_at) : void 0;
4270
5043
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4271
- /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "r\xE9sum\xE9 du job" }) }),
5044
+ /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("buildSummary.heading") }) }),
4272
5045
  /* @__PURE__ */ jsx19(JobSummary, { build: page2.build }),
4273
5046
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4274
5047
  SelectList,
4275
5048
  {
4276
5049
  items: [
4277
5050
  active ? {
4278
- label: "Voir le job en cours",
5051
+ label: t("buildSummary.watchLive"),
4279
5052
  value: "rewatch",
4280
5053
  labelColor: COLORS.emerald,
4281
5054
  prefix: /* @__PURE__ */ jsx19(Twinkle, {})
4282
- } : { label: "Revoir le flux complet", value: "rewatch", hint: "rejouer les events" },
4283
- backItem("Retourner \xE0 l'accueil"),
4284
- { label: "Quitter", value: "quit" }
5055
+ } : { label: t("buildSummary.rewatch"), value: "rewatch", hint: t("buildSummary.rewatch.hint") },
5056
+ backItem(t("common.backToHome")),
5057
+ { label: t("common.quit"), value: "quit" }
4285
5058
  ],
4286
5059
  onSelect: (i) => {
4287
5060
  if (i.value === "rewatch")
@@ -4317,8 +5090,8 @@ function App({
4317
5090
  SelectList,
4318
5091
  {
4319
5092
  items: [
4320
- { label: "Retourner en arri\xE8re", value: "back", hint: "ou \xC9chap" },
4321
- { label: "Quitter", value: "quit" }
5093
+ { label: t("common.back"), value: "back", hint: t("common.orEscape") },
5094
+ { label: t("common.quit"), value: "quit" }
4322
5095
  ],
4323
5096
  onSelect: (i) => i.value === "back" ? setPage(page2.back) : quit()
4324
5097
  }
@@ -4339,22 +5112,10 @@ function App({
4339
5112
  justifyContent: "center",
4340
5113
  flexDirection: "column",
4341
5114
  children: [
4342
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: "Terminal trop petit" }),
5115
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: t("common.terminalTooSmall") }),
4343
5116
  /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", alignItems: "center", children: [
4344
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, wrap: "truncate", children: [
4345
- "Agrandissez \xE0 ",
4346
- MIN_COLS,
4347
- "\xD7",
4348
- MIN_ROWS,
4349
- " minimum"
4350
- ] }),
4351
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, wrap: "truncate", children: [
4352
- "(actuel : ",
4353
- columns,
4354
- "\xD7",
4355
- rows,
4356
- ")"
4357
- ] })
5117
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, wrap: "truncate", children: t("common.terminalEnlarge", { cols: MIN_COLS, rows: MIN_ROWS }) }),
5118
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, wrap: "truncate", children: t("common.terminalCurrent", { cols: columns, rows }) })
4358
5119
  ] })
4359
5120
  ]
4360
5121
  }
@@ -4646,8 +5407,8 @@ function Banner({ animate, onDone }) {
4646
5407
  onDone?.();
4647
5408
  return;
4648
5409
  }
4649
- const t = setTimeout(() => setFrame((f) => f + 1), FRAME_MS3);
4650
- return () => clearTimeout(t);
5410
+ const t2 = setTimeout(() => setFrame((f) => f + 1), FRAME_MS3);
5411
+ return () => clearTimeout(t2);
4651
5412
  }, [animate, frame, onDone]);
4652
5413
  const color = colorEnabled();
4653
5414
  const angle = 0.9 + frame * 0.05;
@@ -4657,7 +5418,8 @@ function Banner({ animate, onDone }) {
4657
5418
  /* @__PURE__ */ jsx20(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text20, { children: planetFrame(angle, { orbitAngle }, color) }) }),
4658
5419
  /* @__PURE__ */ jsx20(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text20, { color: COLORS.zinc500, children: [
4659
5420
  /* @__PURE__ */ jsx20(Text20, { color: COLORS.emerald, children: "$" }),
4660
- " la plateforme sumdae, depuis le terminal"
5421
+ " ",
5422
+ t("banner.tagline")
4661
5423
  ] }) })
4662
5424
  ] });
4663
5425
  }
@@ -4666,23 +5428,20 @@ function Banner({ animate, onDone }) {
4666
5428
  import { Box as Box18, Text as Text21 } from "ink";
4667
5429
  import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
4668
5430
  var COMMANDS = [
4669
- { cmd: "sumdae login", desc: "se connecter (OAuth + PKCE)" },
4670
- { cmd: "sumdae whoami", desc: "profil + organisation active" },
4671
- { cmd: "sumdae push <fichier>", desc: "uploader poids/pipeline (R2)" },
4672
- { cmd: "sumdae build <artifact>", desc: "lancer un build + suivi live" },
4673
- { cmd: "sumdae watch <buildId>", desc: "suivre un build en cours" },
4674
- { cmd: "sumdae cancel <buildId>", desc: "annuler un build proprement" },
4675
- { cmd: "sumdae quota", desc: "builds restants de l'org active" },
4676
- { cmd: "sumdae org list", desc: "lister vos organisations" },
4677
- { cmd: "sumdae org switch", desc: "changer d'organisation active" },
4678
- { cmd: "sumdae logout", desc: "se d\xE9connecter" }
5431
+ { cmd: "sumdae login", desc: t("help.login") },
5432
+ { cmd: "sumdae whoami", desc: t("cli.whoami.description") },
5433
+ { cmd: "sumdae push <fichier>", desc: t("help.push") },
5434
+ { cmd: "sumdae build <artifact>", desc: t("help.build") },
5435
+ { cmd: "sumdae watch <buildId>", desc: t("help.watch") },
5436
+ { cmd: "sumdae cancel <buildId>", desc: t("help.cancel") },
5437
+ { cmd: "sumdae quota", desc: t("help.quota") },
5438
+ { cmd: "sumdae org list", desc: t("cli.org.list.description") },
5439
+ { cmd: "sumdae org switch", desc: t("cli.org.switch.description") },
5440
+ { cmd: "sumdae logout", desc: t("help.logout") }
4679
5441
  ];
4680
5442
  function Help({ version }) {
4681
5443
  return /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
4682
- /* @__PURE__ */ jsxs19(Text21, { color: COLORS.zinc600, children: [
4683
- "COMMANDES \xB7 v",
4684
- version
4685
- ] }),
5444
+ /* @__PURE__ */ jsx21(Text21, { color: COLORS.zinc600, children: t("help.heading", { version }) }),
4686
5445
  COMMANDS.map((c, i) => /* @__PURE__ */ jsxs19(Box18, { marginTop: i === 0 ? 1 : 0, children: [
4687
5446
  /* @__PURE__ */ jsx21(Box18, { width: 9, children: /* @__PURE__ */ jsx21(Text21, { color: COLORS.zinc600, children: `cmd_${String(i + 1).padStart(2, "0")}` }) }),
4688
5447
  /* @__PURE__ */ jsx21(Box18, { width: 22, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.zinc100, children: [
@@ -4720,8 +5479,8 @@ async function homeAction(version) {
4720
5479
  if (!interactive) {
4721
5480
  const cfg2 = loadConfig();
4722
5481
  const status = creds ? [
4723
- creds.kind === "api-key" ? "session : cl\xE9 API" : "session : oauth",
4724
- cfg2.activeOrgName ? `organisation : ${cfg2.activeOrgName}` : "organisation : \u2014 (sumdae org switch)"
5482
+ creds.kind === "api-key" ? t("home.static.sessionApiKey") : t("home.static.sessionOauth"),
5483
+ cfg2.activeOrgName ? `organisation : ${cfg2.activeOrgName}` : t("home.static.orgNone")
4725
5484
  ] : null;
4726
5485
  const app2 = render3(/* @__PURE__ */ jsx22(Home, { animate: false, status, version }));
4727
5486
  await app2.waitUntilExit();
@@ -4757,8 +5516,8 @@ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u282
4757
5516
  function Spinner({ label }) {
4758
5517
  const [i, setI] = useState11(0);
4759
5518
  useEffect10(() => {
4760
- const t = setInterval(() => setI((x) => (x + 1) % FRAMES.length), 80);
4761
- return () => clearInterval(t);
5519
+ const t2 = setInterval(() => setI((x) => (x + 1) % FRAMES.length), 80);
5520
+ return () => clearInterval(t2);
4762
5521
  }, []);
4763
5522
  return /* @__PURE__ */ jsxs21(Text23, { children: [
4764
5523
  /* @__PURE__ */ jsx23(Text23, { color: COLORS.emerald, children: FRAMES[i] }),
@@ -4773,7 +5532,7 @@ function Spinner({ label }) {
4773
5532
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
4774
5533
  function orgItems(orgs, myId) {
4775
5534
  return orgs.map((o) => {
4776
- const role = o.members.find((m) => m.user_id === myId)?.role ?? "member";
5535
+ const role = o.members.find((m) => m.user_id === myId)?.role ?? t("orgs.role.member");
4777
5536
  return { label: o.name, value: o._id, hint: `${o.slug} \xB7 ${role}` };
4778
5537
  });
4779
5538
  }
@@ -4797,7 +5556,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4797
5556
  if (orgs.length === 0) {
4798
5557
  setPhase({
4799
5558
  step: "done",
4800
- lines: [`connect\xE9 : ${me.email}`, "aucune organisation \u2014 cr\xE9ez-en une sur lab.sumdae.fr"]
5559
+ lines: [t("common.connectedAs", { email: me.email }), t("common.noOrgCreateOnLab")]
4801
5560
  });
4802
5561
  setTimeout(exit, 10);
4803
5562
  return;
@@ -4805,7 +5564,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4805
5564
  if (!process.stdin.isTTY) {
4806
5565
  setPhase({
4807
5566
  step: "done",
4808
- lines: [`connect\xE9 : ${me.email}`, "choisissez une organisation avec `sumdae org switch` (TTY)"]
5567
+ lines: [t("common.connectedAs", { email: me.email }), t("login.noOrgCreateThenSwitch")]
4809
5568
  });
4810
5569
  setTimeout(exit, 10);
4811
5570
  return;
@@ -4816,7 +5575,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4816
5575
  process.exitCode = 1;
4817
5576
  setPhase({
4818
5577
  step: "error",
4819
- title: "connexion \xE9chou\xE9e",
5578
+ title: t("common.loginFailed"),
4820
5579
  detail: e instanceof Error ? e.message : String(e)
4821
5580
  });
4822
5581
  setTimeout(exit, 10);
@@ -4825,21 +5584,18 @@ function LoginApp({ noBrowser, skipAuth }) {
4825
5584
  }, []);
4826
5585
  if (phase.step === "auth") {
4827
5586
  return /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
4828
- /* @__PURE__ */ jsx24(Spinner, { label: "en attente de la connexion dans le navigateur\u2026" }),
5587
+ /* @__PURE__ */ jsx24(Spinner, { label: t("common.waitingBrowserLogin") }),
4829
5588
  noBrowser && authorizeUrl ? /* @__PURE__ */ jsxs22(Box20, { marginTop: 1, flexDirection: "column", children: [
4830
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc500, children: "ouvrez cette URL pour vous connecter :" }),
5589
+ /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc500, children: t("login.browser.openUrl") }),
4831
5590
  /* @__PURE__ */ jsx24(Text24, { color: COLORS.emerald, children: authorizeUrl })
4832
5591
  ] }) : null
4833
5592
  ] });
4834
5593
  }
4835
5594
  if (phase.step === "orgs") {
4836
5595
  return /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
4837
- /* @__PURE__ */ jsxs22(Text24, { color: COLORS.zinc400, children: [
4838
- "connect\xE9 : ",
4839
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc100, children: phase.me.email })
4840
- ] }),
5596
+ /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc400, children: t("common.connectedAs", { email: phase.me.email }) }),
4841
5597
  /* @__PURE__ */ jsxs22(Box20, { marginTop: 1, flexDirection: "column", children: [
4842
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc500, children: "organisation active :" }),
5598
+ /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc500, children: t("common.activeOrgLabel") }),
4843
5599
  /* @__PURE__ */ jsx24(
4844
5600
  SelectList,
4845
5601
  {
@@ -4848,7 +5604,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4848
5604
  const org2 = phase.orgs.find((o) => o._id === item.value);
4849
5605
  if (!org2) return;
4850
5606
  saveActiveOrg(org2);
4851
- setPhase({ step: "done", lines: [`organisation active : ${org2.name}`] });
5607
+ setPhase({ step: "done", lines: [t("common.activeOrgSet", { name: org2.name })] });
4852
5608
  setTimeout(exit, 10);
4853
5609
  }
4854
5610
  }
@@ -4866,10 +5622,10 @@ async function loginAction(opts) {
4866
5622
  if (opts.withKey) {
4867
5623
  const { createInterface } = await import("readline/promises");
4868
5624
  const rl = createInterface({ input: process.stdin, output: process.stdout });
4869
- const key = (await rl.question("cl\xE9 API (lab_\u2026) : ")).trim();
5625
+ const key = (await rl.question(t("login.key.prompt"))).trim();
4870
5626
  rl.close();
4871
5627
  if (!key) {
4872
- console.error("aucune cl\xE9 fournie");
5628
+ console.error(t("login.noKeyProvided"));
4873
5629
  process.exitCode = 1;
4874
5630
  return;
4875
5631
  }
@@ -4880,7 +5636,7 @@ async function loginAction(opts) {
4880
5636
  }
4881
5637
  if (!process.stdout.isTTY) {
4882
5638
  console.error(
4883
- "terminal non interactif \u2014 utilisez `sumdae login --no-browser` dans un TTY, ou `--with-key`"
5639
+ t("login.nonInteractiveTty")
4884
5640
  );
4885
5641
  process.exitCode = 1;
4886
5642
  return;
@@ -4896,7 +5652,7 @@ async function logoutAction() {
4896
5652
  delete cfg.activeOrgId;
4897
5653
  delete cfg.activeOrgName;
4898
5654
  saveConfig(cfg);
4899
- console.log(ok("d\xE9connect\xE9"));
5655
+ console.log(ok(t("logout.done")));
4900
5656
  }
4901
5657
 
4902
5658
  // src/commands/org.tsx
@@ -4912,7 +5668,7 @@ async function fetchOrgItems() {
4912
5668
  items: orgs.map((o) => ({
4913
5669
  label: o.name,
4914
5670
  value: o._id,
4915
- hint: `${o.slug} \xB7 ${o.members.find((m) => m.user_id === me.id)?.role ?? "member"}`
5671
+ hint: `${o.slug} \xB7 ${o.members.find((m) => m.user_id === me.id)?.role ?? t("orgs.role.member")}`
4916
5672
  }))
4917
5673
  };
4918
5674
  }
@@ -4920,7 +5676,7 @@ async function orgListAction() {
4920
5676
  try {
4921
5677
  const { items } = await fetchOrgItems();
4922
5678
  if (items.length === 0) {
4923
- console.log("aucune organisation \u2014 cr\xE9ez-en une sur lab.sumdae.fr");
5679
+ console.log(t("common.noOrgCreateOnLab"));
4924
5680
  return;
4925
5681
  }
4926
5682
  const active = loadConfig().activeOrgId;
@@ -4943,14 +5699,11 @@ function SwitchApp({ items, orgs }) {
4943
5699
  if (picked) {
4944
5700
  return /* @__PURE__ */ jsxs23(Text25, { children: [
4945
5701
  /* @__PURE__ */ jsx25(Text25, { color: COLORS.emerald, children: "\u2713 " }),
4946
- /* @__PURE__ */ jsxs23(Text25, { color: COLORS.zinc100, children: [
4947
- "organisation active : ",
4948
- picked.label
4949
- ] })
5702
+ /* @__PURE__ */ jsx25(Text25, { color: COLORS.zinc100, children: t("common.activeOrgSet", { name: picked.label }) })
4950
5703
  ] });
4951
5704
  }
4952
5705
  return /* @__PURE__ */ jsxs23(Box21, { flexDirection: "column", children: [
4953
- /* @__PURE__ */ jsx25(Text25, { color: COLORS.zinc500, children: "organisation active :" }),
5706
+ /* @__PURE__ */ jsx25(Text25, { color: COLORS.zinc500, children: t("common.activeOrgLabel") }),
4954
5707
  /* @__PURE__ */ jsx25(
4955
5708
  SelectList,
4956
5709
  {
@@ -4970,11 +5723,11 @@ async function orgSwitchAction() {
4970
5723
  try {
4971
5724
  const { items, orgs } = await fetchOrgItems();
4972
5725
  if (items.length === 0) {
4973
- console.log("aucune organisation \u2014 cr\xE9ez-en une sur lab.sumdae.fr");
5726
+ console.log(t("common.noOrgCreateOnLab"));
4974
5727
  return;
4975
5728
  }
4976
5729
  if (!process.stdin.isTTY) {
4977
- console.error("terminal non interactif \u2014 impossible de choisir une organisation");
5730
+ console.error(t("orgs.nonInteractiveTty"));
4978
5731
  process.exitCode = 1;
4979
5732
  return;
4980
5733
  }
@@ -5002,7 +5755,7 @@ async function hashFile(path, signal) {
5002
5755
  for await (const chunk of stream) {
5003
5756
  if (signal?.aborted) {
5004
5757
  stream.destroy();
5005
- throw new Error("upload annul\xE9");
5758
+ throw new Error(t("upload.err.cancelled"));
5006
5759
  }
5007
5760
  hash.update(chunk);
5008
5761
  }
@@ -5034,7 +5787,7 @@ var PartUrlProvider = class {
5034
5787
  if (url) return url;
5035
5788
  await this.fetchBatch(partNumber);
5036
5789
  const retry = this.cache.get(partNumber);
5037
- if (!retry) throw new Error(`URL pr\xE9sign\xE9e manquante pour la part ${partNumber}`);
5790
+ if (!retry) throw new Error(t("upload.err.missingPresigned", { n: partNumber }));
5038
5791
  return retry;
5039
5792
  }
5040
5793
  async fetchBatch(from) {
@@ -5095,7 +5848,7 @@ async function pushArtifact(opts) {
5095
5848
  const partNumber = next;
5096
5849
  if (partNumber > partsTotal) return;
5097
5850
  next += 1;
5098
- if (signal?.aborted) throw new Error("upload annul\xE9");
5851
+ if (signal?.aborted) throw new Error(t("upload.err.cancelled"));
5099
5852
  const offset = (partNumber - 1) * partSize;
5100
5853
  const length = Math.min(partSize, totalBytes - offset);
5101
5854
  const buf = Buffer.alloc(length);
@@ -5107,7 +5860,7 @@ async function pushArtifact(opts) {
5107
5860
  const res = await fetch(url, { method: "PUT", body: buf, signal });
5108
5861
  if (!res.ok) throw new Error(`R2 HTTP ${res.status}`);
5109
5862
  const etag = res.headers.get("etag");
5110
- if (!etag) throw new Error("ETag manquant dans la r\xE9ponse R2");
5863
+ if (!etag) throw new Error(t("upload.err.missingEtag"));
5111
5864
  etags.set(partNumber, etag);
5112
5865
  sentBytes += length;
5113
5866
  partsDone += 1;
@@ -5116,13 +5869,18 @@ async function pushArtifact(opts) {
5116
5869
  break;
5117
5870
  } catch (e) {
5118
5871
  lastErr = e;
5119
- if (signal?.aborted) throw new Error("upload annul\xE9");
5872
+ if (signal?.aborted) throw new Error(t("upload.err.cancelled"));
5120
5873
  if (attempt < maxRetries) await sleep(300 * attempt);
5121
5874
  }
5122
5875
  }
5123
5876
  if (lastErr) {
5124
5877
  throw new Error(
5125
- `part ${partNumber}/${partsTotal} en \xE9chec apr\xE8s ${maxRetries} tentatives : ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`
5878
+ t("upload.err.partFailed", {
5879
+ n: partNumber,
5880
+ total: partsTotal,
5881
+ tries: maxRetries,
5882
+ msg: lastErr instanceof Error ? lastErr.message : String(lastErr)
5883
+ })
5126
5884
  );
5127
5885
  }
5128
5886
  }
@@ -5227,11 +5985,11 @@ function PushApp({
5227
5985
  )
5228
5986
  ] });
5229
5987
  }
5230
- if (phase.step === "error") return /* @__PURE__ */ jsx26(ErrorBox, { title: "push \xE9chou\xE9", detail: phase.msg });
5988
+ if (phase.step === "error") return /* @__PURE__ */ jsx26(ErrorBox, { title: t("push.failed.title"), detail: phase.msg });
5231
5989
  return /* @__PURE__ */ jsxs24(Box22, { flexDirection: "column", children: [
5232
5990
  /* @__PURE__ */ jsxs24(Text26, { children: [
5233
5991
  /* @__PURE__ */ jsx26(Text26, { color: COLORS.emerald, children: "\u2713 " }),
5234
- /* @__PURE__ */ jsx26(Text26, { color: COLORS.zinc100, children: "artifact upload\xE9" })
5992
+ /* @__PURE__ */ jsx26(Text26, { color: COLORS.zinc100, children: t("push.uploaded") })
5235
5993
  ] }),
5236
5994
  /* @__PURE__ */ jsxs24(Text26, { color: COLORS.zinc500, children: [
5237
5995
  "id ",
@@ -5249,14 +6007,14 @@ async function pushAction(filePath2, opts) {
5249
6007
  const kind = opts.kind ?? inferKind(filePath2);
5250
6008
  if (kind !== "weights" && kind !== "pipeline") {
5251
6009
  console.error(
5252
- "impossible de d\xE9duire le type \u2014 pr\xE9cisez --kind weights ou --kind pipeline"
6010
+ t("push.inferKindFailed")
5253
6011
  );
5254
6012
  process.exitCode = 1;
5255
6013
  return;
5256
6014
  }
5257
6015
  const active = await resolveActiveOrg().catch(() => null);
5258
6016
  if (!active) {
5259
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
6017
+ console.error(t("common.noActiveOrgRunSwitch"));
5260
6018
  process.exitCode = 1;
5261
6019
  return;
5262
6020
  }
@@ -5273,17 +6031,17 @@ async function quotaAction() {
5273
6031
  try {
5274
6032
  const active = await resolveActiveOrg();
5275
6033
  if (!active) {
5276
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
6034
+ console.error(t("common.noActiveOrgRunSwitch"));
5277
6035
  process.exitCode = 1;
5278
6036
  return;
5279
6037
  }
5280
6038
  const q = await getOrgQuota(active.slug);
5281
6039
  const rows = [
5282
- ["org", `${active.name} (${active.slug})`],
5283
- ["builds restants", String(q.remaining)]
6040
+ [t("whoami.row.org"), `${active.name} (${active.slug})`],
6041
+ [t("quota.row.remaining"), String(q.remaining)]
5284
6042
  ];
5285
6043
  if (q.remaining === 0) {
5286
- rows.push(["note", "quota \xE9puis\xE9 \u2014 demandez \xE0 l'admin d'augmenter build_quota"]);
6044
+ rows.push([t("quota.row.note"), t("quota.exhausted")]);
5287
6045
  }
5288
6046
  render7(/* @__PURE__ */ jsx27(KeyValue, { rows }));
5289
6047
  } catch (e) {
@@ -5294,10 +6052,10 @@ async function quotaAction() {
5294
6052
 
5295
6053
  // src/commands/uninstall.ts
5296
6054
  function printPlan(plan) {
5297
- console.log("D\xE9sinstallation de sumdae :");
6055
+ console.log(t("uninstall.cli.heading"));
5298
6056
  console.log(` canal : ${plan.channel}`);
5299
6057
  if (plan.channel === "npm") {
5300
- console.log(` install : ${plan.manualHint ?? "npm uninstall -g (global)"}`);
6058
+ console.log(` install : ${plan.manualHint ?? t("uninstall.cli.installGlobal")}`);
5301
6059
  } else if (plan.installBlockedReason) {
5302
6060
  console.log(` install : manuel \u2192 ${plan.manualHint ?? plan.installRoot}`);
5303
6061
  } else {
@@ -5305,14 +6063,14 @@ function printPlan(plan) {
5305
6063
  if (plan.launcherPath) console.log(` launcher : ${plan.launcherPath}`);
5306
6064
  }
5307
6065
  console.log(` config : ${plan.configDir}`);
5308
- console.log(" credentials : trousseau + fichier");
6066
+ console.log(t("uninstall.cli.credentials"));
5309
6067
  }
5310
6068
  async function uninstallAction(opts = {}) {
5311
6069
  const interactive = process.stdout.isTTY === true && process.stdin.isTTY === true;
5312
6070
  if (interactive && !opts.yes) {
5313
6071
  const report2 = await runUninstallScreen();
5314
6072
  if (!report2) {
5315
- console.log("annul\xE9.");
6073
+ console.log(t("uninstall.cli.cancelled"));
5316
6074
  return;
5317
6075
  }
5318
6076
  if (report2.some((s) => s.step !== "install" && !s.ok)) process.exitCode = 1;
@@ -5326,13 +6084,13 @@ async function uninstallAction(opts = {}) {
5326
6084
  if (s.ok) {
5327
6085
  console.log(ok(s.step));
5328
6086
  } else if (s.step === "install") {
5329
- console.log(warn(`${s.step} \u2014 ${s.error ?? "\xE0 faire manuellement"}`));
6087
+ console.log(warn(`${s.step} \u2014 ${s.error ?? t("uninstall.cli.manualFallback")}`));
5330
6088
  } else {
5331
- console.log(ko(`${s.step} \u2014 ${s.error ?? "\xE9chec"}`));
6089
+ console.log(ko(`${s.step} \u2014 ${s.error ?? t("uninstall.cli.stepFailed")}`));
5332
6090
  dataFailed = true;
5333
6091
  }
5334
6092
  }
5335
- console.log("config et identifiants nettoy\xE9s ; l\u2019install sera effac\xE9e \xE0 la fermeture.");
6093
+ console.log(t("uninstall.cli.cleaned"));
5336
6094
  if (dataFailed) process.exitCode = 1;
5337
6095
  }
5338
6096
 
@@ -5341,7 +6099,7 @@ import { render as render8 } from "ink";
5341
6099
  import { jsx as jsx28 } from "react/jsx-runtime";
5342
6100
  function machineLine() {
5343
6101
  const s = getSystemInfo();
5344
- return `${s.username}@${s.hostname} \xB7 ${s.localIp ?? "ip inconnue"} \xB7 ${s.os} ${s.arch} \xB7 device ${s.deviceId.slice(0, 8)}`;
6102
+ return `${s.username}@${s.hostname} \xB7 ${s.localIp ?? t("whoami.machine.unknownIp")} \xB7 ${s.os} ${s.arch} \xB7 device ${s.deviceId.slice(0, 8)}`;
5345
6103
  }
5346
6104
  async function whoamiAction() {
5347
6105
  try {
@@ -5349,17 +6107,19 @@ async function whoamiAction() {
5349
6107
  const cfg = loadConfig();
5350
6108
  const creds = await loadCredentials();
5351
6109
  const name = `${me.first_name} ${me.last_name}`.trim();
5352
- const session = creds?.kind === "api-key" ? "cl\xE9 API" : `oauth \xB7 access token expire \xE0 ${new Date(creds?.expiresAt ?? 0).toLocaleTimeString("fr-FR")}`;
6110
+ const session = creds?.kind === "api-key" ? t("whoami.session.apiKey") : t("whoami.session.oauthExpires", {
6111
+ time: new Date(creds?.expiresAt ?? 0).toLocaleTimeString("fr-FR")
6112
+ });
5353
6113
  render8(
5354
6114
  /* @__PURE__ */ jsx28(
5355
6115
  KeyValue,
5356
6116
  {
5357
6117
  rows: [
5358
- ["user", name || me.email],
5359
- ["email", me.email],
5360
- ["org", cfg.activeOrgName ? `${cfg.activeOrgName} (${cfg.activeOrgId})` : "\u2014 aucune (sumdae org switch)"],
5361
- ["session", session],
5362
- ["machine", machineLine()]
6118
+ [t("whoami.row.user"), name || me.email],
6119
+ [t("whoami.row.email"), me.email],
6120
+ [t("whoami.row.org"), cfg.activeOrgName ? `${cfg.activeOrgName} (${cfg.activeOrgId})` : t("whoami.org.noneSwitch")],
6121
+ [t("whoami.row.session"), session],
6122
+ [t("whoami.row.machine"), machineLine()]
5363
6123
  ]
5364
6124
  }
5365
6125
  )
@@ -5396,7 +6156,7 @@ function UpdateApp({
5396
6156
  if (channel === "npm") {
5397
6157
  setPhase({ kind: "install" });
5398
6158
  const ok2 = updateViaNpm(latest.version);
5399
- if (!ok2) throw new Error("npm install a \xE9chou\xE9");
6159
+ if (!ok2) throw new Error(t("update.err.npmFailed"));
5400
6160
  setPhase({ kind: "done" });
5401
6161
  setTimeout(() => relaunch(), 300);
5402
6162
  return;
@@ -5404,10 +6164,10 @@ function UpdateApp({
5404
6164
  const { path, sha256 } = await downloadTarball(latest.tarball_url, (done, total) => {
5405
6165
  if (alive) setPhase({ kind: "download", done, total: total || latest.size });
5406
6166
  });
5407
- if (sha256 !== latest.sha256) throw new Error("sha256 du tarball ne correspond pas");
6167
+ if (sha256 !== latest.sha256) throw new Error(t("update.err.sha256Mismatch"));
5408
6168
  setPhase({ kind: "verify" });
5409
6169
  if (!verifyTarball(path, latest.signature)) {
5410
- throw new Error("signature invalide \u2014 mise \xE0 jour refus\xE9e");
6170
+ throw new Error(t("update.err.invalidSignature"));
5411
6171
  }
5412
6172
  setPhase({ kind: "install" });
5413
6173
  installTarball(path);
@@ -5441,17 +6201,17 @@ function UpdateApp({
5441
6201
  suffix: phase.total ? `${fmtBytes3(phase.done)} / ${fmtBytes3(phase.total)}` : fmtBytes3(phase.done)
5442
6202
  }
5443
6203
  ),
5444
- /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc500, children: "t\xE9l\xE9chargement\u2026" })
6204
+ /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc500, children: t("update.downloading") })
5445
6205
  ] });
5446
6206
  case "verify":
5447
- return /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc400, children: "v\xE9rification de la signature\u2026" });
6207
+ return /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc400, children: t("update.verifyingSignature") });
5448
6208
  case "install":
5449
- return /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc400, children: "installation\u2026" });
6209
+ return /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc400, children: t("update.installing") });
5450
6210
  case "done":
5451
- return /* @__PURE__ */ jsx29(Text27, { color: COLORS.emerald, children: "\u2713 install\xE9 \u2014 red\xE9marrage\u2026" });
6211
+ return /* @__PURE__ */ jsx29(Text27, { color: COLORS.emerald, children: t("update.doneRestarting") });
5452
6212
  case "error":
5453
6213
  return /* @__PURE__ */ jsxs25(Fragment3, { children: [
5454
- /* @__PURE__ */ jsx29(Text27, { color: COLORS.red, children: "\xE9chec de la mise \xE0 jour" }),
6214
+ /* @__PURE__ */ jsx29(Text27, { color: COLORS.red, children: t("update.failed") }),
5455
6215
  /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc500, wrap: "truncate", children: phase.message })
5456
6216
  ] });
5457
6217
  }
@@ -5465,7 +6225,7 @@ function UpdateApp({
5465
6225
  justifyContent: "center",
5466
6226
  flexDirection: "column",
5467
6227
  children: [
5468
- /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc100, bold: true, children: "Mise \xE0 jour" }),
6228
+ /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc100, bold: true, children: t("update.screen.title") }),
5469
6229
  /* @__PURE__ */ jsx29(Box23, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs25(Text27, { color: COLORS.zinc500, children: [
5470
6230
  "v",
5471
6231
  current,
@@ -5506,35 +6266,34 @@ async function maybeSelfUpdate(argv) {
5506
6266
  const err = await runUpdateScreen(decision.latest, decision.current);
5507
6267
  if (err && decision.forced) {
5508
6268
  console.error(
5509
- `Cette version (${decision.current}) n'est plus support\xE9e et la mise \xE0 jour a \xE9chou\xE9 : ${err}
5510
- R\xE9essayez, ou r\xE9installez : https://lab.sumdae.fr/cli`
6269
+ t("update.forcedFailed", { current: decision.current, err })
5511
6270
  );
5512
6271
  process.exit(1);
5513
6272
  }
5514
6273
  }
5515
6274
  await maybeSelfUpdate(process.argv);
5516
6275
  var program = new Command();
5517
- program.name("sumdae").description("CLI de la plateforme sumdae").version(CLI_VERSION).action(async () => {
6276
+ program.name("sumdae").description(t("cli.root.description")).version(CLI_VERSION).action(async () => {
5518
6277
  await homeAction(CLI_VERSION);
5519
6278
  });
5520
- program.command("login").description("se connecter \xE0 sumdae (OAuth + PKCE)").option("--no-browser", "afficher l'URL au lieu d'ouvrir le navigateur").option("--with-key", "coller une cl\xE9 API lab_\u2026 (headless)").action(async (opts) => {
6279
+ program.command("login").description(t("cli.login.description")).option("--no-browser", t("cli.login.opt.noBrowser")).option("--with-key", t("cli.login.opt.withKey")).action(async (opts) => {
5521
6280
  await loginAction({ noBrowser: !opts.browser, withKey: opts.withKey });
5522
6281
  });
5523
- program.command("logout").description("se d\xE9connecter").action(logoutAction);
5524
- program.command("whoami").description("profil + organisation active").action(whoamiAction);
5525
- program.command("uninstall").description("supprimer proprement sumdae de cette machine (tous canaux)").option("--yes", "ne pas demander de confirmation (headless)").action(async (opts) => {
6282
+ program.command("logout").description(t("cli.logout.description")).action(logoutAction);
6283
+ program.command("whoami").description(t("cli.whoami.description")).action(whoamiAction);
6284
+ program.command("uninstall").description(t("cli.uninstall.description")).option("--yes", t("cli.uninstall.opt.yes")).action(async (opts) => {
5526
6285
  await uninstallAction({ yes: opts.yes });
5527
6286
  });
5528
- program.command("quota").description("builds restants de l'organisation active").action(quotaAction);
5529
- program.command("push <fichier>").description("uploader des poids ou un pipeline vers l'organisation active").option("--kind <kind>", "weights | pipeline (d\xE9duit de l\u2019extension sinon)").action(pushAction);
5530
- program.command("build <weightsArtifactId>").description("lancer un build Docker (consomme 1 quota) et suivre en live").option("--pipeline <artifactId>", "artifact pipeline \xE0 int\xE9grer").action(buildAction);
5531
- program.command("simulate <task...>").description("lancer un build SIMULATION \u2014 pas besoin de mod\xE8le/pipeline (consomme 1 quota)").action((taskParts) => simulateAction(taskParts.join(" ")));
5532
- program.command("watch <buildId>").description("suivre un build en cours (WS temps r\xE9el)").action(watchAction);
5533
- program.command("cancel <buildId>").description("annuler un build (environnement lib\xE9r\xE9, quota rembours\xE9 si rien commenc\xE9)").action(cancelAction);
5534
- var agent = program.command("agent").description("piloter les agents d\u2019un build");
5535
- agent.command("kill <buildId>").description("arr\xEAter un agent (d\xE9faut : master)").option("--agent <name>", "master | subagent-N | all", "master").action((buildId, o) => agentCommandAction(buildId, "kill", o));
5536
- agent.command("msg <buildId> <message>").description("envoyer un message \xE0 un agent en cours").option("--agent <name>", "master | subagent-N | all", "master").action((buildId, message, o) => agentCommandAction(buildId, "message", { ...o, message }));
5537
- var org = program.command("org").description("g\xE9rer les organisations");
5538
- org.command("list").description("lister vos organisations").action(orgListAction);
5539
- org.command("switch").description("changer d'organisation active").action(orgSwitchAction);
6287
+ program.command("quota").description(t("cli.quota.description")).action(quotaAction);
6288
+ program.command("push <fichier>").description(t("cli.push.description")).option("--kind <kind>", t("cli.push.opt.kind")).action(pushAction);
6289
+ program.command("build <weightsArtifactId>").description(t("cli.build.description")).option("--pipeline <artifactId>", t("cli.build.opt.pipeline")).action(buildAction);
6290
+ program.command("simulate <task...>").description(t("cli.simulate.description")).action((taskParts) => simulateAction(taskParts.join(" ")));
6291
+ program.command("watch <buildId>").description(t("cli.watch.description")).action(watchAction);
6292
+ program.command("cancel <buildId>").description(t("cli.cancel.description")).action(cancelAction);
6293
+ var agent = program.command("agent").description(t("cli.agent.description"));
6294
+ agent.command("kill <buildId>").description(t("cli.agent.kill.description")).option("--agent <name>", t("cli.agent.opt.agent"), "master").action((buildId, o) => agentCommandAction(buildId, "kill", o));
6295
+ agent.command("msg <buildId> <message>").description(t("cli.agent.msg.description")).option("--agent <name>", t("cli.agent.opt.agent"), "master").action((buildId, message, o) => agentCommandAction(buildId, "message", { ...o, message }));
6296
+ var org = program.command("org").description(t("cli.org.description"));
6297
+ org.command("list").description(t("cli.org.list.description")).action(orgListAction);
6298
+ org.command("switch").description(t("cli.org.switch.description")).action(orgSwitchAction);
5540
6299
  await program.parseAsync();