Internationalization: 7 languages with live switching

Custom signal-based runtime (no i18n dependency): shared src/shared/i18n
dictionaries, ui I18nService.t() reactive by construction so language
switches live, and a backend i18n.ts for native menus + thrown errors.
Every user-facing string keyed across templates, toasts, dialogs, file
kinds, columns, macOS menu/context menus, and errors. Language picker in
Options (auto-detects OS locale, falls back to English); switching
relabels the webview and native chrome instantly. English/Spanish/French/
German fully drafted; Chinese/Japanese/Basque drafted, flagged for native
review. Docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 15:52:54 +02:00
co-authored by Claude Fable 5
parent d8cfc54049
commit a0f56822cd
43 changed files with 2755 additions and 322 deletions
+299
View File
@@ -0,0 +1,299 @@
// French translations. Missing keys fall back to English (see en.ts / index.ts).
export const fr = {
common: {
cancel: "Annuler",
save: "Enregistrer",
close: "Fermer",
delete: "Supprimer",
rename: "Renommer",
create: "Créer",
apply: "Appliquer",
choose: "Choisir…",
connect: "Connecter",
local: "Local",
},
app: {
notConnectedTitle: "Non connecté à l'application",
notConnectedBody: "Cette page s'exécute dans un navigateur classique. Lancez l'application de bureau pour l'utiliser.",
managerOpen: "Le gestionnaire de connexions est ouvert.",
showConnections: "Afficher les connexions",
},
tabBar: {
connections: "Connexions",
manageConnections: "Gérer les connexions",
newTab: "Nouvel onglet ({mod}T)",
closeTab: "Fermer l'onglet ({mod}W)",
options: "Options",
},
options: {
title: "Options",
language: "Langue",
system: "Système",
theme: { label: "Thème", system: "Système", light: "Clair", dark: "Sombre" },
tone: { label: "Ton", dark: "Sombre", darker: "Plus sombre" },
accent: { label: "Accent" },
accentNames: {
graphite: "Graphite",
blue: "Bleu",
indigo: "Indigo",
purple: "Violet",
rose: "Rose",
orange: "Orange",
green: "Vert",
teal: "Sarcelle",
},
fileIcons: { label: "Icônes", plain: "Simple", multi: "Couleur" },
interface: { label: "Interface", compact: "Compacte", default: "Normale", large: "Grande" },
speedUnit: { label: "Unité de vitesse" },
transfers: { label: "Transferts", single: "Un", multi: "Plusieurs" },
atOnce: "Simultanés",
editor: { label: "Éditeur", builtin: "Intégré", custom: "Personnalisé" },
systemDefault: "Valeur par défaut du système",
resetDefault: "Réinitialiser par défaut",
finishedTransfers: { label: "Transferts terminés", autoClear: "Effacer auto.", keep: "Garder" },
},
kinds: {
folder: "Dossier",
symlink: "Lien symbolique",
file: "Fichier",
image: "Image",
audio: "Audio",
video: "Vidéo",
archive: "Archive",
code: "Code",
document: "Document",
},
columns: {
name: "Nom",
size: "Taille",
mtime: "Modifié",
kind: "Type",
rights: "Droits",
owner: "Propriétaire",
group: "Groupe",
},
panel: {
choosePanel: "Choisir ce qu'affiche ce panneau",
copy: "Copier",
copyToRight: "Copier vers le panneau droit (F5)",
copyToLeft: "Copier vers le panneau gauche (F5)",
edit: "Éditer",
editFile: "Éditer le fichier (F4)",
rename: "Renommer (F2)",
newFolder: "Nouveau dossier (F7)",
newFile: "Nouveau fichier",
permissions: "Autorisations",
delete: "Supprimer",
deleteHint: "Supprimer (F8 / Suppr)",
parentDir: "Dossier parent (Retour arrière)",
refresh: "Actualiser",
clearFilter: "Effacer le filtre (Échap)",
sshConfig: "~/.ssh/config",
connectingTo: "Connexion à {name}…",
emptyDir: "Dossier vide",
noMatches: "Aucun résultat",
matches: { one: "{count} résultat · Échap", other: "{count} résultats · Échap" },
},
connMgr: {
title: "Connexions",
importWinscp: "Importer depuis WinSCP…",
importWinscpHint: "Importer des sessions depuis un export WinSCP.ini",
newConnection: "Connexion",
newConnectionHint: "Nouvelle connexion",
newStack: "Groupe",
newStackHint: "Nouveau groupe",
expand: "Développer",
collapse: "Réduire",
renameStack: "Renommer le groupe",
deleteStack: "Supprimer le groupe",
openStack: "Ouvrir le groupe dans des onglets",
empty: "vide",
connectionsHeader: "Connexions",
noneTitle: "Aucune connexion enregistrée.",
noneBody: "Créez-en une ou importez vos sessions WinSCP.",
name: "Nom",
namePlaceholder: "mon serveur",
host: "Hôte",
port: "Port",
stack: "Groupe",
stackNone: "(aucun)",
username: "Utilisateur",
remotePath: "Chemin distant (facultatif)",
remotePathPlaceholder: "dossier personnel",
auth: "Authentification",
authPassword: "Mot de passe",
authKey: "Clé privée",
password: "Mot de passe",
privateKeyFile: "Fichier de clé privée",
passphrase: "Phrase secrète (facultative)",
localDir: "Dossier local (facultatif)",
localDirPlaceholder: "~/projets/mon-serveur",
localDirHint: "L'autre panneau s'ouvre ici lorsqu'il est sur Local et n'a pas été parcouru.",
more: "Plus…",
less: "Moins…",
selectPrompt: "Sélectionnez une connexion ou créez-en une nouvelle.",
newStackTitle: "Nouveau groupe",
newStackPrompt: "nom du groupe",
renameStackTitle: "Renommer le groupe",
deleteStackTitle: "Supprimer le groupe",
deleteStackBody: 'Supprimer le groupe « {name} » ? Ses connexions sont conservées, sans groupe.',
deleteConnectionTitle: "Supprimer la connexion",
deleteConnectionBody: 'Supprimer « {name} » ?',
},
editor: {
noFileTitle: "Aucun fichier à éditer",
noFileBody: "Cette fenêtre a été ouverte sans contexte d'éditeur. Ouvrez des fichiers depuis l'application.",
wrap: "Retour",
wrapHint: "Retour à la ligne — masque les numéros de ligne ({alt}Z)",
findHint: "Rechercher ({mod}F)",
findPlaceholder: "Rechercher dans le fichier",
prevMatch: "Résultat précédent (⇧↵)",
nextMatch: "Résultat suivant (↵)",
closeHint: "Fermer (Échap)",
disconnected: "Déconnecté de {name} — l'enregistrement échouera, mais votre texte est conservé dans cette fenêtre.",
cantOpen: "Impossible d'ouvrir {name}",
closeWindow: "Fermer la fenêtre",
shortcuts: "{mod}S enregistrer · {mod}F rechercher · {mod}W fermer",
lines: { one: "{count} ligne", other: "{count} lignes" },
close: "Fermer",
save: "Enregistrer",
saveChangesTitle: 'Enregistrer les modifications de « {name} » ?',
saveChangesBody: "Vos modifications seront perdues si vous ne les enregistrez pas.",
dontSave: "Ne pas enregistrer",
countLabel: "{current} sur {total}",
noMatches: "Aucun résultat",
},
transfer: {
summary: "{active} en cours · {finished} terminés",
clearFinished: "Effacer les terminés",
cancel: "Annuler",
dismiss: "Ignorer",
showDetails: "Afficher les détails des transferts",
copying: "Copie de {name} · {percent}%{speed}",
queued: "{count} en file",
failed: { one: "{count} transfert échoué", other: "{count} transferts échoués" },
finished: "Transferts terminés",
ready: "Prêt",
items: "{count} éléments",
selected: "{count} sélectionnés",
queuedState: "En file",
scanning: "Analyse…",
failedState: "Échec",
cancelled: "Annulé",
completedFiles: { one: "{count} fichier · {size}", other: "{count} fichiers · {size}" },
left: "{eta} restant",
},
dialogs: {
chmodTitle: "Autorisations",
chmodItems: "{count} éléments",
read: "Lecture",
write: "Écriture",
execute: "Exécution",
roleOwner: "Propriétaire",
roleGroup: "Groupe",
roleOthers: "Autres",
octal: "Octal",
mergeFolder: "Fusionner le dossier",
replace: "Remplacer",
merge: "Fusionner",
mergeAll: "Tout fusionner",
replaceAll: "Tout remplacer",
skip: "Ignorer",
skipAll: "Tout ignorer",
ofTotal: "{index} sur {total}",
overwriteDirBody: "La destination contient déjà un dossier de ce nom. La fusion peut remplacer des fichiers.",
overwriteFileBody: "La destination contient déjà un fichier de ce nom.",
source: "Source",
destination: "Destination",
newer: "plus récent",
},
toasts: {
movedToTrash: '« {name} » mis à la corbeille',
editingExternal: "Édition de {name} — les enregistrements sont envoyés automatiquement",
nothingToCopy: "Rien à copier (les liens symboliques sont ignorés)",
nothingToCopyAllSkipped: "Rien à copier — tous les éléments en conflit ont été ignorés",
sameFolder: "Les deux panneaux affichent le même dossier",
sameFile: "« {name} » ignoré — c'est le même fichier",
typeMismatch: "« {name} » ignoré — un autre type d'élément est déjà là",
transferFailed: 'Le transfert « {name} » a échoué : {error}',
unknownError: "erreur inconnue",
saved: "{name} enregistré",
disconnectedReason: "{title} : déconnecté ({reason})",
imported: { one: "{count} connexion importée", other: "{count} connexions importées" },
importedIntoStacks: { one: " dans {count} nouveau groupe", other: " dans {count} nouveaux groupes" },
importedSkipped: " ({skipped} ignorées)",
leftoverTitle: "Fichiers édités restants",
leftoverMore: ", +{count} de plus",
leftoverBody: {
one: "Une session précédente a laissé {count} fichier édité temporaire ({names}). Les supprimer ?",
other: "Une session précédente a laissé {count} fichiers édités temporaires ({names}). Les supprimer ?",
},
leftoverClear: "Supprimer",
},
fileActions: {
deleteTitle: "Supprimer",
deleteBody: "Supprimer {what} ? Les dossiers sont supprimés récursivement. Action irréversible.",
deleteConfirm: "Supprimer",
renameTitle: 'Renommer « {name} »',
renameConfirm: "Renommer",
newFolderTitle: "Nouveau dossier",
newFolderPlaceholder: "nom du dossier",
newFileTitle: "Nouveau fichier",
newFilePlaceholder: "nom du fichier",
createConfirm: "Créer",
itemsCount: "{count} éléments",
},
menu: {
about: "À propos de {app}",
quit: "Quitter",
file: "Fichier",
newTab: "Nouvel onglet",
closeTab: "Fermer l'onglet",
newFolder: "Nouveau dossier",
newFile: "Nouveau fichier",
editFile: "Éditer le fichier",
connect: "Connecter",
connectionManager: "Gestionnaire de connexions…",
edit: "Édition",
view: "Affichage",
refresh: "Actualiser",
window: "Fenêtre",
},
ctx: {
size: "Taille",
modified: "Modifié",
kind: "Type",
rights: "Droits",
owner: "Propriétaire",
group: "Groupe",
revealFinder: "Afficher dans le Finder",
revealExplorer: "Afficher dans l'Explorateur",
revealFileManager: "Afficher dans le gestionnaire de fichiers",
moveToTrash: "Mettre à la corbeille",
moveToRecycleBin: "Mettre à la corbeille",
newFolder: "Nouveau dossier",
newFile: "Nouveau fichier",
refresh: "Actualiser",
open: "Ouvrir",
openDefault: "Ouvrir avec l'application par défaut",
edit: "Éditer",
copyToOther: "Copier vers l'autre panneau",
rename: "Renommer",
permissions: "Autorisations",
copyPath: "Copier le chemin",
delete: "Supprimer",
columns: "Colonnes",
trashTooltip: "La restauration du Finder n'est pas disponible pour les éléments mis à la corbeille d'ici",
},
errors: {
sshAgentUnavailable: "ssh-agent indisponible (SSH_AUTH_SOCK non défini)",
identityFileNotFound: "fichier d'identité introuvable : {path}",
sessionNotConnected: "La session « {id} » n'est pas connectée",
localCannotDisconnect: "La session locale ne peut pas être déconnectée",
notADirectory: "« {path} » existe et n'est pas un dossier",
sameFileTransfer: '« {name} » : la source et la destination sont le même fichier',
sameFileNoName: "La source et la destination sont le même fichier",
folderOntoItself: "Impossible de copier un dossier sur lui-même",
folderIntoItself: "Impossible de copier un dossier dans lui-même",
},
} as const;