@sumdaelive/cli 0.1.2 → 0.1.4

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 +1190 -425
  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.2";
861
+ var CLI_VERSION = "0.1.4";
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
 
@@ -2859,9 +3618,10 @@ import { useState as useState6 } from "react";
2859
3618
  // src/uninstall.ts
2860
3619
  import { spawn } from "child_process";
2861
3620
  import {
2862
- createWriteStream as createWriteStream2,
3621
+ closeSync,
2863
3622
  existsSync as existsSync2,
2864
3623
  mkdtempSync as mkdtempSync2,
3624
+ openSync,
2865
3625
  readFileSync as readFileSync5,
2866
3626
  realpathSync,
2867
3627
  rmSync as rmSync3,
@@ -2906,12 +3666,12 @@ function semverCmp(a, b) {
2906
3666
  async function fetchLatest() {
2907
3667
  try {
2908
3668
  const ctrl = new AbortController();
2909
- const t = setTimeout(() => ctrl.abort(), CHECK_TIMEOUT_MS);
3669
+ const t2 = setTimeout(() => ctrl.abort(), CHECK_TIMEOUT_MS);
2910
3670
  const res = await fetch(`${labUrl()}/cli/latest`, {
2911
3671
  signal: ctrl.signal,
2912
3672
  headers: { "x-cli-version": CLI_VERSION }
2913
3673
  });
2914
- clearTimeout(t);
3674
+ clearTimeout(t2);
2915
3675
  if (!res.ok) return null;
2916
3676
  const j = await res.json();
2917
3677
  if (!j.version || !j.tarball_url || !j.signature || !j.sha256) return null;
@@ -3007,7 +3767,7 @@ function installTarball(tarPath, opts = {}) {
3007
3767
  const r = spawnSync("tar", ["-xzf", tarPath, "-C", staging], { stdio: "ignore" });
3008
3768
  if (r.status !== 0) {
3009
3769
  rmSync2(staging, { recursive: true, force: true });
3010
- throw new Error("extraction tar \xE9chou\xE9e");
3770
+ throw new Error(t("update.err.extractFailed"));
3011
3771
  }
3012
3772
  const newDist = join4(staging, "dist");
3013
3773
  if (!existsSync(join4(newDist, "main.js"))) {
@@ -3062,9 +3822,9 @@ function realNorm(p) {
3062
3822
  }
3063
3823
  }
3064
3824
  function isDangerousRoot(root) {
3065
- const t = root.trim();
3066
- if (t === "") return true;
3067
- const n = norm(t);
3825
+ const t2 = root.trim();
3826
+ if (t2 === "") return true;
3827
+ const n = norm(t2);
3068
3828
  if (n === sep) return true;
3069
3829
  if (n === norm(homedir2())) return true;
3070
3830
  return false;
@@ -3187,7 +3947,7 @@ async function removeUserData() {
3187
3947
  function errMsg(e) {
3188
3948
  return e instanceof Error ? e.message : String(e);
3189
3949
  }
3190
- function scheduleInstallRemoval(plan) {
3950
+ function scheduleInstallRemoval(plan, opts = {}) {
3191
3951
  if (plan.installBlockedReason) {
3192
3952
  return { step: "install", ok: false, error: plan.manualHint ?? plan.installBlockedReason };
3193
3953
  }
@@ -3198,24 +3958,29 @@ function scheduleInstallRemoval(plan) {
3198
3958
  error: plan.manualHint ?? `Supprimez manuellement : ${plan.installRoot}`
3199
3959
  };
3200
3960
  }
3961
+ const spawnFn = opts.spawnFn ?? spawn;
3201
3962
  try {
3202
3963
  const scriptDir = mkdtempSync2(join5(tmpdir2(), "sumdae-uninstall-"));
3203
3964
  const scriptPath = join5(scriptDir, "uninstall.sh");
3204
3965
  const logPath = join5(scriptDir, "uninstall.log");
3205
3966
  const text = renderUninstallScript({ ...plan, scriptDir }, process.pid);
3206
3967
  writeFileSync5(scriptPath, text, { mode: 448 });
3207
- const out = createWriteStream2(logPath);
3208
- const child = spawn("sh", [scriptPath], {
3209
- detached: true,
3210
- stdio: ["ignore", out, out]
3211
- });
3212
- child.unref();
3968
+ const logFd = openSync(logPath, "a");
3969
+ try {
3970
+ const child = spawnFn("sh", [scriptPath], {
3971
+ detached: true,
3972
+ stdio: ["ignore", logFd, logFd]
3973
+ });
3974
+ child.unref();
3975
+ } finally {
3976
+ closeSync(logFd);
3977
+ }
3213
3978
  return { step: "install", ok: true };
3214
3979
  } catch (e) {
3215
3980
  return {
3216
3981
  step: "install",
3217
3982
  ok: false,
3218
- 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 })
3219
3984
  };
3220
3985
  }
3221
3986
  }
@@ -3241,20 +4006,22 @@ async function performUninstall(plan) {
3241
4006
  // src/ui/UninstallScreen.tsx
3242
4007
  import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
3243
4008
  function channelLabel(c) {
3244
- 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");
3245
4010
  }
3246
4011
  function planRows(plan) {
3247
- const rows = [["canal", channelLabel(plan.channel)]];
4012
+ const rows = [[t("uninstall.row.channel"), channelLabel(plan.channel)]];
4013
+ const install = t("uninstall.row.install");
3248
4014
  if (plan.channel === "npm") {
3249
- rows.push(["install", "npm uninstall -g @sumdae/cli (\xE0 la fermeture)"]);
4015
+ rows.push([install, t("uninstall.install.atClose", { root: plan.manualHint ?? "npm" })]);
3250
4016
  } else if (plan.installBlockedReason) {
3251
- rows.push(["install", `\u26A0 manuel \u2192 ${plan.manualHint ?? plan.installRoot}`]);
4017
+ rows.push([install, t("uninstall.install.manual", { hint: plan.manualHint ?? plan.installRoot })]);
3252
4018
  } else {
3253
- rows.push(["install", `${plan.installRoot} (\xE0 la fermeture)`]);
3254
- 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 })]);
3255
4022
  }
3256
- rows.push(["config", `${plan.configDir} (maintenant)`]);
3257
- 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")]);
3258
4025
  return rows;
3259
4026
  }
3260
4027
  function UninstallView({
@@ -3271,13 +4038,13 @@ function UninstallView({
3271
4038
  if (view.kind === "working") {
3272
4039
  return /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", children: /* @__PURE__ */ jsxs16(Text17, { color: COLORS.zinc400, children: [
3273
4040
  /* @__PURE__ */ jsx17(Text17, { color: COLORS.emerald, children: "\xB7 " }),
3274
- "suppression en cours\u2026"
4041
+ t("uninstall.working")
3275
4042
  ] }) });
3276
4043
  }
3277
4044
  if (view.kind === "report") {
3278
4045
  const dataFailed = view.report.some((s) => s.step !== "install" && !s.ok);
3279
4046
  return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
3280
- /* @__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") }),
3281
4048
  /* @__PURE__ */ jsx17(Box15, { flexDirection: "column", marginTop: 1, children: view.report.map((s) => /* @__PURE__ */ jsxs16(Box15, { children: [
3282
4049
  /* @__PURE__ */ jsx17(Text17, { color: s.ok ? COLORS.emerald : COLORS.red, children: s.ok ? "\u2713 " : "\u2717 " }),
3283
4050
  /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc300, children: s.step }),
@@ -3286,26 +4053,26 @@ function UninstallView({
3286
4053
  s.error
3287
4054
  ] }) : null
3288
4055
  ] }, s.step)) }),
3289
- /* @__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") }) }),
3290
4057
  /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(
3291
4058
  SelectList,
3292
4059
  {
3293
- items: [{ label: "Fermer", value: "close" }],
4060
+ items: [{ label: t("uninstall.close"), value: "close" }],
3294
4061
  onSelect: () => onDone(view.report)
3295
4062
  }
3296
4063
  ) })
3297
4064
  ] });
3298
4065
  }
3299
4066
  return /* @__PURE__ */ jsxs16(Box15, { flexDirection: "column", children: [
3300
- /* @__PURE__ */ jsx17(Text17, { color: COLORS.zinc100, bold: true, children: "D\xE9sinstaller sumdae" }),
3301
- /* @__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") }) }),
3302
4069
  /* @__PURE__ */ jsx17(KeyValue, { rows: planRows(plan) }),
3303
4070
  /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(
3304
4071
  SelectList,
3305
4072
  {
3306
4073
  items: [
3307
- { label: "Annuler", value: "cancel", hint: "ne rien supprimer" },
3308
- { 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 }
3309
4076
  ],
3310
4077
  onSelect: (i) => {
3311
4078
  if (i.value === "go") void run();
@@ -3349,8 +4116,8 @@ var FRAME_MS2 = 120;
3349
4116
  function Twinkle({ color = COLORS.emerald }) {
3350
4117
  const [frame, setFrame] = useState7(0);
3351
4118
  useEffect5(() => {
3352
- const t = setInterval(() => setFrame((f) => (f + 1) % TWINKLE_CYCLE2.length), FRAME_MS2);
3353
- return () => clearInterval(t);
4119
+ const t2 = setInterval(() => setFrame((f) => (f + 1) % TWINKLE_CYCLE2.length), FRAME_MS2);
4120
+ return () => clearInterval(t2);
3354
4121
  }, []);
3355
4122
  return /* @__PURE__ */ jsx18(Text18, { color, children: TWINKLE_CYCLE2[frame] });
3356
4123
  }
@@ -3422,15 +4189,15 @@ function NoOrgPage({
3422
4189
  onQuit
3423
4190
  }) {
3424
4191
  return /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3425
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: "aucune organisation associ\xE9e \xE0 ce compte." }),
3426
- /* @__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") }),
3427
4194
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
3428
4195
  SelectList,
3429
4196
  {
3430
4197
  items: [
3431
- { label: "R\xE9essayer", value: "retry", hint: "recharger vos organisations" },
3432
- { label: "Continuer sans organisation", value: "continue" },
3433
- { 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" }
3434
4201
  ],
3435
4202
  onSelect: (i) => {
3436
4203
  if (i.value === "retry") onRetry();
@@ -3450,6 +4217,12 @@ function App({
3450
4217
  const { exit } = useApp3();
3451
4218
  const { columns, rows } = useTerminalSize();
3452
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;
3453
4226
  const [page2, setPageState] = useState9(
3454
4227
  initialSession ? { name: "home" } : { name: "landing" }
3455
4228
  );
@@ -3478,10 +4251,10 @@ function App({
3478
4251
  setPageState(p);
3479
4252
  };
3480
4253
  const back = () => setPage(parentOf(page2));
3481
- const backItem = (label = "Retourner en arri\xE8re") => ({
4254
+ const backItem = (label = t("common.back")) => ({
3482
4255
  label,
3483
4256
  value: "__back__",
3484
- hint: "ou \xC9chap"
4257
+ hint: t("common.orEscape")
3485
4258
  });
3486
4259
  const [recentBuilds, setRecentBuilds] = useState9(null);
3487
4260
  const [recentError, setRecentError] = useState9(null);
@@ -3495,6 +4268,17 @@ function App({
3495
4268
  },
3496
4269
  { isActive: page2.name === "home" }
3497
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
+ );
3498
4282
  const ESCAPE_OWNED = /* @__PURE__ */ new Set([
3499
4283
  "home",
3500
4284
  "landing",
@@ -3551,7 +4335,7 @@ function App({
3551
4335
  };
3552
4336
  }, [page2.name]);
3553
4337
  const startSimulation = async (task) => {
3554
- setPage({ name: "loading", label: "cr\xE9ation de la simulation\u2026" });
4338
+ setPage({ name: "loading", label: t("simulate.creating") });
3555
4339
  try {
3556
4340
  const active = await resolveActiveOrg();
3557
4341
  if (!active) {
@@ -3561,7 +4345,7 @@ function App({
3561
4345
  const buildId = await createSimulation(active.slug, task);
3562
4346
  setPage({ name: "watch", buildId });
3563
4347
  } catch (e) {
3564
- toError(e, "simulation impossible", { name: "home" });
4348
+ toError(e, t("simulate.impossible"), { name: "home" });
3565
4349
  }
3566
4350
  };
3567
4351
  const hfErrorPage = (e) => {
@@ -3571,38 +4355,38 @@ function App({
3571
4355
  case "no_token":
3572
4356
  return {
3573
4357
  name: "error",
3574
- title: "token Hugging Face manquant",
3575
- 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"),
3576
4360
  back: back2
3577
4361
  };
3578
4362
  case "invalid_token":
3579
4363
  return {
3580
4364
  name: "error",
3581
- title: "token Hugging Face invalide",
3582
- 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"),
3583
4367
  back: back2
3584
4368
  };
3585
4369
  case "gated_or_forbidden":
3586
4370
  return {
3587
4371
  name: "error",
3588
- title: "acc\xE8s refus\xE9 \xE0 ce mod\xE8le",
3589
- 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"),
3590
4374
  back: back2
3591
4375
  };
3592
4376
  case "not_found":
3593
4377
  return {
3594
4378
  name: "error",
3595
- title: "mod\xE8le introuvable",
3596
- 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"),
3597
4381
  back: back2
3598
4382
  };
3599
4383
  default:
3600
- 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 };
3601
4385
  }
3602
4386
  }
3603
4387
  return {
3604
4388
  name: "error",
3605
- title: "v\xE9rification Hugging Face \xE9chou\xE9e",
4389
+ title: t("job.hf.err.checkFailed.title"),
3606
4390
  detail: e instanceof Error ? e.message : String(e),
3607
4391
  back: back2
3608
4392
  };
@@ -3612,13 +4396,13 @@ function App({
3612
4396
  if (!parsed) {
3613
4397
  setPage({
3614
4398
  name: "error",
3615
- title: "r\xE9f\xE9rence invalide",
3616
- 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"),
3617
4401
  back: { name: "job-hf" }
3618
4402
  });
3619
4403
  return;
3620
4404
  }
3621
- setPage({ name: "loading", label: "v\xE9rification de l'acc\xE8s Hugging Face\u2026" });
4405
+ setPage({ name: "loading", label: t("job.hf.checking") });
3622
4406
  try {
3623
4407
  const active = await resolveActiveOrg();
3624
4408
  if (!active) {
@@ -3638,7 +4422,7 @@ function App({
3638
4422
  }
3639
4423
  };
3640
4424
  const startHfJob = async (repo, revision) => {
3641
- setPage({ name: "loading", label: "cr\xE9ation du job\u2026" });
4425
+ setPage({ name: "loading", label: t("common.creatingJob") });
3642
4426
  try {
3643
4427
  const active = await resolveActiveOrg();
3644
4428
  if (!active) {
@@ -3651,7 +4435,7 @@ function App({
3651
4435
  if (e instanceof ApiError && ["no_token", "invalid_token", "gated_or_forbidden", "not_found"].includes(e.code)) {
3652
4436
  setPage(hfErrorPage(e));
3653
4437
  } else {
3654
- toError(e, "job impossible", { name: "home" });
4438
+ toError(e, t("common.jobImpossible"), { name: "home" });
3655
4439
  }
3656
4440
  }
3657
4441
  };
@@ -3671,8 +4455,8 @@ function App({
3671
4455
  if (!st.isFile()) {
3672
4456
  setPage({
3673
4457
  name: "error",
3674
- title: "pas un fichier",
3675
- 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 }),
3676
4460
  back: { name: "job-file" }
3677
4461
  });
3678
4462
  return;
@@ -3681,7 +4465,7 @@ function App({
3681
4465
  } catch {
3682
4466
  setPage({
3683
4467
  name: "error",
3684
- title: "fichier introuvable",
4468
+ title: t("job.file.err.notFound.title"),
3685
4469
  detail: p,
3686
4470
  back: { name: "job-file" }
3687
4471
  });
@@ -3690,7 +4474,7 @@ function App({
3690
4474
  if (size <= 0) {
3691
4475
  setPage({
3692
4476
  name: "error",
3693
- title: "fichier vide",
4477
+ title: t("job.file.err.empty.title"),
3694
4478
  detail: p,
3695
4479
  back: { name: "job-file" }
3696
4480
  });
@@ -3699,7 +4483,7 @@ function App({
3699
4483
  setPage({ name: "job-file-confirm", filePath: p, fileName: path.basename(p), sizeBytes: size });
3700
4484
  };
3701
4485
  const startDirectJob = async (filePath2, fileName, sizeBytes) => {
3702
- setPage({ name: "loading", label: "cr\xE9ation du job\u2026" });
4486
+ setPage({ name: "loading", label: t("common.creatingJob") });
3703
4487
  try {
3704
4488
  const active = await resolveActiveOrg();
3705
4489
  if (!active) {
@@ -3713,8 +4497,8 @@ function App({
3713
4497
  });
3714
4498
  if (!created.upload_token) {
3715
4499
  toError(
3716
- new Error("r\xE9ponse serveur sans upload_token"),
3717
- "job impossible",
4500
+ new Error(t("job.noUploadToken")),
4501
+ t("common.jobImpossible"),
3718
4502
  { name: "home" }
3719
4503
  );
3720
4504
  return;
@@ -3728,11 +4512,11 @@ function App({
3728
4512
  sizeBytes
3729
4513
  });
3730
4514
  } catch (e) {
3731
- toError(e, "job impossible", { name: "home" });
4515
+ toError(e, t("common.jobImpossible"), { name: "home" });
3732
4516
  }
3733
4517
  };
3734
4518
  const openBuildSummary = async (buildId, opts = {}) => {
3735
- if (!opts.quiet) setPage({ name: "loading", label: "r\xE9cup\xE9ration du job\u2026" });
4519
+ if (!opts.quiet) setPage({ name: "loading", label: t("job.fetching") });
3736
4520
  try {
3737
4521
  const active = await resolveActiveOrg();
3738
4522
  if (!active) {
@@ -3742,18 +4526,18 @@ function App({
3742
4526
  const build = await getBuild(active.slug, buildId);
3743
4527
  setPage({ name: "build-summary", build });
3744
4528
  } catch (e) {
3745
- toError(e, "job introuvable", { name: "home" });
4529
+ toError(e, t("job.notFound"), { name: "home" });
3746
4530
  }
3747
4531
  };
3748
4532
  const loadOrgs = async (back2) => {
3749
- setPage({ name: "loading", label: "chargement des organisations\u2026" });
4533
+ setPage({ name: "loading", label: t("orgs.loading") });
3750
4534
  try {
3751
4535
  const me = await labApi("/me");
3752
4536
  const orgs = await labApi("/orgs");
3753
4537
  if (orgs.length === 0) setPage({ name: "no-org" });
3754
4538
  else setPage({ name: "orgs", me, orgs });
3755
4539
  } catch (e) {
3756
- toError(e, "impossible de r\xE9cup\xE9rer les organisations", back2);
4540
+ toError(e, t("orgs.fetchFailed"), back2);
3757
4541
  }
3758
4542
  };
3759
4543
  const startBrowserLogin = async () => {
@@ -3768,19 +4552,19 @@ function App({
3768
4552
  setSession("oauth");
3769
4553
  await loadOrgs({ name: "landing" });
3770
4554
  } catch (e) {
3771
- toError(e, "connexion \xE9chou\xE9e", { name: "landing" });
4555
+ toError(e, t("common.loginFailed"), { name: "landing" });
3772
4556
  }
3773
4557
  };
3774
4558
  const submitKey = async (key) => {
3775
4559
  const trimmed = key.trim();
3776
4560
  if (!trimmed) return;
3777
- setPage({ name: "loading", label: "validation de la cl\xE9\u2026" });
4561
+ setPage({ name: "loading", label: t("login.validatingKey") });
3778
4562
  try {
3779
4563
  await saveCredentials({ kind: "api-key", apiKey: trimmed });
3780
4564
  await labApi("/me");
3781
4565
  } catch (e) {
3782
4566
  await clearCredentials();
3783
- toError(e, "cl\xE9 API invalide", { name: "login-key" });
4567
+ toError(e, t("login.invalidKey"), { name: "login-key" });
3784
4568
  return;
3785
4569
  }
3786
4570
  setSession("api-key");
@@ -3791,7 +4575,7 @@ function App({
3791
4575
  setPage({ name: "home" });
3792
4576
  };
3793
4577
  const showQuota = async () => {
3794
- setPage({ name: "loading", label: "quota de build\u2026" });
4578
+ setPage({ name: "loading", label: t("quota.loading") });
3795
4579
  try {
3796
4580
  const active = await resolveActiveOrg();
3797
4581
  if (!active) {
@@ -3801,20 +4585,20 @@ function App({
3801
4585
  const q = await getOrgQuota(active.slug);
3802
4586
  setPage({ name: "quota", orgName: active.name, remaining: q.remaining });
3803
4587
  } catch (e) {
3804
- toError(e, "quota de build inaccessible", { name: "home" });
4588
+ toError(e, t("quota.unreachable"), { name: "home" });
3805
4589
  }
3806
4590
  };
3807
4591
  const showWhoami = async () => {
3808
- setPage({ name: "loading", label: "profil\u2026" });
4592
+ setPage({ name: "loading", label: t("whoami.loading") });
3809
4593
  try {
3810
4594
  const me = await labApi("/me");
3811
4595
  setPage({ name: "whoami", me });
3812
4596
  } catch (e) {
3813
- toError(e, "profil inaccessible", { name: "home" });
4597
+ toError(e, t("whoami.unreachable"), { name: "home" });
3814
4598
  }
3815
4599
  };
3816
4600
  const logout = async () => {
3817
- setPage({ name: "loading", label: "d\xE9connexion\u2026" });
4601
+ setPage({ name: "loading", label: t("common.signingOut") });
3818
4602
  await clearCredentials();
3819
4603
  const cfg = loadConfig();
3820
4604
  delete cfg.activeOrgId;
@@ -3827,14 +4611,14 @@ function App({
3827
4611
  switch (page2.name) {
3828
4612
  case "landing":
3829
4613
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3830
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "comment voulez-vous vous connecter ?" }),
4614
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("landing.howConnect") }),
3831
4615
  /* @__PURE__ */ jsx19(
3832
4616
  SelectList,
3833
4617
  {
3834
4618
  items: [
3835
- { label: "Se connecter via le navigateur", value: "browser", hint: "OAuth + PKCE" },
3836
- { label: "Coller une cl\xE9 API", value: "key", hint: "lab_\u2026 \xB7 headless" },
3837
- { 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") }
3838
4622
  ],
3839
4623
  onSelect: (i) => {
3840
4624
  if (i.value === "browser") void startBrowserLogin();
@@ -3847,9 +4631,9 @@ function App({
3847
4631
  break;
3848
4632
  case "login-browser":
3849
4633
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3850
- /* @__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 }),
3851
4635
  page2.url ? /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", children: [
3852
- /* @__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") }),
3853
4637
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc400, children: page2.url })
3854
4638
  ] }) : null
3855
4639
  ] });
@@ -3859,13 +4643,13 @@ function App({
3859
4643
  /* @__PURE__ */ jsx19(
3860
4644
  TextField,
3861
4645
  {
3862
- title: "Cl\xE9 API",
3863
- placeholder: "lab_\u2026",
4646
+ title: t("login.key.title"),
4647
+ placeholder: t("login.key.placeholder"),
3864
4648
  onSubmit: (v) => void submitKey(v),
3865
4649
  onCancel: () => back()
3866
4650
  }
3867
4651
  ),
3868
- /* @__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") }] }) })
3869
4653
  ] });
3870
4654
  break;
3871
4655
  case "loading":
@@ -3876,19 +4660,16 @@ function App({
3876
4660
  break;
3877
4661
  case "orgs":
3878
4662
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3879
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc400, children: [
3880
- "connect\xE9 : ",
3881
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.me.email })
3882
- ] }),
4663
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc400, children: t("common.connectedAs", { email: page2.me.email }) }),
3883
4664
  /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", children: [
3884
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "organisation active :" }),
4665
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("common.activeOrgLabel") }),
3885
4666
  /* @__PURE__ */ jsx19(
3886
4667
  SelectList,
3887
4668
  {
3888
4669
  items: page2.orgs.map((o) => ({
3889
4670
  label: o.name,
3890
4671
  value: o._id,
3891
- 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")}`
3892
4673
  })),
3893
4674
  onSelect: (item) => {
3894
4675
  const org2 = page2.orgs.find((o) => o._id === item.value);
@@ -3911,24 +4692,27 @@ function App({
3911
4692
  break;
3912
4693
  case "home": {
3913
4694
  const cfg = loadConfig();
4695
+ const langLabel = `Langue / Language : ${getLocale() === "fr" ? "Fran\xE7ais" : "English"} \u203A`;
3914
4696
  const menuItems = [
3915
- { label: "Lancer un job", value: "job", hint: "int\xE9grer un mod\xE8le (Hugging Face ou fichier local)" },
3916
- { label: "Lancer une simulation", value: "simulate", hint: "t\xE2che libre \u2014 dry-run du pipeline complet" },
3917
- { label: "Quota de build", value: "quota", hint: "builds restants" },
3918
- { label: "Changer d'organisation", value: "orgs" },
3919
- { label: "Qui suis-je", value: "whoami", hint: "profil complet" },
3920
- { label: "Rafra\xEEchir l\u2019historique", value: "refresh" },
3921
- { label: "Se d\xE9connecter", value: "logout" },
3922
- { label: "D\xE9sinstaller sumdae", value: "uninstall", hint: "supprimer la CLI de cette machine" },
3923
- { 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" }
3924
4707
  ];
4708
+ homeLangIndexRef.current = menuItems.findIndex((i) => i.value === "lang");
3925
4709
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
3926
4710
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", marginBottom: 1, children: [
3927
4711
  /* @__PURE__ */ jsx19(Box16, { children: /* @__PURE__ */ jsxs17(Text19, { color: homeFocus === "history" ? COLORS.emerald : COLORS.zinc500, children: [
3928
4712
  homeFocus === "history" ? "\u25B8 " : " ",
3929
- "derniers jobs"
4713
+ t("home.recentJobs")
3930
4714
  ] }) }),
3931
- 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(
3932
4716
  SelectList,
3933
4717
  {
3934
4718
  isActive: homeFocus === "history",
@@ -3936,7 +4720,7 @@ function App({
3936
4720
  items: recentBuilds.map((b) => {
3937
4721
  const running = !isTerminalStatus(b.status);
3938
4722
  return {
3939
- 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}`,
3940
4724
  value: `job:${b.id}`,
3941
4725
  hint: b.simulation_task ? b.simulation_task.slice(0, 50) : b.weights_artifact_id?.slice(0, 8) ?? void 0,
3942
4726
  ...running ? { prefix: /* @__PURE__ */ jsx19(Twinkle, {}), labelColor: COLORS.emerald } : {}
@@ -3951,20 +4735,17 @@ function App({
3951
4735
  ] }),
3952
4736
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "row", children: [
3953
4737
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", flexShrink: 0, marginRight: 3, children: [
3954
- /* @__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") }),
3955
4739
  /* @__PURE__ */ jsxs17(Text19, { children: [
3956
4740
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.emerald, children: "\u2713 " }),
3957
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
3958
- "session : ",
3959
- session ?? "\u2014"
3960
- ] })
4741
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("home.sessionLabel", { session: session ?? "\u2014" }) })
3961
4742
  ] }),
3962
4743
  logoCells ? /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(OrgLogo, { cells: logoCells }) }) : null
3963
4744
  ] }),
3964
4745
  /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", flexGrow: 1, marginTop: logoCells ? 3 : 0, children: [
3965
4746
  /* @__PURE__ */ jsx19(Box16, { children: /* @__PURE__ */ jsxs17(Text19, { color: homeFocus === "menu" ? COLORS.emerald : COLORS.zinc500, children: [
3966
4747
  homeFocus === "menu" ? "\u25B8 " : " ",
3967
- "actions"
4748
+ t("home.actions")
3968
4749
  ] }) }),
3969
4750
  /* @__PURE__ */ jsx19(Box16, { marginLeft: 2, children: /* @__PURE__ */ jsx19(
3970
4751
  SelectList,
@@ -3972,6 +4753,7 @@ function App({
3972
4753
  isActive: homeFocus === "menu",
3973
4754
  showHint: false,
3974
4755
  items: menuItems,
4756
+ onIndexChange: setHomeMenuIndex,
3975
4757
  onSelect: (i) => {
3976
4758
  if (i.value === "job") setPage({ name: "job-source" });
3977
4759
  else if (i.value === "simulate") setPage({ name: "simulate-prompt" });
@@ -3979,6 +4761,7 @@ function App({
3979
4761
  else if (i.value === "orgs") void loadOrgs({ name: "home" });
3980
4762
  else if (i.value === "whoami") void showWhoami();
3981
4763
  else if (i.value === "refresh") void reloadRecentBuilds();
4764
+ else if (i.value === "lang") switchLocale(getLocale() === "fr" ? "en" : "fr");
3982
4765
  else if (i.value === "logout") void logout();
3983
4766
  else if (i.value === "uninstall") setPage({ name: "uninstall-confirm" });
3984
4767
  else quit();
@@ -3991,9 +4774,9 @@ function App({
3991
4774
  Keycaps,
3992
4775
  {
3993
4776
  items: [
3994
- { key: "\u2191\u2193", desc: "naviguer" },
3995
- { key: "tab", desc: "basculer zone" },
3996
- { 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") }
3997
4780
  ]
3998
4781
  }
3999
4782
  ) })
@@ -4008,17 +4791,17 @@ function App({
4008
4791
  KeyValue,
4009
4792
  {
4010
4793
  rows: [
4011
- ["user", name || page2.me.email],
4012
- ["email", page2.me.email],
4013
- ["org", cfg.activeOrgName ? `${cfg.activeOrgName} (${cfg.activeOrgId})` : "\u2014 aucune"],
4014
- ["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"]
4015
4798
  ]
4016
4799
  }
4017
4800
  ),
4018
4801
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4019
4802
  SelectList,
4020
4803
  {
4021
- items: [backItem("Retourner \xE0 l'accueil")],
4804
+ items: [backItem(t("common.backToHome"))],
4022
4805
  onSelect: () => back()
4023
4806
  }
4024
4807
  ) })
@@ -4027,18 +4810,18 @@ function App({
4027
4810
  }
4028
4811
  case "quota": {
4029
4812
  const rows2 = [
4030
- ["org", page2.orgName],
4031
- ["builds restants", String(page2.remaining)]
4813
+ [t("whoami.row.org"), page2.orgName],
4814
+ [t("quota.row.remaining"), String(page2.remaining)]
4032
4815
  ];
4033
4816
  if (page2.remaining === 0) {
4034
- 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")]);
4035
4818
  }
4036
4819
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4037
4820
  /* @__PURE__ */ jsx19(KeyValue, { rows: rows2 }),
4038
4821
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4039
4822
  SelectList,
4040
4823
  {
4041
- items: [backItem("Retourner \xE0 l'accueil")],
4824
+ items: [backItem(t("common.backToHome"))],
4042
4825
  onSelect: () => back()
4043
4826
  }
4044
4827
  ) })
@@ -4050,8 +4833,8 @@ function App({
4050
4833
  /* @__PURE__ */ jsx19(
4051
4834
  TextField,
4052
4835
  {
4053
- title: "T\xE2che pour le master \xB7 mode SIMULATION",
4054
- 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"),
4055
4838
  onCancel: () => back(),
4056
4839
  onSubmit: (task) => void startSimulation(task)
4057
4840
  }
@@ -4060,10 +4843,10 @@ function App({
4060
4843
  Keycaps,
4061
4844
  {
4062
4845
  items: [
4063
- { key: "enter", desc: "envoyer" },
4064
- { key: "\\ + enter", desc: "nouvelle ligne" },
4065
- { key: "\u2190\u2192\u2191\u2193", desc: "naviguer" },
4066
- { 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") }
4067
4850
  ]
4068
4851
  }
4069
4852
  ) })
@@ -4071,21 +4854,21 @@ function App({
4071
4854
  break;
4072
4855
  case "job-source":
4073
4856
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4074
- /* @__PURE__ */ jsx19(Box16, { marginBottom: 1, children: /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, bold: true, children: "lancer un job" }) }),
4075
- /* @__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") }),
4076
4859
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4077
4860
  SelectList,
4078
4861
  {
4079
4862
  items: [
4080
4863
  {
4081
- label: "Lien Hugging Face",
4864
+ label: t("job.source.hf"),
4082
4865
  value: "hf",
4083
- hint: "t\xE9l\xE9charg\xE9 directement sur l\u2019environnement (token org requis)"
4866
+ hint: t("job.source.hf.hint")
4084
4867
  },
4085
4868
  {
4086
- label: "Fichier local",
4869
+ label: t("job.source.file"),
4087
4870
  value: "file",
4088
- hint: "stream\xE9 depuis cette machine vers l\u2019environnement"
4871
+ hint: t("job.source.file.hint")
4089
4872
  },
4090
4873
  backItem()
4091
4874
  ],
@@ -4103,30 +4886,26 @@ function App({
4103
4886
  /* @__PURE__ */ jsx19(
4104
4887
  TextField,
4105
4888
  {
4106
- title: "Mod\xE8le Hugging Face",
4107
- 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"),
4108
4891
  onCancel: () => back(),
4109
4892
  onSubmit: (v) => void submitHfRepo(v)
4110
4893
  }
4111
4894
  ),
4112
4895
  hfTokenHint ? /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, children: [
4113
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: "token HF : " }),
4896
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc600, children: t("job.hf.tokenLabel") }),
4114
4897
  hfTokenHint.account ? /* @__PURE__ */ jsxs17(Text19, { color: COLORS.emerald, children: [
4115
4898
  hfTokenHint.account,
4116
4899
  " "
4117
4900
  ] }) : null,
4118
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
4119
- "(",
4120
- hfTokenHint.masked,
4121
- ")"
4122
- ] })
4123
- ] }) : /* @__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") }) }),
4124
4903
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4125
4904
  Keycaps,
4126
4905
  {
4127
4906
  items: [
4128
- { key: "enter", desc: "v\xE9rifier l\u2019acc\xE8s" },
4129
- { key: "esc", desc: "retour" }
4907
+ { key: "enter", desc: t("keycaps.checkAccess") },
4908
+ { key: "esc", desc: t("keycaps.back") }
4130
4909
  ]
4131
4910
  }
4132
4911
  ) })
@@ -4134,9 +4913,9 @@ function App({
4134
4913
  break;
4135
4914
  case "job-hf-confirm":
4136
4915
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4137
- /* @__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") }) }),
4138
4917
  /* @__PURE__ */ jsxs17(Text19, { children: [
4139
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "repo : " }),
4918
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.hfConfirm.repo") }),
4140
4919
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.repo }),
4141
4920
  page2.revision ? /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
4142
4921
  " @ ",
@@ -4144,17 +4923,17 @@ function App({
4144
4923
  ] }) : null
4145
4924
  ] }),
4146
4925
  /* @__PURE__ */ jsxs17(Text19, { children: [
4147
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "taille estim\xE9e : " }),
4148
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.sizeBytes !== null ? formatBytes(page2.sizeBytes) : "inconnue" }),
4149
- 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
4150
4929
  ] }),
4151
- /* @__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") }) }),
4152
4931
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4153
4932
  SelectList,
4154
4933
  {
4155
4934
  items: [
4156
- { label: "Lancer le job", value: "go" },
4157
- { label: "Modifier le lien", value: "edit" },
4935
+ { label: t("job.hfConfirm.launch"), value: "go" },
4936
+ { label: t("job.hfConfirm.editLink"), value: "edit" },
4158
4937
  backItem()
4159
4938
  ],
4160
4939
  onSelect: (i) => {
@@ -4171,8 +4950,8 @@ function App({
4171
4950
  /* @__PURE__ */ jsx19(
4172
4951
  TextField,
4173
4952
  {
4174
- title: "Fichier de poids / archive locale",
4175
- placeholder: "ex. : ~/models/mistral-7b.safetensors ou ./weights.tar.gz",
4953
+ title: t("job.file.title"),
4954
+ placeholder: t("job.file.placeholder"),
4176
4955
  onCancel: () => back(),
4177
4956
  onSubmit: (v) => void submitFilePath(v)
4178
4957
  }
@@ -4181,8 +4960,8 @@ function App({
4181
4960
  Keycaps,
4182
4961
  {
4183
4962
  items: [
4184
- { key: "enter", desc: "valider" },
4185
- { key: "esc", desc: "retour" }
4963
+ { key: "enter", desc: t("keycaps.validate") },
4964
+ { key: "esc", desc: t("keycaps.back") }
4186
4965
  ]
4187
4966
  }
4188
4967
  ) })
@@ -4190,9 +4969,9 @@ function App({
4190
4969
  break;
4191
4970
  case "job-file-confirm":
4192
4971
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4193
- /* @__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") }) }),
4194
4973
  /* @__PURE__ */ jsxs17(Text19, { children: [
4195
- /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: "fichier : " }),
4974
+ /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc500, children: t("job.fileConfirm.fileLabel") }),
4196
4975
  /* @__PURE__ */ jsx19(Text19, { color: COLORS.zinc100, children: page2.fileName }),
4197
4976
  /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, children: [
4198
4977
  " (",
@@ -4201,15 +4980,15 @@ function App({
4201
4980
  ] })
4202
4981
  ] }),
4203
4982
  /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", children: [
4204
- /* @__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)." }),
4205
- /* @__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") })
4206
4985
  ] }),
4207
4986
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4208
4987
  SelectList,
4209
4988
  {
4210
4989
  items: [
4211
- { label: "J'ai compris \u2014 lancer le job", value: "go" },
4212
- { label: "Changer de fichier", value: "edit" },
4990
+ { label: t("job.fileConfirm.gotItLaunch"), value: "go" },
4991
+ { label: t("job.fileConfirm.changeFile"), value: "edit" },
4213
4992
  backItem()
4214
4993
  ],
4215
4994
  onSelect: (i) => {
@@ -4235,7 +5014,7 @@ function App({
4235
5014
  onDone: () => setPage({ name: "watch", buildId: page2.buildId }),
4236
5015
  onError: (message) => setPage({
4237
5016
  name: "error",
4238
- title: "upload interrompu",
5017
+ title: t("upload.interrupted.title"),
4239
5018
  detail: message,
4240
5019
  back: { name: "home" }
4241
5020
  })
@@ -4262,20 +5041,20 @@ function App({
4262
5041
  const active = !isTerminalStatus(page2.build.status);
4263
5042
  const createdAtMs = page2.build.created_at ? Date.parse(page2.build.created_at) : void 0;
4264
5043
  body = /* @__PURE__ */ jsxs17(Box16, { flexDirection: "column", children: [
4265
- /* @__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") }) }),
4266
5045
  /* @__PURE__ */ jsx19(JobSummary, { build: page2.build }),
4267
5046
  /* @__PURE__ */ jsx19(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx19(
4268
5047
  SelectList,
4269
5048
  {
4270
5049
  items: [
4271
5050
  active ? {
4272
- label: "Voir le job en cours",
5051
+ label: t("buildSummary.watchLive"),
4273
5052
  value: "rewatch",
4274
5053
  labelColor: COLORS.emerald,
4275
5054
  prefix: /* @__PURE__ */ jsx19(Twinkle, {})
4276
- } : { label: "Revoir le flux complet", value: "rewatch", hint: "rejouer les events" },
4277
- backItem("Retourner \xE0 l'accueil"),
4278
- { 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" }
4279
5058
  ],
4280
5059
  onSelect: (i) => {
4281
5060
  if (i.value === "rewatch")
@@ -4311,8 +5090,8 @@ function App({
4311
5090
  SelectList,
4312
5091
  {
4313
5092
  items: [
4314
- { label: "Retourner en arri\xE8re", value: "back", hint: "ou \xC9chap" },
4315
- { label: "Quitter", value: "quit" }
5093
+ { label: t("common.back"), value: "back", hint: t("common.orEscape") },
5094
+ { label: t("common.quit"), value: "quit" }
4316
5095
  ],
4317
5096
  onSelect: (i) => i.value === "back" ? setPage(page2.back) : quit()
4318
5097
  }
@@ -4333,22 +5112,10 @@ function App({
4333
5112
  justifyContent: "center",
4334
5113
  flexDirection: "column",
4335
5114
  children: [
4336
- /* @__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") }),
4337
5116
  /* @__PURE__ */ jsxs17(Box16, { marginTop: 1, flexDirection: "column", alignItems: "center", children: [
4338
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, wrap: "truncate", children: [
4339
- "Agrandissez \xE0 ",
4340
- MIN_COLS,
4341
- "\xD7",
4342
- MIN_ROWS,
4343
- " minimum"
4344
- ] }),
4345
- /* @__PURE__ */ jsxs17(Text19, { color: COLORS.zinc500, wrap: "truncate", children: [
4346
- "(actuel : ",
4347
- columns,
4348
- "\xD7",
4349
- rows,
4350
- ")"
4351
- ] })
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 }) })
4352
5119
  ] })
4353
5120
  ]
4354
5121
  }
@@ -4640,8 +5407,8 @@ function Banner({ animate, onDone }) {
4640
5407
  onDone?.();
4641
5408
  return;
4642
5409
  }
4643
- const t = setTimeout(() => setFrame((f) => f + 1), FRAME_MS3);
4644
- return () => clearTimeout(t);
5410
+ const t2 = setTimeout(() => setFrame((f) => f + 1), FRAME_MS3);
5411
+ return () => clearTimeout(t2);
4645
5412
  }, [animate, frame, onDone]);
4646
5413
  const color = colorEnabled();
4647
5414
  const angle = 0.9 + frame * 0.05;
@@ -4651,7 +5418,8 @@ function Banner({ animate, onDone }) {
4651
5418
  /* @__PURE__ */ jsx20(Box17, { marginTop: 1, children: /* @__PURE__ */ jsx20(Text20, { children: planetFrame(angle, { orbitAngle }, color) }) }),
4652
5419
  /* @__PURE__ */ jsx20(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs18(Text20, { color: COLORS.zinc500, children: [
4653
5420
  /* @__PURE__ */ jsx20(Text20, { color: COLORS.emerald, children: "$" }),
4654
- " la plateforme sumdae, depuis le terminal"
5421
+ " ",
5422
+ t("banner.tagline")
4655
5423
  ] }) })
4656
5424
  ] });
4657
5425
  }
@@ -4660,23 +5428,20 @@ function Banner({ animate, onDone }) {
4660
5428
  import { Box as Box18, Text as Text21 } from "ink";
4661
5429
  import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
4662
5430
  var COMMANDS = [
4663
- { cmd: "sumdae login", desc: "se connecter (OAuth + PKCE)" },
4664
- { cmd: "sumdae whoami", desc: "profil + organisation active" },
4665
- { cmd: "sumdae push <fichier>", desc: "uploader poids/pipeline (R2)" },
4666
- { cmd: "sumdae build <artifact>", desc: "lancer un build + suivi live" },
4667
- { cmd: "sumdae watch <buildId>", desc: "suivre un build en cours" },
4668
- { cmd: "sumdae cancel <buildId>", desc: "annuler un build proprement" },
4669
- { cmd: "sumdae quota", desc: "builds restants de l'org active" },
4670
- { cmd: "sumdae org list", desc: "lister vos organisations" },
4671
- { cmd: "sumdae org switch", desc: "changer d'organisation active" },
4672
- { 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") }
4673
5441
  ];
4674
5442
  function Help({ version }) {
4675
5443
  return /* @__PURE__ */ jsxs19(Box18, { flexDirection: "column", children: [
4676
- /* @__PURE__ */ jsxs19(Text21, { color: COLORS.zinc600, children: [
4677
- "COMMANDES \xB7 v",
4678
- version
4679
- ] }),
5444
+ /* @__PURE__ */ jsx21(Text21, { color: COLORS.zinc600, children: t("help.heading", { version }) }),
4680
5445
  COMMANDS.map((c, i) => /* @__PURE__ */ jsxs19(Box18, { marginTop: i === 0 ? 1 : 0, children: [
4681
5446
  /* @__PURE__ */ jsx21(Box18, { width: 9, children: /* @__PURE__ */ jsx21(Text21, { color: COLORS.zinc600, children: `cmd_${String(i + 1).padStart(2, "0")}` }) }),
4682
5447
  /* @__PURE__ */ jsx21(Box18, { width: 22, children: /* @__PURE__ */ jsxs19(Text21, { color: COLORS.zinc100, children: [
@@ -4714,8 +5479,8 @@ async function homeAction(version) {
4714
5479
  if (!interactive) {
4715
5480
  const cfg2 = loadConfig();
4716
5481
  const status = creds ? [
4717
- creds.kind === "api-key" ? "session : cl\xE9 API" : "session : oauth",
4718
- 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")
4719
5484
  ] : null;
4720
5485
  const app2 = render3(/* @__PURE__ */ jsx22(Home, { animate: false, status, version }));
4721
5486
  await app2.waitUntilExit();
@@ -4751,8 +5516,8 @@ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u282
4751
5516
  function Spinner({ label }) {
4752
5517
  const [i, setI] = useState11(0);
4753
5518
  useEffect10(() => {
4754
- const t = setInterval(() => setI((x) => (x + 1) % FRAMES.length), 80);
4755
- return () => clearInterval(t);
5519
+ const t2 = setInterval(() => setI((x) => (x + 1) % FRAMES.length), 80);
5520
+ return () => clearInterval(t2);
4756
5521
  }, []);
4757
5522
  return /* @__PURE__ */ jsxs21(Text23, { children: [
4758
5523
  /* @__PURE__ */ jsx23(Text23, { color: COLORS.emerald, children: FRAMES[i] }),
@@ -4767,7 +5532,7 @@ function Spinner({ label }) {
4767
5532
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
4768
5533
  function orgItems(orgs, myId) {
4769
5534
  return orgs.map((o) => {
4770
- 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");
4771
5536
  return { label: o.name, value: o._id, hint: `${o.slug} \xB7 ${role}` };
4772
5537
  });
4773
5538
  }
@@ -4791,7 +5556,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4791
5556
  if (orgs.length === 0) {
4792
5557
  setPhase({
4793
5558
  step: "done",
4794
- 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")]
4795
5560
  });
4796
5561
  setTimeout(exit, 10);
4797
5562
  return;
@@ -4799,7 +5564,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4799
5564
  if (!process.stdin.isTTY) {
4800
5565
  setPhase({
4801
5566
  step: "done",
4802
- lines: [`connect\xE9 : ${me.email}`, "choisissez une organisation avec `sumdae org switch` (TTY)"]
5567
+ lines: [t("common.connectedAs", { email: me.email }), t("login.noOrgCreateThenSwitch")]
4803
5568
  });
4804
5569
  setTimeout(exit, 10);
4805
5570
  return;
@@ -4810,7 +5575,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4810
5575
  process.exitCode = 1;
4811
5576
  setPhase({
4812
5577
  step: "error",
4813
- title: "connexion \xE9chou\xE9e",
5578
+ title: t("common.loginFailed"),
4814
5579
  detail: e instanceof Error ? e.message : String(e)
4815
5580
  });
4816
5581
  setTimeout(exit, 10);
@@ -4819,21 +5584,18 @@ function LoginApp({ noBrowser, skipAuth }) {
4819
5584
  }, []);
4820
5585
  if (phase.step === "auth") {
4821
5586
  return /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
4822
- /* @__PURE__ */ jsx24(Spinner, { label: "en attente de la connexion dans le navigateur\u2026" }),
5587
+ /* @__PURE__ */ jsx24(Spinner, { label: t("common.waitingBrowserLogin") }),
4823
5588
  noBrowser && authorizeUrl ? /* @__PURE__ */ jsxs22(Box20, { marginTop: 1, flexDirection: "column", children: [
4824
- /* @__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") }),
4825
5590
  /* @__PURE__ */ jsx24(Text24, { color: COLORS.emerald, children: authorizeUrl })
4826
5591
  ] }) : null
4827
5592
  ] });
4828
5593
  }
4829
5594
  if (phase.step === "orgs") {
4830
5595
  return /* @__PURE__ */ jsxs22(Box20, { flexDirection: "column", children: [
4831
- /* @__PURE__ */ jsxs22(Text24, { color: COLORS.zinc400, children: [
4832
- "connect\xE9 : ",
4833
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc100, children: phase.me.email })
4834
- ] }),
5596
+ /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc400, children: t("common.connectedAs", { email: phase.me.email }) }),
4835
5597
  /* @__PURE__ */ jsxs22(Box20, { marginTop: 1, flexDirection: "column", children: [
4836
- /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc500, children: "organisation active :" }),
5598
+ /* @__PURE__ */ jsx24(Text24, { color: COLORS.zinc500, children: t("common.activeOrgLabel") }),
4837
5599
  /* @__PURE__ */ jsx24(
4838
5600
  SelectList,
4839
5601
  {
@@ -4842,7 +5604,7 @@ function LoginApp({ noBrowser, skipAuth }) {
4842
5604
  const org2 = phase.orgs.find((o) => o._id === item.value);
4843
5605
  if (!org2) return;
4844
5606
  saveActiveOrg(org2);
4845
- setPhase({ step: "done", lines: [`organisation active : ${org2.name}`] });
5607
+ setPhase({ step: "done", lines: [t("common.activeOrgSet", { name: org2.name })] });
4846
5608
  setTimeout(exit, 10);
4847
5609
  }
4848
5610
  }
@@ -4860,10 +5622,10 @@ async function loginAction(opts) {
4860
5622
  if (opts.withKey) {
4861
5623
  const { createInterface } = await import("readline/promises");
4862
5624
  const rl = createInterface({ input: process.stdin, output: process.stdout });
4863
- const key = (await rl.question("cl\xE9 API (lab_\u2026) : ")).trim();
5625
+ const key = (await rl.question(t("login.key.prompt"))).trim();
4864
5626
  rl.close();
4865
5627
  if (!key) {
4866
- console.error("aucune cl\xE9 fournie");
5628
+ console.error(t("login.noKeyProvided"));
4867
5629
  process.exitCode = 1;
4868
5630
  return;
4869
5631
  }
@@ -4874,7 +5636,7 @@ async function loginAction(opts) {
4874
5636
  }
4875
5637
  if (!process.stdout.isTTY) {
4876
5638
  console.error(
4877
- "terminal non interactif \u2014 utilisez `sumdae login --no-browser` dans un TTY, ou `--with-key`"
5639
+ t("login.nonInteractiveTty")
4878
5640
  );
4879
5641
  process.exitCode = 1;
4880
5642
  return;
@@ -4890,7 +5652,7 @@ async function logoutAction() {
4890
5652
  delete cfg.activeOrgId;
4891
5653
  delete cfg.activeOrgName;
4892
5654
  saveConfig(cfg);
4893
- console.log(ok("d\xE9connect\xE9"));
5655
+ console.log(ok(t("logout.done")));
4894
5656
  }
4895
5657
 
4896
5658
  // src/commands/org.tsx
@@ -4906,7 +5668,7 @@ async function fetchOrgItems() {
4906
5668
  items: orgs.map((o) => ({
4907
5669
  label: o.name,
4908
5670
  value: o._id,
4909
- 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")}`
4910
5672
  }))
4911
5673
  };
4912
5674
  }
@@ -4914,7 +5676,7 @@ async function orgListAction() {
4914
5676
  try {
4915
5677
  const { items } = await fetchOrgItems();
4916
5678
  if (items.length === 0) {
4917
- console.log("aucune organisation \u2014 cr\xE9ez-en une sur lab.sumdae.fr");
5679
+ console.log(t("common.noOrgCreateOnLab"));
4918
5680
  return;
4919
5681
  }
4920
5682
  const active = loadConfig().activeOrgId;
@@ -4937,14 +5699,11 @@ function SwitchApp({ items, orgs }) {
4937
5699
  if (picked) {
4938
5700
  return /* @__PURE__ */ jsxs23(Text25, { children: [
4939
5701
  /* @__PURE__ */ jsx25(Text25, { color: COLORS.emerald, children: "\u2713 " }),
4940
- /* @__PURE__ */ jsxs23(Text25, { color: COLORS.zinc100, children: [
4941
- "organisation active : ",
4942
- picked.label
4943
- ] })
5702
+ /* @__PURE__ */ jsx25(Text25, { color: COLORS.zinc100, children: t("common.activeOrgSet", { name: picked.label }) })
4944
5703
  ] });
4945
5704
  }
4946
5705
  return /* @__PURE__ */ jsxs23(Box21, { flexDirection: "column", children: [
4947
- /* @__PURE__ */ jsx25(Text25, { color: COLORS.zinc500, children: "organisation active :" }),
5706
+ /* @__PURE__ */ jsx25(Text25, { color: COLORS.zinc500, children: t("common.activeOrgLabel") }),
4948
5707
  /* @__PURE__ */ jsx25(
4949
5708
  SelectList,
4950
5709
  {
@@ -4964,11 +5723,11 @@ async function orgSwitchAction() {
4964
5723
  try {
4965
5724
  const { items, orgs } = await fetchOrgItems();
4966
5725
  if (items.length === 0) {
4967
- console.log("aucune organisation \u2014 cr\xE9ez-en une sur lab.sumdae.fr");
5726
+ console.log(t("common.noOrgCreateOnLab"));
4968
5727
  return;
4969
5728
  }
4970
5729
  if (!process.stdin.isTTY) {
4971
- console.error("terminal non interactif \u2014 impossible de choisir une organisation");
5730
+ console.error(t("orgs.nonInteractiveTty"));
4972
5731
  process.exitCode = 1;
4973
5732
  return;
4974
5733
  }
@@ -4996,7 +5755,7 @@ async function hashFile(path, signal) {
4996
5755
  for await (const chunk of stream) {
4997
5756
  if (signal?.aborted) {
4998
5757
  stream.destroy();
4999
- throw new Error("upload annul\xE9");
5758
+ throw new Error(t("upload.err.cancelled"));
5000
5759
  }
5001
5760
  hash.update(chunk);
5002
5761
  }
@@ -5028,7 +5787,7 @@ var PartUrlProvider = class {
5028
5787
  if (url) return url;
5029
5788
  await this.fetchBatch(partNumber);
5030
5789
  const retry = this.cache.get(partNumber);
5031
- 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 }));
5032
5791
  return retry;
5033
5792
  }
5034
5793
  async fetchBatch(from) {
@@ -5089,7 +5848,7 @@ async function pushArtifact(opts) {
5089
5848
  const partNumber = next;
5090
5849
  if (partNumber > partsTotal) return;
5091
5850
  next += 1;
5092
- if (signal?.aborted) throw new Error("upload annul\xE9");
5851
+ if (signal?.aborted) throw new Error(t("upload.err.cancelled"));
5093
5852
  const offset = (partNumber - 1) * partSize;
5094
5853
  const length = Math.min(partSize, totalBytes - offset);
5095
5854
  const buf = Buffer.alloc(length);
@@ -5101,7 +5860,7 @@ async function pushArtifact(opts) {
5101
5860
  const res = await fetch(url, { method: "PUT", body: buf, signal });
5102
5861
  if (!res.ok) throw new Error(`R2 HTTP ${res.status}`);
5103
5862
  const etag = res.headers.get("etag");
5104
- if (!etag) throw new Error("ETag manquant dans la r\xE9ponse R2");
5863
+ if (!etag) throw new Error(t("upload.err.missingEtag"));
5105
5864
  etags.set(partNumber, etag);
5106
5865
  sentBytes += length;
5107
5866
  partsDone += 1;
@@ -5110,13 +5869,18 @@ async function pushArtifact(opts) {
5110
5869
  break;
5111
5870
  } catch (e) {
5112
5871
  lastErr = e;
5113
- if (signal?.aborted) throw new Error("upload annul\xE9");
5872
+ if (signal?.aborted) throw new Error(t("upload.err.cancelled"));
5114
5873
  if (attempt < maxRetries) await sleep(300 * attempt);
5115
5874
  }
5116
5875
  }
5117
5876
  if (lastErr) {
5118
5877
  throw new Error(
5119
- `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
+ })
5120
5884
  );
5121
5885
  }
5122
5886
  }
@@ -5221,11 +5985,11 @@ function PushApp({
5221
5985
  )
5222
5986
  ] });
5223
5987
  }
5224
- 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 });
5225
5989
  return /* @__PURE__ */ jsxs24(Box22, { flexDirection: "column", children: [
5226
5990
  /* @__PURE__ */ jsxs24(Text26, { children: [
5227
5991
  /* @__PURE__ */ jsx26(Text26, { color: COLORS.emerald, children: "\u2713 " }),
5228
- /* @__PURE__ */ jsx26(Text26, { color: COLORS.zinc100, children: "artifact upload\xE9" })
5992
+ /* @__PURE__ */ jsx26(Text26, { color: COLORS.zinc100, children: t("push.uploaded") })
5229
5993
  ] }),
5230
5994
  /* @__PURE__ */ jsxs24(Text26, { color: COLORS.zinc500, children: [
5231
5995
  "id ",
@@ -5243,14 +6007,14 @@ async function pushAction(filePath2, opts) {
5243
6007
  const kind = opts.kind ?? inferKind(filePath2);
5244
6008
  if (kind !== "weights" && kind !== "pipeline") {
5245
6009
  console.error(
5246
- "impossible de d\xE9duire le type \u2014 pr\xE9cisez --kind weights ou --kind pipeline"
6010
+ t("push.inferKindFailed")
5247
6011
  );
5248
6012
  process.exitCode = 1;
5249
6013
  return;
5250
6014
  }
5251
6015
  const active = await resolveActiveOrg().catch(() => null);
5252
6016
  if (!active) {
5253
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
6017
+ console.error(t("common.noActiveOrgRunSwitch"));
5254
6018
  process.exitCode = 1;
5255
6019
  return;
5256
6020
  }
@@ -5267,17 +6031,17 @@ async function quotaAction() {
5267
6031
  try {
5268
6032
  const active = await resolveActiveOrg();
5269
6033
  if (!active) {
5270
- console.error("aucune organisation active \u2014 lancez `sumdae org switch`");
6034
+ console.error(t("common.noActiveOrgRunSwitch"));
5271
6035
  process.exitCode = 1;
5272
6036
  return;
5273
6037
  }
5274
6038
  const q = await getOrgQuota(active.slug);
5275
6039
  const rows = [
5276
- ["org", `${active.name} (${active.slug})`],
5277
- ["builds restants", String(q.remaining)]
6040
+ [t("whoami.row.org"), `${active.name} (${active.slug})`],
6041
+ [t("quota.row.remaining"), String(q.remaining)]
5278
6042
  ];
5279
6043
  if (q.remaining === 0) {
5280
- 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")]);
5281
6045
  }
5282
6046
  render7(/* @__PURE__ */ jsx27(KeyValue, { rows }));
5283
6047
  } catch (e) {
@@ -5288,10 +6052,10 @@ async function quotaAction() {
5288
6052
 
5289
6053
  // src/commands/uninstall.ts
5290
6054
  function printPlan(plan) {
5291
- console.log("D\xE9sinstallation de sumdae :");
6055
+ console.log(t("uninstall.cli.heading"));
5292
6056
  console.log(` canal : ${plan.channel}`);
5293
6057
  if (plan.channel === "npm") {
5294
- console.log(" install : npm uninstall -g @sumdae/cli");
6058
+ console.log(` install : ${plan.manualHint ?? t("uninstall.cli.installGlobal")}`);
5295
6059
  } else if (plan.installBlockedReason) {
5296
6060
  console.log(` install : manuel \u2192 ${plan.manualHint ?? plan.installRoot}`);
5297
6061
  } else {
@@ -5299,14 +6063,14 @@ function printPlan(plan) {
5299
6063
  if (plan.launcherPath) console.log(` launcher : ${plan.launcherPath}`);
5300
6064
  }
5301
6065
  console.log(` config : ${plan.configDir}`);
5302
- console.log(" credentials : trousseau + fichier");
6066
+ console.log(t("uninstall.cli.credentials"));
5303
6067
  }
5304
6068
  async function uninstallAction(opts = {}) {
5305
6069
  const interactive = process.stdout.isTTY === true && process.stdin.isTTY === true;
5306
6070
  if (interactive && !opts.yes) {
5307
6071
  const report2 = await runUninstallScreen();
5308
6072
  if (!report2) {
5309
- console.log("annul\xE9.");
6073
+ console.log(t("uninstall.cli.cancelled"));
5310
6074
  return;
5311
6075
  }
5312
6076
  if (report2.some((s) => s.step !== "install" && !s.ok)) process.exitCode = 1;
@@ -5320,13 +6084,13 @@ async function uninstallAction(opts = {}) {
5320
6084
  if (s.ok) {
5321
6085
  console.log(ok(s.step));
5322
6086
  } else if (s.step === "install") {
5323
- console.log(warn(`${s.step} \u2014 ${s.error ?? "\xE0 faire manuellement"}`));
6087
+ console.log(warn(`${s.step} \u2014 ${s.error ?? t("uninstall.cli.manualFallback")}`));
5324
6088
  } else {
5325
- console.log(ko(`${s.step} \u2014 ${s.error ?? "\xE9chec"}`));
6089
+ console.log(ko(`${s.step} \u2014 ${s.error ?? t("uninstall.cli.stepFailed")}`));
5326
6090
  dataFailed = true;
5327
6091
  }
5328
6092
  }
5329
- console.log("config et identifiants nettoy\xE9s ; l\u2019install sera effac\xE9e \xE0 la fermeture.");
6093
+ console.log(t("uninstall.cli.cleaned"));
5330
6094
  if (dataFailed) process.exitCode = 1;
5331
6095
  }
5332
6096
 
@@ -5335,7 +6099,7 @@ import { render as render8 } from "ink";
5335
6099
  import { jsx as jsx28 } from "react/jsx-runtime";
5336
6100
  function machineLine() {
5337
6101
  const s = getSystemInfo();
5338
- 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)}`;
5339
6103
  }
5340
6104
  async function whoamiAction() {
5341
6105
  try {
@@ -5343,17 +6107,19 @@ async function whoamiAction() {
5343
6107
  const cfg = loadConfig();
5344
6108
  const creds = await loadCredentials();
5345
6109
  const name = `${me.first_name} ${me.last_name}`.trim();
5346
- 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
+ });
5347
6113
  render8(
5348
6114
  /* @__PURE__ */ jsx28(
5349
6115
  KeyValue,
5350
6116
  {
5351
6117
  rows: [
5352
- ["user", name || me.email],
5353
- ["email", me.email],
5354
- ["org", cfg.activeOrgName ? `${cfg.activeOrgName} (${cfg.activeOrgId})` : "\u2014 aucune (sumdae org switch)"],
5355
- ["session", session],
5356
- ["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()]
5357
6123
  ]
5358
6124
  }
5359
6125
  )
@@ -5390,7 +6156,7 @@ function UpdateApp({
5390
6156
  if (channel === "npm") {
5391
6157
  setPhase({ kind: "install" });
5392
6158
  const ok2 = updateViaNpm(latest.version);
5393
- if (!ok2) throw new Error("npm install a \xE9chou\xE9");
6159
+ if (!ok2) throw new Error(t("update.err.npmFailed"));
5394
6160
  setPhase({ kind: "done" });
5395
6161
  setTimeout(() => relaunch(), 300);
5396
6162
  return;
@@ -5398,10 +6164,10 @@ function UpdateApp({
5398
6164
  const { path, sha256 } = await downloadTarball(latest.tarball_url, (done, total) => {
5399
6165
  if (alive) setPhase({ kind: "download", done, total: total || latest.size });
5400
6166
  });
5401
- 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"));
5402
6168
  setPhase({ kind: "verify" });
5403
6169
  if (!verifyTarball(path, latest.signature)) {
5404
- throw new Error("signature invalide \u2014 mise \xE0 jour refus\xE9e");
6170
+ throw new Error(t("update.err.invalidSignature"));
5405
6171
  }
5406
6172
  setPhase({ kind: "install" });
5407
6173
  installTarball(path);
@@ -5435,17 +6201,17 @@ function UpdateApp({
5435
6201
  suffix: phase.total ? `${fmtBytes3(phase.done)} / ${fmtBytes3(phase.total)}` : fmtBytes3(phase.done)
5436
6202
  }
5437
6203
  ),
5438
- /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc500, children: "t\xE9l\xE9chargement\u2026" })
6204
+ /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc500, children: t("update.downloading") })
5439
6205
  ] });
5440
6206
  case "verify":
5441
- 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") });
5442
6208
  case "install":
5443
- return /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc400, children: "installation\u2026" });
6209
+ return /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc400, children: t("update.installing") });
5444
6210
  case "done":
5445
- 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") });
5446
6212
  case "error":
5447
6213
  return /* @__PURE__ */ jsxs25(Fragment3, { children: [
5448
- /* @__PURE__ */ jsx29(Text27, { color: COLORS.red, children: "\xE9chec de la mise \xE0 jour" }),
6214
+ /* @__PURE__ */ jsx29(Text27, { color: COLORS.red, children: t("update.failed") }),
5449
6215
  /* @__PURE__ */ jsx29(Text27, { color: COLORS.zinc500, wrap: "truncate", children: phase.message })
5450
6216
  ] });
5451
6217
  }
@@ -5459,7 +6225,7 @@ function UpdateApp({
5459
6225
  justifyContent: "center",
5460
6226
  flexDirection: "column",
5461
6227
  children: [
5462
- /* @__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") }),
5463
6229
  /* @__PURE__ */ jsx29(Box23, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsxs25(Text27, { color: COLORS.zinc500, children: [
5464
6230
  "v",
5465
6231
  current,
@@ -5500,35 +6266,34 @@ async function maybeSelfUpdate(argv) {
5500
6266
  const err = await runUpdateScreen(decision.latest, decision.current);
5501
6267
  if (err && decision.forced) {
5502
6268
  console.error(
5503
- `Cette version (${decision.current}) n'est plus support\xE9e et la mise \xE0 jour a \xE9chou\xE9 : ${err}
5504
- R\xE9essayez, ou r\xE9installez : https://lab.sumdae.fr/cli`
6269
+ t("update.forcedFailed", { current: decision.current, err })
5505
6270
  );
5506
6271
  process.exit(1);
5507
6272
  }
5508
6273
  }
5509
6274
  await maybeSelfUpdate(process.argv);
5510
6275
  var program = new Command();
5511
- 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 () => {
5512
6277
  await homeAction(CLI_VERSION);
5513
6278
  });
5514
- 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) => {
5515
6280
  await loginAction({ noBrowser: !opts.browser, withKey: opts.withKey });
5516
6281
  });
5517
- program.command("logout").description("se d\xE9connecter").action(logoutAction);
5518
- program.command("whoami").description("profil + organisation active").action(whoamiAction);
5519
- 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) => {
5520
6285
  await uninstallAction({ yes: opts.yes });
5521
6286
  });
5522
- program.command("quota").description("builds restants de l'organisation active").action(quotaAction);
5523
- 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);
5524
- 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);
5525
- program.command("simulate <task...>").description("lancer un build SIMULATION \u2014 pas besoin de mod\xE8le/pipeline (consomme 1 quota)").action((taskParts) => simulateAction(taskParts.join(" ")));
5526
- program.command("watch <buildId>").description("suivre un build en cours (WS temps r\xE9el)").action(watchAction);
5527
- program.command("cancel <buildId>").description("annuler un build (environnement lib\xE9r\xE9, quota rembours\xE9 si rien commenc\xE9)").action(cancelAction);
5528
- var agent = program.command("agent").description("piloter les agents d\u2019un build");
5529
- 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));
5530
- 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 }));
5531
- var org = program.command("org").description("g\xE9rer les organisations");
5532
- org.command("list").description("lister vos organisations").action(orgListAction);
5533
- 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);
5534
6299
  await program.parseAsync();