diff --git a/src/apps/officer-web/lib/locales.ts b/src/apps/officer-web/lib/locales.ts index 96126a69..c1ee8d54 100644 --- a/src/apps/officer-web/lib/locales.ts +++ b/src/apps/officer-web/lib/locales.ts @@ -19,7 +19,7 @@ const autonym = (code: string): string => { return name.charAt(0).toUpperCase() + name.slice(1); }; -export const SHIPPED_LOCALES: ShippedLocale[] = ['en-GB', 'pt-PT'].map((code) => ({ +export const SHIPPED_LOCALES: ShippedLocale[] = ['en-GB', 'pt-PT', 'fr-FR'].map((code) => ({ code, label: autonym(code), })); diff --git a/src/apps/officer-web/locales/fr-FR.json b/src/apps/officer-web/locales/fr-FR.json new file mode 100644 index 00000000..700adf39 --- /dev/null +++ b/src/apps/officer-web/locales/fr-FR.json @@ -0,0 +1,704 @@ +{ + "common": { + "save": "Enregistrer", + "saving": "Enregistrement...", + "cancel": "Annuler" + }, + "settings": { + "profile": { + "userData": { + "email": "E-mail", + "username": "Nom d'utilisateur", + "name": "Nom", + "namePlaceholder": "Votre nom", + "updated": "Profil mis à jour", + "updateFailed": "Échec de la mise à jour du profil", + "selectImage": "Veuillez sélectionner un fichier image", + "imageTooLarge": "L'image doit faire moins de 384 Ko" + }, + "apiKeys": { + "intro": "Une clé API vous connecte comme vous-même, sans mot de passe et sans expirer. Une clé fonctionne partout — une clé n'est pas liée à une application. Donner sa propre clé à chaque appareil sert seulement à pouvoir en couper une sans toucher aux autres. Envoyez-la comme", + "loading": "Chargement des clés…", + "mintedTitle": "Clé pour “{{name}}” — affichée une seule fois", + "savedIt": "Je l'ai enregistrée", + "copyKey": "Copier la clé", + "copied": "Clé copiée", + "copyFailed": "Impossible de copier — sélectionnez et copiez manuellement", + "newKey": "Nouvelle clé", + "namePlaceholder": "iPhone, application Musique, CLI du portable…", + "create": "Créer", + "createFailed": "Impossible de créer la clé", + "noneYet": "Aucune clé pour l'instant.", + "noneInUse": "Aucune clé utilisée.", + "revoked": "révoquée", + "expired": "expirée", + "lastUsed": "dernière utilisation {{date}}", + "neverUsed": "jamais utilisée", + "forget": "Supprimer cet enregistrement", + "forgetFailed": "Impossible de le supprimer", + "revoke": "Révoquer", + "revokeDone": "Révoquée — tout ce qui utilisait cette clé est déconnecté", + "revokeFailed": "Impossible de révoquer", + "hideDead": "Masquer les révoquées et expirées", + "showDead": { + "one": "Afficher {{count}} clé révoquée ou expirée", + "other": "Afficher {{count}} clés révoquées ou expirées" + } + }, + "voice": { + "label": "Voix", + "refresh": "Actualiser les voix", + "serverDefault": "Valeur par défaut du serveur", + "hint": "Choisissez une voix pour la synthèse vocale. Laissez la valeur par défaut du serveur pour utiliser la voix configurée par l'administrateur.", + "stop": "Arrêter", + "listen": "Écouter" + }, + "dock": { + "visible": "Visibles", + "hidden": "Masqués", + "dragToShow": "Faites glisser des éléments ici pour les afficher dans le Dock", + "allVisible": "Tous les éléments sont visibles", + "reset": "Rétablir les valeurs par défaut" + }, + "languages": { + "spokenLabel": "Langues que vous parlez", + "addLanguage": "Ajouter une langue...", + "defaultLabel": "Langue par défaut", + "defaultHint": "Utilisée pour la localisation de l'interface.", + "translateToLabel": "Traduire vers", + "translateToHint": "Langue cible pour traduire le contenu que vous ne parlez pas." + }, + "changePassword": { + "currentPasswordLabel": "Mot de passe actuel", + "currentPasswordPlaceholder": "Mot de passe actuel", + "newPasswordLabel": "Nouveau mot de passe", + "newPasswordPlaceholder": "Nouveau mot de passe", + "confirmPasswordLabel": "Confirmer le nouveau mot de passe", + "confirmPasswordPlaceholder": "Confirmez le nouveau mot de passe", + "changeButton": "Changer le mot de passe", + "changingButton": "Modification...", + "passwordChanged": "Mot de passe modifié", + "passwordChangeFailed": "Échec de la modification du mot de passe" + }, + "models": { + "defaultModel": "Modèle par défaut", + "sameAsChat": "Identique à la valeur par défaut du Chat", + "taskDefaultsSaved": "Valeurs par défaut des tâches enregistrées", + "saveFailed": "Échec de l'enregistrement des paramètres", + "defaultChat": "Modèle par défaut du Chat", + "defaultChatHint": "Utilisé au démarrage d'un nouveau Chat depuis l'écran d'accueil", + "defaultProject": "Modèle par défaut du projet", + "defaultProjectHint": "Utilisé au démarrage d'un nouveau Chat dans un Dashboard de projet", + "defaultTask": "Modèle par défaut des tâches", + "defaultTaskHint": "Utilisé lors de l'exécution de tâches depuis l'explorateur de fichiers", + "aiDefaultsSaved": "Valeurs par défaut des modèles IA enregistrées" + } + }, + "sections": { + "profile": { + "label": "Profil", + "hint": "avatar, nom, e-mail", + "description": "Avatar, nom et e-mail" + }, + "changePassword": { + "label": "Changer le mot de passe", + "description": "Mettez à jour votre mot de passe" + }, + "apiKeys": { + "label": "Clés API", + "hint": "clés qui vous connectent comme vous-même", + "description": "Clés qui vous connectent comme vous-même, révocables une à une" + }, + "languages": { + "label": "Langues", + "hint": "parlées et traduction", + "description": "Parlées, par défaut et traduction" + }, + "voice": { + "label": "Voix", + "hint": "synthèse vocale", + "description": "Préférence de voix pour la synthèse vocale" + }, + "dock": { + "label": "Dock", + "hint": "choisir et réorganiser", + "description": "Choisir et réorganiser les éléments du Dock" + }, + "myModels": { + "label": "Mes modèles", + "description": "Afficher ou masquer des modèles pour vous-même" + }, + "channelModels": { + "label": "Modèles du canal", + "description": "Modèles accessibles aux comptes non propriétaires" + }, + "defaultModels": { + "label": "Modèles par défaut", + "description": "Modèles par défaut pour le Chat, les projets et les tâches" + }, + "providers": { + "label": "Fournisseurs", + "hint": "harnesses", + "description": "Fournisseurs d'IA distants et locaux" + }, + "tts": { + "label": "Synthèse vocale", + "description": "Fournisseur et voix pour la synthèse vocale" + }, + "stt": { + "label": "Reconnaissance vocale", + "description": "URL du serveur Whisper" + }, + "ocr": { + "label": "OCR", + "description": "Modèle de vision pour l'extraction de texte" + }, + "maintenance": { + "label": "Reconstruire et redémarrer", + "hint": "maintenance", + "description": "Charger les modifications sans terminal" + }, + "smtp": { + "label": "SMTP", + "hint": "courrier sortant", + "description": "Fournisseur de distribution d'e-mails" + }, + "googleOauth": { + "label": "Google OAuth", + "description": "Client ID et secret pour les API Google" + }, + "apify": { + "label": "Apify", + "description": "Jeton API pour les acteurs de web scraping" + }, + "davSync": { + "label": "Synchronisation Calendrier et Contacts", + "hint": "mots de passe d'application", + "description": "Mots de passe par appareil pour les clients CalDAV/CardDAV" + }, + "accounts": { + "label": "Comptes", + "hint": "utilisateurs", + "description": "Qui a accès, et à quoi" + }, + "permissions": { + "label": "Permissions", + "description": "Ce que chaque rôle peut atteindre" + } + }, + "searchPlaceholder": "Rechercher dans les paramètres…", + "selectSection": "Sélectionnez une section", + "groups": { + "models": "Modèles", + "defaults": "Valeurs par défaut", + "providers": "Fournisseurs", + "ai": "IA", + "maintenance": "Maintenance", + "email": "E-mail" + }, + "nav": { + "profile": "Profil", + "ai": "IA", + "system": "Système", + "integrations": "Intégrations", + "users": "Utilisateurs" + }, + "ai": { + "noModels": "Aucun modèle disponible. Configurez les clés API dans Fournisseurs.", + "dragToShow": "Faites glisser des modèles ici pour les afficher", + "allVisible": "Tous les modèles sont visibles", + "dragToEnable": "Faites glisser des modèles ici pour les activer", + "allEnabled": "Tous les modèles sont activés", + "visible": "Visibles", + "hidden": "Masqués", + "enabled": "Activés", + "disabled": "Désactivés", + "refresh": "Actualiser les modèles depuis les fournisseurs", + "refreshed": "Modèles actualisés", + "refreshFailed": "Échec de l'actualisation des modèles" + }, + "integrations": { + "enterprise": "Entreprise", + "personal": "Personnel" + }, + "server": { + "loading": "Chargement…", + "provider": "Fournisseur", + "model": "Modèle", + "voice": "Voix", + "ocr": { + "serverUrl": "URL du serveur du modèle de vision", + "refreshModels": "Actualiser les modèles", + "selectModel": "Sélectionnez un modèle", + "saved": "Paramètres OCR enregistrés", + "saveFailed": "Échec de l'enregistrement des paramètres OCR", + "reachable": "Le serveur du modèle de vision est accessible" + }, + "stt": { + "serverUrl": "URL du serveur Whisper", + "saved": "Paramètres de reconnaissance vocale enregistrés", + "saveFailed": "Échec de l'enregistrement des paramètres de reconnaissance vocale", + "reachable": "Le serveur Whisper est accessible" + }, + "tts": { + "openaiCompatible": "Compatible OpenAI (Kokoro, OpenAI, etc.)", + "elevenLabs": "ElevenLabs", + "refreshVoices": "Actualiser les voix", + "selectVoice": "Sélectionnez une voix", + "saved": "Paramètres de synthèse vocale enregistrés", + "saveFailed": "Échec de l'enregistrement des paramètres de synthèse vocale", + "playing": "Lecture de l'audio de test", + "testFailed": "Échec du test — impossible de joindre le serveur de synthèse vocale" + }, + "smtp": { + "mailhog": "MailHog (test local)", + "resend": "Resend", + "custom": "SMTP personnalisé", + "apiKey": "Clé API", + "host": "Hôte", + "port": "Port", + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "tls": "TLS / SSL", + "fromName": "Nom de l'expéditeur", + "fromEmail": "E-mail de l'expéditeur", + "sendTest": "Envoyer un e-mail de test", + "userExample": "utilisateur@exemple.com", + "recipientExample": "destinataire@exemple.com", + "saved": "Paramètres SMTP enregistrés", + "saveFailed": "Échec de l'enregistrement des paramètres SMTP", + "connected": "Connexion réussie", + "testSent": "E-mail de test envoyé à {{email}}" + }, + "maintenance": { + "updateTitle": "Mettre à jour la plateforme", + "updateHint": "Ne change rien à ce qui est en cours d'exécution. Ce dont la mise à jour a besoin est listé ici, et le choix vous revient.", + "rebuild": "Reconstruire le frontend", + "restart": "Redémarrer le serveur", + "upToDate": "Déjà à jour", + "rebuilt": "Frontend reconstruit — actualisez cette page pour le voir", + "restarting": "Redémarrage — la page se reconnectera d'elle-même", + "pulled": { + "one": "{{count}} commit récupéré", + "other": "{{count}} commits récupérés" + } + }, + "harnesses": { + "piMono": "Pi Mono", + "checking": "Vérification…", + "localProviders": "Fournisseurs locaux", + "remoteProviders": "Fournisseurs distants", + "connect": "Connecter", + "authenticate": "Authentifier", + "connectLocal": "Connecter un fournisseur local", + "connectProvider": "Connecter un fournisseur", + "none": "Aucun fournisseur trouvé.", + "updatePiMono": "Mettre à jour Pi Mono", + "removeProvider": "Supprimer le fournisseur", + "namePlaceholder": "Nom (ex. Mon Ollama)", + "username": "Nom d'utilisateur", + "password": "Mot de passe", + "apiKey": "Clé API", + "editKey": "Modifier la clé", + "disconnect": "Déconnecter le fournisseur", + "saveKey": "Enregistrer la clé", + "enterKey": "Saisissez la clé API", + "search": "Rechercher des fournisseurs…", + "connectFailed": "Échec de la connexion", + "authFailed": "Échec de l'authentification", + "saveFailed": "Échec de l'enregistrement du fournisseur", + "connectedTo": "Connecté à {{name}}" + } + }, + "users": { + "loadingPermissions": "Chargement des permissions…", + "permissionsFailed": "Impossible de charger les permissions.", + "deniedUnlessGranted": "Tout est refusé sauf ce qui est accordé ici.", + "read": "Lecture", + "full": "Total", + "noAccess": "Aucun accès", + "role": "Rôle", + "permissionsSaved": "Ce que les {{role}} peuvent atteindre a été enregistré", + "copyBeforeClosing": "Copiez ce dont vous avez besoin avant de fermer — rien de tout cela ne peut être réaffiché.", + "password": "Mot de passe", + "linuxAccount": "Compte Linux", + "sshKey": "Leur clé publique SSH", + "addToGitea": "Ajoutez ceci à leur compte Gitea", + "linuxIncomplete": "Le compte fonctionne, mais sa partie Linux ne s'est pas terminée", + "done": "Terminé", + "addAccount": "Ajouter un compte", + "newAccount": "Nouveau compte", + "email": "E-mail", + "name": "Nom", + "username": "Nom d'utilisateur", + "usernameHint": "Lettres, chiffres, points, tirets et underscores.", + "generatePassword": "Générer un mot de passe", + "copyPassword": "Copier le mot de passe", + "passwordHint": "Au moins 12 caractères, avec majuscules et minuscules, un chiffre et un symbole.", + "sshPlaceholder": "ssh-ed25519 AAAAC3Nza… ana@portable", + "passwordCopied": "Mot de passe copié", + "loadingAccounts": "Chargement des comptes…", + "accountsFailed": "Impossible de charger les comptes.", + "removeAccount": "Supprimer le compte", + "generateNewPassword": "Générer un nouveau mot de passe", + "serverOwner": "Propriétaire du serveur", + "copySshTitle": "Copier leur clé publique SSH (ajoutez-la à leur compte Gitea)", + "resetPasswordTitle": "Générer un nouveau mot de passe — affiché une seule fois, et les déconnecte partout", + "copyNewPassword": "Copier le nouveau mot de passe", + "publicKeyCopied": "Clé publique copiée", + "copyFailed": "Impossible de copier — sélectionnez-la et copiez-la à la main", + "repairFor": "Réparer le compte Linux de {{email}}", + "linuxAccountFor": "Compte Linux : {{osUser}} — cliquez pour réparer ou remplacer sa clé SSH", + "copySshFor": "Copier la clé publique SSH de {{email}}", + "resetPasswordFor": "Réinitialiser le mot de passe de {{email}}", + "removeFor": "Supprimer {{email}}", + "provisionPrompt": "Compte Linux pour {{email}}.\n\nCollez une clé publique SSH pour lui permettre de se connecter en SSH, ou laissez vide pour conserver l'actuelle.", + "createLinux": "Créer un compte Linux", + "noLinux": "Aucun compte Linux — cliquez pour en créer un", + "removeTitle": "Supprimer {{email}} ?", + "removeBody": "Cela supprime le compte et tout ce qui lui appartient — passkeys, Dashboards, dispositions enregistrées, et tout ce qu'un plugin installé conserve pour lui. Cette action est irréversible.", + "resetTitle": "Réinitialiser le mot de passe de {{email}} ?", + "resetBody": "Un nouveau mot de passe est généré et vous est affiché une seule fois — il n'est stocké nulle part et ne peut plus être consulté ensuite. Son mot de passe actuel cesse de fonctionner immédiatement, et il est déconnecté partout.", + "resetLinuxNote": "Son compte Linux ({{osUser}}) n'est pas affecté : il n'a pas de mot de passe, et les clés SSH restent inchangées." + }, + "dav": { + "newDevice": "Nouvel appareil", + "create": "Créer", + "none": "Aucun appareil pour l'instant.", + "placeholder": "Pixel 9, iPad, Thunderbird…", + "revoke": "Révoquer", + "delete": "Supprimer", + "copyFailed": "Impossible de copier — sélectionnez et copiez manuellement", + "createFailed": "Impossible de créer le mot de passe d'application", + "revoked": "Révoqué — cet appareil arrêtera de se synchroniser", + "revokeFailed": "Impossible de révoquer", + "deleteFailed": "Impossible de supprimer", + "copyField": "Copier {{field}}", + "copied": "{{what}} copié" + }, + "google": { + "guideToggle": "Guide de configuration pas à pas", + "step1": { + "title": "Créer un projet Google Cloud", + "body": "Ouvrez la page de création de projet, donnez-lui un nom (par exemple “Officer”), et cliquez sur :", + "link": "New Project" + }, + "step2": { + "title": "Activer les API", + "body": "Ouvrez la bibliothèque d'API, recherchez chacune d'entre elles, puis cliquez sur :", + "link": "API Library" + }, + "step3": { + "title": "Configurer l'écran de consentement OAuth", + "link": "OAuth Branding", + "appName": "Définissez le nom de votre organisation, ou “Officer”, comme :", + "supportEmail": "Définissez l'e-mail de votre administrateur comme :", + "devContact": "Ajoutez l'e-mail de votre administrateur sous :", + "save": "Puis cliquez sur :" + }, + "step4": { + "title": "Définir l'audience", + "link": "OAuth Audience", + "internal": "Si votre équipe utilise Google Workspace, choisissez — aucune vérification requise :", + "external": "Sinon choisissez, et ajoutez les e-mails de votre équipe sous Test users (obligatoire tant que l'application n'est pas vérifiée ; limite de 100 utilisateurs de test) :" + }, + "step5": { + "title": "Ajouter des scopes", + "link": "Data Access", + "body": "Ouvrez l'accès aux données, recherchez et ajoutez les scopes ci-dessous, puis cliquez sur :", + "note": "Google classe le scope du calendrier comme sensible et celui de Gmail comme restreint. Cela ne pose pas de problème pour les applications Internal (Google Workspace) ni pour les applications External en mode test ; publier en production avec des scopes restreints nécessite une vérification par Google." + }, + "step6": { + "title": "Créer des identifiants OAuth", + "link": "Clients", + "body": "Ouvrez la page des clients et cliquez sur :", + "appType": "Type d'application :", + "nameAnything": "Nom : n'importe lequel (par exemple “Officer”)", + "redirectUris": "URI de redirection autorisés — ajoutez :", + "thenClick": "Puis cliquez sur :" + }, + "step7": { + "title": "Copier les identifiants", + "body": "Une boîte de dialogue affiche votre client ID et votre client secret. Copiez les deux et collez-les dans les champs ci-dessous." + }, + "scopeGmail": "Lire les e-mails", + "scopeCalendar": "Lire les événements du calendrier", + "verifying": "Vérification des identifiants…", + "valid": "Identifiants valides", + "invalid": "Identifiants invalides", + "verifyFailed": "La demande de vérification a échoué", + "clientId": "Client ID", + "clientSecret": "Client Secret", + "redirectUri": "URI de redirection", + "redirectUriHint": "Ajoutez cet URI aux URI de redirection autorisés de votre client OAuth Google", + "saved": "Configuration Google OAuth enregistrée", + "saveFailed": "Échec de l'enregistrement de la configuration Google OAuth" + }, + "googleAccount": { + "title": "Compte Google", + "hint": "Connectez votre compte Google pour le Calendrier, les Contacts et d'autres services Google.", + "connected": "Connecté", + "disconnect": "Déconnecter", + "connect": "Connecter le compte Google", + "connectedToast": "Compte Google connecté", + "connectFailed": "Échec de la connexion du compte Google", + "disconnectedToast": "Compte Google déconnecté", + "disconnectFailed": "Échec de la déconnexion du compte Google" + }, + "apify": { + "token": "Jeton API", + "saved": "Jeton API Apify enregistré", + "saveFailed": "Échec de l'enregistrement du jeton API Apify" + } + }, + "header": { + "userMenu": { + "profile": "Profil", + "systemSettings": "Paramètres système", + "signOut": "Se déconnecter", + "ai": "IA", + "integrations": "Intégrations", + "userManagement": "Gestion des utilisateurs" + }, + "tabName": "Nom de l'onglet", + "tabNameHint": "Cliquez pour renommer cet onglet — videz-le pour revenir au nom de la page", + "logoAlt": "Officer", + "navigation": "Navigation", + "jobsRunning": "Job en cours", + "jobsNothingRunning": "Rien en cours", + "jobsQueued": "Jobs en file d'attente" + }, + "nav": { + "home": "Accueil", + "files": "Fichiers", + "chat": "Chat", + "commandHq": "Command HQ", + "editor": "Éditeur", + "jobs": "Jobs", + "terminal": "Terminal", + "browser": "Navigateur", + "monitor": "Monitor", + "activity": "Activité", + "dashboards": "Dashboards", + "sendOff": "SendOff", + "calendar": "Calendrier", + "contacts": "Contacts", + "appStore": "Boutique d'applications", + "marketplace": "Marketplace", + "codeEditor": "Éditeur de code" + }, + "theme": { + "pillTitle": "Thème : {{name}} · ctrl+shift+←/→ parcourir · ctrl+shift+space explorer · ctrl+shift+f {{starAction}}", + "pillLabel": "Thème : {{name}}", + "star": "marquer", + "unstar": "démarquer", + "cycleHint": "ctrl+shift+←/→ parcourir · ctrl+shift+space explorer", + "everyShortcutHint": "ctrl+? pour tous les raccourcis", + "favouritesOnly": "Favoris uniquement ({{count}})", + "favourites": "Favoris ({{count}})", + "noFavourites": "Aucun favori pour l'instant — marquez-en un avec le bouton sur sa ligne.", + "starNamed": "Marquer {{name}}", + "unstarNamed": "Démarquer {{name}}", + "addToFavourites": "Ajouter aux favoris", + "removeFromFavourites": "Retirer des favoris", + "addToFavouritesKey": "Ajouter aux favoris (f)", + "removeFromFavouritesKey": "Retirer des favoris (f)", + "active": "actif", + "browseHint": "j/k ou ↑/↓ déplacer · l ou → aperçu · f marquer · h ou ← réinitialiser · entrée garder · esc annuler" + }, + "spotlight": { + "placeholder": "Aller à…", + "noResults": "Rien ne correspond à “{{query}}”." + }, + "shortcutsModal": { + "title": "Raccourcis clavier", + "hint": "Ctrl+? · Échap pour fermer" + }, + "screenError": { + "title": "Cet écran n'a pas pu s'afficher", + "description": "Rien n'a été perdu — c'est un échec d'affichage, pas un échec de données. Les transcriptions de l'agent, les jobs et les fichiers sont intacts.", + "unknownError": "Erreur inconnue", + "tryAgain": "Réessayer", + "reload": "Recharger", + "goHome": "Retour à l'accueil", + "resetHint": "Si cela échoue à nouveau au même endroit, une disposition de panneaux enregistrée en est généralement la cause. La réinitialisation remet les panneaux de tous les écrans à leurs valeurs par défaut.", + "dashboardsSafe": "Vos Dashboards ne sont pas touchés.", + "resetting": "Réinitialisation…", + "resetConfirm": "Oui — réinitialiser la disposition de tous les écrans", + "reset": "Réinitialiser les dispositions des écrans" + }, + "rescan": { + "label": "Réanalyser les items", + "title": "Réanalyser officer-items (appliquer les changements sans redémarrer)", + "failed": "Échec de la réanalyse des items", + "done": "Items réanalysés — {{summary}}", + "counts": { + "tasks": { + "one": "{{count}} tâche", + "other": "{{count}} tâches" + }, + "agents": { + "one": "{{count}} agent", + "other": "{{count}} agents" + }, + "tools": { + "one": "{{count}} outil", + "other": "{{count}} outils" + }, + "skills": { + "one": "{{count}} skill", + "other": "{{count}} skills" + }, + "processes": { + "one": "{{count}} processus", + "other": "{{count}} processus" + }, + "affordances": { + "one": "{{count}} affordance", + "other": "{{count}} affordances" + } + }, + "newChatsOnly": "S'applique aux nouveaux Chats — un Chat déjà ouvert garde ce avec quoi il a démarré.", + "noDescription": { + "one": "{{count}} skill n'a pas de description, donc elle n'est proposée à aucun agent.", + "other": "{{count}} skills n'ont pas de description, donc elles ne sont proposées à aucun agent." + } + }, + "bugReport": { + "label": "Signaler un bug", + "title": "Signaler un bug", + "describePlaceholder": "Décrivez ce qui s'est mal passé...", + "screenshot": "Capture d'écran", + "noScreenshot": "Aucune capture d'écran effectuée", + "collectedNote": "Les journaux de la console, les informations du navigateur et les erreurs API récentes sont collectés automatiquement.", + "submitted": "Signalement de bug envoyé", + "failed": "Échec de l'envoi du signalement de bug", + "submit": "Envoyer" + }, + "auth": { + "signInTitle": "Content de vous revoir", + "signInSubtitle": "Connectez-vous à votre compte", + "emailOrUsername": "E-mail ou nom d'utilisateur", + "password": "Mot de passe", + "forgotPassword": "Mot de passe oublié ?", + "emailPlaceholder": "vous@exemple.com", + "passwordPlaceholder": "Votre mot de passe", + "forgotTitle": "Mot de passe oublié", + "forgotSubtitle": "Saisissez votre e-mail pour recevoir un lien de réinitialisation", + "email": "E-mail", + "backToLogin": "Retour à la connexion", + "checkEmail": "Consultez votre e-mail", + "checkEmailHint": "Cliquez dessus pour réinitialiser votre mot de passe.", + "resetOk": "Mot de passe réinitialisé avec succès. Veuillez vous connecter.", + "verifying": "Vérification…", + "invalidLink": "Lien invalide", + "invalidLinkHint": "Ce lien de réinitialisation est invalide ou a expiré.", + "resetTitle": "Réinitialiser le mot de passe", + "resetSubtitle": "Saisissez votre nouveau mot de passe", + "confirmPassword": "Confirmer le mot de passe", + "minChars": "12 caractères minimum", + "confirmPlaceholder": "Confirmez votre mot de passe", + "bootstrapTitle": "Bienvenue, Administrateur", + "bootstrapSubtitle": "Créez votre compte administrateur", + "name": "Nom", + "username": "Nom d'utilisateur", + "namePlaceholder": "Votre nom", + "adminEmailPlaceholder": "admin@exemple.com", + "bootstrapOk": "Compte administrateur créé. Veuillez vous connecter.", + "debug": { + "controls": "Contrôles :", + "camera": "Caméra :", + "model": "Modèle :" + } + }, + "shortcuts": { + "navigation": "Navigation", + "spotlight": "Spotlight — aller à n'importe quel écran, plugin ou paramètre par son nom", + "thisList": "Cette liste", + "themes": "Thèmes", + "themesNote": "L'explorateur applique le thème au fur et à mesure que vous vous déplacez, pour que vous voyiez l'application changer plutôt que de lire des noms.", + "openBrowser": "Ouvrir l'explorateur de thèmes", + "prevNextTheme": "Thème précédent / suivant, sans rien ouvrir", + "starCurrent": "Marquer ou démarquer le thème actuel, depuis n'importe où", + "inBrowser": "Dans l'explorateur de thèmes", + "moveList": "Parcourir la liste", + "applyKeepBrowsing": "Appliquer le thème en focus et continuer à explorer", + "restoreStart": "Rétablir le thème sur lequel vous avez commencé", + "starFocused": "Marquer ou démarquer le thème en focus", + "keepLooking": "Garder ce que vous regardez", + "cancelRestore": "Annuler et restaurer", + "inSpotlight": "Dans Spotlight", + "moveResults": "Parcourir les résultats", + "goThere": "Y aller", + "close": "Fermer", + "chat": "Chat", + "chatNote": "Le rappel n'utilise la touche fléchée que sur un compositeur VIDE, et toute autre touche met fin au rappel — un brouillon n'est ainsi jamais écrasé.", + "prevNextPrompt": "Prompt précédent / suivant que vous avez envoyé, à la manière d'un shell", + "send": "Envoyer · sur un compositeur vide avec quelque chose en file, envoyer cela maintenant", + "newline": "Nouvelle ligne", + "stopTurn": "Arrêter le tour en cours de génération" + }, + "jobs": { + "runningQueued": "En cours et en file d'attente", + "loading": "Chargement…", + "nothingRunning": "Rien en cours", + "history": "Historique", + "clearAll": "Tout effacer", + "selectJob": "Sélectionner un job", + "notFound": "Job introuvable.", + "backToJobs": "Retour aux jobs", + "stop": "Arrêter", + "testNotification": "Envoyer une notification de test à tous les canaux configurés", + "search": "Rechercher…", + "deleteFinished": "Supprimer tous les jobs terminés", + "noChannels": "Aucun canal configuré", + "notifyFailed": "Échec de l'envoi de la notification", + "stopRequested": "Arrêt demandé", + "stopFailed": "Échec de l'arrêt", + "titles": "Titres", + "download": "Télécharger", + "input": "Entrée", + "output": "Sortie", + "steps": "Étapes", + "selectStep": "Sélectionnez une étape ou une sous-tâche pour voir la sortie", + "noOutput": "Aucune sortie pour l'instant", + "tokens": "Tokens :", + "cost": "Coût :", + "status": { + "running": "En cours", + "queued": "En file d'attente", + "completed": "Terminé", + "failed": "Échoué", + "stopped": "Arrêté", + "interrupted": "Interrompu" + }, + "failedWithCode": "Échoué (sortie {{code}})" + }, + "activity": { + "title": "Activité", + "backgroundTasks": "Tâches en arrière-plan", + "detached": "Détachée", + "backToList": "Retour à la liste", + "selectTask": "Sélectionnez une tâche pour suivre sa sortie en direct", + "noneRunning": "aucune en cours" + }, + "passkeyGate": { + "setUp": "Configurez votre passkey" + }, + "chatHistory": { + "loadFailed": "Impossible de charger cette conversation : {{reason}}" + }, + "capability": { + "frontmatter": "Frontmatter", + "noMatches": "Aucun résultat", + "emptyFile": "Fichier vide", + "createFailed": "Impossible de créer le {{kind}}", + "deleteFailed": "Impossible de supprimer le {{kind}}", + "searchPlaceholder": "Rechercher {{kind}}…", + "selectOne": "Sélectionnez un {{kind}}", + "selectToView": "Sélectionnez un {{kind}} pour voir son contenu", + "delete": "Supprimer", + "createPrompt": "Aide-moi à créer le contenu de ce nouveau fichier {{kind}}", + "improvePrompt": "Aide-moi à comprendre et améliorer ce fichier {{kind}}" + } +} diff --git a/src/apps/officer-web/locales/parity.test.ts b/src/apps/officer-web/locales/parity.test.ts index 1e69955f..9c43d3fd 100644 --- a/src/apps/officer-web/locales/parity.test.ts +++ b/src/apps/officer-web/locales/parity.test.ts @@ -2,11 +2,12 @@ import { describe, expect, test } from 'bun:test'; import { findMissingKeys } from 'i18n'; import en from './en-GB.json'; import ptPT from './pt-PT.json'; +import frFR from './fr-FR.json'; -// D2: every shipped locale ships a COMPLETE catalog. A key en-GB has and pt-PT lacks is a red build, -// not a warning — there is no regional fallback to paper over the gap. +// D2: every shipped locale ships a COMPLETE catalog. A key en-GB has and another locale lacks is a red +// build, not a warning — there is no regional fallback to paper over the gap. describe('officer-web (app.*) locale catalogs', () => { - test('pt-PT has every key en-GB has', () => { - expect(findMissingKeys(en, { 'pt-PT': ptPT })).toEqual([]); + test('every shipped locale has every key en-GB has', () => { + expect(findMissingKeys(en, { 'pt-PT': ptPT, 'fr-FR': frFR })).toEqual([]); }); }); diff --git a/src/workspaces/components/locales/fr-FR.json b/src/workspaces/components/locales/fr-FR.json new file mode 100644 index 00000000..d0aa6131 --- /dev/null +++ b/src/workspaces/components/locales/fr-FR.json @@ -0,0 +1,81 @@ +{ + "button": { + "cancel": "Annuler", + "confirm": "Confirmer", + "ok": "OK", + "close": "Fermer" + }, + "search": { + "placeholder": "Rechercher", + "open": "Ouvrir la recherche", + "close": "Fermer la recherche", + "clear": "Effacer la recherche" + }, + "select": { + "placeholder": "Sélectionner..." + }, + "tabs": { + "filterPlaceholder": "Filtrer" + }, + "commandBlock": { + "run": "Exécuter dans le terminal", + "copy": "Copier la commande" + }, + "colorPicker": { + "placeholder": "Couleur hexadécimale" + }, + "state": { + "loading": "Chargement…" + }, + "dataTable": { + "empty": "Aucune donnée trouvée.", + "pagination": { + "showingPage": "Page {{current}} sur {{total}}", + "showingPageShort": "p {{current}}/{{total}}", + "rowsPerPage": "Lignes par page", + "perPage": "{{size}} / page", + "perPageShort": "{{size}}/p", + "first": "Première", + "prev": "Précédente", + "next": "Suivante", + "last": "Dernière" + } + }, + "pagination": { + "label": "Pagination", + "previous": "Précédent", + "next": "Suivant", + "morePages": "Plus de pages", + "goToPrevious": "Aller à la page précédente", + "goToNext": "Aller à la page suivante" + }, + "carousel": { + "previous": "Diapositive précédente", + "next": "Diapositive suivante" + }, + "breadcrumb": { + "more": "Plus" + }, + "theme": { + "toggle": "Basculer le mode de couleur" + }, + "error": { + "forbidden": { + "title": "Accès non autorisé", + "description": "Vous n'êtes pas autorisé à effectuer cette action.", + "hint": "Si vous pensez qu'il s'agit d'une erreur, contactez votre responsable." + }, + "unauthorized": { + "title": "Identifiants invalides", + "description": "Votre jeton d'accès a expiré", + "hint": "Reconnectez-vous" + }, + "server": { + "title": "Erreur du serveur" + } + }, + "logo": { + "icon": "Icône Officer", + "logo": "Logo Officer" + } +} diff --git a/src/workspaces/components/locales/parity.test.ts b/src/workspaces/components/locales/parity.test.ts index cf0a4d31..655ed439 100644 --- a/src/workspaces/components/locales/parity.test.ts +++ b/src/workspaces/components/locales/parity.test.ts @@ -2,11 +2,12 @@ import { describe, expect, test } from 'bun:test'; import { findMissingKeys } from 'i18n'; import en from './en-GB.json'; import ptPT from './pt-PT.json'; +import frFR from './fr-FR.json'; -// D2: every shipped locale ships a COMPLETE catalog. Phase 0 scaffolds this catalog with no keys yet -// (ui.* strings land in phase 1) — the test still runs so it is load-bearing from day one. +// D2: every shipped locale ships a COMPLETE catalog. A key en-GB has and another locale lacks is a red +// build, not a warning — there is no regional fallback to paper over the gap. describe('shared components (ui.*) locale catalogs', () => { - test('pt-PT has every key en-GB has', () => { - expect(findMissingKeys(en, { 'pt-PT': ptPT })).toEqual([]); + test('every shipped locale has every key en-GB has', () => { + expect(findMissingKeys(en, { 'pt-PT': ptPT, 'fr-FR': frFR })).toEqual([]); }); }); diff --git a/src/workspaces/officerdev/src/locales/fr-FR.json b/src/workspaces/officerdev/src/locales/fr-FR.json new file mode 100644 index 00000000..1d61b307 --- /dev/null +++ b/src/workspaces/officerdev/src/locales/fr-FR.json @@ -0,0 +1,652 @@ +{ + "common": { + "save": "Enregistrer", + "delete": "Supprimer" + }, + "minesweeper": { + "notInstalled": "Minesweeper n'est pas installé sur ce serveur.", + "needs": "Il faut wine, un préfixe 32 bits, et ce paquet à côté de la plateforme :", + "notRunning": "La session Minesweeper n'est pas en cours d'exécution.", + "error": { + "connectionFailed": "La connexion à la session Minesweeper a échoué.", + "refused": "Le serveur VNC a refusé la connexion.", + "authUnsupported": "Le serveur VNC demande une authentification que ce client ne prend pas en charge.", + "handshakeRejected": "Le serveur VNC a rejeté la prise de contact.", + "unknownMessage": "Le serveur VNC a envoyé un type de message inconnu ({{type}}).", + "unknownEncoding": "Le serveur VNC a utilisé l'encodage {{encoding}}, que ce client n'a pas demandé." + } + }, + "qrTransfer": { + "chooseFile": "Choisissez un fichier à envoyer", + "startScanning": "Démarrer le scan", + "receiveAnother": "Recevoir un autre", + "stop": "Arrêter" + }, + "dav": { + "empty": "Rien ici pour l'instant. Ajoutez-en un depuis un client mobile ou de bureau via CalDAV/CardDAV.", + "calendar": "Calendrier", + "contacts": "Contacts", + "readFailed": "Impossible de lire {{name}} : {{reason}}" + }, + "terminal": { + "title": "Terminal", + "find": "Rechercher", + "runningShells": "Shells en cours d'exécution", + "loading": "Chargement…", + "noShells": "Aucun shell en cours d'exécution.", + "refresh": "Actualiser", + "kill": "Terminer ce shell" + }, + "appStore": { + "loading": "Chargement", + "loadFailed": "Impossible de charger la boutique d'applications.", + "installed": "Installées", + "available": "Disponibles", + "needsYou": "Nécessite votre intervention", + "needsYouShort": "nécessite votre intervention", + "selectApp": "Sélectionnez une application", + "installFailed": "L'installation a échoué", + "uninstall": "Désinstaller", + "enable": "Activer", + "disable": "Désactiver", + "enabled": "activée", + "disabled": "désactivée", + "removalNote": "Désactiver arrête le sidecar et son conteneur. Désinstaller supprime aussi les conteneurs — vos données, la configuration et les tables de cette application sont conservées dans les deux cas.", + "installing": "installation en cours", + "failed": "échec", + "installedIcon": "installée" + }, + "systemMonitor": { + "cpuPackage": "Boîtier CPU", + "user": "Utilisateur", + "cpuPct": "CPU%", + "memPct": "MÉM%", + "command": "Commande", + "memory": "Mémoire", + "disks": "Disques", + "temperature": "Température", + "network": "Réseau", + "power": "Alimentation", + "topProcesses": "Processus principaux", + "filterPlaceholder": "filtrer par nom ou image…", + "name": "Nom", + "status": "État", + "mem": "Mém", + "uptime": "Temps de fonctionnement", + "scopeDockers": "dockers" + }, + "codeEditor": { + "closeFile": "Fermer {{name}}", + "loading": "Chargement…", + "noFiles": "Aucun fichier", + "explorer": "Explorateur", + "openAFile": "Ouvrez un fichier depuis l'explorateur pour commencer à modifier", + "refresh": "Actualiser l'arborescence et recharger les fichiers ouverts non modifiés", + "readFailed": "Échec de la lecture du fichier", + "saved": "Fichier enregistré", + "saveFailed": "Échec de l'enregistrement du fichier", + "title": "Éditeur de code" + }, + "widgets": { + "resync": "Resynchroniser les widgets (rechargez pour prendre en compte les widgets récemment déployés)" + }, + "preview": { + "title": "Aperçu", + "reload": "Recharger", + "changeUrl": "Changer d'URL", + "openNewTab": "Ouvrir dans un nouvel onglet", + "pointAtApp": "Pointez ce panneau vers une application en cours d'exécution", + "open": "Ouvrir", + "hint": "Tout ce que le navigateur peut atteindre. Cela reste sur ce panneau et survit à un rechargement.", + "connecting": "Connexion…" + }, + "sendOff": { + "files": "Fichiers", + "where": "Où", + "loading": "Chargement…", + "landsIn": "Arrive dans leur", + "landedIn": "Arrivé dans", + "whoMaySend": "Qui peut m'envoyer des fichiers", + "allowedLandIn": "Les fichiers de qui vous autorisez arrivent dans", + "removeFile": "Supprimer {{name}}", + "sendTo": "Envoyer à {{name}}", + "turningOff": "Désactiver quelqu'un arrête les envois futurs et laisse intact ce qu'il a déjà envoyé." + }, + "panelName": { + "appStore": "Boutique d'applications", + "appDetail": "Détail de l'application", + "calendar": "Calendrier", + "contacts": "Contacts", + "categories": "Catégories", + "chat": "Chat", + "chatSessions": "Sessions", + "codeEditor": "Éditeur de code", + "commander": "Commander", + "commanders": "Commanders", + "dashboardPreview": "Aperçu du Dashboard", + "dashboards": "Dashboards", + "fileBrowser": "Explorateur de fichiers", + "pluginDetail": "Détail du plugin", + "plugins": "Plugins", + "preview": "Aperçu", + "qrReceive": "Réception QR", + "qrSend": "Envoi QR", + "scope": "Portée", + "systemMonitor": "Moniteur système", + "widgets": "Widgets", + "terminal": "Terminal", + "hostTerminal": "Terminal hôte", + "runningShells": "Shells en cours d'exécution", + "pm2Processes": "processus pm2" + }, + "dashboards": { + "title": "Dashboards", + "new": "Nouveau Dashboard", + "delete": "Supprimer le Dashboard", + "confirmDelete": "Voulez-vous vraiment supprimer", + "search": "Rechercher des Dashboards", + "name": "Nom", + "description": "Description", + "open": "Ouvrir le Dashboard", + "selectOne": "Sélectionnez un Dashboard ou créez-en un nouveau", + "namePlaceholder": "Mon Dashboard", + "descriptionPlaceholder": "À quoi sert ce Dashboard ?", + "template": "Modèle", + "create": "Créer", + "templates": { + "single": "Unique", + "twoColumns": "2 colonnes", + "mainSide": "Principal + latéral", + "sidebar": "Barre latérale", + "grid": "Grille 2x2", + "colsBottom": "Colonnes + bas" + } + }, + "fileViewer": { + "readAloud": "Lire à voix haute", + "ocr": "Extraire le texte (OCR)", + "transcribe": "Transcrire", + "extractAudio": "Extraire l'audio", + "extract": "Extraire", + "saveNextTo": "Enregistrer à côté de l'original", + "download": "Télécharger", + "pdfViewer": "Visionneuse PDF", + "videoFailed": "Échec du chargement de la vidéo", + "imageFailed": "Échec du chargement de l'image", + "audioFailed": "Échec du chargement du fichier audio", + "audioTrack": "Piste audio", + "subtitles": "Sous-titres", + "track": "Piste {{n}}", + "saved": "Fichier enregistré", + "saveFailed": "Échec de l'enregistrement du fichier", + "scrollTop": "Remonter en haut", + "speechFailed": "Échec de la génération de l'audio vocal", + "ocrFailed": "Échec de l'extraction du texte de l'image", + "transcribeFailed": "Échec de la transcription de l'audio", + "extractAudioFailed": "Échec de l'extraction de l'audio de la vidéo", + "extractFailed": "Échec de l'extraction de l'archive", + "savedAs": "Enregistré sous “{{name}}”", + "extractedTo": "Extrait vers “{{name}}”", + "downloadInstead": "Télécharger à la place", + "archiveFile": "Fichier archive" + }, + "plugins": { + "loading": "Chargement…", + "onThisMachine": "Sur cette machine", + "noPluginsIn": "Aucun plugin dans", + "available": "Disponibles", + "needsOnThisMachine": "Nécessaire sur cette machine", + "install": "Installer", + "clonesInto": "Clone le dépôt dans", + "deleteData": "Supprimer aussi toutes ses données", + "noTables": "Ce plugin ne déclare aucune table, il n'y a donc aucune donnée à conserver.", + "selectOne": "Sélectionnez un plugin.", + "disable": "Désactiver", + "enable": "Activer", + "publisher": "Éditeur", + "version": "Version", + "needsPlatform": "Nécessite la plateforme", + "wouldMount": "Serait monté", + "ships": "Inclut", + "permissions": "Permissions", + "source": "Source", + "mountsAt": "Monté sur", + "sidecar": "Sidecar", + "deleteNamed": "Supprimer {{name}}", + "updateTo": "Mettre à jour vers {{version}}" + }, + "commandHq": { + "selectOne": "Sélectionnez un commander, ou démarrez-en un nouveau.", + "startConversation": "Démarrer la conversation", + "name": "Nom", + "nameHint": "Appliqué à la conversation dès son démarrage, au lieu de laisser le harness la nommer d'après votre premier message.", + "workingDirectory": "Répertoire de travail", + "cwdHint": "D'où toute commande qu'il exécute est résolue, et où son historique est conservé. Il ne peut pas être déplacé ensuite.", + "new": "Nouveau commander", + "spawned": "Lancé", + "deleteQ": "Supprimer ?", + "uncategorised": "Sans catégorie", + "loading": "Chargement…", + "platformDeploys": "Déploiements de la plateforme", + "endSession": "Terminer la session — la libère pour qu'elle puisse être reprise ailleurs", + "confirmDeleteHint": "Confirmer la suppression — supprime aussi la conversation, pas seulement l'entrée Command HQ", + "confirmDelete": "Confirmer la suppression", + "cancelDelete": "Annuler la suppression", + "deleteCommander": "Supprimer le commander", + "none": "Aucun commander ici pour l'instant.", + "startOne": "En démarrer un", + "confirmDeleteNamed": "Confirmer la suppression de {{name}}", + "deleteNamed": "Supprimer {{name}}", + "categories": "Catégories", + "noCategories": "Aucune catégorie pour l'instant", + "newCategory": "Nouvelle catégorie", + "allCommanders": "Tous les commanders", + "categoryName": "Nom de la catégorie", + "guide": { + "whatIsIt": "Ce qu'est un commander", + "starting": "En démarrer un", + "examples": "Exemples de premier message", + "whileRunning": "Pendant qu'il tourne", + "whatIsItBody1": "Un agent qui continue de travailler quand vous n'êtes pas là — il attend qu'un événement se produise, agit en conséquence, et rend compte.", + "whatIsItBody2": "C'est une conversation ordinaire. Vous commencez en lui disant, avec vos propres mots, ce qu'il doit surveiller et ce qu'il doit faire.", + "startingBody1": "Décrivez ici la tâche, comme vous le feriez à n'importe qui. Dites ce qu'il doit surveiller, où, et ce qu'il doit faire quand cela se déclenche.", + "startingBody2": "TODO : les étapes concrètes, une fois le flux de création établi. Ceci remplace “ouvrir la boîte de dialogue de tâche en arrière-plan et armer un surveillant”, que personne ne pouvait deviner.", + "examplesBody1": "TODO : deux ou trois exemples de premiers messages, une fois le flux de création établi. Ils transforment “décrivez la tâche” d'une instruction en quelque chose qu'un nouvel utilisateur peut suivre.", + "whileRunningBody1": "TODO : ce que le propriétaire voit — où son coût s'affiche, comment lire ce que font les agents qu'il a lancés, et comment l'arrêter.", + "draft": "brouillon" + }, + "startHint": "Il va regarder autour de ce répertoire, puis vous demander ce qu'il doit surveiller.", + "startBlocked": "{{missing}} — ensuite ceci le démarre.", + "confirmDeletingNamed": "Confirmer la suppression de {{name}}" + }, + "chatHistory": { + "newChat": "Nouveau Chat", + "tryAgain": "Réessayer", + "refresh": "Actualiser", + "refreshSessions": "Actualiser les sessions", + "newChatInDir": "Nouveau Chat dans un répertoire…", + "loadFailed": "Impossible de charger les sessions", + "reading": "Lecture des transcriptions…", + "noSessions": "Aucune session pour l'instant", + "liveFailed": "Impossible de lire les sessions en direct", + "asking": "Question posée à l'agent…", + "tabs": { + "tree": "Arborescence", + "latest": "Récentes", + "live": "En direct", + "agents": "Agents", + "commanders": "Commanders" + }, + "notifyPreference": "Préférence de notification", + "notify": { + "everyTurn": "À chaque fin de tour", + "inputNeeded": "Seulement quand une réponse est nécessaire", + "none": "Aucune" + }, + "notificationsLabel": "Notifications : {{choice}}", + "chooseDir": "Choisissez un répertoire de travail", + "create": "Créer", + "newFolder": "Nouveau dossier", + "selectFolder": "Sélectionner ce dossier", + "newFolderName": "Nom du nouveau dossier", + "noSubfolders": { + "one": "Aucun sous-dossier visible — {{count}} sous-dossier masqué est filtré", + "other": "Aucun sous-dossier visible — {{count}} sous-dossiers masqués sont filtrés" + }, + "showHidden": "Afficher les masqués", + "showHiddenCount": "Afficher les masqués ({{count}})", + "selectSession": "Sélectionnez une session à afficher", + "endSession": "Terminer la session — arrête tout tour en cours et la libère pour qu'elle puisse être reprise ailleurs", + "noReadable": "Aucune conversation lisible ici.", + "noConversations": "Aucune conversation pour l'instant", + "newChatIn": "Nouveau Chat dans {{path}}", + "sessionTitle": "Titre de la session", + "saveTitle": "Enregistrer le titre", + "cancelRename": "Annuler le renommage", + "confirmDelete": "Confirmer la suppression", + "cancelDelete": "Annuler la suppression", + "rename": "Renommer", + "deleteSession": "Supprimer la session", + "renameFailed": "Impossible de renommer la session : {{reason}}", + "deleteFailed": "Impossible de supprimer la session : {{reason}}", + "deleteAll": "Supprimer les {{count}} ?", + "confirmDeletingNamed": "Confirmer la suppression de {{name}}", + "renameNamed": "Renommer {{name}}", + "deleteNamed": "Supprimer {{name}}", + "newChatTitle": "Nouveau Chat", + "renameHint": "Cliquez pour renommer ce Chat", + "hideHidden": "Masquer les masqués" + }, + "chat": { + "newChat": "Nouveau Chat", + "rename": "Renommer", + "deleteQ": "Supprimer “{{title}}” ?", + "duplicateAgent": "Il existe déjà un agent nommé “{{name}}” sur ce Dashboard", + "composerPlaceholder": "Sur quoi voulez-vous travailler maintenant ?", + "turnFinished": "Tour terminé · pas de message de clôture", + "job": { + "title": "Tâche en arrière-plan", + "runsIn": "S'exécute dans", + "command": "Commande — exécuter exactement ceci", + "prompt": "Prompt — décrivez-le, l'agent se débrouille", + "watch": "Surveiller quelque chose", + "watchHint": "En texte libre. L'agent posera des questions avant d'armer si quelque chose n'est pas clair.", + "handBack": "Sortir et rendre la main à l'agent, puis réarmer", + "keepGoing": "Continuer — agir à chaque fois et poursuivre", + "stopAfter": "Arrêter après", + "start": "Démarrer", + "watchExample": "surveiller nos dépôts Gitea ; à chaque nouveau push, le récupérer et l'envoyer vers le miroir GitHub", + "startJob": "Démarrer une tâche en arrière-plan ou un surveillant" + }, + "tasks": { + "restart": "Redémarrer", + "pinned": "Épinglée", + "clearFinished": "Effacer toutes les terminées", + "clearFinishedTasks": "Effacer toutes les tâches terminées", + "stopJob": "Arrêter cette tâche", + "dismiss": "Ignorer", + "rerun": "L'arrêter et relancer la même commande", + "collapse": "Replier", + "collapseDetail": "Replier le détail de la tâche", + "stopNamed": "Arrêter {{label}}", + "dismissNamed": "Ignorer {{label}}", + "status": "Tâche {{status}}." + }, + "usage": { + "noSignIn": "Aucune connexion enregistrée — exécutez /login pour utiliser celle-ci", + "updating": "Mise à jour…", + "unreadable": "Impossible de lire l'usage sur cette machine.", + "loading": "Chargement…", + "limits": "Limites", + "byDay": "Tokens par jour", + "byModel": "Tokens par modèle", + "title": "Usage et limites", + "modelBreakdown": "Entrée {{input}} · sortie {{output}} · lecture de cache {{cacheRead}} · écriture de cache {{cacheWrite}}", + "switchTo": "Basculer vers {{account}}", + "switchToOrg": "Basculer vers {{account}} ({{org}})", + "signInExpired": "{{account}} — connexion expirée, exécutez /login" + }, + "attach": { + "image": "Image", + "textFile": "Fichier texte", + "webpageUrl": "URL de page web", + "attach": "Joindre", + "readFailed": "Impossible de lire {{name}} : {{reason}}", + "unknownError": "erreur inconnue", + "webpageTitle": "Joindre une page web", + "webpageHint": "Saisissez une URL à extraire et joindre comme contexte.", + "scrapeFailed": "Échec de l'extraction de la page web", + "uploadFailed": "Échec de l'envoi de l'image", + "dropImages": "Déposez des images à joindre" + }, + "signIn": { + "title": "Se connecter à Claude", + "done": "Connecté. Les nouveaux tours utilisent ces identifiants — aucun redémarrage nécessaire.", + "asking": "Demande d'un lien de connexion à la CLI…", + "openHint": "Ouvrez ce lien, autorisez, puis collez ici le code qui vous est donné.", + "authorise": "Autoriser sur claude.com", + "copyLink": "Copier le lien, pour l'ouvrir dans un autre navigateur ou sur votre téléphone", + "pasteCode": "collez le code" + }, + "message": { + "copyCode": "Copier le code", + "copyMarkdown": "Copier en bloc markdown", + "showMore": "Afficher plus ({{count}} lignes de plus)", + "subagent": "Sous-agent", + "outputError": "Sortie — erreur", + "contextCleared": "Contexte effacé — rien au-dessus de ceci n'est en mémoire", + "interrupted": "Interrompu par l'utilisateur", + "agentRestarted": "Agent redémarré — tour interrompu", + "retry": "Réessayer", + "planUnchanged": "Mode plan inchangé", + "approve": "Approuver", + "frontmatter": "Frontmatter", + "systemPrompt": "Prompt système", + "reviseHint": "Que faut-il changer ? L'agent révise le plan à partir de ceci.", + "waiting": "En attente d'une réponse", + "narrationFailed": "La narration a été générée mais n'a pas pu être lue.", + "readAloudFailed": "Impossible de lire ce message à voix haute : {{reason}}", + "compacted": "Contexte compacté", + "compactedAuto": "Contexte compacté automatiquement", + "answerSent": "Réponse envoyée — en attente de l'agent", + "other": "Autre..." + }, + "composer": { + "stop": "Arrêter", + "sendIntoTurn": "Envoyer ceci dans le tour déjà en cours", + "removeFromQueue": "Retirer de la file", + "send": "Envoyer", + "startHint": "Envoyez un message pour commencer", + "jumpToLatest": "Aller au message le plus récent", + "noAudio": "Aucun audio n'a été capté" + }, + "agent": { + "gone": "L'agent de ce panneau n'existe plus.", + "forget": "L'oublier", + "name": "Nommer cet agent", + "peers": "Pairs : {{names}}" + }, + "copy": "Copier" + }, + "files": { + "folderGone": "Ce dossier a disparu — retour au début", + "searchFailed": "La recherche a échoué", + "createFolderFailed": "Échec de la création du dossier", + "createFileFailed": "Échec de la création du fichier", + "uploadFailed": "Échec de l'envoi des fichiers", + "renameFailed": "Échec du renommage", + "deleteFailed": "Échec de la suppression", + "deleteSomeFailed": "Échec de la suppression de certains éléments", + "downloadFailed": "Échec du téléchargement", + "pathCopied": "Chemin copié", + "generatingSpeech": "Génération de l'audio vocal…", + "speechFailed": "Échec de la génération de l'audio vocal", + "extracting": "Extraction de l'archive…", + "extractFailed": "Échec de l'extraction de l'archive", + "cloned": "Dépôt cloné", + "cloneFailed": "Échec du clonage du dépôt", + "cloning": "Clonage…", + "duplicateFailed": "Échec de la duplication", + "compressing": "Compression…", + "compressFailed": "Échec de la compression", + "pasteFailed": "Échec du collage", + "pasteNeedsSecure": "Coller depuis le presse-papiers nécessite un contexte sécurisé (https). Faites glisser le fichier à la place.", + "copiedItems": { + "one": "{{count}} élément copié", + "other": "{{count}} éléments copiés" + }, + "cutItems": { + "one": "{{count}} élément coupé", + "other": "{{count}} éléments coupés" + }, + "pastedItems": { + "one": "{{count}} élément collé", + "other": "{{count}} éléments collés" + }, + "uploadedFiles": { + "one": "{{count}} fichier envoyé", + "other": "{{count}} fichiers envoyés" + }, + "deleteItemsQ": { + "one": "Supprimer {{count}} élément ?", + "other": "Supprimer {{count}} éléments ?" + }, + "deletedItems": { + "one": "{{count}} élément supprimé", + "other": "{{count}} éléments supprimés" + }, + "createdFolder": "Dossier “{{name}}” créé", + "created": "“{{name}}” créé", + "renamedTo": "Renommé en “{{name}}”", + "deleteQ": "Supprimer “{{name}}” ?", + "deleted": "“{{name}}” supprimé", + "extractedTo": "Extrait vers “{{name}}”", + "duplicated": "“{{name}}” dupliqué", + "pastedInto": "Collé dans “{{name}}”", + "unpinned": "“{{name}}” désépinglé", + "pinned": "“{{name}}” épinglé", + "menu": { + "copyPath": "Copier le chemin", + "chatAboutThis": "En discuter dans le Chat", + "paste": "Coller", + "newFile": "Nouveau fichier", + "newFolder": "Nouveau dossier", + "uploadFiles": "Envoyer des fichiers", + "uploadFolder": "Envoyer un dossier", + "cloneRepository": "Cloner le dépôt", + "hideHiddenFiles": "Masquer les fichiers cachés", + "showHiddenFiles": "Afficher les fichiers cachés", + "createDashboardHere": "Créer un Dashboard ici", + "downloadVideo": "Télécharger la vidéo", + "dictate": "Dicter", + "dropToUpload": "Déposez des fichiers pour les envoyer", + "openFolder": "Ouvrir le dossier", + "showLocation": "Afficher l'emplacement", + "download": "Télécharger", + "noResults": "Aucun résultat trouvé", + "open": "Ouvrir", + "openInEditor": "Ouvrir dans l'éditeur", + "openInEditorTooLarge": "Ouvrir dans l'éditeur (trop volumineux)", + "pasteIntoFolder": "Coller dans le dossier", + "readAloud": "Lire à voix haute", + "extract": "Extraire", + "runTask": "Exécuter la tâche", + "runAgent": "Exécuter l'agent", + "cut": "Couper", + "copy": "Copier", + "duplicate": "Dupliquer", + "rename": "Renommer", + "compress": "Compresser", + "compressSelection": "Compresser la sélection", + "unpin": "Désépingler", + "pin": "Épingler", + "chat": "Chat...", + "delete": "Supprimer", + "gitClone": "Cloner via Git", + "searchFiles": "Rechercher des fichiers...", + "clearSelection": "Effacer la sélection", + "selected": "sélectionnés", + "refresh": "Actualiser" + }, + "grid": { + "empty": "Ce dossier est vide", + "sort": "Trier :", + "name": "Nom", + "type": "Type", + "size": "Taille", + "date": "Date" + } + }, + "taskRunner": { + "stop": "Arrêter", + "taskFailed": "La tâche a échoué", + "taskComplete": "Tâche terminée", + "noSubtitles": "Aucun sous-titre", + "none": "Aucun", + "noVideos": "Aucune vidéo trouvée dans ce dossier.", + "differentLayout": "Disposition différente — exécutez-les séparément :", + "skipped": "Ignorés (convertir séparément) :", + "analyzing": "Analyse des fichiers…", + "close": "Fermer", + "runningOn": "Exécuté sur", + "queue": "File d'attente", + "concurrency": "Concurrence", + "model": "Modèle", + "steps": "Étapes", + "stopPipeline": "Arrêter le pipeline", + "pipelineFailed": "Le pipeline a échoué", + "pipelineComplete": "Pipeline terminé", + "viewInJobs": "Voir dans les Jobs", + "copyOutput": "Copier la sortie", + "track": "Piste {{n}}", + "runFromStep": "Exécuter à partir de l'étape {{n}}" + }, + "newEntry": { + "nameReserved": "Ce nom est réservé", + "nameSlash": "Le nom ne peut pas contenir \"/\"", + "nameExists": "\"{{name}}\" existe déjà ici", + "urlSpaces": "Une URL ne peut pas contenir d'espaces", + "cancel": "Annuler", + "file": { + "title": "Nouveau fichier", + "description": "Incluez l'extension — c'est ce que l'éditeur utilise pour choisir le langage. Il s'ouvre dès sa création.", + "label": "Nom du fichier", + "placeholder": "notes.txt", + "submit": "Créer et ouvrir" + }, + "folder": { + "title": "Nouveau dossier", + "description": "Créé dans le dossier que vous consultez.", + "label": "Nom du dossier", + "placeholder": "documents", + "submit": "Créer le dossier" + }, + "clone": { + "title": "Cloner le dépôt", + "description": "Cloné dans le dossier que vous consultez, sous son propre nom.", + "label": "URL du dépôt", + "placeholder": "https://github.com/owner/repo.git", + "submit": "Cloner" + } + }, + "videoDownload": { + "title": "Télécharger la vidéo", + "descriptionInput": "Collez une URL de vidéo ou de playlist — les détails sont récupérés avant le téléchargement.", + "descriptionPreview": "Vérifiez et téléchargez.", + "urlPlaceholder": "https://www.youtube.com/watch?v=… ou …/playlist?list=…", + "audioOnly": "Extraire uniquement l'audio (mp3)", + "fetch": "Récupérer", + "back": "Retour", + "audioOnlyLabel": "Audio uniquement", + "video": "Vidéo", + "itemCount": { + "one": "{{count}} élément", + "other": "{{count}} éléments" + }, + "subfolderPlaceholder": "Sous-dossier (facultatif) — laissez vide pour ce dossier", + "downloadAll": "Tout télécharger", + "couldNotFetch": "Impossible de récupérer", + "downloading": "Téléchargement…", + "saving": "Enregistrement…", + "saved": "Enregistré", + "retry": "Réessayer", + "download": "Télécharger", + "couldNotFetchInfo": "Impossible de récupérer les informations", + "timedOut": "Délai dépassé", + "downloadFailed": "Échec du téléchargement", + "couldNotStart": "Impossible de démarrer le téléchargement" + }, + "fileBrowserWidget": { + "title": "Explorateur de fichiers", + "search": "Rechercher...", + "browse": "Parcourir", + "recent": "Récents", + "pinned": "Épinglés", + "noResults": "Aucun résultat", + "emptyDirectory": "Répertoire vide", + "noPinnedFiles": "Aucun fichier épinglé", + "noRecentFiles": "Aucun fichier récent", + "uploading": "Envoi en cours...", + "empty": "Ce dossier est vide", + "sort": "Trier :" + }, + "agentRunner": { + "agentRunning": "Agent en cours d'exécution", + "keepsGoing": "Il continue à s'exécuter, que cette fenêtre reste ouverte ou non. Suivez-le dans le Chat — la session la plus récente en haut.", + "openChat": "Ouvrir le Chat", + "target": "Cible", + "starting": "Démarrage…", + "runAgent": "Exécuter l'agent", + "startFailed": "Échec du démarrage de l'agent" + }, + "dictate": { + "title": "Dicter", + "description": "Enregistrement audio en cours. Appuyez sur Entrée ou cliquez sur Arrêter pour enregistrer.", + "recording": "Enregistrement...", + "startingStatus": "Démarrage...", + "stopSave": "Arrêter et enregistrer", + "noAudioCaptured": "Aucun audio n'a été capté", + "micAccessFailed": "Impossible d'accéder au microphone", + "recordingFailed": "Échec de l'enregistrement" + } +} diff --git a/src/workspaces/officerdev/src/locales/parity.test.ts b/src/workspaces/officerdev/src/locales/parity.test.ts index 5c603fa4..008e8a60 100644 --- a/src/workspaces/officerdev/src/locales/parity.test.ts +++ b/src/workspaces/officerdev/src/locales/parity.test.ts @@ -2,11 +2,12 @@ import { describe, expect, test } from 'bun:test'; import { findMissingKeys } from 'i18n'; import en from './en-GB.json'; import ptPT from './pt-PT.json'; +import frFR from './fr-FR.json'; -// D2: every shipped locale ships a COMPLETE catalog. Phase 0 scaffolds this catalog with no keys yet -// (no panel is converted in this phase) — the test still runs so it is load-bearing from day one. +// D2: every shipped locale ships a COMPLETE catalog. A key en-GB has and another locale lacks is a red +// build, not a warning — there is no regional fallback to paper over the gap. describe('officerdev (panel.*) locale catalogs', () => { - test('pt-PT has every key en-GB has', () => { - expect(findMissingKeys(en, { 'pt-PT': ptPT })).toEqual([]); + test('every shipped locale has every key en-GB has', () => { + expect(findMissingKeys(en, { 'pt-PT': ptPT, 'fr-FR': frFR })).toEqual([]); }); });