From 2e36a3ce52bd33cbb74889805aed19567b57a711 Mon Sep 17 00:00:00 2001 From: bamiebot Date: Wed, 29 Jul 2026 17:47:00 +0100 Subject: [PATCH] feat: support multi-language localizations across entire dashboard --- frontend/messages/en.json | 5 +- frontend/messages/es.json | 7 +- frontend/messages/fr.json | 402 ++++++++++++++++++++++++++++++++++++ frontend/messages/pt.json | 7 +- frontend/src/i18n/config.ts | 4 +- 5 files changed, 416 insertions(+), 9 deletions(-) create mode 100644 frontend/messages/fr.json diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 4fa7e774..ba9b769a 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -29,8 +29,9 @@ "ariaLabel": "Select language", "options": { "en": "English", - "es": "Espanol", - "pt": "Portugues" + "es": "Spanish", + "pt": "Portuguese", + "fr": "French" } }, "nav": { diff --git a/frontend/messages/es.json b/frontend/messages/es.json index bc70513e..989fb167 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -28,9 +28,10 @@ "label": "Idioma", "ariaLabel": "Seleccionar idioma", "options": { - "en": "English", - "es": "Espanol", - "pt": "Portugues" + "en": "Inglés", + "es": "Español", + "pt": "Portugués", + "fr": "Francés" } }, "nav": { diff --git a/frontend/messages/fr.json b/frontend/messages/fr.json new file mode 100644 index 00000000..61a8c2a3 --- /dev/null +++ b/frontend/messages/fr.json @@ -0,0 +1,402 @@ +{ + "twoFactorAuth": { + "ariaLabel": "Configuration de l'authentification à deux facteurs", + "stepsLabel": "Étapes de configuration", + "title": "Activer l'authentification à deux facteurs", + "description": "Protégez votre compte avec une application d'authentification. Scannez un code QR pour commencer.", + "enableButton": "Activer 2FA", + "settingUp": "Configuration…", + "scanTitle": "Scannez avec votre application d'authentification", + "scanDescription": "Utilisez Google Authenticator, Authy ou toute application compatible TOTP pour scanner le code ci-dessous.", + "qrCodeAlt": "Code QR TOTP — scannez avec votre application d'authentification", + "qrCodeSkeletonLabel": "Génération du code QR…", + "manualKeyLabel": "Ou entrez la clé manuellement", + "manualKeyAria": "Clé de configuration manuelle : {key}", + "codeInputLabel": "Entrez le code à 6 chiffres", + "codeInputPlaceholder": "000000", + "verifyButton": "Vérifier & Activer", + "verifying": "Vérification…", + "successTitle": "Authentification à deux facteurs activée", + "successDescription": "Votre compte est désormais protégé. Un code vous sera demandé à chaque connexion.", + "error": { + "setupFailed": "Échec du démarrage de la configuration 2FA. Veuillez réessayer.", + "invalidCode": "Code invalide. Veuillez réessayer.", + "codeLength": "Veuillez entrer le code à 6 chiffres de votre application d'authentification." + } + }, + "localeSwitcher": { + "label": "Langue", + "ariaLabel": "Choisir la langue", + "options": { + "en": "Anglais", + "es": "Espagnol", + "pt": "Portugais", + "fr": "Français" + } + }, + "nav": { + "home": "Accueil", + "docs": "Documentation", + "login": "Connexion", + "register": "Inscription", + "toggleMenu": "Basculer le menu" + }, + "sidebar": { + "overview": "Aperçu", + "payments": "Paiements", + "webhookLogs": "Journaux Webhook", + "apiKeys": "Clés API", + "settings": "Paramètres", + "workspace": "Espace authentifié", + "shortcuts": "Raccourcis", + "createPayment": "Créer un paiement", + "network": "Mainnet-testnet", + "collapse": "Réduire la barre latérale", + "expand": "Agrandir la barre latérale", + "close": "Fermer la navigation" + }, + "breadcrumbs": { + "home": "Accueil", + "segments": { + "dashboard": "Tableau de bord", + "create": "Créer", + "payments": "Paiements", + "settings": "Paramètres", + "webhook-logs": "Journaux Webhook", + "api-keys": "Clés API" + } + }, + "copyButton": { + "ariaLabel": "Copier dans le presse-papiers", + "copied": "Copié !", + "copiedState": "Copié" + }, + "dashboardPage": { + "title": "Aperçu Marchand", + "description": "Suivez votre volume de transactions, suivez les paiements en temps réel et gérez votre intégration PLUTO.", + "paymentMetrics": "Statistiques de Paiement", + "recentActivity": "Activité Récente", + "viewAllPayments": "Voir tous les paiements", + "quickActions": "Actions Rapides", + "createPaymentLink": "Créer un lien de paiement", + "withdrawFunds": "Retirer des fonds (SEP-24)", + "development": "Développement", + "apiKeysTip": "Utilisez la page Clés API pour renouveler régulièrement votre jeton secret.", + "webhookLogsTip": "Vérifiez les journaux Webhook si les paiements ne déclenchent pas votre serveur." + }, + "realTimeBalanceSync": { + "title": "Soldes en temps réel", + "refreshButton": "Actualiser", + "syncing": "Synchronisation…", + "sectionAriaLabel": "Informations de solde en temps réel", + "balancesListAriaLabel": "Soldes du compte", + "emptyState": "Aucun solde disponible.", + "updatedLabel": "Mis à jour", + "balanceItemAriaLabel": "Solde {asset} : {balance}", + "liveRegion": { + "syncing": "Synchronisation des soldes…", + "error": "Erreur de synchronisation du solde : {error}", + "updatedAt": "Soldes mis à jour à {time}." + } + }, + "createPaymentPage": { + "eyebrow": "Tableau de bord", + "title": "Créer un lien de paiement", + "description": "Définissez un montant, choisissez un actif et saisissez une adresse destinataire pour générer un lien de paiement PLUTO.", + "newHere": "Nouveau ici ?", + "registerMerchant": "Créer un compte marchand" + }, + "createPaymentForm": { + "invalidAmount": "Le montant doit être un nombre positif.", + "invalidRecipient": "Le destinataire doit être une clé publique Stellar valide (56 caractères, commence par G).", + "invalidHexColor": "{field} doit être une couleur hexadécimale valide.", + "failedCreate": "Échec de la création du lien de paiement", + "createdToast": "Lien de paiement créé !", + "noApiKeyTitle": "Aucune clé API trouvée", + "noApiKeyDescription": "Enregistrez un compte marchand pour obtenir votre clé API et commencer à créer des liens de paiement.", + "registerAsMerchant": "S'inscrire comme marchand", + "readyEyebrow": "Lien de paiement prêt", + "readyTitle": "Lien créé avec succès", + "readyDescription": "Partagez ce lien avec votre client pour recevoir le paiement.", + "shareLink": "Partager le lien", + "shareTitle": "Lien de paiement prêt", + "shareText": "Ouvrez ce lien de paiement PLUTO.", + "shareFailed": "Le partage natif est indisponible pour le moment.", + "paymentLink": "Lien de paiement", + "paymentId": "ID de paiement", + "status": "Statut", + "createAnother": "Créer un autre lien de paiement", + "amount": "Montant", + "amountPlaceholder": "Entrez {exampleAmount} {asset}", + "asset": "Actif", + "selectAsset": "Sélectionner un actif", + "issuer": "Émetteur", + "trustedAddresses": "Sélectionner parmi les adresses de confiance", + "selectSavedAddress": "-- Sélectionner une adresse enregistrée --", + "recipientAddress": "Adresse du destinataire", + "recipientPlaceholder": "Collez le portefeuille destinataire {asset}", + "recipientPlaceholderSelected": "{label} sélectionné pour {asset}", + "descriptionLabel": "Description", + "optional": "optionnel", + "descriptionPlaceholder": "À quoi sert cette demande en {asset} ?", + "brandingTitle": "Personnalisation de la session", + "brandingDescription": "Appliquez des couleurs personnalisées uniquement à ce lien de paiement.", + "enabled": "Activé", + "disabled": "Désactivé", + "primary": "Primaire", + "secondary": "Secondaire", + "background": "Arrière-plan", + "checkoutPreview": "Aperçu du paiement", + "payNow": "Payer maintenant", + "generating": "Génération en cours...", + "generate": "Générer le lien de paiement", + "rateLimitError": "Vous créez des liens trop rapidement. Réessayez dans {seconds} secondes.", + "retryWait": "Patientez {seconds}s…", + "integrationCode": "Code d'intégration", + "snippetsHelper": "Les extraits de code se mettent à jour automatiquement avec vos valeurs." + }, + "paymentMetrics": { + "downloadImage": "Télécharger l'image", + "downloadPng": "Télécharger PNG", + "downloadSvg": "Télécharger SVG", + "exporting": "Exportation...", + "exportSuccess": "Graphique téléchargé au format {format}", + "exportFailed": "Échec de l'exportation du graphique.", + "fetchMetricsFailed": "Échec de la récupération des statistiques", + "fetchVolumeFailed": "Échec de la récupération des données de volume", + "retry": "Réessayer", + "sevenDayVolume": "Volume sur 7 jours", + "totalPayments": "Total des paiements", + "paymentsCount": "{count, plural, one {paiement} other {paiements}}", + "chartTitle": "Comparaison de volume multi-actifs", + "chartSubtitle": "Volume quotidien des transactions ventilé par actif", + "heatmapTitle": "Carte thermique d'intensité des paiements", + "heatmapSubtitle": "Intensité d'activité style GitHub sur les 365 derniers jours", + "heatmapTotal": "{count, plural, one {# paiement sur l'année écoulée} other {# paiements sur l'année écoulée}}", + "heatmapTooltip": "{count, plural, one {# paiement} other {# paiements}} le {date}", + "heatmapAriaLabel": "Carte thermique d'intensité de paiement sur 365 jours", + "heatmapEmptyDay": "Aucun paiement enregistré", + "heatmapLegendLess": "Moins", + "heatmapLegendMore": "Plus", + "heatmapWeekdays": { + "sun": "D", + "mon": "L", + "tue": "M", + "wed": "M", + "thu": "J", + "fri": "V", + "sat": "S" + }, + "showRange": "Afficher {range}", + "toggleAssetVisibility": "Basculer la visibilité de l'actif", + "showAsset": "Afficher {asset}", + "hideAsset": "Masquer {asset}", + "noPayments": "Aucun paiement effectué sur cette période.", + "weeklyAvgLabel": "Moy. 7J", + "ranges": { + "7D": "7 jours", + "30D": "30 jours", + "1Y": "1 an" + } + }, + "paymentsPage": { + "title": "Historique des paiements", + "description": "Suivez toutes les transactions entrantes et leur statut de vérification." + }, + "portfolioChart": { + "valueTitle": "Valeur du portefeuille", + "allocation": "Allocation", + "trend": "Tendance" + }, + "recentPayments": { + "missingApiKey": "Clé API introuvable. Veuillez vous inscrire ou vous connecter.", + "fetchFailed": "Échec de la récupération des paiements", + "loadFailed": "Échec du chargement des paiements", + "connectionError": "Erreur de connexion", + "backendHint": "Assurez-vous que le serveur backend est en cours d'exécution.", + "retryConnection": "Réessayer la connexion", + "testWebhook": "Tester le webhook malgré tout", + "troubleshootingTip": "Conseil de dépannage", + "troubleshootingDescription": "Vous pouvez toujours tester les fonctionnalités webhook pendant la restauration des services.", + "emptyTitle": "Aucun paiement pour le moment", + "emptyDescription": "Commencez à accepter des paiements en créant votre premier lien de paiement.", + "createPaymentLink": "Créer un lien de paiement", + "sendTestWebhook": "Envoyer un webhook de test", + "gettingStartedTitle": "Guide de démarrage", + "gettingStartedDescription": "Utilisez les outils webhook pour tester les notifications de paiement.", + "search": "Rechercher", + "searchPlaceholder": "Rechercher par ID de paiement ou description...", + "status": "Statut", + "allStatuses": "Tous les statuts", + "asset": "Actif", + "allAssets": "Tous les actifs", + "fromDate": "Date de début", + "toDate": "Date de fin", + "activeFilters": "Filtres actifs :", + "searchChip": "Recherche : \"{value}\"", + "statusChip": "Statut : {value}", + "assetChip": "Actif : {value}", + "fromChip": "Du : {value}", + "toChip": "Au : {value}", + "clearSearchFilter": "Effacer le filtre de recherche", + "clearStatusFilter": "Effacer le filtre de statut", + "clearAssetFilter": "Effacer le filtre d'actif", + "clearFromDateFilter": "Effacer le filtre de date de début", + "clearToDateFilter": "Effacer le filtre de date de fin", + "clearAll": "Tout effacer", + "linkCopied": "Lien de paiement copié dans le presse-papiers !", + "showingResults": "Affichage de {shown} sur {total} paiements", + "filteredSuffix": "(filtres appliqués)", + "tableStatus": "Statut", + "tableAmount": "Montant", + "tableRecipient": "Destinataire", + "tableDate": "Date", + "tableLink": "Lien", + "view": "Voir", + "emptyDescriptionValue": "-", + "statuses": { + "all": "Tous", + "pending": "En attente", + "confirmed": "Confirmé", + "failed": "Échoué", + "refunded": "Remboursé" + }, + "pagination": { + "ariaLabel": "Pagination de l'historique des paiements", + "previous": "Page précédente", + "next": "Page suivante", + "goToPage": "Aller à la page {page}", + "currentPage": "Page actuelle, page {page}", + "pageLabel": "Page {page} sur {totalPages}", + "range": "Affichage de {start}-{end} sur {total}" + } + }, + "walletSelector": { + "chooseWallet": "Choisissez un portefeuille", + "description": "Connectez votre portefeuille Stellar pour effectuer ce paiement.", + "connecting": "Connexion...", + "walletConnectWaiting": "En attente du portefeuille...", + "noProjectId": "(aucun ID de projet)", + "notInstalled": "(non installé)", + "tapToConnect": "Cliquer pour connecter", + "scanTitle": "Scannez avec votre application de portefeuille compatible", + "scanDescription": "Scannez avec Freighter mobile ou tout portefeuille compatible WalletConnect", + "pairingFailed": "Échec de l'association WalletConnect", + "userRejected": "Demande de connexion annulée. Veuillez l'approuver dans votre portefeuille.", + "walletConnectUnavailable": "WalletConnect est indisponible pour le moment.", + "extensionNotFound": "Portefeuille introuvable sur cet appareil.", + "noAccountFound": "Portefeuille connecté, mais aucun compte Stellar n'a été retourné.", + "walletConnectFailed": "Échec de la connexion WalletConnect.", + "connectionFailed": "Impossible de se connecter à ce portefeuille." + }, + "support": { + "panelAriaLabel": "Panneau d'aide et de support", + "heading": "Aide & Support", + "title": "Besoin d'aide avec vos paiements ?", + "description": "Envoyez-nous un message pour obtenir un support prioritaire.", + "contactEmail": "Contacter par email", + "openChat": "Ouvrir le chat de support", + "closeChat": "Fermer le chat de support", + "yourWallet": "Votre Portefeuille", + "notConnected": "Non connecté", + "connectingWallet": "Connexion…", + "amount": "Montant", + "loadingBalance": "Chargement du solde...", + "unfunded": "Non financé", + "available": "Disponible : {balance} {asset}", + "availableAriaLabel": "Solde disponible : {balance} {asset}", + "balanceSyncing": "Synchronisation du solde…", + "walletUnfunded": "Portefeuille non financé.", + "balanceSynced": "Solde mis à jour : {balance} {asset}.", + "insufficientBalance": "Solde insuffisant", + "messageLabel": "Laisser un message (optionnel)", + "messagePlaceholder": "Merci pour le super travail !", + "sendSupport": "Envoyer", + "sending": "Envoi…", + "submissionFailed": "Échec de l'envoi", + "failedToRecord": "Échec de l'enregistrement de la transaction" + }, + "checkout": { + "paymentRequest": "Demande de paiement", + "completePayment": "Effectuer le paiement", + "paymentNotFound": "Paiement introuvable", + "loadFailed": "Impossible de charger les détails du paiement.", + "paymentMissing": "Paiement introuvable.", + "loadPaymentFailed": "Échec du chargement du paiement.", + "errorTitle": "Erreur", + "errorDescription": "Vérifiez le lien de paiement et réessayez.", + "processingFallback": "Traitement de la transaction...", + "doNotClose": "Ne fermez pas cet onglet", + "recipient": "Destinataire", + "scanToPay": "Scanner pour payer", + "scanDescription": "Scannez avec Freighter ou tout portefeuille compatible", + "openQrModal": "Payer par code QR", + "qrModalTitle": "Payer par code QR", + "qrModalDescription": "Scannez ce code sur un autre appareil.", + "downloadQr": "Télécharger PNG", + "downloadQrSuccess": "Code QR téléchargé.", + "downloadQrError": "Impossible de télécharger le code QR.", + "viewRawIntent": "Voir le lien direct", + "hideRawIntent": "Masquer le lien direct", + "created": "Créé", + "activeViewers": "{count, plural, one {# personne consulte ce lien} other {# personnes consultent ce lien}}", + "transaction": "Transaction", + "paymentConfirmed": "Paiement confirmé !", + "paymentSent": "Paiement envoyé !", + "paymentFailed": "Paiement échoué. Veuillez réessayer.", + "connectedVia": "Connecté via {provider}", + "payInXlm": "Payer en XLM", + "standardAssetOption": "Payer la facture en {asset}", + "pathPaymentOption": "Payer en XLM", + "approximateCost": "Coût approximatif en XLM", + "merchantReceives": "Le marchand reçoit {amount} {asset}", + "quoteLoading": "Vérification des itinéraires de paiement XLM...", + "quoteUnavailable": "L'itinéraire de paiement XLM est indisponible.", + "quoteBuffer": "Vous pouvez envoyer jusqu'à {amount} XLM.", + "pathPaymentHint": "Utilise le paiement d'itinéraire Stellar via votre portefeuille.", + "approximateCostLabel": "Coût approximatif en XLM", + "approximateCostHelp": "Montant estimé pour livrer {amount} {asset} au marchand.", + "slippageBuffer": "Marge de sécurité de {percent}% incluse. Envoi max : {sendMax} {asset}", + "pathPaymentTogglePrefix": "Payer avec", + "pathPaymentToggleSuffix": "à la place", + "processing": "Traitement...", + "payWith": "Payer avec {provider}", + "payWithFallback": "Payer avec portefeuille", + "reviewPaymentTitle": "Vérifier le paiement", + "loadingNetworkFee": "Récupération des frais réseau...", + "networkFeeLabel": "Frais réseau : ~{amount} XLM", + "networkFeeUnavailable": "Frais réseau indisponibles.", + "cancel": "Annuler", + "confirmPayment": "Confirmer le paiement", + "receivedTitle": "Ce paiement a été reçu.", + "receivedDescription": "La transaction a été confirmée sur la blockchain.", + "downloadReceipt": "Télécharger le reçu", + "downloadReceiptLoading": "Préparation du reçu...", + "receiptDownloaded": "Téléchargement du reçu démarré.", + "receiptDownloadFailed": "Impossible de générer le reçu PDF.", + "receiptHashUnavailable": "Indisponible", + "failedTitle": "Ce paiement a échoué.", + "failedDescription": "Contactez le marchand si vous pensez qu'il s'agit d'une erreur.", + "status": { + "pending": "En attente de paiement", + "confirmed": "Confirmé", + "completed": "Terminé", + "failed": "Échoué" + } + }, + "profileCard": { + "account": "Compte", + "openProfileMenu": "Ouvrir le menu de profil", + "dashboard": "Tableau de bord", + "settings": "Paramètres", + "createPayment": "Créer un paiement", + "logoutAccount": "Se déconnecter", + "loadingProfile": "Chargement du profil…" + }, + "settingsPage": { + "eyebrow": "Paramètres", + "eyebrowAccount": "Compte", + "title": "Paramètres", + "description": "Gérez vos identifiants, votre image de marque et vos intégrations." + } +} diff --git a/frontend/messages/pt.json b/frontend/messages/pt.json index bc0b8aad..a77764ef 100644 --- a/frontend/messages/pt.json +++ b/frontend/messages/pt.json @@ -28,9 +28,10 @@ "label": "Idioma", "ariaLabel": "Selecionar idioma", "options": { - "en": "English", - "es": "Espanol", - "pt": "Portugues" + "en": "Inglês", + "es": "Espanhol", + "pt": "Português", + "fr": "Francês" } }, "nav": { diff --git a/frontend/src/i18n/config.ts b/frontend/src/i18n/config.ts index 71a664a4..25714e82 100644 --- a/frontend/src/i18n/config.ts +++ b/frontend/src/i18n/config.ts @@ -1,4 +1,4 @@ -export const locales = ["en", "es", "pt"] as const; +export const locales = ["en", "es", "pt", "fr"] as const; export type AppLocale = (typeof locales)[number]; @@ -22,6 +22,8 @@ export function localeToLanguageTag(locale: string): string { return "es-ES"; case "pt": return "pt-BR"; + case "fr": + return "fr-FR"; case "en": default: return "en-US";