Files
stupid-simple-network-inven…/frontend/src/i18n.js
T
olivier 44c502dc50 feat(i18n): pluralize sidebar network/device counters
Show singular form when count is 1 (Network, Device, Réseau, Équip., Red, Equipo)
across all three supported languages (fr, en, es).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 10:40:11 +02:00

484 lines
17 KiB
JavaScript

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',
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',
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',
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
}