import { ref } from 'vue' export const locale = ref(localStorage.getItem('locale') || 'fr') export function setLocale(lang) { locale.value = lang localStorage.setItem('locale', lang) } const LANGS = { fr: { // Sidebar / App tabTopology: 'Topologie', tabNetworks: 'Réseaux', tabDevices: 'Équipements', discovery: '🔍 Découverte auto', statsNetwork: 'Réseau', statsNetworks: 'Réseaux', statsDevice: 'Équip.', statsDevices: 'Équip.', exportJson: '⬇ Export JSON', importJson: '⬆ Import JSON', loadError: 'Erreur de chargement : ', importFailed: 'Import échoué : ', importTooLarge: 'Fichier trop volumineux (max 5 Mo).', // TopologyGraph ping: 'Ping', pinging: 'Ping en cours…', refreshPing: 'Rafraîchir le ping', wan: 'Internet / WAN', gateway: 'Passerelle inter-VLAN', unassigned: 'Non assigné', noDevice: 'Aucun équipement', noDevices: 'Aucun équipement à afficher.', noDevicesHint: 'Commencez par créer des réseaux et des équipements.', reachable: 'Joignable', unreachable: 'Injoignable', openWebUI: "Ouvrir l'interface web", // DeviceManager devices: 'Équipements', addDevice: '+ Ajouter un équipement', noDevicesConfigured: 'Aucun équipement configuré. Commencez par en ajouter un.', searchPlaceholder: 'Nom, IP…', filterType: 'Type', filterNetwork: 'Réseau', filterBrand: 'Marque', filterVirt: 'Virt', clearFilters: '✕ Effacer', noDevicesFiltered: 'Aucun équipement ne correspond aux filtres sélectionnés.', editDevice: "Modifier l'équipement", newDevice: 'Nouvel équipement', fieldName: 'Nom *', fieldType: 'Type *', fieldDescription: 'Description', isGateway: 'Passerelle inter-VLAN', isLivebox: 'Livebox / Box FAI', accessUrl: "URL d'accès", runtimeType: "Type d'environnement d'exécution", notSpecified: '— Non précisé', baremetal: 'Bare-metal', lxcContainer: 'Conteneur LXC', vmQemu: 'VM QEMU/KVM', networkInterfaces: 'Interfaces réseau', addInterface: '+ Interface', noInterface: 'Aucune interface configurée', cancel: 'Annuler', save: 'Enregistrer', create: 'Créer', badgeGateway: 'Passerelle', badgeLivebox: 'Livebox', confirmDeleteDevice: '{0} et tous ses liens ?', confirmDeleteNetwork: '{0} ?', saveError: 'Erreur lors de la sauvegarde', deleteError: 'Erreur lors de la suppression', descPlaceholder: 'Rôle, OS, notes…', // Device types typeServer: 'Serveur', typeSwitch: 'Switch', typeRouter: 'Routeur', typeNas: 'NAS', typeGateway: 'Passerelle', typeLivebox: 'Livebox', typeCamera: 'Caméra IP', typeTemperature: 'Sonde température/humidité', typeSensor: 'Capteur (mouvement, ouverture…)', typeHub: 'Hub domotique', typeSmartPlug: 'Prise connectée', typeAlarm: 'Alarme / Détecteur', typeLight: 'Éclairage connecté', typeDoorbell: 'Sonnette / Interphone', typeAccessPoint: 'Borne WiFi / Access Point', typeDesktop: 'Ordinateur fixe', typeLaptop: 'Ordinateur portable', typeSmartTv: 'TV connectée', typePrinter: 'Imprimante', typeSmartphone: 'Smartphone', typeOther: 'Autre', virtBaremetal: 'Bare-metal', virtLxc: 'LXC', virtQemu: 'VM QEMU', // VlanManager networks: 'Réseaux', addNetwork: '+ Ajouter un réseau', noNetworksConfigured: 'Aucun réseau configuré. Commencez par en créer un.', colType: 'Type', colName: 'Nom', colSubnet: 'Sous-réseau', colColor: 'Couleur', colActions: 'Actions', editNetwork: 'Modifier le réseau', newNetwork: 'Nouveau réseau', vlanId: 'ID VLAN', vlanIdHint: '(laisser vide pour LAN classique)', subnet: 'Sous-réseau CIDR', color: 'Couleur', subnetPlaceholder: 'ex: 192.168.10.0/24', // DiscoveryModal autoDiscovery: 'Découverte automatique', dnsServer: 'Serveur DNS', dnsHint: 'Le reverse DNS sera interrogé sur ce serveur pour résoudre les noms.', vlansToScan: 'VLANs à scanner', vlansHint: 'Seuls les VLANs avec un sous-réseau CIDR configuré peuvent être scannés.', noCidrWarning: "Aucun VLAN n'a de CIDR configuré. Renseignez-les dans l'onglet VLANs.", noCidr: 'pas de CIDR', startDiscovery: 'Lancer la découverte', scanning: 'Scan en cours…', scanAddresses: 'adresses sur', scanVlans: 'VLAN(s)', scanNote: 'Chaque hôte est pingé puis interrogé en DNS.', hostsFound: 'hôte(s) découvert(s)', addressesScanned: 'adresses scannées', newHosts: 'nouveaux', noHosts: 'Aucun hôte actif trouvé sur les plages sélectionnées.', colIp: 'IP', colDns: 'Nom (DNS)', colStatus: 'Statut', statusExisting: 'Déjà présent', statusNew: 'Nouveau', newScan: 'Nouveau scan', importingBtn: 'Import…', importBtn: 'Importer', dnsRequired: 'Veuillez renseigner un serveur DNS.', selectVlan: 'Sélectionnez au moins un VLAN.', importError: "Erreur lors de l'import.", scanError: 'Erreur lors du scan.', // Theme / Lang lightTheme: 'Thème clair', darkTheme: 'Thème sombre', // Auth loginTitle: 'Connexion', loginUsername: 'Nom d\'utilisateur', loginPassword: 'Mot de passe', loginBtn: 'Se connecter', loginError: 'Identifiants incorrects.', logout: 'Déconnexion', accountSettings: 'Paramètres du compte', currentPassword: 'Mot de passe actuel', newUsername: 'Nouveau nom d\'utilisateur', newPassword: 'Nouveau mot de passe', confirmPassword: 'Confirmer le mot de passe', leaveBlankToKeep: 'Laisser vide pour ne pas modifier', passwordMismatch: 'Les mots de passe ne correspondent pas.', accountUpdated: 'Compte mis à jour.', wrongPassword: 'Mot de passe actuel incorrect.', mustChangePasswordWarning: 'Pour des raisons de sécurité, vous devez changer votre mot de passe avant de continuer.', newPasswordRequired: 'Un nouveau mot de passe est requis.', passwordTooShort: 'Le mot de passe doit contenir au moins 8 caractères.', passwordTooWeak: 'Le mot de passe doit contenir au moins une lettre et un chiffre.', usernameInvalid: "Le nom d'utilisateur ne peut contenir que des lettres, chiffres, . _ - (1 à 64 caractères).", tooManyAttempts: 'Trop de tentatives, réessayez plus tard.', }, en: { tabTopology: 'Topology', tabNetworks: 'Networks', tabDevices: 'Devices', discovery: '🔍 Auto discovery', statsNetwork: 'Network', statsNetworks: 'Networks', statsDevice: 'Device', statsDevices: 'Devices', exportJson: '⬇ Export JSON', importJson: '⬆ Import JSON', loadError: 'Loading error: ', importFailed: 'Import failed: ', importTooLarge: 'File too large (max 5 MB).', ping: 'Ping', pinging: 'Pinging…', refreshPing: 'Refresh ping', wan: 'Internet / WAN', gateway: 'Inter-VLAN Gateway', unassigned: 'Unassigned', noDevice: 'No devices', noDevices: 'No devices to display.', noDevicesHint: 'Start by creating networks and devices.', reachable: 'Reachable', unreachable: 'Unreachable', openWebUI: 'Open web interface', devices: 'Devices', addDevice: '+ Add device', noDevicesConfigured: 'No devices configured. Start by adding one.', searchPlaceholder: 'Name, IP…', filterType: 'Type', filterNetwork: 'Network', filterBrand: 'Brand', filterVirt: 'Virt', clearFilters: '✕ Clear', noDevicesFiltered: 'No devices match the selected filters.', editDevice: 'Edit device', newDevice: 'New device', fieldName: 'Name *', fieldType: 'Type *', fieldDescription: 'Description', isGateway: 'Inter-VLAN gateway', isLivebox: 'ISP Box / Router', accessUrl: 'Access URL', runtimeType: 'Runtime environment', notSpecified: '— Not specified', baremetal: 'Bare-metal', lxcContainer: 'LXC container', vmQemu: 'VM QEMU/KVM', networkInterfaces: 'Network interfaces', addInterface: '+ Interface', noInterface: 'No interface configured', cancel: 'Cancel', save: 'Save', create: 'Create', badgeGateway: 'Gateway', badgeLivebox: 'ISP Box', confirmDeleteDevice: '{0} and all its links?', confirmDeleteNetwork: '{0}?', saveError: 'Error while saving', deleteError: 'Error while deleting', descPlaceholder: 'Role, OS, notes…', typeServer: 'Server', typeSwitch: 'Switch', typeRouter: 'Router', typeNas: 'NAS', typeGateway: 'Gateway', typeLivebox: 'ISP Box', typeCamera: 'IP Camera', typeTemperature: 'Temperature/humidity sensor', typeSensor: 'Sensor (motion, door…)', typeHub: 'Home automation hub', typeSmartPlug: 'Smart plug', typeAlarm: 'Alarm / Detector', typeLight: 'Smart light', typeDoorbell: 'Doorbell / Intercom', typeAccessPoint: 'WiFi Access Point', typeDesktop: 'Desktop computer', typeLaptop: 'Laptop', typeSmartTv: 'Smart TV', typePrinter: 'Printer', typeSmartphone: 'Smartphone', typeOther: 'Other', virtBaremetal: 'Bare-metal', virtLxc: 'LXC', virtQemu: 'VM QEMU', networks: 'Networks', addNetwork: '+ Add network', noNetworksConfigured: 'No networks configured. Start by creating one.', colType: 'Type', colName: 'Name', colSubnet: 'Subnet', colColor: 'Color', colActions: 'Actions', editNetwork: 'Edit network', newNetwork: 'New network', vlanId: 'VLAN ID', vlanIdHint: '(leave empty for plain LAN)', subnet: 'CIDR subnet', color: 'Color', subnetPlaceholder: 'e.g. 192.168.10.0/24', autoDiscovery: 'Auto discovery', dnsServer: 'DNS server', dnsHint: 'Reverse DNS will be queried on this server to resolve hostnames.', vlansToScan: 'VLANs to scan', vlansHint: 'Only VLANs with a configured CIDR subnet can be scanned.', noCidrWarning: 'No VLAN has a CIDR configured. Set them in the Networks tab.', noCidr: 'no CIDR', startDiscovery: 'Start discovery', scanning: 'Scanning…', scanAddresses: 'addresses on', scanVlans: 'VLAN(s)', scanNote: 'Each host is pinged then queried via DNS.', hostsFound: 'host(s) found', addressesScanned: 'addresses scanned', newHosts: 'new', noHosts: 'No active hosts found on the selected ranges.', colIp: 'IP', colDns: 'Name (DNS)', colStatus: 'Status', statusExisting: 'Already present', statusNew: 'New', newScan: 'New scan', importingBtn: 'Importing…', importBtn: 'Import', dnsRequired: 'Please enter a DNS server.', selectVlan: 'Select at least one VLAN.', importError: 'Error during import.', scanError: 'Error during scan.', lightTheme: 'Light theme', darkTheme: 'Dark theme', // Auth loginTitle: 'Sign in', loginUsername: 'Username', loginPassword: 'Password', loginBtn: 'Sign in', loginError: 'Incorrect credentials.', logout: 'Logout', accountSettings: 'Account settings', currentPassword: 'Current password', newUsername: 'New username', newPassword: 'New password', confirmPassword: 'Confirm password', leaveBlankToKeep: 'Leave blank to keep unchanged', passwordMismatch: 'Passwords do not match.', accountUpdated: 'Account updated.', wrongPassword: 'Current password is incorrect.', mustChangePasswordWarning: 'For security reasons, you must change your password before continuing.', newPasswordRequired: 'A new password is required.', passwordTooShort: 'Password must be at least 8 characters.', passwordTooWeak: 'Password must contain at least one letter and one digit.', usernameInvalid: 'Username may only contain letters, digits, . _ - (1 to 64 characters).', tooManyAttempts: 'Too many attempts, please try again later.', }, es: { tabTopology: 'Topología', tabNetworks: 'Redes', tabDevices: 'Equipos', discovery: '🔍 Descubrimiento auto', statsNetwork: 'Red', statsNetworks: 'Redes', statsDevice: 'Equipo', statsDevices: 'Equipos', exportJson: '⬇ Exportar JSON', importJson: '⬆ Importar JSON', loadError: 'Error de carga: ', importFailed: 'Importación fallida: ', importTooLarge: 'Archivo demasiado grande (máx 5 MB).', ping: 'Ping', pinging: 'Ping en curso…', refreshPing: 'Actualizar ping', wan: 'Internet / WAN', gateway: 'Pasarela inter-VLAN', unassigned: 'Sin asignar', noDevice: 'Sin equipos', noDevices: 'No hay equipos que mostrar.', noDevicesHint: 'Empiece creando redes y equipos.', reachable: 'Alcanzable', unreachable: 'No alcanzable', openWebUI: 'Abrir interfaz web', devices: 'Equipos', addDevice: '+ Añadir equipo', noDevicesConfigured: 'No hay equipos configurados. Empiece añadiendo uno.', searchPlaceholder: 'Nombre, IP…', filterType: 'Tipo', filterNetwork: 'Red', filterBrand: 'Marca', filterVirt: 'Virt', clearFilters: '✕ Borrar', noDevicesFiltered: 'Ningún equipo coincide con los filtros seleccionados.', editDevice: 'Editar equipo', newDevice: 'Nuevo equipo', fieldName: 'Nombre *', fieldType: 'Tipo *', fieldDescription: 'Descripción', isGateway: 'Pasarela inter-VLAN', isLivebox: 'Router / Box ISP', accessUrl: 'URL de acceso', runtimeType: 'Entorno de ejecución', notSpecified: '— No especificado', baremetal: 'Bare-metal', lxcContainer: 'Contenedor LXC', vmQemu: 'VM QEMU/KVM', networkInterfaces: 'Interfaces de red', addInterface: '+ Interfaz', noInterface: 'Sin interfaces configuradas', cancel: 'Cancelar', save: 'Guardar', create: 'Crear', badgeGateway: 'Pasarela', badgeLivebox: 'Router ISP', confirmDeleteDevice: '{0} y todos sus enlaces?', confirmDeleteNetwork: '{0}?', saveError: 'Error al guardar', deleteError: 'Error al eliminar', descPlaceholder: 'Rol, SO, notas…', typeServer: 'Servidor', typeSwitch: 'Switch', typeRouter: 'Router', typeNas: 'NAS', typeGateway: 'Pasarela', typeLivebox: 'Router ISP', typeCamera: 'Cámara IP', typeTemperature: 'Sonda de temperatura/humedad', typeSensor: 'Sensor (movimiento, apertura…)', typeHub: 'Hub domótico', typeSmartPlug: 'Enchufe inteligente', typeAlarm: 'Alarma / Detector', typeLight: 'Iluminación inteligente', typeDoorbell: 'Timbre / Portero', typeAccessPoint: 'Punto de acceso WiFi', typeDesktop: 'Ordenador de sobremesa', typeLaptop: 'Portátil', typeSmartTv: 'Smart TV', typePrinter: 'Impresora', typeSmartphone: 'Smartphone', typeOther: 'Otro', virtBaremetal: 'Bare-metal', virtLxc: 'LXC', virtQemu: 'VM QEMU', networks: 'Redes', addNetwork: '+ Añadir red', noNetworksConfigured: 'No hay redes configuradas. Empiece creando una.', colType: 'Tipo', colName: 'Nombre', colSubnet: 'Subred', colColor: 'Color', colActions: 'Acciones', editNetwork: 'Editar red', newNetwork: 'Nueva red', vlanId: 'ID de VLAN', vlanIdHint: '(dejar vacío para LAN simple)', subnet: 'Subred CIDR', color: 'Color', subnetPlaceholder: 'ej: 192.168.10.0/24', autoDiscovery: 'Descubrimiento automático', dnsServer: 'Servidor DNS', dnsHint: 'El DNS inverso será consultado en este servidor para resolver nombres.', vlansToScan: 'VLANs a escanear', vlansHint: 'Solo los VLANs con subred CIDR configurada pueden escanearse.', noCidrWarning: 'Ninguna VLAN tiene CIDR configurado. Configúrelo en la pestaña Redes.', noCidr: 'sin CIDR', startDiscovery: 'Iniciar descubrimiento', scanning: 'Escaneo en curso…', scanAddresses: 'direcciones en', scanVlans: 'VLAN(s)', scanNote: 'Cada host es pingado y luego consultado en DNS.', hostsFound: 'host(s) descubierto(s)', addressesScanned: 'direcciones escaneadas', newHosts: 'nuevos', noHosts: 'No se encontraron hosts activos en los rangos seleccionados.', colIp: 'IP', colDns: 'Nombre (DNS)', colStatus: 'Estado', statusExisting: 'Ya presente', statusNew: 'Nuevo', newScan: 'Nuevo escaneo', importingBtn: 'Importando…', importBtn: 'Importar', dnsRequired: 'Por favor ingrese un servidor DNS.', selectVlan: 'Seleccione al menos una VLAN.', importError: 'Error durante la importación.', scanError: 'Error durante el escaneo.', lightTheme: 'Tema claro', darkTheme: 'Tema oscuro', // Auth loginTitle: 'Iniciar sesión', loginUsername: 'Nombre de usuario', loginPassword: 'Contraseña', loginBtn: 'Iniciar sesión', loginError: 'Credenciales incorrectas.', logout: 'Cerrar sesión', accountSettings: 'Configuración de cuenta', currentPassword: 'Contraseña actual', newUsername: 'Nuevo nombre de usuario', newPassword: 'Nueva contraseña', confirmPassword: 'Confirmar contraseña', leaveBlankToKeep: 'Dejar en blanco para no cambiar', passwordMismatch: 'Las contraseñas no coinciden.', accountUpdated: 'Cuenta actualizada.', wrongPassword: 'La contraseña actual es incorrecta.', mustChangePasswordWarning: 'Por razones de seguridad, debe cambiar su contraseña antes de continuar.', newPasswordRequired: 'Se requiere una nueva contraseña.', passwordTooShort: 'La contraseña debe tener al menos 8 caracteres.', passwordTooWeak: 'La contraseña debe contener al menos una letra y un número.', usernameInvalid: 'El nombre de usuario solo puede contener letras, dígitos, . _ - (1 a 64 caracteres).', tooManyAttempts: 'Demasiados intentos, inténtelo de nuevo más tarde.', }, } export function t(key) { return LANGS[locale.value]?.[key] ?? LANGS['fr'][key] ?? key } export function tFmt(key, ...args) { let str = LANGS[locale.value]?.[key] ?? LANGS['fr'][key] ?? key args.forEach((arg, i) => { str = str.replace(`{${i}}`, arg) }) return str }