44c502dc50
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>
484 lines
17 KiB
JavaScript
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
|
|
}
|