diff --git a/applets/weblite/package.json b/applets/weblite/package.json index 034ed3e12..c632833cb 100644 --- a/applets/weblite/package.json +++ b/applets/weblite/package.json @@ -18,7 +18,8 @@ "dependencies": { "@jumpserver/web-proxy": "workspace:*", "@nuxt/ui": "4.11.0", - "vue": "^3.5.35" + "vue": "^3.5.35", + "vue-i18n": "^11.4.8" }, "devDependencies": { "@electron/packager": "18.3.5", diff --git a/applets/weblite/src/launch.ts b/applets/weblite/src/launch.ts index e7d75a485..3ed05cfc7 100644 --- a/applets/weblite/src/launch.ts +++ b/applets/weblite/src/launch.ts @@ -5,6 +5,7 @@ import { webProxyNavigationPolicy } from "@jumpserver/web-proxy/script"; export function standaloneLaunch() { return { + language: undefined as string | undefined, targetUrl: "https://www.jumpserver.org/", safeMode: false, allowedUrls: [], @@ -29,6 +30,7 @@ export function parseLaunch(value: unknown) { if (typeof data.safe_mode !== "boolean") throw new Error("缺少安全模式配置"); const localSession = createLocalCredentialSession(target.toString(), data.login); return { + language: typeof data.language === "string" ? data.language : undefined, targetUrl: target.toString(), safeMode: data.safe_mode, allowedUrls, @@ -67,6 +69,7 @@ function appletLaunchData(data: Record) { const anonymous = account.username === "@ANON"; const config = anonymous ? { autofill: "none" } : spec.autofill ? spec : setting; return { + language: object(data.connect_options ?? {}).lang, target_url: target.toString(), safe_mode: setting.safe_mode ?? false, allowed_urls: spec.allowed_urls, diff --git a/applets/weblite/src/main.ts b/applets/weblite/src/main.ts index 8c34fbca8..aeafde286 100644 --- a/applets/weblite/src/main.ts +++ b/applets/weblite/src/main.ts @@ -76,6 +76,7 @@ async function start() { if (closing || event.senderFrame !== win.webContents.mainFrame) throw new Error("无效的 Web applet 调用"); if (command === "bootstrap") return { + language: launch.language?.trim() || app.getLocale(), targetUrl: launch.targetUrl, proxyUrl: "", safeMode: launch.safeMode, diff --git a/applets/weblite/src/renderer/App.vue b/applets/weblite/src/renderer/App.vue index b57f07d15..9315ff23a 100644 --- a/applets/weblite/src/renderer/App.vue +++ b/applets/weblite/src/renderer/App.vue @@ -1,24 +1,22 @@ diff --git a/applets/weblite/src/renderer/main.ts b/applets/weblite/src/renderer/main.ts index 4f0bb9934..b428ff143 100644 --- a/applets/weblite/src/renderer/main.ts +++ b/applets/weblite/src/renderer/main.ts @@ -1,5 +1,18 @@ import { createApp } from "vue"; +import { createI18n } from "vue-i18n"; +import { normalizeLanguageCode } from "../../../../i18n/language"; import ui from "@nuxt/ui/vue-plugin"; import App from "./App.vue"; import "./style.css"; -createApp(App).use(ui).mount("#app"); +async function start() { + // Resolve the launch language before mounting so no system-language frame flashes + // when a client launches WebLite with a different language. + const request = await (window as any).webApplet.invoke("bootstrap"); + const i18n = createI18n({ + legacy: false, + locale: normalizeLanguageCode(request.language || navigator.language), + fallbackLocale: "en" + }); + createApp(App, { request }).use(i18n).use(ui).mount("#app"); +} +void start(); diff --git a/applets/weblite/tests/launch.test.ts b/applets/weblite/tests/launch.test.ts index f35b8d9f8..5c15795db 100644 --- a/applets/weblite/tests/launch.test.ts +++ b/applets/weblite/tests/launch.test.ts @@ -161,7 +161,7 @@ test("applet retains an optional asset navigation allowlist and rejects malforme "https://sso.example.com" ]); for (const allowed_urls of [null, "*", ["*"], ["file:///tmp"], ["https://example.com/path"]]) - assert.throws(() => parseLaunch({ ...launch, allowed_urls }), /白名单/); + assert.throws(() => parseLaunch({ ...launch, allowed_urls }), /WebProxy\.(AllowedSitesLimit|InvalidAllowedSite)/); }); test("empty or terminal stdin starts an unrestricted standalone browser without credentials", async () => { for (const input of [Readable.from([]), Object.assign(Readable.from([]), { isTTY: true })]) { @@ -260,3 +260,15 @@ test("rejects invalid configuration and oversized pipes", async () => { assert.throws(() => parseLaunch({ ...launch, ...patch })); await assert.rejects(readLaunch(Readable.from([" ".repeat(1_048_577)])), /过长/); }); + +test("launch preserves the client language and leaves standalone language to the OS", async () => { + for (const language of ["fr-CA", "en", "zh-hant", "pt_BR"]) { + assert.equal(parseLaunch({ ...applet, connect_options: { lang: language } }).language, language); + assert.equal(parseLaunch({ ...launch, language }).language, language); + } + for (const lang of [null, undefined, 42, {}]) { + assert.equal(parseLaunch({ ...applet, connect_options: { lang } }).language, undefined); + } + assert.equal(parseLaunch(applet).language, undefined); + assert.equal((await readLaunch(Readable.from([]))).language, undefined); +}); diff --git a/applets/weblite/tests/main.test.ts b/applets/weblite/tests/main.test.ts index d591b2a8c..70eaa0f47 100644 --- a/applets/weblite/tests/main.test.ts +++ b/applets/weblite/tests/main.test.ts @@ -80,3 +80,65 @@ for (const platform of ["darwin", "win32"]) { assert.ok(!dialogs[0].includes("must-not-appear-in-dialog")); }); } + +for (const [language, system, expected] of [ + ["fr-CA", "zh-CN", "fr-CA"], + ["en", "fr-FR", "en"], + [undefined, "fr-FR", "fr-FR"], + [" ", "ja-JP", "ja-JP"] +]) { + test(`bootstrap resolves launch language ${language} before system ${system}`, async (t) => { + const profiles = new Set(); + t.after(async () => { + for (const profile of profiles) await rm(profile, { recursive: true, force: true }); + }); + let invoke; + let shown; + let fail; + const ready = new Promise((resolve, reject) => { + shown = resolve; + fail = reject; + }); + const contents = { mainFrame: {}, on() {}, setWindowOpenHandler() {} }; + const electron = { + app: { + setPath: (_name, value) => profiles.add(value), + setName() {}, + whenReady: async () => {}, + getLocale: () => system, + exit: () => fail(new Error("Startup failed")) + }, + Menu: { setApplicationMenu() {} }, + ipcMain: { + handle: (_name, handler) => { + invoke = handler; + } + }, + dialog: { showErrorBox() {} }, + BrowserWindow: class { + webContents = contents; + on() {} + async loadFile() {} + maximize() {} + show() { + shown(); + } + } + }; + runInNewContext(outputText, { + exports: {}, + __dirname: "/tmp/weblite", + process: { platform: "darwin", stdin: {} }, + require(id: string) { + if (id === "electron") return electron; + if (id === "./launch") return { readLaunch: async () => ({ language, standalone: !language }) }; + if (id === "@jumpserver/web-proxy/manager") return { createWebProxyManager: () => ({}) }; + return require(id); + } + }); + await ready; + const bootstrap = await invoke({ senderFrame: contents.mainFrame }, "bootstrap"); + assert.equal(bootstrap.language, expected); + assert.equal(bootstrap.standalone, !language); + }); +} diff --git a/electron/tests/web-proxy.test.ts b/electron/tests/web-proxy.test.ts index 675d1b9a2..292fc6a37 100644 --- a/electron/tests/web-proxy.test.ts +++ b/electron/tests/web-proxy.test.ts @@ -149,7 +149,10 @@ test("private certificate trust is scoped to configured HTTPS hosts and authorit for (const hostname of ["other.test", "plain.test", "sub.example.test", "example.test.evil.test"]) assert.equal(verify(hostname), -3, hostname); assert.equal(verify("manual.test"), -3); - await assert.rejects(invoke("navigate_web_proxy_view", { targetUrl: "https://manual.test" }), /白名单/); + await assert.rejects( + invoke("navigate_web_proxy_view", { targetUrl: "https://manual.test" }), + /WebProxy.AddressNotAllowed/ + ); assert.equal(verify("manual.test"), -3); } finally { clearInterval(managed.proxyHeartbeatTimer); @@ -175,11 +178,11 @@ test("proxy connection failures identify the selected proxy during login and lat contents.emit("did-fail-load", {}, -111, description, "https://example.test/login", true); assert.equal( finishAutofill.mock.calls.at(-1)!.arguments[2], - `登录页面加载失败:${description}(代理:http://localhost:5001)` + `WebProxy.LoginPageLoadFailed: ${description} (proxy: http://localhost:5001)` ); managed.autofillPending = false; contents.emit("did-fail-load", {}, -111, description, "https://example.test/login", true); - assert.equal(state.mock.calls.at(-1)!.arguments[1].error, `${description}(代理:http://localhost:5001)`); + assert.equal(state.mock.calls.at(-1)!.arguments[1].error, `${description} (proxy: http://localhost:5001)`); } contents.emit("did-fail-load", {}, -105, "ERR_NAME_NOT_RESOLVED", "https://example.test/login", true); assert.equal(state.mock.calls.at(-1)!.arguments[1].error, "ERR_NAME_NOT_RESOLVED"); @@ -258,7 +261,7 @@ test("configured asset allowlist blocks links, redirects and popups while preser const popup = contents.setWindowOpenHandler.mock.calls[0].arguments[0]; popup({ url: "https://outside.test" }); assert.equal(contents.loadURL.mock.callCount(), 1); - assert.match(state.mock.calls.at(-1).arguments[1].navigationError, /白名单/); + assert.match(state.mock.calls.at(-1).arguments[1].navigationError, /WebProxy.AddressNotAllowed/); popup({ url: "https://sso.test/login" }); assert.equal(contents.loadURL.mock.callCount(), 2); }); @@ -276,14 +279,17 @@ test("login script open commands respect the same asset navigation allowlist", a frame() {} } ); - await assert.rejects(runner.run(), /白名单/); + await assert.rejects(runner.run(), /WebProxy.AddressNotAllowed/); assert.equal(contents.loadURL.mock.callCount(), 0); }); test("safe mode blocks manual navigation and popups while allowing cross-origin links and redirects", async () => { const { contents, preferences, invoke } = await setupNavigation(true); assert.equal(preferences.devTools, false); - await assert.rejects(invoke("navigate_web_proxy_view", { targetUrl: "https://example.test/dashboard" }), /手动输入/); + await assert.rejects( + invoke("navigate_web_proxy_view", { targetUrl: "https://example.test/dashboard" }), + /WebProxy.ManualNavigationUnsupported/ + ); for (const url of ["https://example.test/dashboard", "https://other.test/"]) { assert.deepEqual(contents.setWindowOpenHandler.mock.calls[0].arguments[0]({ url }), { action: "deny" }); } @@ -343,7 +349,7 @@ test("manual navigation remains disabled outside safe mode while page navigation const { contents, preferences, invoke } = await setupNavigation(safeMode); assert.equal(preferences.devTools, true); for (const targetUrl of ["https://example.test/dashboard", "https://other.test/"]) { - await assert.rejects(invoke("navigate_web_proxy_view", { targetUrl }), /手动输入/); + await assert.rejects(invoke("navigate_web_proxy_view", { targetUrl }), /WebProxy.ManualNavigationUnsupported/); } contents.setWindowOpenHandler.mock.calls[0].arguments[0]({ url: "https://other.test/popup" }); assert.equal(contents.loadURL.mock.callCount(), 2); @@ -365,7 +371,10 @@ test("standalone browser navigation accepts only HTTP pages without unlocking ma await assert.rejects(invoke("navigate_web_proxy_view", { targetUrl })); const restricted = await setupNavigation(false, ["https://sso.test"], true); - await assert.rejects(restricted.invoke("navigate_web_proxy_view", { targetUrl: "https://outside.test/" }), /白名单/); + await assert.rejects( + restricted.invoke("navigate_web_proxy_view", { targetUrl: "https://outside.test/" }), + /WebProxy.AddressNotAllowed/ + ); }); test("script supports explicit SSO origins without an asset allowlist and validates terminal success", () => { @@ -508,9 +517,9 @@ test("required script conditions still time out and cancelling optional verifica { active: () => true, state: () => {}, interaction: () => {}, frame: () => {} } ); for (const command of ["code", "check", "success"]) { - await assert.rejects(runnerFor(command).run(), new RegExp(`(${command})超时`)); + await assert.rejects(runnerFor(command).run(), new RegExp(`WebProxy.ScriptStepTimeout: .*${command}`)); } - await assert.rejects(runnerFor("interactive", false).run(), /(interactive)超时/); + await assert.rejects(runnerFor("interactive", false).run(), /WebProxy.ScriptStepTimeout: .*interactive/); const cancelled = runnerFor("interactive"); const result = cancelled.run(); const reason = new Error("connection closed"); @@ -537,7 +546,7 @@ test("optional verification still requires completion once its target has appear }, { active: () => true, state: () => {}, interaction, frame: () => {} } ); - await assert.rejects(runner.run(), /人工验证超时/); + await assert.rejects(runner.run(), /WebProxy.ScriptVerificationTimeout/); assert.ok(interaction.mock.calls[0].arguments[0] instanceof WebProxyInteraction); assert.deepEqual(interaction.mock.calls.at(-1).arguments, [null, false]); }); @@ -690,10 +699,10 @@ test("ends a stalled login even before form detection starts and retains the tim assert.equal(webContents.stop.mock.callCount(), 1); api.emitWebProxyState(managed, { loading: true }); assert.equal(events.at(-1).state.loading, false); - assert.match(events.at(-1).state.error, /60 秒/); + assert.match(events.at(-1).state.error, /WebProxy.SecureLoginTimeout/); assert.equal(managed.view.setVisible.mock.calls.at(-1).arguments[0], false); api.finishWebProxyAutofill(managed, "success", "late success"); - assert.match(managed.autofillFailure, /60 秒/); + assert.match(managed.autofillFailure, /WebProxy.SecureLoginTimeout/); }); test("waits for the safe preview before releasing credentials, then hides the live page", async () => { @@ -981,7 +990,7 @@ test("interactive verification replaces both automatic login deadlines without e managed.verificationReady(); t.mock.timers.tick(120_000); assert.equal(managed.autofillPending, false); - assert.match(managed.autofillFailure, /3 分钟/); + assert.match(managed.autofillFailure, /WebProxy.ManualVerificationTimeout/); assert.equal(interaction.dispose.mock.callCount(), 1); assert.equal(managed.autofillVisibilityBlocked, true); }); @@ -1112,7 +1121,7 @@ for (const success of ["", "id=success"]) { cleared.resolve(true); assert.equal(await completed, false); assert.equal(managed.autofillVisibilityBlocked, true); - assert.match(managed.autofillFailure, /3 分钟/); + assert.match(managed.autofillFailure, /WebProxy.ManualVerificationTimeout/); }); } diff --git a/i18n/language.ts b/i18n/language.ts new file mode 100644 index 000000000..ef30ad154 --- /dev/null +++ b/i18n/language.ts @@ -0,0 +1,40 @@ +export type LangType = "zh" | "zh_hant" | "en" | "fr" | "ja" | "pt_br" | "es" | "ru" | "ko" | "vi"; + +const INTL_LOCALE_BY_LANGUAGE: Record = { + zh: "zh-CN", + zh_hant: "zh-TW", + en: "en", + fr: "fr", + ja: "ja", + pt_br: "pt-BR", + es: "es", + ru: "ru", + ko: "ko", + vi: "vi" +}; + +/** + * @description 获取操作系统的语言 + */ +export function normalizeLanguageCode(lang: string | null | undefined): LangType { + const normalized = (lang || "").trim().toLowerCase().replaceAll("_", "-"); + if (!normalized) return "en"; + + if (normalized.startsWith("zh")) { + return /(?:^|-)hant(?:$|-)|^zh-(?:tw|hk|mo)(?:-|$)/.test(normalized) ? "zh_hant" : "zh"; + } + + const primary = normalized.split("-")[0] || ""; + if (primary === "pt") return "pt_br"; + if (["en", "fr", "ja", "es", "ru", "ko", "vi"].includes(primary)) return primary as LangType; + return "en"; +} + +export function toDjangoLanguageCode(lang: LangType) { + if (lang === "zh") return "zh-hans"; + return lang.replaceAll("_", "-"); +} + +export function toIntlLocale(lang: string | null | undefined) { + return INTL_LOCALE_BY_LANGUAGE[normalizeLanguageCode(lang)]; +} diff --git a/i18n/locales/fr.json b/i18n/locales/fr.json new file mode 100644 index 000000000..abf04de57 --- /dev/null +++ b/i18n/locales/fr.json @@ -0,0 +1,2568 @@ +{ + "Monitor": { + "Unavailable": "Cette session est terminée ou ne peut pas être surveillée.", + "PauseSent": "La tâche de suspension a été envoyée", + "ResumeSent": "La tâche de reprise a été envoyée" + }, + "Replay": { + "Title": "Relecture de session", + "User": "Utilisateur", + "Asset": "Actif", + "Account": "Compte", + "StartTime": "Heure de début", + "Download": "Télécharger", + "Play": "Lire", + "Pause": "Pause", + "Restart": "Redémarrer", + "HistoryCommands": "Historique des commandes", + "Playlist": "Liste de lecture", + "SearchCommands": "Rechercher des commandes…", + "TextIndex": "Index textuel", + "SearchIndex": "Rechercher dans le texte reconnu…", + "OCRTextEvidence": "Texte à l’écran (OCR)", + "OCREvidenceHint": "Observations OCR des images vidéo, et non actions utilisateur vérifiées.", + "IndexBaseline": "Écran initial (OCR)", + "IndexNoText": "Aucun texte disponible", + "IndexEmpty": "Aucun événement indexé", + "IndexEmptyHint": "Cet enregistrement ne contient aucun événement textuel OCR.", + "IndexNoMatch": "Aucun événement correspondant", + "IndexNoMatchHint": "Essayez un autre mot ou une autre expression.", + "IndexFailed": "Impossible de charger l’index textuel", + "IndexPartUnavailable": "Le segment d’enregistrement de cet événement n’est pas encore disponible.", + "NoCommands": "Aucun enregistrement de commande", + "NoCommandsHint": "Cette session ne contient aucune commande enregistrée", + "CommandsFailed": "Impossible de charger les commandes", + "CommandsFailedHint": "L’historique des commandes est temporairement indisponible", + "Loading": "Chargement…", + "HideRail": "Masquer le panneau", + "ShowRail": "Afficher le panneau", + "Seeking": "Recherche de position…", + "WaitingForSeekable": "En attente de la navigation dans l’enregistrement…", + "SeekUnavailable": "Cette position n’est pas encore accessible. Réessayez après le chargement de l’enregistrement.", + "Preparing": "Préparation de l’enregistrement", + "PreparingHint": "L’enregistrement est en cours de traitement. Veuillez patienter.", + "NotFound": "Enregistrement introuvable", + "NotFoundHint": "L’enregistrement de cette session est manquant ou a été supprimé.", + "PlayError": "Échec de la lecture", + "PlayErrorHint": "Impossible d’ouvrir l’enregistrement. Rechargez la page ou contactez un administrateur.", + "UnsupportedHint": "Ce type d’enregistrement n’est pas pris en charge par le lecteur en ligne.", + "Reload": "Recharger", + "DesktopBlocked": "Navigateur uniquement", + "DesktopBlockedHint": "La relecture de session est disponible uniquement dans un navigateur web. Copiez le lien et ouvrez-le dans un navigateur.", + "ShortcutsTitle": "Raccourcis clavier", + "ShortcutPlayPause": "suspendre / reprendre", + "ShortcutFullscreen": "basculer en mode plein écran", + "ShortcutKeystrokeOverlay": "afficher ou masquer les frappes clavier", + "ShortcutHelp": "afficher cette fenêtre d’aide" + }, + "VideoPlayer": { + "PageTitle": "Lecteur vidéo", + "Playlist": "Liste de lecture", + "AddRecording": "Ajouter un enregistrement", + "CollapsePlaylist": "Réduire la liste de lecture", + "ExpandPlaylist": "Développer la liste de lecture", + "Playing": "Lecture en cours", + "LocalFile": "Fichier local", + "Session": "Session", + "Part": "Partie", + "PartIndex": "Partie {index} / {total}", + "PartCount": "{count} parties", + "DropToImport": "Déposez les fichiers pour importer des enregistrements", + "ImportTitle": "Importer des enregistrements", + "ImportHint": "Déposez les fichiers ici ou cliquez pour choisir des fichiers .mp4, .gz ou .tar.", + "ChooseFiles": "Choisir des fichiers", + "Importing": "Importation de {count} fichiers…", + "ImportFailed": "Échec de l'import", + "SelectFailed": "Impossible de sélectionner les fichiers", + "UnrecognizedTitle": "Aucun fichier lisible trouvé", + "UnrecognizedHint": "Importez des fichiers mp4, cast.gz, replay.gz, part.gz ou une archive tar qui en contient.", + "SkippedTitle": "Certains fichiers ont été ignorés", + "SkippedHint": "{count} éléments portant le même nom existent déjà et n’ont pas été réimportés.", + "FileFilter": "Enregistrements", + "Parsing": "Analyse de l’enregistrement…", + "ReadFailed": "Impossible de lire l’enregistrement : HTTP {status}" + }, + "VideoPlayerTour": { + "ClickTitle": "Cliquez pour importer", + "ClickDescription": "Cliquez dans la zone centrale pour choisir des fichiers d’enregistrement.", + "DropTitle": "Déposez les fichiers ici", + "DropDescription": "Vous pouvez aussi faire glisser des fichiers au centre. Même si la liste de lecture contient déjà des enregistrements, vous pouvez en ajouter ici.", + "AddTitle": "Ajouter depuis la liste de lecture", + "AddDescription": "Cliquez sur + à droite pour ajouter des enregistrements.", + "PartsTitle": "Segments de session", + "PartsDescription": "Les parties d’une même session sont regroupées. Cliquez sur un segment pour reprendre à cet endroit.", + "MetaTitle": "Détails de la session", + "MetaDescription": "Survolez un enregistrement pour consulter l’utilisateur, l’actif, le compte, le protocole et l’heure issus des métadonnées JSON.", + "progress": "Étape {current} sur {total}", + "next": "Suivant", + "previous": "Précédent", + "done": "Terminé" + }, + "Common": { + "AI": "IA", + "Cancel": "Annuler", + "About": "À propos", + "Confirm": "Confirmer", + "Copy": "Copier", + "CopySuccess": "Copié", + "CopyFailed": "Échec de la copie", + "CopyOnly": "Copier uniquement", + "UnSigned": "Non connecté", + "Version": "Version", + "Workspace": "Espace de travail", + "NoData": "Aucune donnée", + "LoginSite": "Site de connexion", + "LoginFirst": "Veuillez d’abord vous connecter", + "ConnectionSettings": "Paramètres de connexion", + "All": "Tous", + "Active": "Actif", + "Inactive": "Inactif", + "Language": "Langue", + "More": "Plus", + "Login": "Connexion", + "Connect": "Connecter", + "Theme": "Thème", + "SwitchTheme": "Changer de thème", + "System": "Système", + "FollowAppTheme": "Suivre le thème de l’application", + "TerminalColorScheme": "Apparence du terminal", + "CodeMirrorColorScheme": "Apparence de l’éditeur", + "Light": "Clair", + "Dark": "Sombre", + "LightThemes": "Thèmes clairs", + "DarkThemes": "Thèmes sombres", + "Fonts": "Polices", + "WithSystem": "Suivre le système", + "Account": "Compte", + "User": "Utilisateur", + "Appearance": "Apparence", + "CustomizeAppearance": "Personnaliser…", + "AppMenu": "Menu de l’application", + "Preferences": "Préférences", + "Settings": "Paramètres", + "PrimaryColor": "Couleur principale", + "LightThemePalette": "Palette claire", + "DarkThemePalette": "Palette sombre", + "SystemDefault": "Valeur par défaut du système", + "General": "Général", + "Advanced": "Avancé", + "ChooseColor": "Choisir une couleur", + "OpenWith": "Paramètres de l’application", + "Refresh": "Actualiser", + "Save": "Enregistrer", + "Delete": "Supprimer", + "Clear": "Effacer", + "OperationFailed": "Échec de l’opération", + "EmptyDescription": "Aucun actif autorisé n’est disponible pour le type actuel", + "SelectFromMenu": "Sélectionnez un élément à gauche", + "Actions": "Actions", + "Close": "Fermer", + "Remove": "Retirer", + "SelectAll": "Tout sélectionner", + "SelectRow": "Sélectionner la ligne", + "Untitled": "Sans titre", + "ClearInput": "Effacer la saisie", + "Unsaved": "Non enregistré" + }, + "DesktopMenu": { + "File": "Fichier", + "Edit": "Modifier", + "View": "Voir", + "Window": "Fenêtre", + "Help": "Aide", + "Undo": "Annuler", + "Redo": "Rétablir", + "Cut": "Couper", + "Copy": "Copier", + "Paste": "Coller", + "SelectAll": "Tout sélectionner", + "LeftPanel": "Panneau gauche", + "RightPanel": "Panneau droit", + "StatusBar": "Barre d’état inférieure", + "Fullscreen": "Plein écran", + "Restore": "Restaurer", + "About": "À propos" + }, + "StatusFooter": { + "LoggedOut": "Déconnecté", + "LoggedIn": "Connecté", + "LoggedInUser": "Connecté en tant que {user}", + "LoggedInUserSite": "Connecté en tant que {user} ({site})", + "Disconnected": "Non connecté", + "Tabs": "{count} onglets", + "Connected": "{count} connectés", + "Pending": "{count} en attente", + "Failed": "{count} en échec", + "Transfer": "Centre de transfert", + "TransferCount": "Centre de transfert {count}" + }, + "WorkspaceEmpty": { + "LoginHint": "Connectez-vous pour consulter les actifs et ouvrir une connexion", + "ResetConnectionConfig": "Réinitialiser la connexion", + "ReselectConnectionHint": "Clic droit sur un actif → Connexion", + "SwitchSession": "Changer de session", + "Or": "ou" + }, + "WorkspaceTour": { + "ConnectAssetTitle": "Cliquer sur un actif → Connexion", + "ConnectAssetDescription": "Si une connexion est enregistrée, faites un clic droit pour choisir à nouveau la méthode", + "ExpandNodeTitle": "Clic droit sur un nœud → Tout développer", + "ExpandNodeDescription": "Développer tous les actifs du nœud", + "AddSessionTitle": "Cliquez sur + dans l’en-tête pour créer une session", + "AddSessionDescription": "Rechercher un actif et ouvrir une nouvelle connexion", + "SwitchOrganizationTitle": "Changer d’organisation", + "SwitchOrganizationDescription": "Chaque organisation possède ses propres actifs et permissions", + "FavoriteTitle": "Favoris", + "FavoriteDescription": "Faites un clic droit sur un actif pour l’ajouter aux favoris.", + "FavoriteGroupHint": "Créez d’abord un dossier pour les regrouper.", + "DemoNode": "Exemple de nœud", + "DemoAsset": "Exemple d’actif", + "progress": "Étape {current} sur {total}", + "next": "Suivant", + "previous": "Précédent", + "done": "Terminé" + }, + "AddSession": { + "NewConnection": "Nouvelle connexion", + "SearchPlaceholder": "Rechercher par nom ou adresse d’actif…", + "Local": "Local", + "LocalShell": "Shell local", + "LocalShellHint": "Ouvrir un terminal local", + "SearchResults": "Résultats de recherche", + "NoMatchingAssets": "Aucun actif correspondant" + }, + "Auth": { + "SignInTitle": "Connectez-vous depuis votre navigateur pour continuer", + "BrowserHintPrefix": "Si votre navigateur ne s’est pas ouvert pour la connexion,", + "OpenManually": "ouvrez-le manuellement", + "CopyUrl": "copiez l’URL", + "Or": "ou", + "Back": "Retour", + "CopySuccess": "Copie réussie" + }, + "ToolTips": { + "Back": "Retour", + "LightMode": "Mode clair", + "DarkMode": "Mode sombre", + "SwitchLanguage": "Langue", + "Sort": "Trier", + "Refresh": "Actualiser", + "Layout": "Changer la disposition", + "Settings": "Paramètres de connexion", + "Minimize": "Réduire", + "Maximize": "Agrandir", + "Close": "Fermer" + }, + "Menu": { + "AuthorizedTree": "Actifs", + "TypeTree": "Arborescence par type", + "Linux": "Linux", + "Other": "Autre", + "Device": "Appareil", + "Tool": "Outils", + "MyTools": "Mes outils", + "Player": "Lecteur", + "Face": "Centre de reconnaissance faciale", + "Windows": "Windows", + "DirectoryService": "Service d’annuaire", + "Resource": "Ressource", + "Database": "Base de données", + "Web": "Web", + "Transcode": "Transcodage des enregistrements", + "OfflinePlayer": "Lecteur hors ligne", + "Favorite": "Favoris", + "Snippets": "Scripts", + "RecentConnections": "Connexions récentes", + "MyAssets": "Actifs", + "FileManager": "Gestionnaire de fichiers" + }, + "Snippets": { + "Empty": "Aucun script", + "Create": "Ajouter un script", + "Untitled": "Script sans titre", + "Edit": "Modifier le script", + "Name": "Nom du script", + "Content": "Contenu du script", + "Comment": "Commentaire (facultatif)", + "Save": "Enregistrer", + "More": "Plus", + "SaveFailed": "Impossible d’enregistrer le script", + "FillBatchCommand": "Insérer dans la commande par lot", + "GroupShell": "Shell", + "GroupPowerShell": "PowerShell", + "GroupPython": "Python", + "GroupRaw": "Brut", + "GroupDatabase": "Base de données", + "GroupOther": "Autre", + "VariableFormTitle": "Saisir les variables du script", + "VariableFormLoadFailed": "Impossible de charger les variables du script", + "VariableFormEmpty": "Aucune configuration des variables du script n’a été renvoyée. Vérifiez les paramètres des variables.", + "VariableSelectPlaceholder": "Sélectionnez une option", + "GeneratedCommand": "Commande générée", + "GeneratedCommandHint": "Vous pouvez la modifier avant de l’insérer dans la commande par lot.", + "BatchCommandUnsupported": "Type de script non pris en charge", + "BatchCommandUnsupportedHint": "Les scripts {module} ne sont pas pris en charge par les commandes de terminal par lot. Utilisez l’espace de travail correspondant ou le centre des travaux.", + "DiscardTitle": "Abandonner les modifications ?", + "Discard": "Abandonner", + "DiscardChanges": "Ce script contient des modifications non enregistrées. Les abandonner ?", + "ScopePrivate": "Privé", + "ScopePublic": "Public", + "VariablesCount": "Variables {count}", + "VariableEditorTitle": "Variables du script", + "VariableEditorHint": "Référencez les variables avec les espaces réservés jms_variable. Les valeurs par défaut ne sont pas envoyées à l’IA.", + "VariableAdd": "Ajouter une variable", + "VariableEmpty": "Aucune variable de script", + "VariableName": "Nom d'affichage", + "VariableKey": "Nom de la variable", + "VariableType": "Type", + "VariableTypeText": "Texte", + "VariableTypeSelect": "Sélectionner", + "VariableRequired": "Requis", + "VariableDefault": "Valeur par défaut", + "VariableOptions": "Options", + "VariableOptionsHint": "Une par ligne, au format libellé:valeur", + "VariableTips": "Texte d'aide", + "VariableInvalid": "Les variables du script sont incomplètes", + "VariableInvalidHint": "Vérifiez les noms d’affichage, les noms de variables, les doublons et les options de sélection." + }, + "RecentConnections": { + "Empty": "Aucune connexion récente", + "Clear": "Effacer les connexions récentes" + }, + "Favorite": { + "Ungrouped": "Sans groupe", + "All": "Tous les favoris", + "CreateFolder": "Créer un dossier", + "CreateSubfolder": "Créer un sous-dossier", + "DefaultFolderName": "Nouveau dossier", + "FolderName": "Nom du dossier", + "DuplicateName": "Un dossier portant ce nom existe déjà", + "NameTooLong": "Le nom du dossier ne peut pas dépasser {max} caractères", + "CreateFailed": "Impossible de créer le dossier", + "RenameFailed": "Impossible de renommer le dossier", + "AddToFolder": "Ajouter aux favoris", + "AddSuccess": "Ajouté aux favoris", + "AddFailed": "Impossible d’ajouter aux favoris", + "DeleteFolder": "Supprimer le dossier", + "DeleteFailed": "Impossible de supprimer le dossier", + "DeleteFolderConfirm": "Supprimer « {name} » ?" + }, + "Tree": { + "SwitchToType": "Afficher l’arborescence par type", + "SwitchToAuthorization": "Afficher mes actifs", + "Expand": "Développer", + "ExpandAll": "Tout développer", + "Collapse": "Réduire", + "CollapseAll": "Tout réduire", + "LoadMore": "Charger plus", + "AuthorizationAssetCount": "Actifs autorisés, y compris les nœuds descendants", + "UngroupedAssetCount": "Actifs autorisés sans nœud", + "BatchActions": "Actions par lot", + "OpenMultiple": "Ouvrir plusieurs éléments", + "OpenSelected": "Ouvrir la sélection", + "FavoriteMultiple": "Ajouter plusieurs éléments aux favoris", + "FavoriteSelected": "Ajouter la sélection aux favoris", + "SelectedCount": "{count} sélectionnés", + "MultiOpenNoConnectable": "Les actifs sélectionnés ne prennent pas en charge les connexions SSH", + "MultiOpenSkipped": "{count} actifs ignorés : SSH non pris en charge ou échec du chargement", + "MultiFavoriteSuccess": "{count} actifs ajoutés aux favoris", + "MultiFavoritePartial": "{success} actifs ajoutés aux favoris ; {failed} échecs", + "MultiFavoriteFailed": "Impossible d’ajouter les actifs sélectionnés aux favoris" + }, + "Operation": { + "Search": "Rechercher" + }, + "Sidebar": { + "ManageSections": "Gérer les sections", + "ShowSection": "Afficher {section}", + "AtLeastOneSection": "Gardez au moins une section visible", + "Collapse": "Réduire la barre latérale", + "Expand": "Développer la barre latérale", + "SearchAssetsHint": "Saisissez un nom, une adresse ou un mot-clé pour rechercher des actifs" + }, + "RightPanel": { + "AIClearLocalHistory": "Effacer l’historique local et le brouillon", + "FileAIReconnectDescription": "La connexion aux fichiers n’est pas prête. Connectez ou reconnectez l’actif pour démarrer une nouvelle conversation. Les messages existants peuvent être consultés ou effacés.", + "Open": "Ouvrir le panneau droit", + "Close": "Fermer le panneau droit", + "AIDisabled": "L’assistant IA est désactivé dans les paramètres du système", + "AIOpen": "Ouvrir l’assistant IA", + "AIClose": "Fermer l’assistant IA", + "AIMovePanel": "Déplacer le panneau IA : faites glisser la barre de titre ou utilisez les flèches ; double-cliquez ou appuyez sur Début pour réinitialiser", + "AIResizePanel": "Redimensionner le panneau IA : faites glisser ou utilisez les flèches", + "Session": "Session", + "Control": "Contrôle", + "Files": "Fichiers", + "AI": "IA", + "LunaAiTool_list_terminal_targets": "Rechercher les terminaux connectés", + "LunaAiTool_start_terminal_task": "Démarrer une tâche de terminal", + "LunaAiTool_get_terminal_task": "Vérifier la progression de la tâche", + "LunaAiTool_read_local_shell": "Lire le shell local", + "LunaAiTool_run_local_shell_command": "Exécuter une commande dans le shell local", + "LunaAiName": "IA JumpServer", + "LunaAiAutomatic": "Automatique", + "LunaAiWorkspaceOnly": "Espace de travail uniquement", + "LunaAiTarget": "Cible de la tâche", + "LunaAiTask": "Tâche de terminal", + "LunaAiDescription": "Rechercher des actifs, connecter des sessions et réaliser des tâches de terminal.", + "LunaAiCapabilitySearchAssets": "Rechercher des actifs", + "LunaAiCapabilityConnectSessions": "Connecter des sessions", + "LunaAiCapabilityTerminalTasks": "Tâches de terminal", + "LunaAiCapabilityLocalShell": "Shell local", + "LunaAiCapabilityEditScripts": "Modifier des scripts", + "LunaAiCapabilityWriteSql": "Écrire du SQL", + "LunaAiCapabilityManageFiles": "Gérer les fichiers", + "LunaAiPlaceholder": "Recherchez un actif ou décrivez une tâche…", + "LunaAiEmptyTitle": "Que souhaitez-vous faire ?", + "LunaAiWorkspaceEmpty": "Rechercher des actifs autorisés, connecter un hôte ou gérer les sessions ouvertes.", + "LunaAiTerminalEmpty": "Utiliser le terminal actuel ou rechercher et connecter un autre actif.", + "LunaAiSuggestSearch": "Rechercher un actif", + "LunaAiSuggestSessions": "Afficher les sessions ouvertes", + "LunaAiSuggestDisk": "Vérifier l’utilisation actuelle du disque", + "LunaAiSuggestError": "Analyser l’erreur du terminal actuel", + "LunaAiTargetChanged": "Le terminal sélectionné a été fermé ou reconnecté. Sélectionnez une cible actuelle.", + "LunaAiResourceHistory": "Conversation actuelle sur les ressources", + "WorkspaceAssistantName": "Assistant de l’espace de travail Luna", + "AINewSession": "Nouvelle discussion", + "AINewSessionDescription": "Effacer la conversation et le brouillon actuels, puis démarrer une nouvelle conversation", + "WorkspaceAssistantOpen": "Ouvrir l’assistant de l’espace de travail", + "WorkspaceAssistantBack": "Assistant de la ressource actuelle", + "WorkspaceAssistantDescription": "Rechercher des actifs autorisés et contrôler l’espace de travail Luna au moyen d’actions auditées.", + "WorkspaceAssistantEmptyTitle": "Décrivez l’actif auquel vous souhaitez vous connecter", + "WorkspaceAssistantEmptyDescription": "Inspecter l’espace de travail, gérer les sessions et les volets, connecter des actifs autorisés et organiser les favoris et les connexions récentes.", + "WorkspaceAssistantUnavailableTitle": "L’assistant de l’espace de travail est indisponible", + "WorkspaceAssistantUnavailableDescription": "Connectez-vous et sélectionnez une organisation avant d’utiliser les actions de l’espace de travail.", + "WorkspaceAssistantInputPlaceholder": "Décrivez ce que vous souhaitez faire dans l’espace de travail…", + "WorkspaceAssistantApprovalTitle": "Approuver cette action dans l’espace de travail ?", + "WorkspaceAssistantApprovalDescription": "Vérifiez l’action et les cibles. Les permissions, les ACL et les vérifications du contenu non enregistré restent applicables.", + "AIApprovalModeUpdateFailed": "Impossible de modifier le mode d’approbation", + "SFTP": "Fichiers", + "SFTPTooltip": "Fichiers de la session actuelle (mode léger)", + "Clipboard": "Presse-papiers", + "PasteClipboard": "Lire et envoyer le presse-papiers local", + "ClipboardPlaceholder": "Saisissez le texte à envoyer à la session distante", + "ClipboardLength": "Caractères", + "ClipboardLimitExceeded": "Le texte du presse-papiers ne peut pas dépasser {limit} caractères", + "ShowRemoteClipboard": "Afficher le presse-papiers distant", + "VirtualKeyboard": "Clavier virtuel", + "MoveVirtualKeyboard": "Déplacer le clavier virtuel", + "KeyboardLayout": "Disposition du clavier", + "ShortcutKeys": "Touches de raccourci", + "DisplayControl": "Afficher", + "AutoFit": "Ajustement automatique", + "Scale": "Échelle", + "ZoomIn": "Zoom avant", + "ZoomOut": "Zoom arrière", + "LionUnavailableTitle": "Les commandes distantes ne sont pas prêtes", + "LionUnavailableDescription": "Les commandes du presse-papiers, du clavier et de l’affichage apparaissent après la connexion de la session Lion.", + "LionFilesUnavailableTitle": "Lecteur de fichiers indisponible", + "LionFilesUnavailableDescription": "La session Lion actuelle ne fournit pas de lecteur de fichiers distant.", + "SessionEmptyTitle": "Aucune session active", + "SessionEmptyDescription": "Connectez-vous à un hôte SSH depuis l’arborescence des actifs pour afficher ici les informations de session et les paramètres de partage.", + "SessionUnsupportedTitle": "Session non prise en charge", + "SessionUnsupportedDescription": "Le panneau de session prend en charge les sessions de terminal SSH et SFTP.", + "SessionWaitingTitle": "Connexion à la session", + "SessionWaitingDescription": "Les détails de session et le partage apparaîtront une fois le terminal connecté.", + "SessionDisconnectedDescription": "La session est terminée. La surveillance et le partage seront rétablis après la reconnexion.", + "SessionAsset": "Actif", + "SessionAddress": "Adresse", + "SessionAccount": "Compte", + "SessionProtocol": "Protocole", + "SessionDuration": "Durée", + "SessionId": "ID de session", + "SessionStatusConnected": "Connecté", + "SessionStatusConnecting": "Connexion en cours", + "SessionStatusFailed": "Échec", + "SessionStatusDisconnected": "Déconnecté", + "SessionStatusIdle": "Inactif", + "BasicInfo": "Informations de base", + "MonitorInfo": "Surveillance", + "ShareInfo": "Partage", + "MetricsTitle": "État du système", + "MetricsMemory": "Mémoire", + "MetricsDisk": "Disque", + "MetricsNetwork": "Réseau", + "MetricsLoading": "Lecture des métriques système Linux…", + "MetricsUnavailable": "Les métriques système sont indisponibles pour cette connexion", + "MetricsCached": "En cache", + "MetricsUptimeDays": "{days} j {hours} h", + "MetricsUptimeHours": "{hours} h", + "OnlineUsers": "Utilisateurs en ligne", + "Share": "Partager", + "NoOnlineUsers": "Aucun utilisateur en ligne", + "ShareLink": "Lien de partage", + "ShareDisabled": "Le partage n’est pas activé pour cette session", + "ExpiredTime": "Expiration", + "ExpiredMinutes": "{count} min", + "ExpiredHour": "{count} h", + "ActionPerm": "Autorisation", + "Writable": "Modifiable", + "ReadOnly": "Lecture seule", + "ShareUser": "Utilisateurs du partage", + "GetShareUser": "Rechercher et sélectionner des utilisateurs", + "LoadMoreUsers": "Charger plus d’utilisateurs", + "CreateLink": "Créer un lien", + "VerifyCode": "Code de vérification", + "CopyLink": "Copier le lien", + "Back": "Retour", + "PrimaryUser": "Principal", + "OnlineUserSourceIp": "IP source", + "OnlineUserSourceIpHint": "Adresse du client depuis lequel cet utilisateur a rejoint la session, et non IP de l’actif", + "RemoveShareUser": "Retirer l’utilisateur", + "ShareSessionUnavailable": "La session Lion n’est pas prête", + "ShareSearchFailed": "Impossible de rechercher les utilisateurs du partage", + "ShareCreateFailed": "Impossible de créer le lien de partage", + "ShareRemoveFailed": "Impossible de retirer l’utilisateur du partage", + "ShareCopySuccess": "Lien de partage copié", + "ShareCopyFailed": "Impossible de copier le lien de partage", + "BatchCommand": "Lot", + "FileAIName": "IA des fichiers", + "FileAIHeaderDescription": "Utilise le répertoire distant actif et les fichiers sélectionnés", + "FileAIUnavailableTitle": "L’IA des fichiers est indisponible", + "FileAIUnavailableDescription": "Ouvrez un volet de fichiers distant connecté avec l’IA des fichiers activée.", + "FileAIEmptyTitle": "Prêt à vous aider avec les fichiers", + "FileAIEmptyDescription": "Posez une question sur ce répertoire, inspectez les fichiers sélectionnés ou décrivez une opération sur les fichiers.", + "FileAIInputPlaceholder": "Posez une question sur ces fichiers ou décrivez une opération…", + "FileAIInterrupt": "Interrompre la tâche sur les fichiers", + "FileAIFailed": "Échec de l’IA des fichiers", + "FileAISelection": "{count} sélectionnés", + "FileAIStageAnalyzing": "Analyse du répertoire actuel", + "FileAIStageReading": "Lecture des informations de fichiers autorisées", + "FileAIStageApproval": "En attente d’approbation de l’opération sur les fichiers", + "FileAIStageExecuting": "Exécution de l’opération approuvée sur les fichiers", + "FileAIStageReady": "Prêt", + "FileAIDirectoryAnalysis": "Analyse du répertoire", + "FileAIToolCount": "{count} outils", + "FileAIEntryCount": "{count} entrées", + "FileAIDirectoryLimit": "Jusqu’à {count} entrées", + "FileAITextLimit": "Jusqu’à {count} octets de texte", + "FileAIPlan": "Plan d’opération sur les fichiers", + "FileAIFileAction": "Opération sur les fichiers", + "FileAIDiff": "Modification de fichier", + "FileAIBefore": "Avant", + "FileAIAfter": "Proposition", + "FileAITruncated": "Aperçu tronqué", + "FileAIApprovalTitle": "Approuver l’opération sur les fichiers ?", + "FileAIApprovalExpires": "Expire dans {count} s", + "FileAIApprovalResolved": "Décision envoyée", + "FileAIResult": "Résultat de l’opération sur les fichiers", + "FileAIStateProposed": "Proposition", + "FileAIStateAwaitingApproval": "En attente d’approbation", + "FileAIStateRunning": "En cours d'exécution", + "FileAIStateCompleted": "Terminé", + "FileAIStateFailed": "Échec", + "FileAIStateRejected": "Rejeté", + "FileAIStatePending": "En attente", + "FileAIStateAnalyzing": "Analyse en cours", + "FileAIStateReading": "Lecture en cours", + "FileAIStateExecuting": "Exécution en cours", + "FileAIStateReady": "Prêt", + "ScriptAIName": "IA des scripts", + "ScriptAIHeaderDescription": "Utilise le script actuel non enregistré pour une analyse en lecture seule et des propositions vérifiables", + "ScriptAIUnavailableTitle": "L’IA des scripts est indisponible", + "ScriptAIUnavailableDescription": "Connectez-vous et vérifiez qu’un modèle IA est activé et configuré.", + "ScriptAIEmptyTitle": "Prêt à vous aider avec les scripts", + "ScriptAIEmptyDescription": "Décrivez votre besoin dans le champ ci-dessous ou posez une question sur le script actuel.", + "ScriptAIInputPlaceholder": "Décrivez un script ou posez une question sur le script actuel…", + "ScriptAIInterrupt": "Interrompre la tâche de script", + "ScriptAIFailed": "Échec de l’IA des scripts", + "ScriptAIContextBuffer": "tampon non enregistré", + "ScriptAIStageAnalyzing": "Analyse du script actuel", + "ScriptAIStagePlanning": "Préparation d’une proposition de script", + "ScriptAIStageReading": "Lecture du script actuel", + "ScriptAIStageReady": "Prêt", + "ScriptAIProposal": "Proposition de script", + "ScriptAIBefore": "Avant", + "ScriptAIAfter": "Proposition", + "ScriptAINewScript": "Nouveau script", + "ScriptAIVariables": "{count} variables", + "ScriptAIProposalInvalid": "Aucune proposition de script valide n’a été produite.", + "ScriptAIApply": "Appliquer à l’éditeur", + "ScriptAIApplied": "Proposition de script appliquée", + "ScriptAIAppliedHint": "La modification n’est pas encore enregistrée et n’a pas été exécutée. Vérifiez-la, puis enregistrez-la manuellement.", + "ScriptAIProposalStale": "Le script a changé depuis la création de cette proposition. Demandez une nouvelle proposition.", + "ScriptAIProposalState": { + "applied": "Appliqué", + "rejected": "Rejeté", + "stale": "Obsolète" + }, + "AIUnavailableTitle": "L’IA est indisponible pour ce terminal", + "AIUnavailableDescription": "Sélectionnez un terminal Koko connecté avec l’IA du terminal activée.", + "SQLAIName": "IA SQL", + "SQLAIUnavailableTitle": "L’IA SQL est indisponible", + "SQLAIUnavailableDescription": "Connectez une session de base de données Chen et configurez un fournisseur IA dans Wisp.", + "SQLAIDisabledDescription": "L’IA SQL est désactivée pour cette session de base de données.", + "SQLAIHeaderDescription": "Utilise les métadonnées de schéma autorisées et le contexte de l’éditeur actif", + "SQLAIEmptyTitle": "Prêt à vous aider avec SQL", + "SQLAIEmptyDescription": "Générez du SQL en langage naturel ou utilisez Expliquer et Corriger dans la barre d’outils de l’éditeur.", + "SQLAIInputPlaceholder": "Décrivez le SQL que vous souhaitez générer…", + "SQLAIGenerate": "Générer", + "SQLAIExplain": "Expliquer", + "SQLAIRepair": "Corriger", + "SQLAIExplainPrompt": "Expliquer le SQL sélectionné ou tout le contenu de l’éditeur en l’absence de sélection.", + "SQLAIRepairPrompt": "Corriger le SQL sélectionné ou tout le contenu de l’éditeur en l’absence de sélection. Utiliser la dernière erreur d’exécution si elle est disponible.", + "SQLAIAnalysis": "Analyse SQL", + "SQLAIValid": "Valide", + "SQLAIInvalid": "Invalide", + "SQLAITables": "Tables", + "SQLAIColumns": "Colonnes", + "SQLAIProposal": "Proposition SQL", + "SQLAIBefore": "Avant", + "SQLAIAfter": "Proposition", + "SQLAINewQuery": "Nouvelle requête", + "SQLAIApply": "Appliquer", + "SQLAIApplied": "Proposition SQL appliquée", + "SQLAIApplyFailed": "La proposition SQL n’a pas été appliquée", + "SQLAIProposalStale": "L’éditeur a changé depuis la création de cette proposition. Demandez une nouvelle proposition.", + "SQLAIProposalInvalid": "La proposition SQL ne correspond pas à cet éditeur de base de données.", + "SQLAISendFailed": "Impossible d’envoyer la demande à l’IA SQL", + "SQLAIFailed": "Échec de l’IA SQL", + "SQLAICancel": "Annuler", + "SQLAIThoughtSummary": "Résumé du raisonnement", + "SQLAITiming": "Durée de la requête", + "SQLAIModelDuration": "Durée du modèle", + "SQLAIToolDuration": "Durée de vérification", + "SQLAIQueueDuration": "File d'attente", + "SQLAIStageAnalyzing": "Analyse de la demande", + "SQLAIStageModel": "L’IA analyse et génère une réponse", + "SQLAIStageReviewing": "Vérification de la réponse de l’IA", + "SQLAIStageTool": "Lecture du schéma de la base de données", + "SQLAIStageValidation": "Validation du SQL", + "SQLAIStageCancelled": "Demande annulée", + "SQLAIMetadataApprovalStage": "En attente d’approbation d’accès au schéma", + "SQLAIMetadataApprovalTitle": "Autoriser l’accès aux métadonnées de la base de données ?", + "SQLAIMetadataApprovalDescription": "Chen lira les métadonnées de schéma dans le périmètre ci-dessous et fournira le résultat au modèle IA actuel.", + "SQLAIMetadataInitialContextNotice": "La demande actuelle inclut déjà le SQL de l’éditeur et le contexte de base, notamment la base de données ou le schéma actuel. Cette confirmation contrôle uniquement la requête supplémentaire de métadonnées ci-dessous.", + "SQLAIMetadataExpandedScope": "Cette demande dépasse le périmètre des métadonnées précédemment approuvé pour cette session et nécessite une confirmation.", + "SQLAIMetadataDatabase": "Base de données", + "SQLAIMetadataSchema": "Schéma", + "SQLAIMetadataExpires": "La confirmation expire dans", + "SQLAIMetadataExpiresValue": "{count} secondes", + "SQLAIMetadataTables": "Périmètre des tables", + "SQLAIMetadataDiscovery": "Périmètre de découverte des tables", + "SQLAIMetadataDiscoveryValue": "Tables et vues métier de la base de données ou du schéma actuel", + "SQLAIMetadataDiscoveryLimit": "Renvoie au maximum {count} objets. Le modèle peut sélectionner jusqu’à {followUp} tables candidates pour en examiner ensuite la structure.", + "SQLAIMetadataSearch": "Recherche par nom de table", + "SQLAIMetadataSearchLimit": "Cette recherche peut correspondre à {count} tables au maximum. Toute modification de la recherche nécessite une nouvelle approbation.", + "SQLAIMetadataFields": "Métadonnées fournies au modèle", + "SQLAIMetadataNoRows": "Les lignes de données métier ne sont ni interrogées ni envoyées. L’approbation n’étend pas les permissions de la session JumpServer ou du compte de base de données actuel.", + "SQLAIMetadataReject": "Rejeter", + "SQLAIMetadataAllowOnce": "Autoriser pour cette demande", + "SQLAIMetadataAllowSession": "Autoriser pour cette session", + "SQLAIMetadataSessionHint": "L’approbation de la demande couvre la découverte des tables et l’inspection ultérieure de la structure de 8 tables candidates au maximum. L’approbation de session couvre uniquement un périmètre identique ou inférieur. Toute extension nécessite une nouvelle approbation, qui expire à la fermeture de la connexion.", + "AIMetadataApprovalDescription": "L’IA du terminal lira les métadonnées de schéma dans le périmètre ci-dessous et les fournira au modèle IA actuel.", + "AIMetadataInitialContextNotice": "La demande actuelle inclut déjà le contexte du terminal et de la base de données. Cette confirmation contrôle uniquement la requête supplémentaire de métadonnées ci-dessous.", + "AIMetadataSessionHint": "L’approbation de la demande couvre la découverte des tables et l’inspection de la structure de {count} tables candidates au maximum. L’approbation de session pour la base actuelle expire à la fermeture de la connexion.", + "SQLAIMetadataCategory": { + "connection_metadata": "Versions de la base de données et du pilote", + "tables": "Noms des tables et des vues", + "columns": "Noms et types de données des colonnes", + "nullable": "Acceptation des valeurs NULL par les colonnes", + "primary_keys": "Clés primaires", + "foreign_keys": "Relations de clés étrangères", + "indexes": "Index", + "comments": "Commentaires des tables et des colonnes", + "default_values": "Valeurs par défaut des colonnes" + }, + "AISchemaResultTitle": "Schéma de table chargé", + "AISchemaResultEmpty": "Aucun schéma de table correspondant trouvé.", + "AISchemaResultTruncated": "La limite de sécurité a été atteinte ; seules les 1 000 premières colonnes sont affichées et fournies.", + "AISchemaColumnName": "Colonne", + "AISchemaColumnType": "Type", + "AISchemaColumnNullable": "Nullable", + "AISchemaColumnDefault": "Par défaut", + "AISchemaYes": "Oui", + "AISchemaNo": "Non", + "SQLAIProposalState": { + "cancelled": "Annulé", + "expired": "Expiré", + "applied": "Appliqué", + "rejected": "Rejeté", + "stale": "Obsolète" + }, + "AIHeaderDescription": "Utilise le contexte du terminal actif", + "AIContext": "Contexte", + "AITools": "Outils", + "AICapabilities": "Capacités", + "AIContextCurrentScreen": "écran actuel", + "AIContextSelection": "sélection", + "AIContextLastError": "dernière erreur", + "AIEmptyTitle": "Prêt à vous aider avec ce terminal", + "AIEmptyDescription": "Demandez une analyse ou laissez l’IA du terminal planifier et exécuter une tâche.", + "AIInputPlaceholder": "Posez une question sur ce terminal ou décrivez une tâche…", + "AISend": "Envoyer", + "AIYou": "Vous", + "AIAgentApprovalAlwaysShort": "Toujours demander", + "AIAgentApprovalAutoShort": "Automatique", + "AIAgentApprovalNeverShort": "Ne jamais demander", + "AIAgentApprovalAlways": "Exiger une approbation avant chaque appel d’outil", + "AIAgentApprovalAuto": "Demander uniquement lorsque l’agent estime qu’une approbation est nécessaire", + "AIAgentApprovalNever": "Ne pas demander d’approbation interactive", + "AIApprovalAllShort": "Tous", + "AIApprovalRisk2Short": "Risque faible", + "AIApprovalRisk3Short": "Risque moyen", + "AIApprovalRisk4Short": "Risque élevé", + "AIApprovalAll": "Exiger une approbation avant chaque commande", + "AIApprovalRisk2": "Exiger une approbation pour les commandes à risque faible, moyen et élevé", + "AIApprovalRisk3": "Exiger une approbation pour les commandes à risque moyen et élevé", + "AIApprovalRisk4": "Exiger une approbation uniquement pour les commandes à risque élevé", + "AIModeAutoShort": "Automatique", + "AIModePtyShort": "PTY", + "AIModeBackgroundShort": "Arrière-plan", + "AIModeAuto": "Choisir automatiquement entre le PTY actuel et l’exécution en arrière-plan", + "AIModePty": "Exécuter les commandes uniquement dans le terminal actuel", + "AIModeBackground": "Exécuter les commandes uniquement en arrière-plan", + "AIExecutionPlan": "Plan d’exécution", + "AIResponding": "Réponse en cours…", + "AIStep": "Étape {count}", + "AICommand": "Commande", + "AITimeout": "Délai maximal {count} s", + "AIDecisionDuration": "Durée de décision", + "AIModelDuration": "Durée du modèle", + "AIToolDuration": "Durée de l’outil", + "AIRisk": "Risque {level}", + "AIRiskReadOnly": "Lecture seule", + "AIRiskLow": "Risque faible", + "AIRiskMedium": "Risque moyen", + "AIRiskHigh": "Risque élevé", + "AIAutoApproved": "Approuvé automatiquement", + "AICurrentPty": "PTY", + "AIBackgroundExecution": "Arrière-plan", + "AIBackgroundInitializing": "Initialisation de l’exécution en arrière-plan…", + "AIBackgroundPtyOnly": "Cette connexion permet uniquement l’exécution dans le PTY actuel", + "AIBackgroundDisabledByRules": "L’exécution en arrière-plan est désactivée par les règles de l’IA du terminal", + "AIBackgroundUnavailable": "L’exécution en arrière-plan est indisponible pour cette connexion", + "AIProgressReadingTerminal": "Lecture de l’état actuel du terminal…", + "AIProgressAnalyzing": "Génération du plan et de la première commande…", + "AIProgressPlanning": "Vérification du résultat et planification de l’étape suivante…", + "AIProgressExecuting": "Exécution de la commande…", + "AIProgressExecutingPty": "La commande s’exécute dans le PTY actuel ; vous pouvez interagir avec elle dans le terminal…", + "AIProgressMetadataApproval": "En attente d’approbation d’accès au schéma de la base de données…", + "AIProgressMetadataLookup": "Lecture du schéma des tables de la base de données…", + "AIProgressSummarizing": "Génération du résumé d’exécution…", + "AIMetadataApprovalFailed": "Impossible d’envoyer l’approbation d’accès au schéma de la base de données", + "AIApprove": "Approuver", + "AIApproveForSession": "Toujours autoriser cette commande", + "AIReject": "Rejeter", + "AIExecutionDetails": "Détails d'exécution", + "AIRawOutput": "Sortie brute", + "AIExecutionResult": "Résultat de l’exécution", + "AIActualExecution": "Exécution réelle", + "AIExecutionDuration": "Durée", + "AIExitCode": "Code de sortie {code}", + "AICommandAcl": "ACL des commandes", + "AICommandAclAccepted": "Autorisé", + "AICommandAclReview": "Validation requise", + "AICommandAclWaitingReview": "En attente de validation ACL", + "AICommandAclNotifyAndWarn": "Notifier et avertir", + "AICommandAclWarning": "Avertissement", + "AIInterrupt": "Interrompre la tâche", + "AISendFailed": "Impossible d’envoyer le message à l’IA du terminal", + "AIPolicyFailed": "Impossible de modifier la politique de l’IA du terminal", + "AIStatusApprovalExpired": "Approbation expirée", + "AIApprovalExpired": "L’approbation a expiré. Cette opération n’a pas été exécutée. Envoyez une nouvelle demande pour l’approuver à nouveau.", + "AIStatusTimeout": "Délai dépassé", + "AIStatusUnknown": "Résultat non confirmé", + "AIToolTimeout": "L’outil a dépassé le délai imparti. Vérifiez l’état du terminal ou de la ressource avant de réessayer.", + "AIToolResultUnknown": "Le résultat de l’exécution n’a pas pu être confirmé. Vérifiez l’état du terminal ou de la ressource avant de réessayer.", + "AIRunTimeout": "Cette demande a atteint sa durée maximale. Vérifiez les opérations terminées avant de continuer.", + "AIApprovalFailed": "Impossible d’envoyer la décision à l’IA du terminal", + "AIInterruptFailed": "Impossible d’interrompre la tâche de l’IA du terminal", + "AIFailed": "Échec de l’IA du terminal", + "AIUnavailableForTerminal": "L’IA du terminal est indisponible pour le terminal actif", + "AIErrorResponseActive": "Une autre tâche de l’IA du terminal est déjà en cours", + "AIErrorInvalidMessage": "Le message de l’IA du terminal est invalide", + "AIErrorApprovalRejected": "L’approbation de la commande a été refusée", + "AIStatusApproved": "Approuvé", + "AIStatusAutoApproved": "Approuvé automatiquement", + "AIStatusAwaitingApproval": "En attente d’approbation", + "AIStatusCompleted": "Terminé", + "AIConnectionDisconnected": "Connexion perdue. Rétablissez votre connexion réseau avant de réessayer.", + "AIConnectionReconnecting": "Connexion interrompue. Reconnexion…", + "AIStatusDisconnected": "Déconnecté", + "AIStatusReconnecting": "Reconnexion", + "AIStatusFailed": "Échec", + "AIStatusRunning": "En cours d'exécution", + "AIStatusCancelled": "Annulé", + "AIToolCall": "Appel d’outil", + "AIToolArguments": "Arguments", + "AIToolResult": "Résultat", + "AIStatusInterrupted": "Interrompu", + "AIStatusPending": "En attente", + "AIStatusRejected": "Rejeté", + "AIStatusReviewing": "Vérification en cours", + "AIStatusWaitingInput": "En attente de saisie", + "AINoOutputDuration": "Aucune sortie depuis {duration}", + "AIExecutionRemaining": "Temps d’exécution restant : {duration}", + "AIStatusCancelling": "Annulation en cours", + "AIStatusReady": "Prêt", + "AIStatusSkipped": "Ignoré", + "SFTPSearchPlaceholder": "Rechercher des actifs pour le transfert de fichiers…", + "SFTPEmptyTitle": "Choisir un actif à parcourir", + "SFTPEmptyDescription": "Recherchez un actif ou réutilisez l’hôte du terminal actif. Ouvrez le gestionnaire de fichiers complet pour les transferts locaux.", + "UseActiveAsset": "Utiliser l’actif", + "OpenSFTP": "Ouvrir les fichiers", + "OpenDevelopmentWorkspace": "Ouvrir l’espace de développement", + "OpenDevelopmentWorkspaceHint": "Ouvrir l’éditeur de fichiers complet au-dessus du terminal actif", + "OpenDevelopmentWorkspaceFailed": "Impossible d’ouvrir l’espace de développement", + "DevelopmentWorkspaceUnavailable": "Le terminal actif n’est pas prêt pour l’accès aux fichiers", + "OpenProfessional": "Ouvrir dans le gestionnaire de fichiers complet", + "OpenProfessionalHint": "Transférer entre le système local et le système distant", + "BatchCommandPlaceholder": "Saisissez la commande à exécuter sur chaque terminal… ({shortcut} pour envoyer)", + "BatchConnectedTabs": "Terminaux connectés", + "BatchNoConnectedTabs": "Aucun terminal connecté", + "BatchNoConnectedTabsDesc": "Connectez-vous à des hôtes SSH depuis l’arborescence des actifs pour les afficher ici.", + "BatchConnected": "Connecté", + "BatchSelectAll": "Tout sélectionner", + "BatchDeselectAll": "Tout désélectionner", + "BatchSend": "Envoyer", + "BatchSendToCount": "Envoyer à {count} terminal(aux)", + "BatchCommandSent": "Commande envoyée", + "BatchCommandSentDesc": "Commande envoyée à {count} terminal(aux)", + "BatchCommandSendResult": "Résultat de l’envoi par lot", + "BatchCommandSendResultDesc": "Envoyé à {sent}/{total} terminal(aux) ; {failed} non envoyés", + "BatchPanelClose": "Fermer le panneau de commandes par lot" + }, + "FileManagement": { + "ConnectRemoteSftp": "Connecter un serveur SFTP distant", + "RemoteSftp": "SFTP distant", + "LocalSftp": "SFTP", + "DisconnectRemote": "Déconnecter le serveur distant", + "RemoteSftpHint": "Connecter un autre serveur SFTP pour transférer des fichiers entre les deux côtés", + "RemoteConnected": "SFTP distant connecté", + "RemoteConnectFailed": "Impossible de connecter le serveur SFTP distant", + "AddRemoteSftp": "Ajouter un serveur SFTP distant", + "DisconnectAllRemote": "Tout déconnecter", + "TransferTarget": "Cible du transfert", + "TransferToRemote": "Envoyer à la sélection", + "TransferToLocal": "Envoyer à la cible actuelle", + "TransferSuccess": "Fichier transféré", + "TransferPartialSuccess": "Transféré vers {success}/{total} cibles", + "TransferFailed": "Échec du transfert de fichier", + "FolderTransferUnsupported": "Le transfert de dossiers n’est pas encore pris en charge", + "DownloadFolderConfirm": "Télécharger ce dossier ?" + }, + "FileTransfer": { + "Title": "Transferts de fichiers", + "ActiveCount": "Transferts ({count})", + "ClearFinished": "Effacer les transferts terminés", + "Empty": "Aucun transfert de fichier", + "Expand": "Développer les transferts", + "Collapse": "Réduire les transferts", + "Resume": "Reprendre", + "Pause": "Pause", + "Retry": "Réessayer", + "Cancel": "Annuler", + "IdleRate": "—", + "TransferredSize": "{size} transférés", + "SpeedRemaining": "{speed} · {remaining}", + "ConflictTitle": "Gestion des fichiers en double", + "ConflictDescription": "La destination contient un fichier du même nom. Choisissez comment traiter ce transfert.", + "Overwrite": "Écraser", + "Skip": "Ignorer", + "EndpointUnavailable": "Ce téléversement a échoué. Sélectionnez à nouveau les fichiers et relancez le téléversement.", + "EndpointUnavailableDownload": "Ce téléchargement a échoué. Réessayez plus tard.", + "Status": { + "queued": "En file d'attente", + "preparing": "Préparation", + "transferring": "Transfert en cours", + "verifying": "Vérification", + "paused": "En pause", + "completed": "Terminé", + "skipped": "Ignoré", + "failed": "Échec", + "canceled": "Annulé", + "partial": "Partiellement terminé" + } + }, + "Transcode": { + "FfmpegRequired": "Extension de transcodage FFmpeg requise", + "FfmpegRequiredDescription": "Téléchargez l’encodeur facultatif dans les paramètres avant de lancer le transcodage des enregistrements.", + "OpenPluginSettings": "Ouvrir les paramètres", + "Title": "Transcodage des enregistrements", + "Description": "Choisissez une ou plusieurs archives d’enregistrement .tar à convertir en MP4.", + "SelectedArchives": "{count} archive(s) sélectionnée(s)", + "TotalProgress": "Progression globale {progress}%", + "CompletedCount": "Terminés {completed}/{total}", + "InProgress": "En cours {processing}/{total}", + "NotStarted": "Non démarré", + "ArchivesTitle": "Archives d’enregistrements", + "ReplayArchiveFile": "Archive d’enregistrement", + "ArchivesHint": "La sélection simple ou multiple est prise en charge. Vous pouvez ajouter d’autres archives ultérieurement.", + "SelectArchives": "Ajouter des archives", + "ClearArchives": "Vider la liste", + "EmptyArchives": "Ajouter des archives d’enregistrements à transcoder", + "OutputTitle": "Répertoire de sortie", + "OutputHint": "Les fichiers MP4 générés seront enregistrés dans ce répertoire.", + "OutputDirectory": "Répertoire de sortie", + "OutputDirPlaceholder": "Aucun répertoire de sortie sélectionné", + "SelectOutputDir": "Sélectionner le répertoire de sortie", + "Start": "Démarrer le transcodage", + "Running": "Transcodage", + "ProgressTitle": "Progression de la tâche", + "ProgressHint": "Chaque fichier d’enregistrement affiche son étape actuelle, sa progression et son résultat final.", + "SuccessCount": "Réussites {count}", + "FailedCount": "Échecs {count}", + "QueuedCount": "En attente {count}", + "OutputFile": "Fichier de sortie :", + "OpenFile": "Ouvrir", + "OpenFailed": "Impossible d’ouvrir le fichier", + "ErrorDetail": "Erreur :", + "Completed": "Transcodage terminé", + "CompletedAll": "Tous les transcodages sont terminés", + "CompletedWithErrors": "Terminé avec des échecs", + "CompletedSummary": "{count} fichier(s) MP4 généré(s) avec succès", + "CompletedSummaryWithErrors": "{success} réussites, {failed} échecs", + "StartFailed": "Impossible de démarrer le transcodage", + "SelectArchivesFirst": "Sélectionnez d’abord des archives d’enregistrements", + "SelectOutputDirFirst": "Le répertoire de sortie n’est pas défini. Ouvrir les paramètres maintenant ?", + "OutputDirRequired": "Veuillez sélectionner un répertoire de sortie", + "SelectArchivesFailed": "Impossible de sélectionner les archives d’enregistrements", + "SelectOutputDirFailed": "Impossible de sélectionner le répertoire de sortie", + "StatusPending": "En attente", + "StatusQueued": "En file d'attente", + "StatusProcessing": "Traitement en cours", + "StatusSuccess": "Succès", + "StatusFailed": "Échec", + "Waiting": "En attente de démarrage", + "EmptyProgress": "La progression de chaque fichier apparaîtra ici après le démarrage du transcodage", + "UnknownError": "Erreur inconnue", + "MetaUser": "Utilisateur", + "MetaAsset": "Actif", + "MetaAccount": "Compte", + "MetaRemoteAddr": "IP distante", + "MetaProtocol": "Protocole", + "MetaDateStart": "Début", + "MetaDateEnd": "Fin", + "Settings": "Paramètres", + "Prompt": "Avis", + "Close": "Fermer", + "Cancel": "Annuler", + "Confirm": "Confirmer", + "FilenameStyle": "Format des noms de fichiers", + "FilenameOriginal": "Original (UUID)", + "FilenameFriendly": "Nom lisible (utilisateur-actif-compte)", + "FilenameFriendlyUuid": "Nom lisible + UUID", + "OutputResolution": "Résolution de sortie", + "ResolutionOriginal": "Originale", + "Resolution1080p": "1080p (1920×1080)", + "Resolution720p": "720p (1280×720)", + "Resolution360p": "360p (640×360)", + "TranscodeStarted": "Le transcodage peut prendre du temps. Gardez l’application ouverte et patientez !", + "TranscodePower": "Puissance de transcodage", + "TranscodePowerHint": "Chaque mode utilise une proportion différente des ressources CPU du système, ce qui affecte la vitesse du transcodage", + "PowerAuto": "Automatique", + "PowerAutoHint": "Windows utilise la chaîne d’encodage du système ; la puissance de transcodage est gérée automatiquement", + "PowerFull": "Vitesse maximale (tous les CPU)", + "PowerFast": "Rapide (3/4 du CPU)", + "PowerMedium": "Moyenne (1/2 du CPU)", + "PowerLow": "Faible (1/4 du CPU)" + }, + "AssetCard": { + "Activated": "Activé", + "Deactivated": "Désactivé", + "Address": "Adresse", + "User": "Utilisateur", + "Protocol": "Protocole", + "AssetName": "Nom de l’actif", + "Actions": "Actions", + "Status": "Statut", + "RenameSuccess": "Renommage réussi", + "RenameFail": "Échec du renommage", + "DuplicateName": "Un actif portant ce nom existe déjà", + "NameTooLong": "Le nom de l’actif ne peut pas dépasser {max} caractères" + }, + "ContextMenu": { + "QuickConnect": "Connexion rapide", + "Connect": "Connecter", + "MoreConnect": "Autres connexions", + "OpenInCurrentTab": "Ouvrir dans l’onglet actuel", + "OpenInNewWindow": "Ouvrir dans une nouvelle fenêtre", + "Rename": "Renommer", + "Use": "Utiliser", + "Favorite": "Favori", + "Edit": "Modifier", + "Delete": "Supprimer", + "Unfavorite": "Retirer des favoris", + "FavoriteSuccess": "Ajout aux favoris réussi", + "FavoriteFailed": "Impossible d’enregistrer dans les favoris", + "UnfavoriteSuccess": "Retrait des favoris réussi", + "UnfavoriteFailed": "Impossible de retirer des favoris" + }, + "TabMenu": { + "ScrollLeft": "Faire défiler les onglets vers la gauche", + "ScrollRight": "Faire défiler les onglets vers la droite", + "SelectTab": "Sélectionner un onglet", + "SwitchTab": "Changer d’onglet", + "CloneConnect": "Dupliquer la connexion", + "Reconnect": "Reconnecter", + "RenameTitle": "Renommer l’onglet", + "RenamePlaceholder": "Saisir le nom de l’onglet", + "SplitVertically": "Diviser verticalement", + "SplitHorizontally": "Diviser horizontalement", + "FocusCurrent": "Mode épuré", + "FullscreenCurrent": "Mode plein écran", + "ExitFocusMode": "Quitter le mode épuré", + "ExitFocusModeHint": "Quitter le mode épuré (maintenir Échap)", + "CloseCurrent": "Fermer l’onglet actuel", + "CloseAll": "Fermer tous les onglets", + "CloseOther": "Fermer les autres onglets", + "CloseLeft": "Fermer les onglets à gauche", + "CloseRight": "Fermer les onglets à droite" + }, + "TerminalAi": { + "Title": "IA du terminal", + "PromptLabel": "Décrire une tâche pour l’IA du terminal", + "Placeholder": "Décrivez une tâche, par exemple : lister les fichiers du répertoire actuel…", + "ShortcutHint": "Appuyez sur {shortcut} pour interroger l’IA", + "Send": "Envoyer", + "Sending": "Envoi en cours…", + "History": "Historique", + "ViewDetails": "Voir les détails", + "LeaveTitle": "Une tâche IA est en cours", + "LeaveTab": "Fermer cet onglet interrompra la tâche IA en cours. Le fermer quand même ?", + "LeaveOrg": "Changer d’organisation interrompra la tâche IA en cours. Continuer quand même ?", + "LeaveAccount": "Changer d’utilisateur interrompra la tâche IA en cours. Continuer quand même ?", + "LeaveLogout": "La déconnexion interrompra la tâche IA en cours. Se déconnecter quand même ?", + "ContinueHint": "Cet échange reste ici. L’historique ouvre la conversation complète.", + "TourHudTitle": "Interroger depuis le terminal", + "TourHudDescription": "Appuyez sur {shortcut} pour ouvrir l’IA du terminal au niveau du curseur. Décrivez une tâche et envoyez-la.", + "TourHistoryTitle": "Historique complet", + "TourHistoryDescription": "Ce panneau affiche l’échange actuel. L’historique ouvre la conversation complète.", + "TourCloseTitle": "Fermer", + "TourCloseDescription": "Fermez ce panneau à tout moment. Appuyez sur {shortcut} pour le rouvrir." + }, + "WorkspacePane": { + "EmptyTitle": "Non connecté", + "EmptyDescription": "Connecter l’actif actuel ou choisir un autre actif dans la boîte de dialogue", + "SelectingOtherHint": "Choisir un actif dans la boîte de dialogue pour le connecter ici", + "SetupTitle": "Configuration de la connexion", + "SetupHint": "Choisissez les options de connexion pour continuer", + "ConnectCurrent": "Connecter l’actif actuel", + "ConnectOther": "Connecter un autre actif", + "CurrentOrganization": "Organisation actuelle", + "Reconnect": "Reconnecter", + "ClosePane": "Fermer le volet", + "CloseSidePanel": "Fermer le panneau latéral", + "ResizeSidebar": "Redimensionner la barre latérale", + "ResizeRightPanel": "Redimensionner le panneau droit", + "ResizeColumns": "Redimensionner les colonnes des volets", + "ResizeRows": "Redimensionner les lignes des volets", + "SplitLimitTitle": "Limite de volets atteinte", + "SplitLimitDescription": "Cet onglet prend en charge quatre volets au maximum. Fermez-en un avant d’en ouvrir un autre.", + "MergeLimitDescription": "Cet onglet prend en charge quatre volets au maximum ; vous ne pouvez pas y déposer un autre onglet.", + "MergeSourceSplitTitle": "Impossible de fusionner un onglet divisé", + "MergeSourceSplitDescription": "Seul un onglet qui n’a pas été divisé peut être déplacé dans cette fenêtre.", + "MergeDropHint": "Relâchez pour fusionner dans cet onglet", + "SwapPositionHint": "Relâchez pour échanger les positions", + "PlaceLeftHint": "Relâchez pour placer à gauche", + "PlaceRightHint": "Relâchez pour placer à droite", + "PlaceTopHint": "Relâchez pour placer en haut", + "PlaceBottomHint": "Relâchez pour placer en bas", + "StatusConnected": "Connecté", + "StatusConnecting": "Connexion en cours", + "StatusFailed": "Échec de la connexion", + "StatusIdle": "Inactif" + }, + "EditModal": { + "AppletConnectMethod": "Mode de connexion RemoteApp", + "DownloadRdpFile": "Télécharger le fichier RDP", + "ModifyConnectionInfo": "Connecter", + "Description": "Clic droit pour modifier", + "RememberSelection": "Connexion automatique la prochaine fois", + "RememberSelectionDescription": "Faites un clic droit sur un actif dans l’arborescence pour choisir une autre méthode de connexion.", + "OptionalProtocol": "Protocole", + "ConnectMethod": "Méthode de connexion", + "OptionalAccount": "Compte", + "CurrentOnline": "Actuellement en ligne : {count}" + }, + "Layout": { + "Grid": "Vue en grille", + "Table": "Vue en tableau" + }, + "Sort": { + "A-z": "A–Z", + "Z-A": "Z–A", + "NewestToOldest": "Du plus récent au plus ancien", + "OldestToNewest": "Du plus ancien au plus récent" + }, + "Loading": { + "Loading": "Chargement", + "NoMore": "Aucun autre résultat", + "ScrollToLoadMore": "Faites défiler pour charger plus" + }, + "Login": { + "Title": "Saisir le site de connexion", + "Description": "Saisissez l’adresse complète du site, par exemple https://example.com ou http://192.168.1.1", + "SiteName": "Nom du site", + "LoginSuccess": "Connexion réussie", + "LoginSuccessDescription": "Votre opération a été effectuée avec succès", + "LoginFailed": "Échec de la connexion", + "EmptyUrlError": "Veuillez saisir une adresse de site", + "EmptySiteNameError": "Veuillez saisir un nom de site", + "DuplicateSiteNameError": "Ce nom de site existe déjà. Utilisez un autre nom", + "LoginFailedDescription": "Échec de la connexion : le compte actuel a expiré. Veuillez vous reconnecter", + "InvalidUrlError": "Adresse de site invalide. Saisissez une adresse complète commençant par http:// ou https://", + "InvalidSiteError": "Échec de la connexion : le site est inaccessible ou invalide. Vérifiez et réessayez.", + "AddAccount": "Ajouter un site", + "SwitchSite": "Changer de site", + "SwitchSiteSessionsDescription": "Cet espace de travail contient encore des sessions. Continuer les déconnectera et les fermera. Les modifications non enregistrées risquent d’être perdues.", + "RecentSites": "Sites récents", + "ClearRecentSites": "Effacer", + "RemoveRecentSite": "Retirer", + "LoginAuthenticationExpired": "L’authentification a expiré. Veuillez vous reconnecter", + "LoginAuthenticationExpiredDescription": "S’il existe plusieurs sites, vous serez automatiquement redirigé vers le site suivant", + "Logout": "Déconnexion", + "LogoutSessionsDescription": "La déconnexion fermera les sessions actuelles de l’espace de travail. Les modifications non enregistrées risquent d’être perdues.", + "AlreadyLoggedInError": "Vous êtes déjà connecté à ce site", + "LoginFailedErrorPage": "Échec de la connexion. La page actuelle n’est pas une page de connexion. Veuillez réessayer", + "LoginFailedTimeout": "Échec de la connexion. Le délai de connexion a expiré. Veuillez vous reconnecter", + "InvalidCertificateMac": "Échec de la connexion : certificat invalide ou bloqué par la sécurité du système (WebView macOS). Installez un certificat valide ou ouvrez le site dans le navigateur système.", + "InvalidCertificateGeneric": "Échec de la connexion : problème de certificat ou de connexion sécurisée.", + "NetworkError": "Réseau indisponible. Vérifiez votre connexion.", + "ServerError": "Le serveur est temporairement indisponible. Réessayez plus tard.", + "VersionIncompatible": "Cette version du client est incompatible avec les versions de JumpServer antérieures à v4.10.12-lts. Téléchargez et utilisez le client v3.0.7.", + "VersionNoMatch": "La version du client ne correspond pas à celle de JumpServer. Téléchargez et utilisez le client v{version} ou mettez JumpServer à jour vers la dernière version." + }, + "Asset": { + "GetAssetFailed": "Impossible de récupérer l’actif. Actualisez et réessayez" + }, + "Account": { + "NoAuthorizedAccounts": "Aucun compte n’a été autorisé pour cet actif", + "Hosted": "Comptes avec secret", + "Manual": "Comptes manuels", + "Username": "Nom d'utilisateur", + "Password": "Mot de passe", + "DynamicUser": "Utilisateur dynamique", + "ManualInput": "Saisie manuelle", + "Anonymous": "Anonyme", + "RememberPassword": "Mémoriser le mot de passe", + "PersonalCredential": "Identifiant personnel", + "ManualOtherAccount": "Saisir un autre compte", + "SavedAccount": "Compte enregistré : {username}", + "SaveAsPersonalCredential": "Enregistrer comme identifiant personnel", + "UpdatePersonalCredential": "Modifier le mot de passe enregistré", + "CancelPersonalCredentialUpdate": "Annuler la modification du mot de passe", + "UseSavedPassword": "Le mot de passe enregistré sera utilisé", + "LoadPersonalCredentialsFailed": "Impossible de charger les identifiants enregistrés. Vous pouvez toujours en saisir un manuellement.", + "ShowPassword": "Afficher le mot de passe", + "HidePassword": "Masquer le mot de passe" + }, + "ConnectionSetup": { + "MoveDialog": "Déplacer la fenêtre de connexion : faites glisser la barre de titre ou utilisez les flèches ; Début pour centrer", + "ClientLaunchStarted": "Lancement du client démarré", + "LaunchWithClient": "Nous avons tenté d’ouvrir cette connexion dans {client}.", + "LaunchWithLocalClient": "Nous avons tenté d’ouvrir cette connexion dans un client local.", + "LaunchHint": "Si le client ne s’est pas ouvert, relancez-le ou revenez en arrière pour changer de méthode de connexion.", + "ConnectionTarget": "Cible de connexion", + "Client": "Client", + "OpenInClient": "Ouvrir dans le client", + "Establishing": "Établissement de la connexion…", + "CreateToken": "Créer un jeton de connexion", + "OpenSession": "Ouvrir une session", + "Connected": "Connecté", + "OpenAgain": "Ouvrir à nouveau", + "BackToForm": "Retour au formulaire", + "StartFaceVerify": "Démarrer la vérification" + }, + "ConnectionGuide": { + "TokenCommand": "Commande de connexion (jeton)", + "DirectCommand": "Commande de connexion (directe par ID d’actif)", + "TokenPasswordHelp": "Utilisez le mot de passe du jeton dans le tableau ci-dessus.", + "DirectPasswordHelp": "Utilisez votre mot de passe de connexion JumpServer.", + "DirectUnavailable": "Le nom d’utilisateur de connexion ou le compte de l’actif est indisponible. Rouvrez le guide de connexion.", + "Title": "Guide de connexion", + "Name": "Nom", + "Host": "Hôte", + "Port": "Port", + "Username": "Nom d'utilisateur", + "Password": "Mot de passe", + "Database": "Base de données", + "Protocol": "Protocole", + "ExpireTime": "Date d’expiration", + "ShowPassword": "Afficher le mot de passe", + "ConnectCommand": "Ligne de commande de connexion", + "CopyCommand": "Copier la commande", + "SetReusable": "Rendre réutilisable", + "ReusableHint": "Permettre une réutilisation prolongée après activation", + "TokenExpired": "Le jeton a expiré", + "HelpText": "Texte d'aide", + "DatabaseTokenHelp": "Le jeton de base de données utilisé par le client est mis en cache par le composant pendant 5 minutes. Il n’est donc pas invalidé immédiatement après utilisation, mais cinq minutes après la déconnexion du client" + }, + "UserProfile": { + "ViewDetails": "Voir les détails de l’utilisateur", + "NoRole": "Aucun rôle", + "BasicInfo": "Informations de base", + "AccountAndRoles": "Compte et rôles", + "SecurityStatus": "État de sécurité", + "LoginInfo": "Informations de connexion", + "FullName": "Nom", + "Username": "Nom d'utilisateur", + "Email": "E-mail", + "Phone": "Téléphone", + "Wechat": "WeChat", + "Source": "Source de l’utilisateur", + "CurrentSite": "Site actuel", + "CurrentOrganization": "Organisation actuelle", + "SystemRoles": "Rôles système", + "OrganizationRoles": "Rôles d'organisation", + "AccountStatus": "Statut du compte", + "MFA": "Authentification multifacteur", + "Enabled": "Activé", + "Disabled": "Désactivé", + "Valid": "Valide", + "Invalid": "Indisponible", + "LastLogin": "Dernière connexion", + "JoinedAt": "Date de participation", + "ExpiresAt": "Date d'expiration", + "SiteAccounts": "Comptes des sites", + "SiteAccountsDescription": "Gérer les sites JumpServer auxquels ce client est connecté.", + "Current": "Actuel", + "Switch": "Commutateur", + "LoadFailed": "Impossible de charger le profil utilisateur", + "Retry": "Réessayer", + "NoUser": "Non connecté", + "NoUserDescription": "Connectez-vous à un site JumpServer pour consulter les détails de l’utilisateur." + }, + "Setting": { + "BackToApp": "Retour à l’application", + "DisableAutoCompletion": "Désactiver la saisie semi-automatique", + "SearchPlaceholder": "Rechercher dans les paramètres", + "NoSearchResults": "Aucun paramètre correspondant", + "UserDescription": "Profil utilisateur, état de sécurité et comptes des sites.", + "GeneralDescription": "Langue et paramètres de connexion par défaut.", + "AppearanceDescription": "Thème, couleurs, polices et apparence de l’éditeur.", + "ThemeModeDescription": "Suivre le système ou toujours utiliser une apparence claire ou sombre.", + "ThemePalette": "Styles de thème", + "ThemePaletteDescription": "Choisir le style d’interface et la couleur d’accentuation des modes clair et sombre.", + "ThemeStyle": "Style de l’interface", + "LightThemeDescription": "Utilisé lorsque l’application est en mode clair.", + "DarkThemeDescription": "Utilisé lorsque l’application est en mode sombre.", + "PrimaryColorDescription": "Utilisée pour les boutons, les sélections et les informations mises en évidence.", + "WorkspaceAppearance": "Apparence de l’espace de travail", + "WorkspaceAppearanceDescription": "Ajuster l’affichage du texte de l’interface, des terminaux de commande et des éditeurs de texte.", + "ModernIsland": "Îlots modernes", + "ModernIslandDescription": "Séparer l’arborescence des actifs, l’espace de session et le panneau droit en îlots redimensionnables et réductibles.", + "UiRadius": "Rayon des angles", + "UiRadiusDescription": "Aucun ; petit utilise le rayon actuel de 3 px des contrôles ; grand utilise le rayon de 10 px des îlots.", + "UiRadiusNone": "Aucun", + "UiRadiusSmall": "Petit", + "UiRadiusLarge": "Grand", + "ResetAccent": "Rétablir le thème", + "DebugLog": "Activer les journaux de débogage", + "DebugLogDescription": "Enregistrer des diagnostics plus détaillés du client sur cet appareil. Activez cette option temporairement pour signaler un problème ; elle est désactivée par défaut.", + "ClearLogs": "Effacer les journaux", + "CopyLogs": "Copier les journaux", + "DownloadLogs": "Télécharger les journaux", + "LogsCleared": "Journaux effacés", + "LogsCopied": "Journaux copiés", + "LogsDownloaded": "Téléchargé", + "LogsEmpty": "Aucun journal pour le moment", + "InterfaceFont": "Police de l’interface", + "InterfaceFontDescription": "Police utilisée dans les menus, la navigation et les zones de contenu.", + "InterfaceFontSize": "Taille de police de l’interface", + "InterfaceFontSizeDescription": "Ajuster la taille de police de base de l’interface de l’application.", + "CodeFontSize": "Taille de police du code", + "CodeFontSizeDescription": "Ajuster la taille de police de base des terminaux et des éditeurs de texte.", + "SearchFonts": "Rechercher des polices", + "TerminalAppearanceDescription": "Palette de couleurs des terminaux de commande.", + "EditorAppearanceDescription": "Palette de couleurs des éditeurs de scripts et de texte.", + "ApplicationDescription": "Choisir les applications de bureau pour chaque protocole de connexion.", + "AboutDescription": "Informations de version, de produit et de licence.", + "Product": "Produit", + "EnterpriseEdition": "Édition Entreprise", + "CommunityEdition": "Édition Communauté", + "LicenseCompany": "Entreprise titulaire de la licence", + "LicensedTo": "Licence accordée à", + "OfficialWebsite": "Site officiel", + "CommandTerminal": "Terminal de commande", + "CharacterProtocols": "Protocoles en mode texte", + "FileTransfer": "Transfert de fichiers", + "RemoteDesktop": "Bureau à distance", + "Database": "Base de données", + "Charset": "Jeu de caractères", + "CharsetDescription": "Définir l’encodage des caractères des sessions de terminal. Essayez une autre option si le texte est illisible. S’applique à la prochaine connexion SSH/Telnet ; les sessions ouvertes restent inchangées.", + "TerminalBackspace": "Utiliser Ctrl + H pour Retour arrière dans le terminal", + "TerminalBackspaceDescription": "Choisir si Retour arrière envoie Ctrl + H dans les sessions de terminal.", + "TerminalRightClickPaste": "Collage rapide par clic droit", + "TerminalRightClickPasteDescription": "Coller le contenu du presse-papiers par clic droit dans les terminaux. Ctrl + clic droit ouvre toujours le menu.", + "TerminalCommandSuggestions": "Suggestions de commandes du terminal", + "TerminalCommandSuggestionsDescription": "Afficher les suggestions du catalogue local de commandes et de l’historique pendant la saisie dans les terminaux distants.", + "ClearTerminalCommandHistory": "Effacer l’historique des commandes", + "ClearTerminalCommandHistoryConfirm": "Effacer tout l’historique des commandes de terminal enregistré pour le site et le compte actuels ?", + "TerminalCommandHistoryCleared": "Historique des commandes du terminal effacé", + "Resolution": "Résolution", + "ResolutionDescription": "Les modifications définissent la résolution par défaut des options avancées pour les futures connexions Windows. Une sélection propre à une connexion s’applique uniquement à celle-ci et ne modifie pas ce paramètre.", + "ConnectionResolutionDescription": "S’applique uniquement à cette connexion et ne modifie pas le paramètre général.", + "RdpColorQuality": "Qualité de couleur RDP", + "RdpColorQuality8": "8 bits", + "RdpColorQuality16": "16 bits", + "RdpColorQuality24": "24 bits", + "RdpColorQuality32": "Couleurs vraies (32 bits)", + "RdpSmartSize": "Dimensionnement intelligent RDP", + "RdpFullScreen": "Plein écran", + "RdpMultiScreen": "Plusieurs écrans", + "RdpDrivesRedirect": "Redirection des lecteurs", + "RdpLocalClientDescription": "S’applique uniquement au client RDP local, et non au bureau distant web.", + "RemoteMicrophone": "Microphone distant", + "RdpFileReusable": "Fichier RDP réutilisable", + "RdpConnectionSpeed": "Vitesse de connexion RDP", + "RdpLowSpeedBroadband": "Haut débit lent (256 Kbit/s – 2 Mbit/s)", + "RdpHighSpeedBroadband": "Haut débit rapide (2 Mbit/s – 10 Mbit/s)", + "Default": "Par défaut", + "Auto": "Automatique", + "Enable": "Activer", + "Disable": "Désactiver", + "DefaultApplication": "Par défaut", + "SetDefaultApplication": "Définir par défaut", + "EnableFailed": "Impossible d’activer", + "NoClientConfigured": "Aucun client n’est configuré pour ce protocole", + "ConfigureClient": "Ouvrir les paramètres de l’application", + "ExecutableNotFound": "Application introuvable. Veuillez la télécharger et l’installer correctement.", + "BuiltInTerminal": "Terminal intégré", + "AlwaysEnabled": "Toujours activé", + "DownloadApplication": "Télécharger l’application", + "DownloadCenter": "Centre de téléchargement", + "DownloadClient": "Télécharger le client", + "CopyPath": "Copier le chemin", + "CopyPathSuccess": "Chemin copié", + "SelectPath": "Sélectionner un chemin", + "PluginManager": "Gestionnaire d’extensions", + "JumpServerPluginFile": "Extension JumpServer", + "ZipArchiveFile": "Archive ZIP", + "ExecutableFile": "Exécutable", + "ManagePlugins": "Gérer les extensions", + "UploadPlugin": "Téléverser une extension", + "UninstallPlugin": "Désinstaller", + "UserPlugin": "Extension utilisateur", + "PluginSummary": "{installed} extension(s) installée(s), {total} au total", + "PluginUploadHint": "Téléversez un paquet .jscplugin pour ajouter des applications de bureau disponibles.", + "PluginVersion": "Version {version}", + "PluginInstallSuccess": "Extension installée", + "PluginInstallFailed": "Échec de l’installation de l’extension", + "PluginUninstallSuccess": "Extension supprimée", + "PluginUninstallFailed": "Échec de la suppression de l’extension", + "FfmpegPlugin": "Extension de transcodage FFmpeg", + "FfmpegPluginDescription": "Encodeur facultatif pour les enregistrements. Téléchargé uniquement si nécessaire ; le FFmpeg du système n’est pas utilisé. Taille du téléchargement : environ {size}.", + "Installed": "Installé", + "NotInstalled": "Non installé", + "DownloadFfmpeg": "Télécharger l’extension", + "UninstallFfmpeg": "Supprimer l’extension", + "FfmpegInstallSuccess": "Extension FFmpeg installée", + "FfmpegInstallFailed": "Échec du téléchargement de l’extension FFmpeg", + "FfmpegUninstallSuccess": "Extension FFmpeg supprimée", + "FfmpegUninstallFailed": "Échec de la suppression de l’extension FFmpeg", + "CustomTerminal": "Terminal personnalisé", + "CustomTerminalDescription": "Si votre terminal ne figure pas dans la liste intégrée, ajoutez-le ici.", + "AddCustomTerminal": "Ajouter un terminal personnalisé", + "EditCustomTerminal": "Modifier le terminal personnalisé", + "DeleteCustomTerminal": "Supprimer le terminal personnalisé", + "DeleteCustomTerminalConfirm": "Supprimer le terminal personnalisé « {name} » ?", + "CustomTerminalUpdateSuccess": "Terminal personnalisé mis à jour", + "CustomTerminalUpdateFailed": "Impossible de mettre à jour le terminal personnalisé", + "CustomTerminalName": "Nom", + "CustomTerminalNamePlaceholder": "Par exemple Ghostty Nightly", + "CustomTerminalPath": "Chemin de l’exécutable", + "CustomTerminalPathPlaceholder": "Par exemple /Applications/Ghostty.app/Contents/MacOS/ghostty", + "CustomTerminalTemplate": "Modèle de lancement", + "CustomTerminalTemplateHelp": "Vous pouvez utiliser des variables telles que {helper}, {protocol}, {username}, {host}, {port} et {value}.", + "CustomTerminalTemplatePlaceholder": "Par exemple -e {helper} {protocol} {username}{'@'}{host} -p {port} -P {value}", + "CustomTerminalCreateSuccess": "Terminal personnalisé créé", + "CustomTerminalCreateFailed": "Impossible de créer le terminal personnalisé", + "CustomTerminalMissingFields": "Veuillez renseigner le nom, le chemin et le modèle de lancement", + "TerminalSettings": "Composants", + "TerminalHosts": "Hôtes de terminal", + "TerminalHostsDescription": "Choisir la fenêtre de terminal qui doit s’ouvrir et exécuter la commande de connexion JumpServer.", + "TerminalOption": "Composants", + "TerminalOptionDescription": "Ouvrir ce protocole dans le terminal sélectionné dans les paramètres du terminal.", + "TerminalCurrentPrefix": "Terminal actuel : ", + "TerminalClients": "Clients de terminal dédiés", + "TerminalClientsDescription": "Ces clients communiquent directement en SSH ou Telnet avec leurs propres arguments de ligne de commande.", + "TerminalSettingsDescription": "Configurez la fenêtre de terminal à utiliser par JumpServer avant d’ouvrir des sessions SSH ou Telnet.", + "TerminalProgramDescription": "Choisissez le programme de terminal à utiliser par JumpServer pour les sessions en mode texte. Si nécessaire, configurez le chemin de l’exécutable pour l’élément sélectionné.", + "SshTerminalDescription": "Sélectionnez l’hôte de terminal qui doit ouvrir les sessions SSH.", + "TelnetTerminalDescription": "Sélectionnez l’hôte de terminal qui doit ouvrir les sessions Telnet." + }, + "ConnectMethodType": { + "BuiltIn": "Intégré", + "Application": "Application", + "RemoteApplication": "Application distante", + "VirtualApplication": "Application virtuelle" + }, + "ConnectMethod": { + "Empty": "Aucune méthode de connexion", + "BuiltinTerminal": "Terminal intégré", + "BuiltinTerminalDescription": "Les connexions en mode texte utilisent l’espace de terminal Koko, qui couvre les protocoles d’hôtes et de bases de données.", + "DatabaseWorkbench": "Atelier de base de données", + "DatabaseWorkbenchDescription": "Les connexions aux bases de données utilisent l’espace Chen, avec une arborescence de ressources, un éditeur SQL et des vues de résultats.", + "FileManager": "Gestionnaire de fichiers", + "FileManagerDescription": "La gestion de fichiers SFTP utilise l’espace de gestionnaire de fichiers Koko.", + "FileEditor": "Éditeur de fichiers", + "FileEditorDescription": "L’édition de fichiers SFTP utilise l’espace Éditeur de fichiers Koko.", + "Kubernetes": "Kubernetes", + "KubernetesDescription": "Les connexions Kubernetes utilisent l’espace d’interface Kubernetes Koko.", + "RemoteDesktop": "Bureau distant", + "RemoteDesktopDescription": "Les connexions RDP/VNC utilisent l’espace de bureau distant Lion.", + "BuiltinWebProxy": "Navigateur intégré", + "BuiltinWebProxyDescription": "Les actifs web utilisent la WebView du système, avec un trafic acheminé via Koko Web Proxy." + }, + "ConnectError": { + "DownloadRdpFailed": "Impossible de télécharger le fichier RDP", + "ConnectFailed": "Échec de la connexion", + "RequestTimeout": "La demande de connexion a expiré. Réessayez.", + "SessionClosed": "La session de connexion a été fermée. Veuillez vous reconnecter.", + "CredentialServiceUnavailable": "Le service d’identifiants (tel qu’OpenBao) est indisponible. Vérifiez son état et réessayez.", + "PersonalCredentialNotFound": "L’identifiant personnel sélectionné est introuvable. Sélectionnez-en un autre ou saisissez-en un manuellement.", + "AccountUnavailable": "Le compte sélectionné est indisponible. Sélectionnez un autre compte et réessayez.", + "PermissionExpired": "Votre autorisation de connexion a expiré. Contactez un administrateur pour la renouveler.", + "ManualAccountDenied": "Vous n’êtes pas autorisé à vous connecter à cet actif avec un compte manuel.", + "AssetUnavailable": "Cet actif est indisponible. Vérifiez son état et réessayez.", + "SessionInvalid": "Votre session n’est plus valide. Reconnectez-vous et réessayez.", + "CredentialChanged": "L’identifiant personnel a changé. Sélectionnez-le à nouveau et réessayez.", + "CertificateServiceUnavailable": "Le service de certificats SSH est indisponible. Réessayez plus tard ou contactez un administrateur.", + "ServiceError": "Le service est temporairement indisponible. Réessayez plus tard.", + "ProtocolUnavailable": "Cet actif ne prend pas en charge le protocole sélectionné. Sélectionnez-en un autre.", + "MethodUnavailable": "La méthode de connexion sélectionnée est indisponible. Sélectionnez-en une autre.", + "UsernameInvalid": "Saisissez un nom d’utilisateur valide.", + "SecretRequired": "Saisissez un mot de passe ou une clé pour vous connecter.", + "SecretTypeUnsupported": "Le type d’identifiant sélectionné n’est pas pris en charge par ce protocole.", + "AclFailed": "L’actif actuel est soumis à des politiques ACL et n’est pas accessible via le client. Utilisez la console web pour vous connecter.", + "MethodDisabled": "Cette méthode de connexion est désactivée", + "ClientNotFound": "Client de bureau introuvable. Installez-le ou configurez son chemin.", + "ClientLaunchFailed": "Impossible de lancer le client de bureau. Vérifiez son installation ou les permissions.", + "ClientExited": "Le client de bureau s’est fermé de manière inattendue.", + "RdpAppMissing": "Aucune application RDP configurée ou trouvée. Définissez le chemin de l’exécutable dans les paramètres.", + "VncAppMissing": "Aucune application VNC configurée ou trouvée. Définissez le chemin de l’exécutable dans les paramètres.", + "DbAppMissing": "Aucune application de base de données configurée ou trouvée. Définissez le chemin de l’exécutable dans les paramètres.", + "RdpAppFailed": "Impossible de démarrer l’application RDP.", + "VncAppFailed": "Impossible de démarrer l’application VNC.", + "DbAppFailed": "Impossible de démarrer l’application de base de données." + }, + "AclDialog": { + "LoginReview": "Validation de connexion", + "FaceVerify": "Vérification faciale", + "LoginReminder": "Rappel de connexion", + "NeedReview": "Cette connexion nécessite une approbation. Voulez-vous continuer ?", + "ReviewPending": "La demande d’approbation de connexion est en attente.", + "DoNotClose": "Ne fermez pas cette page", + "CancelConfirmTitle": "Annuler cette demande d’approbation ?", + "CancelConfirmDescription": "Le ticket d’approbation actuel sera fermé et la connexion ne se poursuivra pas.", + "ReviewRejected": "La demande d’approbation de connexion a été refusée.", + "ReviewClosed": "La demande d’approbation de connexion a été fermée.", + "NeedFaceVerify": "Une vérification faciale est requise pour se connecter à cet actif.", + "CompleteFaceVerify": "Veuillez effectuer la vérification faciale.", + "NeedFaceOnline": "Une surveillance faciale en ligne est requise pour se connecter à cet actif.", + "FaceOnlineNotSupported": "La méthode de connexion actuelle ne prend pas en charge la surveillance faciale en ligne.", + "NoFaceFeature": "Aucune caractéristique faciale trouvée. Enregistrez-les d’abord dans votre profil.", + "Reject": "Cette connexion a été refusée par la politique de contrôle d’accès.", + "AccountNotFound": "Compte introuvable.", + "Restricted": "Cette connexion est restreinte par une politique de contrôle d’accès.", + "ReviewGroupDescription": "Les actifs suivants nécessitent une approbation. Confirmez pour envoyer toutes les demandes.", + "FaceGroupDescription": "Les actifs suivants nécessitent une vérification faciale. Effectuez-les un par un dans cette boîte de dialogue.", + "FacePageUnavailable": "La page de vérification faciale n’a pas été chargée. Réessayez.", + "FaceRequestTimedOut": "La demande de vérification faciale a expiré. Réessayez.", + "FaceVerificationTimedOut": "La vérification faciale a expiré. Réessayez ou annulez.", + "FaceVerificationFailed": "La vérification faciale n’a pas été effectuée.", + "ErrorGroupDescription": "Les actifs suivants sont soumis au même type de restriction de connexion.", + "Assignees": "Responsables : {value}", + "SubmitAll": "Envoyer toutes les demandes de validation", + "Restore": "Restaurer la fenêtre d’approbation", + "MinimizedReady": "{count} demandes d’approbation sont prêtes à être envoyées", + "MinimizedPending": "{count} demandes d’approbation attendent une validation", + "MinimizedFinished": "{count} demandes d’approbation terminées ; cliquez pour voir les résultats", + "RequestFailed": "La demande d’approbation a échoué.", + "ExpandError": "Développer les détails de l’erreur", + "CollapseError": "Réduire les détails de l’erreur", + "Status": { + "ready": "Prêt", + "submitting": "Envoi en cours", + "pending": "En attente", + "verifying": "Vérification", + "approved": "Approuvé", + "rejected": "Rejeté", + "closed": "Fermé", + "failed": "Échec" + } + }, + "Update": { + "FoundNewVersion": "Nouvelle version disponible", + "CurrentVersion": "Version actuelle", + "ConfirmInstall": "Télécharger et installer maintenant ?", + "Start": "Démarrage de la mise à jour", + "Downloading": "Téléchargement en cours", + "Installing": "Installation de la mise à jour ; l’application peut redémarrer", + "Completed": "Mise à jour terminée", + "CompletedDesc": "Installation terminée. L’application redémarrera automatiquement ; sinon, redémarrez-la manuellement", + "Failed": "Échec de la mise à jour", + "FailedDesc": "Erreur lors du téléchargement ou de l’installation de la mise à jour. Réessayez plus tard", + "UpToDate": "À jour" + }, + "Face": { + "Title": "Centre de reconnaissance faciale", + "Description": "Enrôlement local, encodage, comparaison, détection du vivant et surveillance de présence.", + "Enrollment": "Enrôlement", + "Authentication": "Authentification", + "Monitoring": "Surveillance de présence", + "DesktopRequired": "Le moteur facial local est disponible uniquement dans le client de bureau Electron.", + "EngineReady": "Moteur prêt", + "EngineStopped": "Moteur arrêté", + "EngineStarting": "Démarrage du moteur", + "EngineUnavailable": "Moteur facial non installé", + "EngineStartFailed": "Impossible de démarrer le moteur facial", + "StartEngine": "Démarrer le moteur", + "EngineSettings": "Paramètres du moteur", + "ComputeDevice": "Périphérique de calcul", + "DeviceAuto": "Sélection automatique", + "ModelName": "Modèle InsightFace", + "DefaultThreshold": "Seuil de confiance par défaut", + "RecognitionThreshold": "Seuil de confiance", + "LivenessMode": "Mode de détection du vivant", + "LivenessMotion": "Détection de base par mouvement et texture", + "LivenessOnnx": "Modèle ONNX anti-usurpation", + "LivenessHybrid": "Détection hybride", + "LivenessOff": "Désactiver la détection du vivant", + "LivenessThreshold": "Seuil de détection du vivant", + "MinFaceSize": "Taille minimale du visage", + "AntiSpoofModel": "Modèle ONNX anti-usurpation", + "ApplyAndRestart": "Appliquer et démarrer", + "MotionWarning": "La détection par mouvement et texture est destinée au développement et au diagnostic. En production, utilisez un modèle ONNX anti-usurpation validé avec des défis d’actions aléatoires.", + "CameraPolicy": "Politique de caméra", + "CameraAny": "Autoriser toutes les caméras", + "CameraAllowlist": "Autoriser uniquement la caméra sélectionnée", + "CameraLabelPattern": "Motif du nom de périphérique", + "CameraPolicyWarning": "Les noms de périphériques et les valeurs deviceId du navigateur ne fournissent qu’une restriction faible. Une identité matérielle forte nécessite un SDK fournisseur, un numéro de série matériel ou une attestation signée.", + "SelectCamera": "Sélectionner une caméra", + "RefreshCamera": "Actualiser les caméras", + "CameraIdle": "Choisir un processus et démarrer la capture", + "CameraFailed": "Impossible d’accéder à la caméra", + "CameraRequired": "Sélectionnez une caméra.", + "PersonName": "Nom de la personne", + "PersonNamePlaceholder": "Saisir un nom", + "NameRequired": "Saisissez le nom d’une personne.", + "EnrollTimeout": "Délai d’enrôlement (secondes)", + "RequiredSamples": "Échantillons", + "RegisteredPeople": "Personnes enregistrées", + "SampleCount": "{count} échantillons", + "TargetPerson": "Personne cible", + "SelectPerson": "Sélectionner une personne", + "PersonRequired": "Sélectionnez une personne cible.", + "AuthTimeout": "Délai d’authentification (secondes)", + "MonitorInitTimeout": "Délai d’initialisation (secondes)", + "AwayAfter": "Rappel d’absence (secondes)", + "MonitorLivenessOnce": "L’initialisation effectue la reconnaissance, la détection du vivant et un défi d’action aléatoire. La surveillance vérifie ensuite uniquement que la personne reste dans le cadre, sans répéter la détection du vivant.", + "AuthSuccessAction": "Authentification réussie", + "AuthFailedAction": "Échec de l’authentification", + "MonitorInitFailedAction": "Échec de l’initialisation", + "MonitorAwayAction": "Délai d’absence dépassé", + "MonitorReturnedAction": "Personne revenue", + "ActionNone": "Aucune action", + "ActionRedirect": "Redirection", + "ActionApi": "Appeler l’API", + "ActionMethod": "Émettre un événement de méthode", + "ActionUrl": "URL cible", + "ActionMethodName": "Nom de l’événement de méthode", + "Start": "Démarrer le processus", + "Stop": "Arrêter le processus", + "FlowStarted": "Processus {mode} démarré.", + "FlowStartFailed": "Impossible de démarrer le processus", + "Recognition": "Reconnaissance", + "Similarity": "Similarité", + "Liveness": "Détection du vivant", + "NoFace": "Aucun visage détecté", + "Passed": "Réussi", + "Pending": "En attente", + "CompletedOnce": "Initialisation terminée", + "DebugMode": "Superposition de débogage", + "DebugModeDescription": "Afficher les cadres, les points de repère et les scores en direct sur l’aperçu de la caméra.", + "DrawBox": "Cadre de détection", + "DrawLandmarks": "Points de repère", + "DrawLiveness": "Résultat de détection du vivant", + "EventLog": "Événements du processus", + "NoEvents": "Aucun événement", + "PersonDeleted": "Tous les échantillons faciaux locaux de {name} ont été supprimés.", + "DeletePerson": "Supprimer la personne", + "DeletePersonConfirm": "Supprimer tous les échantillons faciaux locaux de {name} ? Cette action est irréversible.", + "DeleteFailed": "Échec de la suppression", + "MethodExecuted": "Événement de méthode {method} émis.", + "ApiSucceeded": "Rappel API réussi avec le statut {status}.", + "ApiFailed": "Échec du rappel API : {error}", + "Remote": { + "Title": "Reconnaissance faciale", + "EnrollTitle": "Enrôlement facial", + "AuthTitle": "Authentification faciale", + "MonitorTitle": "Surveillance faciale en ligne", + "PreparingCamera": "Préparation de la caméra", + "AllowCamera": "Autorisez l’accès à la caméra pour continuer", + "Connecting": "Connexion au service de reconnaissance faciale", + "ConnectingHint": "Gardez cette fenêtre ouverte pendant l’établissement du canal sécurisé", + "CameraReady": "Caméra prête", + "LookStraight": "Regardez la caméra de face et gardez votre visage dans le cadre", + "Guidance": { + "multiple_faces": "Plusieurs visages détectés. Restez seul dans le champ", + "face_missing": "Gardez l’intégralité de votre visage dans le champ de la caméra", + "too_far": "Rapprochez-vous de la caméra", + "replayed_frame": "L’image est statique ou répétée. Vérifiez la caméra et bougez naturellement", + "identity_mismatch": "Ce visage ne correspond pas à l’utilisateur enregistré", + "template_mismatch": "Le modèle facial a changé. Enregistrez à nouveau votre visage", + "liveness": "Vérification du vivant. Gardez une image nette et bougez naturellement" + }, + "CapturingSamples": "Capture des échantillons faciaux {accepted}/{required}", + "Completed": "Vérification faciale terminée", + "Failed": "Échec de la vérification faciale", + "Present": "Présent", + "Away": "Absent", + "AwayWarning": "La personne a quitté le champ de la caméra ; suspension de la session dans {seconds} s", + "SessionPaused": "La session surveillée est suspendue. Revenez devant la caméra pour la reprendre.", + "MonitorDisconnected": "Surveillance par caméra déconnectée ; les sessions protégées seront suspendues après le délai de grâce.", + "MonitoringHint": "La surveillance est active. La détection du vivant a été effectuée une fois lors de l’initialisation.", + "SessionCount": "{count} sessions", + "Retry": "Réessayer", + "Expand": "Développer", + "Challenge": { + "shake_head": "Tournez la tête à gauche et à droite, puis regardez devant vous", + "nod_head": "Hochez la tête une fois, puis regardez devant vous", + "open_mouth": "Ouvrez et fermez la bouche" + }, + "Error": { + "browser_unsupported": "Caméra indisponible", + "camera_denied": "Autorisation de caméra refusée", + "camera_not_found": "Aucune caméra trouvée", + "camera_failed": "Échec de la caméra", + "connection_failed": "Impossible de se connecter", + "connection_closed": "Connexion fermée", + "invalid_token": "Capture expirée", + "server_error": "Service facial indisponible" + }, + "ErrorHint": { + "browser_unsupported": "Ce navigateur ne peut pas accéder à une caméra.", + "camera_denied": "Autorisez l’accès à la caméra dans les paramètres du navigateur ou du système, puis réessayez.", + "camera_not_found": "Connectez une caméra disponible, puis réessayez.", + "camera_failed": "Impossible d’ouvrir la caméra. Vérifiez si une autre application l’utilise.", + "connection_failed": "Impossible de joindre le service de reconnaissance faciale.", + "connection_closed": "La connexion de reconnaissance faciale a été fermée.", + "invalid_token": "Cette capture a expiré. Réessayez pour créer une nouvelle capture.", + "server_error": "Le service de reconnaissance faciale est indisponible." + } + }, + "Status": { + "idle": "Inactif", + "enrolling": "Enrôlement en cours", + "enrollment_complete": "Enrôlement terminé", + "enrollment_failed": "Échec de l’enrôlement", + "authenticating": "Authentification en cours", + "auth_success": "Authentification réussie", + "auth_failed": "Échec de l'authentification", + "monitor_initializing": "Initialisation de la surveillance", + "monitor_started": "Surveillance démarrée", + "monitor_init_failed": "Échec de l’initialisation de la surveillance", + "monitoring": "Surveillance", + "monitor_away_warning": "Avertissement d’absence", + "monitor_away_timeout": "Délai d’absence dépassé", + "monitor_paused": "Session suspendue", + "finished": "Processus terminé" + }, + "Event": { + "enrollment_complete": "Enrôlement facial terminé pour {name}.", + "enrollment_failed": "Échec de l’enrôlement facial pour {name}.", + "auth_success": "Authentification réussie pour {name}.", + "auth_failed": "Échec de l’authentification pour {name}.", + "monitor_started": "{name} a réussi la vérification unique du vivant ; la surveillance a démarré.", + "monitor_init_failed": "Échec de l’initialisation de la surveillance pour {name}.", + "monitor_away_warning": "{name} a quitté le champ de la caméra.", + "monitor_away_timeout": "{name} est resté absent ; l’action configurée a été déclenchée.", + "monitor_returned": "{name} est revenu dans le champ de la caméra." + } + }, + "koko": { + "actions": { + "back": "Retour", + "cancel": "Annuler", + "close": "Fermer", + "confirm": "Confirmer", + "copy": "Copier", + "delete": "Supprimer", + "download": "Télécharger", + "paste": "Coller", + "rename": "Renommer", + "retry": "Réessayer", + "save": "Enregistrer", + "search": "Rechercher", + "upload": "Téléverser" + }, + "terminal": { + "title": "Composants", + "space": "Espace", + "enter": "Entrée", + "fileTransferInterrupted": "Transfert de fichier interrompu", + "downloadFailed": "Échec du téléchargement : le fichier distant n’existe pas ou le transfert a été annulé", + "uploadSkipped": "Téléversement ignoré : le fichier distant n’a pas été accepté", + "downloadProgress": "Téléchargement de {name} : {size} {percent}%", + "uploadProgress": "Téléversement de {name} : {size} {percent}% [{progress}]", + "uploadTitle": "Téléverser un fichier", + "uploadTips": "Sélectionner un fichier à téléverser", + "uploadComplete": "Téléversement terminé", + "downloaded": "Téléchargé", + "transferComplete": "Transfert de fichier terminé", + "transferSizeExceeded": "Limite de taille du transfert dépassée", + "selectFile": "Sélectionner un fichier", + "writeFailed": "Impossible d’écrire dans le terminal", + "failedCreateConnection": "Impossible de créer la connexion", + "themeSynced": "Thème synchronisé", + "joinedShare": "a rejoint la session partagée", + "leftShare": "a quitté la session partagée", + "permissionValid": "Autorisation valide", + "permissionExpired": "Autorisation expirée", + "pausedSession": "a suspendu la session", + "resumedSession": "a repris la session", + "removedFromShare": "Retiré de la session partagée", + "websocketConnectionFailed": "Impossible de se connecter à Koko. Vérifiez que le service Koko fonctionne et est accessible.", + "websocketClosed": "WebSocket fermé", + "closedByKoko": "Koko a terminé la session : {reason}", + "networkOffline": "Votre appareil est hors ligne. Terminal déconnecté (code {code})", + "connectionInterrupted": "Connexion interrompue (code {code}). Aucune notification de fermeture reçue de Koko ; vérifiez le réseau, le proxy ou le service Koko", + "connectionClosedWithoutReason": "Connexion fermée (code {code}) sans motif de fin de session fourni par Koko", + "closeReason": { + "sessionClosed": "Session terminée", + "idle": "Durée maximale d’inactivité dépassée", + "maxDuration": "Durée maximale de session atteinte", + "permissionExpired": "Autorisation d’accès expirée", + "admin": "Session terminée par un administrateur", + "assetClosed": "Connexion à l’actif terminée", + "connectFailed": "Échec de l’établissement de la connexion", + "readTimeout": "Délai d’attente de Koko dépassé pour les données du client", + "writeTimeout": "Délai d’envoi des données de Koko dépassé", + "writeFailed": "Koko n’a pas pu envoyer les données", + "serviceClosed": "Le service Koko a terminé la connexion", + "shareRemoved": "Retiré de la session partagée" + }, + "websocketConnectionClosed": "La connexion WebSocket est fermée. Actualisez la page.", + "invalidMessage": "Message de terminal invalide reçu", + "clipboardCopyDenied": "La copie depuis le terminal n’est pas autorisée", + "clipboardPasteDenied": "Le collage dans le terminal n’est pas autorisé", + "clipboardTextLimitExceeded": "Le texte de l’action {action} dépasse la limite de {limit} caractères", + "splitVertically": "Diviser verticalement", + "splitHorizontally": "Diviser horizontalement", + "shareLink": "Lien", + "verificationCode": "Code", + "shareLinkCopied": "Lien de partage copié", + "virtualKeyboard": "Clavier virtuel", + "noActiveTerminal": "Aucun terminal actif disponible", + "commandSuggestions": "Suggestions de commandes", + "commandSuggestionHistory": "Historique", + "commandSuggestionCatalog": "Catalogue" + }, + "drawer": { + "general": "Général", + "fileManagement": "Fichiers", + "shortcutKeys": "Touches de raccourci", + "up": "Haut", + "down": "Bas", + "left": "Gauche", + "right": "Droite", + "onlineUsers": "Utilisateurs en ligne", + "primary": "Principal", + "noOnlineUsers": "Aucun utilisateur en ligne", + "copyShareUrl": "Copier l’URL de partage", + "shareDisabled": "Le partage n’est pas activé pour cette session" + }, + "fileManagement": { + "consoleTitle": "Console de gestion des fichiers", + "expired": "Session de gestion des fichiers expirée", + "reconnect": "Reconnecter", + "back": "Retour", + "forward": "Suivant", + "home": "Répertoire personnel", + "refresh": "Actualiser", + "name": "Nom", + "size": "Taille", + "modifiedTime": "Modifié", + "permissions": "Autorisations", + "items": "{count} élément(s)", + "newFolder": "Nouveau dossier", + "newFile": "Nouveau fichier", + "nameTooLong": "Le nom ne peut pas dépasser {max} caractères", + "invalidName": "Le nom ne peut pas contenir \\ / : * ? \" {'<'} {'>'} {'|'}", + "newEntry": "Nouveau", + "editPath": "Modifier le chemin", + "filterCurrentDirectory": "Filtrer le dossier actuel", + "folderCreated": "Dossier {name} créé", + "fileCreated": "Fichier {name} créé", + "entryRenamed": "Renommé en {name}", + "entryDeleted": "{name} supprimé", + "entryDownloaded": "{name} téléchargé", + "operationFailed": "Échec de l’opération sur le fichier", + "refreshFailed": "Impossible d’actualiser la liste des fichiers", + "pathNotFound": "Le chemin n’existe pas", + "pathPermissionDenied": "Permission refusée", + "operationUnsupported": "Opération non prise en charge", + "remoteSftp": "SFTP distant", + "localSftp": "SFTP", + "connectRemoteSftp": "Connecter un serveur SFTP distant", + "disconnectAllRemote": "Tout déconnecter", + "addRemoteSftp": "Ajouter un serveur SFTP distant", + "remoteSftpHint": "Connecter un autre serveur SFTP pour transférer des fichiers entre les deux côtés", + "remoteConnected": "SFTP distant connecté", + "remoteConnectFailed": "Impossible de connecter le serveur SFTP distant", + "transferTarget": "Cible du transfert", + "transferToRemote": "Envoyer à la sélection", + "transferToLocal": "Envoyer à la cible actuelle", + "transferSuccess": "Fichier transféré", + "transferPartialSuccess": "Transféré vers {success}/{total} cibles", + "transferFailed": "Échec du transfert de fichier", + "folderTransferUnsupported": "Les téléversements et transferts de dossiers ne sont pas pris en charge. Sélectionnez des fichiers ordinaires.", + "downloadFolderConfirm": "Télécharger ce dossier ?", + "deleteConfirm": "Supprimer {name} ?", + "unsupportedAsset": "Cet actif ne prend pas en charge SFTP", + "unsupportedAssetDescription": "Choisissez un actif prenant en charge le protocole SFTP.", + "selectOrganization": "Sélectionner une organisation", + "currentOrganization": "Organisation actuelle", + "endpointUnavailable": "Le serveur n’a renvoyé aucun point de terminaison SFTP disponible", + "selectRemoteTarget": "Connectez d’abord un serveur SFTP cible à droite", + "selectFilesToTransfer": "Sélectionnez d’abord les fichiers à transférer", + "uploadedFiles": "{count} fichier(s) téléversé(s)", + "uploadedFilesPartial": "{success}/{total} fichier(s) téléversé(s)", + "localFiles": "Fichiers locaux", + "localUpload": "Téléversement local", + "preparingLocalFolder": "Préparation du dossier local", + "connectSftpServer": "Connecter un actif distant", + "lightweightHint": "Fichiers de la session actuelle (mode léger)", + "openProfessional": "Ouvrir dans le gestionnaire de fichiers complet", + "preconnectingAsset": "Connexion à {name}…", + "recentConnections": "Connexions SFTP récentes", + "dropSameEndpoint": "Impossible de déposer sur le même point de terminaison", + "pinTab": "Épingler", + "unpinTab": "Désépingler", + "searchAssets": "Rechercher des actifs pour le transfert de fichiers…", + "dropFiles": "Déposez les fichiers ici", + "dropFilesHint": "La sélection de fichiers ouvre la boîte d’envoi pour les téléverser par lot vers les cibles SFTP sélectionnées", + "dropTabToLeftPane": "Déplacer l’onglet vers le volet gauche", + "dropTabToRightPane": "Déplacer l’onglet vers le volet droit", + "chooseFiles": "Choisir des fichiers", + "connectionNotReady": "La connexion SFTP n’est pas prête", + "connectionReset": "Connexion SFTP réinitialisée", + "connectionFailed": "Échec de la connexion WebSocket SFTP", + "connectionClosed": "Connexion SFTP fermée", + "requestTimeout": "La demande SFTP a expiré. Vérifiez la connexion et réessayez", + "sessionExpired": "Session SFTP expirée", + "missingConnectionToken": "Jeton de connexion manquant", + "paneNotReady": "Le volet SFTP n’est pas prêt", + "tokenRequestTimedOut": "La demande de jeton SFTP a expiré", + "unavailableInSession": "SFTP est indisponible pour cette session", + "featureTour": "Présentation des fonctionnalités", + "connect": "Connecter", + "type": "Type", + "folder": "Dossier", + "file": "Fichier", + "sendTo": "Envoyer vers", + "sendToEllipsis": "Envoyer vers…", + "sendToOpposite": "Envoyer vers le côté opposé", + "copyTo": "Copier vers", + "releaseToCurrentDirectory": "Relâchez pour envoyer vers cette machine · répertoire actuel", + "selectedItems": "{count} sélectionnés", + "selectedPrefix": "Sélectionné", + "selectedSuffix": "éléments", + "selectedSizeTotal": "{size} au total", + "selectAllVisibleFiles": "Sélectionner tous les fichiers visibles", + "selectFile": "Sélectionner le fichier {name}", + "clearSelection": "Effacer la sélection", + "showHiddenFiles": "Afficher les fichiers cachés", + "hideHiddenFiles": "Masquer les fichiers cachés", + "sendToMultipleTargets": "Envoyer vers plusieurs cibles", + "selectTransferTarget": "Sélectionner {name} comme cible de transfert", + "sendToMultipleTargetsDescription": "Choisissez une ou plusieurs cibles SFTP connectées pour les fichiers sélectionnés.", + "collapseFileList": "Réduire la liste des fichiers", + "viewFileList": "Afficher la liste des fichiers", + "selectedFiles": "{count} fichier sélectionné | {count} fichiers sélectionnés", + "collapse": "Réduire", + "viewFiles": "Afficher les fichiers", + "targetMachines": "Machines cibles", + "selectOnline": "Sélectionner les machines en ligne", + "searchTargets": "Rechercher par machine ou organisation…", + "connected": "Connecté", + "disconnected": "Déconnecté", + "noMatchingTargets": "Aucune cible disponible ne correspond à votre recherche", + "nameConflictPolicy": "Politique de conflit de noms", + "askWhenNeeded": "Demander si nécessaire", + "transferTaskUnit": "tâches de transfert", + "distributionQueueHint": "Les tâches sont mises en file selon la charge des connexions. Suivez leur progression dans le centre de transfert.", + "distributeToTargets": "Distribuer vers {count} machine | Distribuer vers {count} machines", + "activeTransferCloseTitle": "Téléversements en cours", + "activeTransferCloseDescription": "Fermer cette session arrêtera les transferts de fichiers qui l’utilisent.", + "activeTransferCloseConfirm": "Fermer quand même" + }, + "sftpTransferCenter": { + "title": "Centre de transfert", + "queueTitle": "File de transfert", + "queueSummary": "{active} actifs · {attention} nécessitent votre attention", + "resizeHandle": "Redimensionner la file de transfert", + "collapse": "Réduire la file de transfert", + "columns": { + "file": "Fichier", + "direction": "Sens", + "progress": "Progression", + "rate": "Vitesse / temps restant", + "status": "Statut" + }, + "batchCount": "{count} lot de distribution | {count} lots de distribution", + "filters": { + "all": "Tous", + "active": "Actif", + "failed": "Échec", + "completed": "Terminé", + "canceled": "Annulé" + }, + "fileDistribution": "Distribution de fichiers", + "taskCount": "{count} tâche | {count} tâches", + "targetCount": "{count} cible | {count} cibles", + "completedCount": "{count} terminés", + "failedCount": "{count} en échec", + "cancelBatch": "Annuler la distribution", + "cancelTarget": "Annuler la cible", + "pauseBatch": "Suspendre la distribution", + "resumeBatch": "Reprendre la distribution", + "pauseTarget": "Suspendre la cible", + "resumeTarget": "Reprendre la cible", + "schedulingHint": "Les transferts sont limités par point de terminaison ; une connexion SFTP ne fait pas l’objet de lectures ou d’écritures simultanées.", + "topology": "Topologie des transferts", + "justNow": "À l’instant", + "minutesAgo": "Il y a {0} minute | Il y a {0} minutes", + "hoursAgo": "Il y a {0} heure | Il y a {0} heures", + "fileCount": "{count} fichier | {count} fichiers", + "targetMachineCount": "{count} cible | {count} cibles", + "completedTargets": "{count} terminés", + "failedTargets": "{count} en échec", + "failedTargetBanner": "{count} machine contient des éléments en échec | {count} machines contiennent des éléments en échec", + "retryFailedFiles": "Réessayer les fichiers en échec", + "overwrite": "Écraser", + "skip": "Ignorer", + "keepBoth": "Conserver les deux", + "source": "Source {source}", + "writingTo": "Écriture vers {path}", + "targetConflict": "Un fichier nommé {filename} existe déjà sur la cible", + "retryFile": "Réessayer le fichier", + "retryTarget": "Réessayer les fichiers en échec pour cette cible", + "batchProgress": "Progression de la distribution", + "targetProgress": "Progression du transfert vers {target}", + "fileProgress": "Progression du transfert de {file}", + "fileConflict": "Le fichier cible existe", + "fileConflictDetail": "La cible contient déjà le fichier {filename} ({size}, {date}). Choisissez une action." + }, + "sftpTour": { + "workspaceTitle": "Espace de fichiers SFTP", + "workspaceDescription": "Voici l’espace SFTP de l’actif actuel. Parcourez les fichiers ou ajoutez un serveur SFTP distant pour travailler côte à côte et transférer des fichiers entre les deux.", + "navigationTitle": "Chemin et navigation", + "navigationDescription": "Utilisez Précédent, Suivant et Dossier parent pour naviguer. Cliquez dans un espace vide du chemin pour le modifier. À droite, vous pouvez filtrer le dossier et afficher ou masquer les fichiers cachés.", + "fileActionsTitle": "Actions sur les fichiers", + "fileActionsDescription": "Créez des dossiers et des fichiers ou téléversez-en depuis votre ordinateur. Ces boutons à icônes correspondent au reste de la barre d’outils.", + "fileTableTitle": "Liste des fichiers", + "fileTableDescription": "Sélectionnez des fichiers pour les envoyer vers le côté opposé ou ouvrir la boîte de transfert. Faites un clic droit pour télécharger, renommer ou supprimer. Vous pouvez aussi faire glisser les fichiers entre les volets.", + "remoteConnectTitle": "Ajouter un serveur SFTP distant", + "remoteConnectDescription": "Connectez un autre actif SFTP pour passer en mode double volet. Utilisez les flèches centrales pour un transfert direct rapide ou Envoyer pour ouvrir la boîte de transfert.", + "transferCenterTitle": "Centre de transfert", + "transferCenterDescription": "Le badge Transfert de la barre d’état ouvre la file. Il reste désactivé tant qu’aucun transfert n’existe, puis affiche la progression, la vitesse, le temps restant et l’état.", + "progress": "Étape {current} sur {total}", + "next": "Suivant", + "previous": "Précédent", + "done": "Terminé" + }, + "localFile": { + "title": "Définir le dossier local", + "chooseFolder": "Choisir le dossier local", + "folder": "Dossier local", + "notReady": "Le dossier local n’est pas prêt", + "openFailed": "Impossible d’ouvrir le dossier local", + "permissionHint": "Choisissez un répertoire local une fois, comme dans les autres applications de bureau. Le gestionnaire de fichiers l’ouvrira ensuite directement.", + "resetDefault": "Restaurer le dossier par défaut", + "setupDescription": "Choisissez un répertoire local accessible avant d’ouvrir le gestionnaire de fichiers local pour la première fois.", + "setupHint": "Votre répertoire personnel est utilisé par défaut. Vous pouvez choisir Téléchargements, Bureau ou un répertoire dédié aux téléversements. Ce choix sera mémorisé.", + "recommendedSteps": "Étapes recommandées", + "stepChooseFolder": "Cliquez sur « Choisir le dossier local ».", + "stepSelectDirectory": "Sélectionnez le répertoire à utiliser pour les téléversements et téléchargements dans le sélecteur système.", + "stepResetDefault": "Cliquez sur « Restaurer le dossier par défaut » pour rétablir le paramètre initial.", + "quickHome": "Accueil", + "quickDesktop": "Bureau", + "quickDownload": "Téléchargements", + "revealInFinder": "Afficher dans le Finder", + "revealInFileExplorer": "Afficher dans l’Explorateur de fichiers", + "revealFailed": "Impossible d’afficher dans le gestionnaire de fichiers du système" + }, + "sftpEditor": { + "explorerTitle": "Éditeur de fichiers", + "plainText": "Texte brut", + "nameRequired": "Saisir un nom", + "nameCannotContainPathSeparator": "Le nom ne peut pas contenir de séparateurs de chemin", + "nameAlreadyExists": "Un fichier ou un répertoire portant ce nom existe déjà", + "renamePrompt": "Renommer", + "deleteConfirm": "Supprimer {name} ?", + "unsavedCloseConfirm": "{name} contient des modifications non enregistrées. Le fermer ?", + "newFile": "Nouveau fichier", + "newDirectory": "Nouveau répertoire", + "setAsRootDirectory": "Définir comme répertoire racine", + "restoreRootDirectory": "Restaurer la racine par défaut", + "refreshTree": "Actualiser l’arborescence des fichiers", + "directoryName": "Nom du répertoire", + "fileName": "Nom du fichier", + "loading": "Chargement", + "connectingAndLoading": "Connexion et chargement des fichiers…", + "checkingCapabilities": "Vérification de la prise en charge de l’édition sécurisée des fichiers…", + "capabilityUnavailableTitle": "L’édition de fichiers est indisponible", + "capabilityUpgradeRequired": "Cette version de Koko ne signale pas la prise en charge de l’enregistrement en ligne sécurisé. Mettez Koko à jour et réessayez.", + "capabilityPermissionRequired": "Cette session doit disposer des permissions de téléchargement et de téléversement pour utiliser l’éditeur.", + "unsupportedPreview": "Ce fichier ne peut pas encore être prévisualisé ou modifié en ligne", + "unsupportedEncoding": "L’encodage du fichier n’a pas pu être identifié de manière fiable. L’éditeur ne l’a pas ouvert afin d’éviter toute corruption", + "fileTooLarge": "Ce fichier dépasse {size} et peut affecter les performances de l’éditeur", + "openAnyway": "Ouvrir quand même", + "editorEmpty": "Sélectionnez un fichier à gauche pour commencer à le modifier", + "fileStatus": "{count} caractères · Ctrl/Cmd+S pour enregistrer", + "requestTimeout": "La demande SFTP a expiré. Vérifiez la connexion et réessayez", + "encoding": "Encodage du fichier", + "lineEnding": "Fin de ligne", + "wordWrap": "Activer ou désactiver le retour à la ligne", + "enableWordWrap": "Activer le retour à la ligne", + "disableWordWrap": "Le retour à la ligne est activé ; cliquez pour le désactiver", + "resizeExplorer": "Faites glisser pour redimensionner la liste des fichiers ; double-cliquez pour réinitialiser", + "unsavedChanges": "Non enregistré", + "saved": "Enregistré", + "viewChanges": "Voir les modifications locales", + "localChangesTitle": "Modifications locales de {name}", + "savedVersion": "Dernier enregistrement", + "currentVersion": "Modifications actuelles", + "addedLines": "{count} lignes ajoutées", + "removedLines": "{count} lignes supprimées", + "encodingChanged": "Encodage : {before} → {after}", + "lineEndingChanged": "Fin de ligne : {before} → {after}", + "onlyFilePropertiesChanged": "Le texte est inchangé ; seul l’encodage ou la fin de ligne a été modifié", + "contentTypeMismatch": "L’extension est {extension}, mais le contenu semble être du {detected} ; la coloration syntaxique a été ajustée", + "useExtensionLanguage": "Utiliser l’extension", + "languageMode": "Mode de langage", + "saveAll": "Tout enregistrer", + "saveAllShortcut": "Tout enregistrer (Ctrl/Cmd+Maj+S)", + "savedAll": "{count} fichier(s) enregistré(s)", + "cursorPosition": "Lig {line}, Col {column}", + "draftsRestored": "{count} brouillon(s) non enregistré(s) restauré(s)", + "draftsRestoredDescription": "Les brouillons restent sur cet appareil et sont supprimés automatiquement après un enregistrement réussi", + "draftRestored": "Ce brouillon local a été récupéré après la fermeture inattendue de l’éditeur", + "remoteChangedWhileEditing": "Le fichier distant a changé pendant votre modification", + "compareChanges": "Comparer les modifications", + "saveConflictTitle": "Le fichier distant a changé", + "saveConflictChanged": "{name} a été modifié après son ouverture. Rechargez la version distante ou écrasez-la.", + "saveConflictDeleted": "{name} a été supprimé à distance. Annulez ou recréez le fichier.", + "yourChanges": "Vos modifications", + "remoteChanges": "Version distante", + "diffTruncated": "La modification est volumineuse ; seule une partie de la première zone modifiée est affichée.", + "reloadRemote": "Recharger", + "overwriteRemote": "Écraser la version distante", + "keepMine": "Conserver ma version", + "recreateRemote": "Recréer le fichier", + "remoteFileDeleted": "Le fichier distant a été supprimé. Vos modifications locales restent dans cet onglet", + "saveInProgress": "Un fichier est encore en cours d’enregistrement. Attendez la fin avant de fermer", + "unsavedWorkspaceTitle": "Fichiers non enregistrés", + "unsavedWorkspaceCloseConfirm": "{count} fichier(s) de cet espace contiennent des modifications non enregistrées. La fermeture les abandonnera.", + "saveAndClose": "Enregistrer et fermer", + "saveAllAndClose": "Tout enregistrer et fermer", + "discardAndClose": "Abandonner et fermer", + "moveTabFirst": "Déplacer en premier", + "moveTabLast": "Déplacer en dernier", + "moveToRightEditor": "Déplacer vers le groupe d’éditeurs droit", + "moveToLeftEditor": "Déplacer vers le groupe d’éditeurs gauche", + "openToSide": "Ouvrir sur le côté", + "splitEditorRight": "Diviser l’éditeur à droite", + "closeRightEditor": "Fermer le groupe d’éditeurs droit", + "selectFileForPane": "Sélectionnez un fichier dans l’explorateur pour l’ouvrir ici", + "resizeEditorGroups": "Faites glisser pour redimensionner les groupes d’éditeurs ; double-cliquez pour les répartir également", + "dropToLeftEditor": "Ouvrir dans le groupe d’éditeurs gauche", + "dropToRightEditor": "Ouvrir dans le groupe d’éditeurs droit", + "dropToSplitRight": "Ouvrir dans un nouveau groupe d’éditeurs à droite", + "editorEmptyHint": "Déposez un fichier ici ou appuyez sur Ctrl/Cmd+P pour en ouvrir un", + "quickOpenShortcut": "Ouverture rapide (Ctrl/Cmd+P)", + "quickOpenPlaceholder": "Saisissez un nom ou un chemin de fichier", + "quickOpenNoResults": "Aucun fichier correspondant", + "quickOpenLoadedHint": "Recherche dans les fichiers ouverts et les répertoires chargés", + "opened": "Ouvrir", + "fileTree": "Arborescence des fichiers distants", + "previewTabHint": "Onglet d’aperçu ; double-cliquez pour le garder ouvert", + "pinTab": "Garder ouvert", + "editorHistory": "Historique de l’éditeur", + "navigateBack": "Revenir en arrière (Alt+←)", + "navigateForward": "Avancer (Alt+→)", + "reopenClosedEditor": "Rouvrir l’éditeur fermé (Ctrl/Cmd+Maj+T)", + "closeTab": "Fermer", + "closeOtherTabs": "Fermer les autres", + "closeTabsToLeft": "Fermer les onglets à gauche", + "closeTabsToRight": "Fermer les onglets à droite", + "closeAllTabs": "Fermer tous les onglets", + "closeTabsTitle": "Fermer {count} onglets", + "closeTabsConfirm": "{dirty} de ces fichiers contiennent des modifications non enregistrées. Les enregistrer avant de fermer ?" + }, + "workspace": { + "preparingSftp": "Préparation du gestionnaire de fichiers SFTP…", + "preparingFileEditor": "Préparation de l’éditeur de fichiers SFTP…", + "preparingConnection": "Préparation de la connexion Koko…", + "preparingRemoteDesktop": "Préparation de la connexion au bureau distant…", + "missingToken": "Paramètre de requête token manquant" + }, + "kubernetes": { + "name": "Kubernetes", + "connectionFailed": "Échec de la connexion Kubernetes", + "websocketConnectionFailed": "Échec de la connexion WebSocket Kubernetes", + "preparingConnection": "Préparation de la connexion Kubernetes…", + "connectCluster": "Connecter le cluster", + "resizeSidebar": "Redimensionner la barre latérale Kubernetes", + "recentContainers": "Conteneurs récents", + "clearRecentContainers": "Effacer les conteneurs récents", + "refreshTree": "Actualiser l’arborescence", + "filterContainers": "Filtrer les conteneurs", + "noMatchingContainers": "Aucun conteneur correspondant", + "loadingPods": "Chargement de la liste des pods…", + "empty": "Sélectionnez un conteneur à gauche ou connectez-vous au cluster" + }, + "search": { + "caseSensitive": "Respecter la casse", + "wholeWords": "Mots entiers", + "regex": "Utiliser des expressions régulières" + }, + "errors": { + "zmodemBlocked": "Zmodem bloqué : transfert en cours" + } + }, + "NotFound": { + "Title": "Page introuvable", + "Description": "Désolé, la page que vous recherchez est introuvable.", + "RequestedPath": "Chemin demandé", + "GoHome": "Aller à l’accueil" + }, + "ExecutionPlan": { + "title": "Plan d’exécution", + "explain": "Expliquer le plan", + "raw": "Plan brut", + "tree": "Arborescence du plan", + "rows": "Lignes", + "cost": "Coût", + "loading": "Estimation du plan…", + "empty": "Demandez un plan estimé pour examiner la requête.", + "emptyTree": "Aucun nœud de plan normalisé.", + "emptyRaw": "Aucun plan brut disponible.", + "modeEstimated": "Estimé", + "connectionDiscarded": "La connexion a été abandonnée car l’état de la session n’a pas pu être restauré.", + "auxiliaryResidual": "Des lignes auxiliaires de plan peuvent subsister pour cette requête.", + "status": { + "SUCCESS": "Prêt", + "RAW_ONLY": "Plan brut uniquement", + "UNSUPPORTED_STATEMENT": "Cette instruction ne peut pas être estimée", + "UNSUPPORTED_DATABASE": "Base de données non encore prise en charge", + "PREREQUISITES_UNMET": "Les prérequis ne sont pas remplis", + "ERROR": "Échec", + "CANCELLED": "Annulé", + "CONNECTION_INVALIDATED": "Connexion abandonnée" + }, + "estimatedRows": "Lignes estimées", + "estimatedCost": "Coût estimé", + "details": "Détails", + "hideDetails": "Masquer les détails", + "factJoinType": "Type de jointure", + "factSortKey": "Clé de tri", + "factStrategy": "Stratégie", + "factGroupKey": "Clé de regroupement", + "factIndexName": "Nom de l’index", + "factCteName": "Nom de la CTE", + "factSubplanName": "Nom du sous-plan", + "factParentRelationship": "Relation parente", + "factHashCondition": "Condition de hachage", + "factJoinFilter": "Filtre de jointure", + "factMergeCondition": "Condition de fusion", + "factIndexCondition": "Condition d’index", + "factFilter": "Filtre", + "factOutput": "Sortie", + "factStartupCost": "Coût de démarrage" + }, + "Chen": { + "TerminatedByAdmin": "Terminé par l’administrateur {name}", + "DatabaseExplorer": "Explorateur de bases de données", + "RecentlyTables": "Tables récentes", + "DatabaseWorkspace": "Espace de base de données", + "SelectDatabaseAction": "Sélectionnez une action de base de données pour commencer.", + "OpenDatabaseWorkspaceFailed": "Impossible d’ouvrir l’espace de base de données", + "OpeningDatabaseWorkspace": "Ouverture de l’espace de base de données", + "Retry": "Réessayer", + "ResizeDatabaseSidebar": "Redimensionner la barre latérale de base de données", + "CloseDatabaseExplorer": "Fermer l’explorateur de bases de données", + "RefreshResourceTree": "Actualiser l’arborescence des ressources", + "WaitingConnection": "En attente des informations de connexion…", + "AuthenticatingSession": "Authentification de votre session de base de données…", + "ConnectingDatabaseService": "Connexion au service de base de données…", + "PreparingSession": "Préparation de votre session de base de données…", + "LoadingDatabaseResources": "Chargement des ressources de la base de données…", + "WorkspaceSettings": "Paramètres de l’espace de travail", + "WorkspaceSettingsTitle": "Paramètres de l’espace Chen", + "WorkspaceSettingsDescription": "Les paramètres sont enregistrés automatiquement et appliqués à l’espace de base de données.", + "TableTabName": "Nom de l’onglet de table", + "SqlKeywordCompletion": "Complétion des mots-clés SQL", + "TabTitleOnly": "Nom de table uniquement", + "TabTitleTableSchema": "Table.Schéma", + "SqlCaseLower": "Minuscule", + "SqlCaseUpper": "Majuscule", + "ServerRequestFailedPrefix": "Échec de la requête au serveur Chen : ", + "DatabaseAddressPrefix": "Adresse de la base de données : ", + "DatabaseConnectionFailed": "Échec de la connexion à la base de données", + "WebSocketFailedPrefix": "Échec de la connexion WebSocket Chen : ", + "BasicInfo": "Informations de base", + "Columns": "Colonnes", + "Indexes": "Index", + "ForeignKeys": "Clés étrangères", + "Constraints": "Contraintes", + "Diagram": "Diagramme", + "Name": "Nom", + "Schema": "Schéma", + "Type": "Type", + "Table": "Table", + "View": "Voir", + "MaterializedView": "Vue matérialisée", + "Database": "Base de données", + "EstimatedRows": "Nombre de lignes estimé", + "Data": "Données", + "BuildFilter": "Créer un filtre", + "Filter": "Filtre", + "ApplyWhereCondition": "Appliquer la condition WHERE", + "ApplyWhereConditionShortcut": "Appliquer la condition WHERE (Entrée)", + "Apply": "Appliquer", + "AddRow": "Ajouter une ligne", + "DeleteRow": "Supprimer une ligne", + "UpdateRows": "Mettre à jour les lignes", + "RefreshBeforeSavingAgain": "Actualisez avant d’enregistrer à nouveau", + "LoadingTableMetadata": "Chargement des métadonnées de la table…", + "Nullable": "Nullable", + "Key": "Clé", + "ProtectedIndexesHint": "Les index de clé primaire et ceux associés à une contrainte ne peuvent pas être supprimés ici.", + "CreateIndex": "Créer un index", + "IndexChangesNotSupported": "La modification des index n’est pas prise en charge pour ce type de base de données", + "NewIndex": "Nouvel index", + "Unique": "Unique", + "Method": "Méthode", + "ManagedByConstraint": "Géré par une contrainte", + "ProtectedIndexCannotDrop": "Les index de clé primaire ou associés à une contrainte ne peuvent pas être supprimés ici", + "DropNamedIndex": "Supprimer l’index {name}", + "IndexMetadataNotSupported": "Les métadonnées d’index ne sont pas prises en charge.", + "NoIndexes": "Aucun index.", + "Column": "Colonne", + "References": "Références", + "ForeignKeyMetadataNotSupported": "Les métadonnées de clés étrangères ne sont pas prises en charge.", + "NoForeignKeys": "Aucune clé étrangère.", + "ConstraintMetadataNotSupported": "Les métadonnées de contraintes ne sont pas prises en charge.", + "Definition": "Définition", + "TableDiagram": "Diagramme de table", + "TableDdlNotSupported": "Le DDL des tables n’est pas pris en charge par ce fournisseur de métadonnées.", + "DropIndexTitle": "Supprimer l’index · {name}", + "DropIndexDescription": "Vérifiez le SQL qui supprimera cet index de {table}.", + "DropIndex": "Supprimer l’index", + "DropIndexWarning": "La suppression d’un index peut ralentir les requêtes et verrouiller brièvement les métadonnées de la base. Cette action ne peut pas être annulée ici.", + "IndexTitle": "Index · {name}", + "DefinitionUnavailable": "La définition n’est pas disponible sur le serveur.", + "Loading": "Chargement…", + "NoItems": "Aucun élément", + "LoadFailed": "Échec du chargement", + "NotLoaded": "Non chargé", + "DatabaseType": "Type de base de données", + "ObjectType": "Type d’objet", + "Schemas": "Schémas", + "Tables": "Tables", + "Views": "Vues", + "NotSupported": "Non pris en charge", + "LoadingSchemaMetadata": "Chargement des métadonnées du schéma…", + "LoadingCatalog": "Chargement du catalogue…", + "Yes": "Oui", + "No": "Non", + "Rows": "Lignes", + "Size": "Taille", + "Engine": "Moteur", + "CharacterSet": "Jeu de caractères", + "Collation": "Collation", + "Comment": "Description", + "RowCountEstimateHint": "Peut être une estimation selon la base de données", + "TableMetadataHint": "Le nombre de lignes peut être estimé par la base de données. Les statistiques, le moteur, le jeu de caractères et la collation nécessitent les métadonnées de table du serveur ; les champs indisponibles sont affichés sous la forme « - ».", + "SchemaIndexesUnavailable": "Ce fournisseur de métadonnées n’expose pas les index au niveau du schéma.", + "NoCatalogItems": "Aucun objet de type {type} trouvé dans le catalogue chargé.", + "DdlUnavailableTitle": "Le DDL de {object} n’est pas encore disponible", + "SchemaDdlNotSupported": "Ce fournisseur de métadonnées n’expose pas le DDL du schéma.", + "NoSchemaDdl": "Le serveur n’a renvoyé aucun DDL de schéma.", + "LoadingSchemaDiagram": "Chargement du diagramme du schéma…", + "SchemaDiagramNotSupported": "Le diagramme du schéma n’est pas pris en charge", + "TableColumnMetadataUnavailable": "Ce fournisseur n’expose pas les métadonnées des colonnes de table.", + "NoTablesInSchema": "Aucune table trouvée dans ce schéma.", + "Open": "Ouvrir", + "UploadSql": "Téléverser du SQL", + "SqlFormatFailed": "Échec du formatage SQL", + "SqlUploadFailed": "Échec du téléversement SQL", + "ChooseSqlFile": "Choisissez un fichier .sql.", + "SqlFileEmpty": "Le fichier SQL est vide.", + "LoadSqlFailed": "Impossible de charger le SQL", + "SaveSucceeded": "Enregistrement réussi", + "SaveSqlFailed": "Impossible d’enregistrer le SQL", + "DeleteSucceeded": "Suppression réussie", + "DeleteSqlFailed": "Impossible de supprimer le SQL", + "Stop": "Arrêter", + "RunSelected": "Exécuter la sélection", + "RunCurrent": "Exécuter l’instruction actuelle", + "Format": "Format", + "Context": "Contexte", + "Message": "Message", + "ResizeSqlEditorResults": "Redimensionner l’éditeur SQL et les résultats de requête", + "Results": "Résultats", + "RunQueryForResults": "Exécutez une requête pour afficher les résultats ici.", + "Running": "En cours d'exécution", + "Cancelling": "Annulation en cours", + "Completed": "Terminé", + "Failed": "Échec", + "Cancelled": "Annulé", + "RowsShownTruncated": "{shown} affichés · limités à {limit}", + "RowsShownTotal": "{shown} affichés · {total} au total", + "RowCount": "{count} ligne(s)", + "Console": "Console", + "ClearConsole": "Vider la console", + "RunSqlForTimeline": "Exécutez une instruction SQL pour démarrer la chronologie.", + "ResultNumber": "Résultat {number}", + "WaitingForResults": "En attente des résultats…", + "ResizeConsoleInput": "Redimensionner la zone de saisie de la console", + "ConsoleInputPlaceholder": "Entrée pour exécuter · Maj+Entrée pour un saut de ligne", + "NewTable": "Nouvelle table", + "RunStatement": "Exécuter l’instruction", + "CloseNamedResult": "Fermer {name}", + "StatementExecutedSuccessfully": "Instruction exécutée avec succès", + "Blank": "Vide", + "RefreshData": "Actualiser les données", + "ImportCsv": "Importer un CSV", + "ExportData": "Exporter les données", + "TableDisplaySettings": "Paramètres d’affichage du tableau", + "TableDisplay": "Affichage du tableau", + "TableDisplayHint": "Appliqué à toutes les grilles de résultats de base de données.", + "NullValues": "Valeurs NULL", + "MarkEmptyStrings": "Signaler les chaînes vides", + "ZebraStripes": "Lignes alternées", + "CellBorders": "Bordures des cellules", + "CompactRows": "Lignes compactes", + "VisibleFields": "Champs visibles", + "ShowAll": "Tout afficher", + "SearchFields": "Rechercher des champs", + "UnpinResult": "Désépingler le résultat", + "PinResult": "Épingler le résultat", + "RowsAffected": "{count} ligne(s) affectée(s)", + "RowRange": "Lignes {first}–{last} sur {total}", + "Elapsed": "Durée écoulée {duration}", + "RowsPerPage": "Lignes par page", + "FirstPage": "Première page", + "PreviousPage": "Page précédente", + "NextPage": "Page suivante", + "LastPage": "Dernière page", + "CreateTab": "Créer un onglet", + "ToggleLogConsole": "Afficher ou masquer la console des journaux", + "LogConsole": "Console des journaux", + "LogConsoleNewErrors": "Console des journaux · {count} nouvelles erreurs", + "RenameQuery": "Renommer la requête", + "QueryName": "Nom de la requête", + "Rename": "Renommer", + "ClearRecentTables": "Effacer les tables récentes", + "ResizeLogConsole": "Redimensionner la console des journaux", + "ClearLogConsole": "Vider la console des journaux", + "CloseLogConsole": "Fermer la console des journaux", + "NoSessionLogs": "Aucun journal dans cette session de base de données.", + "SaveSql": "Enregistrer le SQL", + "SelectSql": "Sélectionner du SQL", + "NoSavedSql": "Aucun SQL enregistré.", + "Content": "Contenu", + "Insert": "Insérer", + "DeleteSql": "Supprimer le SQL", + "CurrentPage": "Page actuelle", + "AllRows": "Toutes les lignes", + "Scope": "Portée", + "Export": "Exporter", + "SaveChanges": "Enregistrer les modifications", + "ReviewOperationsBeforeSaving": "Vérifiez les opérations préparées avant d’enregistrer.", + "Updates": "Mises à jour", + "Inserts": "Insertions", + "Deletes": "Suppressions", + "DiscardUnsavedChanges": "Abandonner les modifications non enregistrées ?", + "Discard": "Abandonner", + "UpdateSelectedRows": "Mettre à jour les lignes sélectionnées", + "Field": "Champ", + "SelectField": "Sélectionner un champ", + "Value": "Valeur", + "EnterNewValue": "Saisir la nouvelle valeur", + "SetValueNull": "Définir à NULL", + "BatchUpdateHint": "Le champ sélectionné sera défini à la même valeur dans {count} lignes. Les modifications restent en attente jusqu’à ce que vous cliquiez sur Enregistrer.", + "UpdateRowCount": "Mettre à jour {count} lignes", + "CreateTable": "Créer une table", + "CreateTableHint": "Définissez les colonnes de la table, puis envoyez le SQL généré.", + "Created": "Créé", + "TableName": "Nom de la table", + "TableNameExample": "par ex. users", + "LengthPrecision": "Longueur / précision", + "Primary": "Principal", + "ColumnName": "Nom de la colonne", + "RemoveColumnNumber": "Retirer la colonne {number}", + "AddAtLeastOneColumn": "Ajoutez au moins une colonne.", + "AddColumn": "Ajouter une colonne", + "SqlDialect": "Dialecte SQL : {dialect}", + "Default": "par défaut", + "PreviewAndSave": "Prévisualiser et enregistrer", + "CreateTableTitle": "Créer la table · {name}", + "AlterTableTitle": "Modifier la table · {name}", + "EditStructureHint": "Ajouter, renommer, modifier ou supprimer des colonnes.", + "Saved": "Enregistré", + "Status": "Statut", + "Delete": "Supprimer", + "New": "Nouveau", + "Existing": "Existant", + "RestoreNamedColumn": "Restaurer {name}", + "DeleteNamedColumn": "Supprimer {name}", + "Reset": "Réinitialiser", + "AlterTableDescription": "Ces instructions SQL seront exécutées sur {table}.", + "ApplyChanges": "Appliquer les modifications", + "DeleteExistingColumnsWarning": "{count} colonne(s) existante(s) seront définitivement supprimées, y compris leurs données.", + "SchemaDiagram": "Diagramme du schéma", + "DiagramSummary": "{visibleTables} tables sur {tables} · {visibleRelationships} relations sur {relationships}", + "ForeignKeyMetadataUnsupported": "Métadonnées de clés étrangères non prises en charge", + "SearchTables": "Rechercher des tables…", + "NoMatchingTables": "Aucune table correspondante.", + "ViewRelated": "Voir les tables liées", + "Focus": "Centrer", + "ClearDiagramSelection": "Effacer la sélection du diagramme", + "ZoomOut": "Zoom arrière", + "ZoomIn": "Zoom avant", + "FitDiagram": "Ajuster le diagramme", + "DirectlyRelatedTables": "{count} tables directement liées", + "NoRelatedTables": "Aucune table liée.", + "SelectNamedTable": "Sélectionner {name}", + "OpenNamedTable": "Ouvrir la table {name}", + "MoreColumns": "+{count} colonnes supplémentaires", + "NoColumns": "Aucune colonne", + "FilterRows": "Filtrer les lignes", + "SelectColumn": "Sélectionner une colonne", + "Operator": "Opérateur", + "EnterValue": "Saisir une valeur", + "NotRequired": "Non requis", + "InvalidFilter": "Filtre invalide", + "WherePreview": "Aperçu WHERE", + "ApplyFilter": "Appliquer le filtre", + "IndexName": "Nom de l’index", + "IndexNameExample": "par ex. users_email_idx", + "SelectIndexColumns": "Sélectionnez les colonnes dans l’ordre de l’index", + "UniqueIndex": "Index unique", + "SqlPreview": "Aperçu SQL", + "EmptyString": "Chaîne vide", + "MapCsvColumnsFailed": "Impossible d’associer les colonnes CSV", + "CsvTooLarge": "Les fichiers CSV de plus de 10 Mo ne sont pas pris en charge", + "CsvRowLimit": "Le CSV contient {count} lignes ; la limite actuelle est de {limit}", + "ReadCsvFailed": "Impossible de lire le fichier CSV", + "ChooseCsvFile": "Choisir un fichier CSV", + "CsvImportHint": "La première ligne doit contenir les noms des colonnes de la table. Maximum : 10 Mo / 10 000 lignes.", + "ChooseFile": "Choisir un fichier", + "CsvCannotImport": "Le CSV ne peut pas être importé", + "EmptyCsvValuesBecome": "Les valeurs CSV vides deviennent", + "ImportRowsStagedHint": "Les lignes importées sont préparées dans la grille. Vérifiez-les, puis cliquez sur Enregistrer pour prévisualiser et valider les modifications dans la base de données.", + "ImportRowCount": "Importer {count} lignes", + "ImportRows": "Importer des lignes", + "WhereConditionPlaceholder": "Condition WHERE, par ex. status = 'active'", + "TableWhereCondition": "Condition WHERE de la table", + "ClearWhereCondition": "Effacer la condition WHERE", + "RowNumber": "Numéro de ligne", + "SelectionCopied": "Sélection copiée", + "InsertSqlCopied": "SQL INSERT copié", + "UpdateSqlCopied": "SQL UPDATE copié", + "CopySelection": "Copier la sélection", + "CopyInsertSql": "Copier le SQL INSERT", + "CopyInsertSqlUnavailable": "Copier le SQL INSERT (métadonnées de table indisponibles)", + "CopyUpdateSql": "Copier le SQL UPDATE", + "CopyUpdateSqlUnavailable": "Copier le SQL UPDATE (clé primaire indisponible)", + "CollapsePlanNode": "Réduire le nœud du plan", + "ExpandPlanNode": "Développer le nœud du plan", + "FilterOperator": { + "equals": "Égal à (=)", + "not_equals": "Différent de (!=)", + "contains": "Contient", + "not_contains": "Ne contient pas", + "greater_than": "Supérieur à (>)", + "greater_than_or_equal": "Supérieur ou égal à (>=)", + "less_than": "Inférieur à (<)", + "less_than_or_equal": "Inférieur ou égal à (<=)", + "is_null": "Est NULL", + "is_not_null": "N’est pas NULL" + }, + "LoadNodeFailed": "Impossible de charger le nœud", + "StartDatabaseWorkspaceFailed": "Impossible de démarrer l’espace de base de données", + "ExportDownloaded": "Export téléchargé", + "ExportCanceled": "Export annulé", + "DatabaseSessionNotReady": "La session de base de données n’est pas prête", + "ConsoleWebSocketNotConnected": "Le WebSocket de la console n’est pas connecté", + "DataViewRequestInProgress": "Requête de vue de données en cours", + "WaitForCurrentRequest": "Attendez la fin de la requête actuelle avant de continuer.", + "DiscardDataViewEditsMessage": "Cette vue de données contient des modifications non enregistrées. Les abandonner et continuer ?", + "PreviewFailed": "Échec de l’aperçu", + "PreviewOutOfDate": "L’aperçu est obsolète", + "PreviewOutOfDateDescription": "Les données ont changé après le début de l’aperçu. Prévisualisez à nouveau les modifications.", + "SaveOutcomeUnknown": "Résultat de l’enregistrement inconnu", + "SaveOutcomeUnknownDescription": "La connexion a été réinitialisée. Actualisation avec une nouvelle connexion pour vérifier l’état de la base de données ; ne réessayez pas avant la fin.", + "SaveFailed": "Échec de l’enregistrement", + "ConnectionResetRefreshBeforeSaving": "La connexion a été réinitialisée. Actualisez avant d’enregistrer à nouveau ; l’état de la transaction et de la session a été perdu.", + "RefreshSkippedForLocalEdits": "L’actualisation a été ignorée afin de conserver les modifications locales plus récentes.", + "SaveAppliedNewerEditsKept": "Enregistrement appliqué ; modifications plus récentes conservées", + "SaveSucceededConnectionReset": "Enregistrement réussi ; connexion réinitialisée", + "ChangesAppliedConnectionReset": "Modifications de la base appliquées ; connexion réinitialisée", + "ConnectionResetAuditFailed": "L’état de la transaction et de la session a été perdu. L’enregistrement d’audit a aussi échoué ; ne réessayez pas l’enregistrement. Actualisation avec une nouvelle connexion.", + "ConnectionResetRefreshing": "L’état de la transaction et de la session a été perdu. Actualisation avec une nouvelle connexion.", + "SaveAppliedAuditFailed": "Enregistrement appliqué ; échec de l’audit", + "DatabaseCommitSucceededNoRetry": "La validation dans la base de données a réussi. Ne réessayez pas l’enregistrement.", + "TransactionChangesAppliedNoRetry": "Les modifications ont été appliquées à la transaction actuelle. Ne réessayez pas l’enregistrement.", + "DatabaseChangesApplied": "Modifications de la base de données appliquées", + "CreateIndexFailed": "Impossible de créer l’index", + "DropIndexFailed": "Impossible de supprimer l’index", + "DatabaseRejectedIndexStatement": "La base de données a rejeté l’instruction d’index.", + "IndexCreated": "Index créé", + "IndexDropped": "Index supprimé", + "DatabaseRejectedCreateTable": "La base de données a rejeté l’instruction CREATE TABLE.", + "CreateTableFailed": "Impossible de créer la table", + "TableCreated": "Table créée", + "DatabaseRejectedAlterTable": "La base de données a rejeté l’instruction ALTER TABLE.", + "UpdateTableStructureFailed": "Impossible de modifier la structure de la table", + "TableStructureUpdated": "Structure de la table mise à jour", + "OpenTableFailed": "Impossible d’ouvrir la table", + "DiscardTableStructureChangesMessage": "Cette structure de table contient des modifications non appliquées. Les abandonner et fermer l’onglet ?", + "NoDatabaseContext": "Aucun contexte de base de données", + "SelectDatabaseContextFirst": "Sélectionnez d’abord un nœud de source de données, de base, de schéma ou de table, puis créez un onglet.", + "LoadActionsFailed": "Impossible de charger les actions", + "ActionFailed": "Échec de l’action", + "RefreshDatabaseTreeTryAgain": "Actualisez l’arborescence de la base de données et réessayez.", + "TableMetadataUnavailable": "Métadonnées de table indisponibles", + "ResolvePendingRowChangesFirst": "Traitez d’abord les modifications de lignes en attente", + "ResolvePendingRowChangesDescription": "Enregistrez ou annulez les modifications de lignes en attente de la table avant de modifier ses index.", + "IndexOperationInProgress": "Opération sur un index en cours", + "ConsoleUnavailable": "Console indisponible", + "TableNoLongerAvailable": "La table n’est plus disponible", + "RefreshDatabaseTreeOpenTableAgain": "Actualisez l’arborescence de la base de données et rouvrez la table.", + "ReadSqlFileFailed": "Impossible de lire le fichier SQL", + "SqlFileUploaded": "Fichier SQL téléversé", + "RefreshRequired": "Actualisation requise", + "RefreshRequiredAfterConnectionReset": "Actualisez les données avant d’enregistrer à nouveau, car la connexion précédente a été réinitialisée.", + "TablesFolderUnavailable": "Le dossier des tables est indisponible", + "NamedTableUnavailable": "La table {name} est indisponible", + "ExecuteWithoutWhereTitle": "Exécuter l’instruction sans WHERE ?", + "ExecuteWithoutWhereDescription": "Une ou plusieurs instructions UPDATE ou DELETE n’ont pas de clause WHERE et peuvent affecter toutes les lignes d’une table. Confirmez que vous souhaitez les exécuter.", + "ExecuteAnyway": "Exécuter quand même", + "StatementResult": "Résultat de l’instruction", + "Query": "Requête", + "DataView": "Vue de données", + "NewQuery": "Nouvelle requête", + "NewConsole": "Nouvelle console", + "SavedSqlCount": "{count} requêtes SQL enregistrées", + "DeleteNamedSql": "Supprimer « {name} » ?", + "LogEntries": "{count} entrées", + "EditStructure": "Modifier la structure", + "DoubleClickToRename": "{title} · Double-cliquez pour renommer", + "SqlPreviewDescription": "Vérifiez le SQL qui sera exécuté sur la base de données.", + "InvalidTableDefinition": "Complétez le nom de la table et les définitions des colonnes.", + "InvalidTableStructureChanges": "Vérifiez les modifications de structure de la table.", + "InvalidIndexDefinition": "Complétez le nom de l’index et la sélection des colonnes.", + "InvalidCsvFile": "Le fichier CSV est invalide ou illisible.", + "CopyOperationFailedDescription": "Impossible de copier les données sélectionnées.", + "TableWithoutPrimaryKeyNotEditable": "Cette table n’a pas de clé primaire ; l’ajout, la modification et la suppression de données ne sont donc pas encore pris en charge.", + "ClickHouseDataViewReadOnly": "Les vues de données ClickHouse sont actuellement en lecture seule. L’ajout, la modification et la suppression de données ne sont pas pris en charge ; les requêtes, les filtres et les exports restent disponibles." + } +} diff --git a/i18n/ui.ts b/i18n/ui.ts new file mode 100644 index 000000000..f40a3e7b5 --- /dev/null +++ b/i18n/ui.ts @@ -0,0 +1,7 @@ +import { en, es, fr, ja, ko, pt_br, ru, vi, zh_cn, zh_tw } from "@nuxt/ui/locale"; +import { normalizeLanguageCode } from "./language"; + +const uiLocales = { en, es, fr, ja, ko, pt_br, ru, vi, zh: zh_cn, zh_hant: zh_tw }; +export function getUiLocale(language: string | null | undefined) { + return uiLocales[normalizeLanguageCode(language)]; +} diff --git a/nuxt.config.ts b/nuxt.config.ts index d8b684afd..8acc0b3ba 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -94,6 +94,7 @@ export default defineNuxtConfig({ locales: [ { code: "zh", name: "简体中文", language: "zh-CN", file: "zh.json" }, { code: "zh_hant", name: "繁體中文", language: "zh-TW", file: "zh_hant.json" }, + { code: "fr", name: "Français", language: "fr", file: "fr.json" }, { code: "en", name: "English", language: "en", file: "en.json" }, { code: "ja", name: "日本語", language: "ja", file: "ja.json" }, { code: "pt_br", name: "Português (Brasil)", language: "pt-BR", file: "pt_br.json" }, diff --git a/packages/web-proxy/package.json b/packages/web-proxy/package.json index 5bb8edf67..defb954aa 100644 --- a/packages/web-proxy/package.json +++ b/packages/web-proxy/package.json @@ -13,6 +13,7 @@ "peerDependencies": { "electron": "^44.0.0", "vue": "^3.5.0", - "@nuxt/ui": "^4.11.0" + "@nuxt/ui": "^4.11.0", + "vue-i18n": "^11.4.8" } } diff --git a/packages/web-proxy/src/WebProxySurface.vue b/packages/web-proxy/src/WebProxySurface.vue index 386dcbe2e..980899aef 100644 --- a/packages/web-proxy/src/WebProxySurface.vue +++ b/packages/web-proxy/src/WebProxySurface.vue @@ -1,6 +1,8 @@