143 lines
28 KiB
Go
143 lines
28 KiB
Go
package web
|
||
|
||
import (
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
const defaultLanguage = "en"
|
||
|
||
var messages = map[string]map[string]string{
|
||
"en": {
|
||
"brand.control": "Docker Gameserver Manager", "nav.primary": "Primary navigation", "nav.dashboard": "Dashboard", "nav.instances": "Instances", "nav.catalog": "Catalog", "nav.backups": "Backups", "nav.audit": "Audit", "nav.settings": "Settings", "nav.administration": "Administration", "nav.planned": "This page is planned for a future milestone",
|
||
"setup.title": "Create administrator", "setup.heading": "Welcome", "setup.introduction": "Create the first administrator to finish setup.", "setup.submit": "Create administrator",
|
||
"login.title": "Sign in", "login.heading": "Sign in", "login.introduction": "Manage your game servers from one secure place.", "login.submit": "Sign in", "logout.submit": "Sign out",
|
||
"dashboard.title": "Dashboard", "dashboard.eyebrow": "Dashboard", "dashboard.heading": "Game servers", "dashboard.introduction": "Overview of all your game server instances.", "dashboard.search": "Search instances", "dashboard.search_placeholder": "Search instance", "dashboard.summary": "Instance summary", "dashboard.total": "Total instances", "dashboard.running": "Running", "dashboard.stopped": "Stopped", "dashboard.updating": "Updating", "dashboard.error": "Error", "dashboard.no_match": "No matching instance", "dashboard.empty_heading": "No instances deployed", "dashboard.empty_copy": "Your game servers will appear here when they are added from the Catalog.", "dashboard.instances_eyebrow": "Servers", "dashboard.instances": "Your instances", "dashboard.activity_eyebrow": "Operations", "dashboard.recent_activity": "Recent activity", "dashboard.no_activity": "No recent activity", "dashboard.system_eyebrow": "Health", "dashboard.system_status": "System status", "dashboard.agent": "Docker agent", "dashboard.database": "Database", "dashboard.storage": "Storage", "dashboard.backups": "Backups", "dashboard.audit_log": "Audit log", "dashboard.online": "Online", "dashboard.offline": "Offline", "dashboard.healthy": "Healthy", "dashboard.unavailable": "Unavailable", "dashboard.last_backup": "Last backup", "dashboard.no_backup": "No backup", "dashboard.retention_days": "days retention", "dashboard.unlimited": "Unlimited retention", "dashboard.by": "by",
|
||
"catalog.title": "Catalog", "catalog.eyebrow": "Game library", "catalog.heading": "Catalog", "catalog.search": "Search games", "catalog.search_placeholder": "Search a game", "catalog.scan": "Scan", "catalog.found": "templates found", "catalog.valid": "valid", "catalog.invalid": "invalid", "catalog.no_match": "No matching game", "catalog.empty": "No game available", "catalog.empty_admin": "Add templates to /var/lib/dogama/templates, then use Scan.", "catalog.back": "Back to catalog", "catalog.minimum": "Minimum", "catalog.recommended": "Recommended", "catalog.memory": "Memory", "catalog.storage": "Storage", "catalog.other": "Other", "catalog.deploy": "Deploy", "catalog.deploy_unavailable": "Deployment will be available in the next milestone.",
|
||
"deployment.title": "Deploy", "deployment.eyebrow": "New instance", "deployment.heading": "Configure your server", "deployment.name": "Instance name", "deployment.description": "Instance description", "deployment.parameters": "Game parameters", "deployment.backup": "Import an external save", "deployment.backup_help": "ZIP, TAR, TAR.GZ or TAR.ZST archives are validated before use.", "deployment.go": "Go",
|
||
"settings.game_runtime": "Game server execution", "settings.game_runtime_help": "These defaults apply only to game-server containers.", "settings.game_runtime_uid": "Default UID", "settings.game_runtime_gid": "Default GID", "settings.game_runtime_policy_help": "Templates that allow an administered identity use these values. Templates using the image's native user ignore them.", "settings.save_game_runtime": "Save game server identity",
|
||
"settings.title": "Settings", "settings.eyebrow": "Administration", "settings.introduction": "Configure product services without crowding the server overview.", "settings.tabs": "Settings sections", "settings.notifications": "Notifications", "settings.audit": "Audit", "settings.containers": "Game containers", "settings.web_access": "Web access", "settings.require_https": "Require HTTPS", "settings.canonical_url": "Canonical base URL", "settings.web_help": "Configure HTTPS enforcement and the public origin. Configure and verify your HTTPS reverse proxy before enabling this lock.", "settings.save_web": "Save web access", "settings.canonical_help": "Verify the canonical URL works before saving it.", "settings.https_help": "When enabled, unsafe HTTP requests are refused and safe navigation is redirected to HTTPS.",
|
||
"settings.channels": "Notification channels", "settings.channels_help": "Secrets remain encrypted and are never displayed after saving.", "settings.no_channels": "No channel configured.", "settings.send_test": "Send test", "settings.delete": "Delete", "settings.add_channel": "Add channel", "settings.name": "Name", "settings.type": "Type", "settings.enabled": "enabled", "settings.disabled": "disabled", "settings.events": "Events (space separated)", "settings.audit_retention": "Audit retention", "settings.audit_help": "Control history size and perform explicit bounded purges.", "settings.view_audit": "View audit events", "settings.retention_days": "Retention days", "settings.maximum_entries": "Maximum entries", "settings.zero_unlimited": "Zero means unlimited and may grow the database indefinitely.", "settings.save_retention": "Save retention", "settings.delete_before": "Delete events before", "settings.confirm_purge": "Confirm bounded audit purge", "settings.purge": "Purge audit events", "settings.container_labels": "Game-container labels", "settings.container_labels_help": "These labels apply only to game-server containers.", "settings.global_labels": "Global labels", "settings.apply": "Application", "settings.next_start": "Apply on next start", "settings.immediate": "Apply immediately", "settings.disconnection": "Immediate application stops and recreates affected containers.", "settings.confirm_disconnection": "I understand the immediate-disconnection warning", "settings.save_labels": "Save game-container labels",
|
||
"audit.title": "Audit", "audit.eyebrow": "Administration", "audit.introduction": "Review significant authentication and mutation events.", "audit.actor": "Actor ID", "audit.instance": "Instance ID", "audit.action": "Action", "audit.outcome": "Outcome", "audit.any": "Any", "audit.allowed": "Allowed", "audit.denied": "Denied", "audit.failed": "Failed", "audit.filter": "Filter audit", "audit.time": "Time", "audit.actor_column": "Actor", "audit.instance_column": "Instance", "audit.empty": "No audit event matches these filters.",
|
||
"field.username": "Username", "field.password": "Password", "language": "Language", "language.english": "English", "language.french": "French", "language.save": "Save language", "account.signed_in": "Signed in as",
|
||
"error.csrf": "Request verification failed.", "error.form": "Invalid form submission.", "error.internal": "The request could not be completed.", "error.credentials": "Invalid username or password.", "error.web_policy": "Web access policy rejected the request.",
|
||
"instance.title": "Instance", "instance.back": "Back to dashboard", "instance.start": "Start", "instance.stop": "Stop", "instance.backup": "Back up", "instance.update": "Update", "instance.update_unknown": "No verified update is available.", "instance.players": "Players", "instance.password": "Server password", "instance.unavailable": "Unavailable", "instance.backups": "Backups", "instance.no_backups": "No backups", "instance.restore": "Restore", "instance.restore_confirm": "Restore this backup? The current server data will be replaced.", "instance.server_functions": "Server functions", "instance.module_unavailable": "No active module exposes server functions for this instance.", "instance.action_done.instance.start": "Instance started.", "instance.action_done.instance.stop": "Instance stopped.", "instance.action_done.backup.create": "Backup created.", "instance.action_done.backup.restore": "Backup restored.", "instance.action_failed.instance.start": "Unable to start the instance.", "instance.action_failed.instance.stop": "Unable to stop the instance.", "instance.action_failed.backup.create": "Unable to create the backup.", "instance.action_failed.backup.restore": "Unable to restore the backup.",
|
||
},
|
||
"fr": {
|
||
"brand.control": "Docker Gameserver Manager", "nav.primary": "Navigation principale", "nav.dashboard": "Tableau de bord", "nav.instances": "Instances", "nav.catalog": "Catalogue", "nav.backups": "Sauvegardes", "nav.audit": "Audit", "nav.settings": "Paramètres", "nav.administration": "Administration", "nav.planned": "Cette page est prévue pour un futur jalon",
|
||
"setup.title": "Créer l'administrateur", "setup.heading": "Bienvenue", "setup.introduction": "Créez le premier administrateur pour terminer la configuration.", "setup.submit": "Créer l'administrateur",
|
||
"login.title": "Connexion", "login.heading": "Se connecter", "login.introduction": "Gérez vos serveurs de jeux depuis un espace sécurisé.", "login.submit": "Se connecter", "logout.submit": "Se déconnecter",
|
||
"dashboard.title": "Tableau de bord", "dashboard.eyebrow": "Tableau de bord", "dashboard.heading": "Serveurs de jeux", "dashboard.introduction": "Vue d'ensemble de toutes vos instances de serveurs de jeux.", "dashboard.search": "Rechercher des instances", "dashboard.search_placeholder": "Rechercher une instance", "dashboard.summary": "Résumé des instances", "dashboard.total": "Instances totales", "dashboard.running": "En cours", "dashboard.stopped": "Arrêtées", "dashboard.updating": "Mise à jour", "dashboard.error": "Erreur", "dashboard.no_match": "Aucune instance correspondante", "dashboard.empty_heading": "Aucune instance déployée", "dashboard.empty_copy": "Vos serveurs de jeux apparaîtront ici lorsqu'ils seront ajoutés depuis le Catalogue.", "dashboard.instances_eyebrow": "Serveurs", "dashboard.instances": "Vos instances", "dashboard.activity_eyebrow": "Opérations", "dashboard.recent_activity": "Activité récente", "dashboard.no_activity": "Aucune activité récente", "dashboard.system_eyebrow": "Santé", "dashboard.system_status": "État du système", "dashboard.agent": "Agent Docker", "dashboard.database": "Base de données", "dashboard.storage": "Stockage", "dashboard.backups": "Sauvegardes", "dashboard.audit_log": "Journal d'audit", "dashboard.online": "En ligne", "dashboard.offline": "Hors ligne", "dashboard.healthy": "Saine", "dashboard.unavailable": "Indisponible", "dashboard.last_backup": "Dernière sauvegarde", "dashboard.no_backup": "Aucune sauvegarde", "dashboard.retention_days": "jours de rétention", "dashboard.unlimited": "Rétention illimitée", "dashboard.by": "par",
|
||
"catalog.title": "Catalogue", "catalog.eyebrow": "Bibliothèque de jeux", "catalog.heading": "Catalogue", "catalog.search": "Rechercher des jeux", "catalog.search_placeholder": "Rechercher un jeu", "catalog.scan": "Scanner", "catalog.found": "templates trouvés", "catalog.valid": "valides", "catalog.invalid": "invalides", "catalog.no_match": "Aucun jeu correspondant", "catalog.empty": "Aucun jeu disponible", "catalog.empty_admin": "Ajoutez des templates dans /var/lib/dogama/templates, puis utilisez Scanner.", "catalog.back": "Retour au catalogue", "catalog.minimum": "Minimum", "catalog.recommended": "Recommandé", "catalog.memory": "Mémoire", "catalog.storage": "Stockage", "catalog.other": "Autre", "catalog.deploy": "Déployer", "catalog.deploy_unavailable": "Le déploiement sera disponible au prochain bloc.",
|
||
"deployment.title": "Déployer", "deployment.eyebrow": "Nouvelle instance", "deployment.heading": "Configurer votre serveur", "deployment.name": "Nom de l’instance", "deployment.description": "Description de l’instance", "deployment.parameters": "Paramètres du jeu", "deployment.backup": "Importer une sauvegarde externe", "deployment.backup_help": "Les archives ZIP, TAR, TAR.GZ et TAR.ZST sont validées avant utilisation.", "deployment.go": "Go",
|
||
"settings.title": "Paramètres", "settings.eyebrow": "Administration", "settings.introduction": "Configurez les services du produit sans encombrer la vue des serveurs.", "settings.tabs": "Sections des paramètres", "settings.notifications": "Notifications", "settings.audit": "Audit", "settings.containers": "Conteneurs de jeux", "settings.web_access": "Accès Web", "settings.require_https": "Exiger HTTPS", "settings.canonical_url": "URL de base canonique", "settings.web_help": "Configurez l'obligation HTTPS et l'origine publique. Configurez et vérifiez votre proxy inverse HTTPS avant d'activer ce verrouillage.", "settings.save_web": "Enregistrer l'accès Web", "settings.canonical_help": "Vérifiez que l'URL canonique fonctionne avant de l'enregistrer.", "settings.https_help": "Une fois activé, les requêtes HTTP non sûres sont refusées et les navigations sûres sont redirigées vers HTTPS.",
|
||
"settings.channels": "Canaux de notification", "settings.channels_help": "Les secrets restent chiffrés et ne sont jamais affichés après enregistrement.", "settings.no_channels": "Aucun canal configuré.", "settings.send_test": "Envoyer un test", "settings.delete": "Supprimer", "settings.add_channel": "Ajouter un canal", "settings.name": "Nom", "settings.type": "Type", "settings.enabled": "activé", "settings.disabled": "désactivé", "settings.events": "Événements (séparés par des espaces)", "settings.audit_retention": "Rétention de l'audit", "settings.audit_help": "Contrôlez la taille de l'historique et effectuez des purges limitées explicites.", "settings.view_audit": "Voir les événements d'audit", "settings.retention_days": "Jours de rétention", "settings.maximum_entries": "Nombre maximal d'entrées", "settings.zero_unlimited": "Zéro signifie illimité et peut faire croître la base indéfiniment.", "settings.save_retention": "Enregistrer la rétention", "settings.delete_before": "Supprimer les événements antérieurs au", "settings.confirm_purge": "Confirmer la purge limitée de l'audit", "settings.purge": "Purger les événements d'audit", "settings.container_labels": "Étiquettes des conteneurs de jeux", "settings.container_labels_help": "Ces étiquettes s'appliquent uniquement aux conteneurs de serveurs de jeux.", "settings.global_labels": "Étiquettes globales", "settings.apply": "Application", "settings.next_start": "Appliquer au prochain démarrage", "settings.immediate": "Appliquer immédiatement", "settings.disconnection": "L'application immédiate arrête et recrée les conteneurs concernés.", "settings.confirm_disconnection": "Je comprends l'avertissement de déconnexion immédiate", "settings.save_labels": "Enregistrer les étiquettes des conteneurs",
|
||
"audit.title": "Audit", "audit.eyebrow": "Administration", "audit.introduction": "Consultez les événements importants d'authentification et de modification.", "audit.actor": "ID de l'acteur", "audit.instance": "ID de l'instance", "audit.action": "Action", "audit.outcome": "Résultat", "audit.any": "Tous", "audit.allowed": "Autorisé", "audit.denied": "Refusé", "audit.failed": "Échec", "audit.filter": "Filtrer l'audit", "audit.time": "Heure", "audit.actor_column": "Acteur", "audit.instance_column": "Instance", "audit.empty": "Aucun événement d'audit ne correspond à ces filtres.",
|
||
"field.username": "Nom d'utilisateur", "field.password": "Mot de passe", "language": "Langue", "language.english": "Anglais", "language.french": "Français", "language.save": "Enregistrer la langue", "account.signed_in": "Connecté en tant que",
|
||
"error.csrf": "La vérification de la requête a échoué.", "error.form": "Envoi de formulaire invalide.", "error.internal": "La requête n'a pas pu être effectuée.", "error.credentials": "Nom d'utilisateur ou mot de passe invalide.", "error.web_policy": "La politique d'accès Web a rejeté la requête.",
|
||
"instance.title": "Instance", "instance.back": "Retour au tableau de bord", "instance.start": "Démarrer", "instance.stop": "Arrêter", "instance.backup": "Sauvegarder", "instance.update": "Mettre à jour", "instance.update_unknown": "Aucune mise à jour vérifiée n’est disponible.", "instance.players": "Joueurs", "instance.password": "Mot de passe serveur", "instance.unavailable": "Indisponible", "instance.backups": "Sauvegardes", "instance.no_backups": "Aucune sauvegarde", "instance.restore": "Restaurer", "instance.restore_confirm": "Restaurer cette sauvegarde ? Les données actuelles seront remplacées.", "instance.server_functions": "Fonctions serveur", "instance.module_unavailable": "Aucun module actif n’expose de fonctions serveur pour cette instance.", "instance.action_done.instance.start": "Instance démarrée.", "instance.action_done.instance.stop": "Instance arrêtée.", "instance.action_done.backup.create": "Sauvegarde créée.", "instance.action_done.backup.restore": "Sauvegarde restaurée.", "instance.action_failed.instance.start": "Impossible de démarrer l’instance.", "instance.action_failed.instance.stop": "Impossible d’arrêter l’instance.", "instance.action_failed.backup.create": "Impossible de créer la sauvegarde.", "instance.action_failed.backup.restore": "Impossible de restaurer la sauvegarde.",
|
||
},
|
||
}
|
||
|
||
func init() {
|
||
for language, values := range map[string]map[string]string{
|
||
"en": {"audit.empty": "No audit events yet.", "audit.empty_filtered": "No audit events match these filters.", "audit.events": "events", "audit.timezone": "Times shown in", "audit.filters": "Filters", "audit.reset": "Reset filters", "audit.period": "Period", "audit.today": "Today", "audit.last_7_days": "Last 7 days", "audit.last_30_days": "Last 30 days", "audit.custom": "Custom", "audit.from": "From", "audit.to": "To", "audit.search": "Search", "audit.results": "Results", "audit.details": "Details", "audit.pagination": "Audit pagination", "audit.previous": "Previous", "audit.next": "Next", "audit.action.auth.login": "Sign in", "audit.action.auth.login.blocked": "Blocked sign in", "audit.action.instance.deploy": "Instance deployment", "audit.action.instance.start": "Instance start", "audit.action.instance.stop": "Instance stop", "audit.action.instance.restart": "Instance restart", "audit.action.instance.update": "Instance update", "audit.action.backup.create": "Backup creation", "audit.action.backup.restore": "Backup restore", "audit.action.players.unban": "Player unban", "audit.action.audit.policy.update": "Audit retention update", "audit.action.audit.purge": "Audit purge", "audit.action.notification.channel.update": "Notification channel update", "audit.action.notification.channel.delete": "Notification channel deletion", "audit.action.notification.channel.test": "Notification channel test"},
|
||
"fr": {"audit.empty": "Aucun événement d’audit.", "audit.empty_filtered": "Aucun événement ne correspond aux filtres.", "audit.events": "événements", "audit.timezone": "Heures affichées en", "audit.filters": "Filtres", "audit.reset": "Réinitialiser les filtres", "audit.period": "Période", "audit.today": "Aujourd’hui", "audit.last_7_days": "7 derniers jours", "audit.last_30_days": "30 derniers jours", "audit.custom": "Personnalisée", "audit.from": "Du", "audit.to": "Au", "audit.search": "Rechercher", "audit.results": "Résultats", "audit.details": "Détails", "audit.pagination": "Pagination de l’audit", "audit.previous": "Précédent", "audit.next": "Suivant", "audit.action.auth.login": "Connexion", "audit.action.auth.login.blocked": "Connexion bloquée", "audit.action.instance.deploy": "Déploiement d’instance", "audit.action.instance.start": "Démarrage d’instance", "audit.action.instance.stop": "Arrêt d’instance", "audit.action.instance.restart": "Redémarrage d’instance", "audit.action.instance.update": "Mise à jour d’instance", "audit.action.backup.create": "Création de sauvegarde", "audit.action.backup.restore": "Restauration de sauvegarde", "audit.action.players.unban": "Débannissement de joueur", "audit.action.audit.policy.update": "Mise à jour de la rétention", "audit.action.audit.purge": "Purge de l’audit", "audit.action.notification.channel.update": "Mise à jour de canal de notification", "audit.action.notification.channel.delete": "Suppression de canal de notification", "audit.action.notification.channel.test": "Test de canal de notification"},
|
||
} {
|
||
for key, value := range values {
|
||
messages[language][key] = value
|
||
}
|
||
}
|
||
for language, values := range map[string]map[string]string{
|
||
"en": {"instance.update_unknown": "Update verification is unavailable.", "instance.update_available": "A verified update is available.", "instance.update_current": "This instance is up to date.", "instance.banned_players": "Banned players", "instance.no_banned_players": "No banned players.", "instance.player_identifier": "Player identifier", "instance.bans_not_supported": "The banned-player list is unavailable for this server. Enter the identifier expected by the game API.", "instance.action_done.instance.update": "Instance updated.", "instance.action_failed.instance.update": "Unable to update the instance."},
|
||
"fr": {"instance.update_unknown": "La vérification de mise à jour est indisponible.", "instance.update_available": "Une mise à jour vérifiée est disponible.", "instance.update_current": "Cette instance est à jour.", "instance.banned_players": "Joueurs bannis", "instance.no_banned_players": "Aucun joueur banni.", "instance.player_identifier": "Identifiant du joueur", "instance.bans_not_supported": "La liste des joueurs bannis n’est pas disponible pour ce serveur. Saisissez l’identifiant attendu par l’API du jeu.", "instance.action_done.instance.update": "Instance mise à jour.", "instance.action_failed.instance.update": "Impossible de mettre l’instance à jour."},
|
||
} {
|
||
for key, value := range values {
|
||
messages[language][key] = value
|
||
}
|
||
}
|
||
for language, values := range map[string]map[string]string{
|
||
"en": {"template_repositories.title": "Template repositories", "template_repositories.heading": "Template repositories", "template_repositories.introduction": "Configure future template sources. Remote synchronization is not implemented.", "template_repositories.users": "Users", "template_repositories.new": "New repository", "template_repositories.name": "Name", "template_repositories.url": "URL", "template_repositories.add": "Add", "template_repositories.delete": "Delete", "template_repositories.delete_confirm": "Delete this template repository?", "template_repositories.not_implemented": "Feature not implemented", "template_repositories.empty": "No template repositories configured.", "template_repositories.validation_error": "Enter a name and a valid HTTP or HTTPS URL.", "template_repositories.duplicate_error": "This template repository URL is already configured.", "template_repositories.not_found": "Template repository not found."},
|
||
"fr": {"template_repositories.title": "Dépôts de templates", "template_repositories.heading": "Dépôts de templates", "template_repositories.introduction": "Configurez de futures sources de templates. La synchronisation distante n’est pas implémentée.", "template_repositories.users": "Utilisateurs", "template_repositories.new": "Nouveau dépôt", "template_repositories.name": "Nom", "template_repositories.url": "URL", "template_repositories.add": "Ajouter", "template_repositories.delete": "Supprimer", "template_repositories.delete_confirm": "Supprimer ce dépôt de templates ?", "template_repositories.not_implemented": "Fonctionnalité non implémentée", "template_repositories.empty": "Aucun dépôt de templates configuré.", "template_repositories.validation_error": "Saisissez un nom et une URL HTTP ou HTTPS valide.", "template_repositories.duplicate_error": "Cette URL de dépôt de templates est déjà configurée.", "template_repositories.not_found": "Dépôt de templates introuvable."},
|
||
} {
|
||
for key, value := range values {
|
||
messages[language][key] = value
|
||
}
|
||
}
|
||
for language, values := range map[string]map[string]string{
|
||
"en": {"settings.notification_general": "General", "settings.notification_language_help": "Choose the language used in delivered notifications.", "settings.notification_email": "Email / SMTP", "settings.notification_discord": "Discord", "settings.notification_gotify": "Gotify", "settings.sender_name": "Sender name", "settings.sender_email": "Sender email", "settings.smtp_server": "SMTP server", "settings.port": "Port", "settings.username": "Username", "settings.password": "Password", "settings.password_retain": "leave empty to retain on edit", "settings.tls_mode": "TLS mode", "settings.test_recipient": "Test recipient (optional)", "settings.save_smtp": "Save SMTP", "settings.webhook_url": "Webhook URL", "settings.save_discord": "Save Discord", "settings.gotify_help": "Create an application in Gotify, then enter its server URL and application token here.", "settings.server_url": "Server URL", "settings.application_token": "Application token", "settings.save_gotify": "Save Gotify", "settings.configured": "configured", "settings.not_configured": "not configured"},
|
||
"fr": {"settings.notification_general": "Général", "settings.notification_language_help": "Choisissez la langue utilisée pour les notifications envoyées.", "settings.notification_email": "E-mail / SMTP", "settings.notification_discord": "Discord", "settings.notification_gotify": "Gotify", "settings.sender_name": "Nom de l'expéditeur", "settings.sender_email": "E-mail de l'expéditeur", "settings.smtp_server": "Serveur SMTP", "settings.port": "Port", "settings.username": "Nom d'utilisateur", "settings.password": "Mot de passe", "settings.password_retain": "laissez vide pour le conserver lors d'une modification", "settings.tls_mode": "Mode TLS", "settings.test_recipient": "Destinataire de test (facultatif)", "settings.save_smtp": "Enregistrer SMTP", "settings.webhook_url": "URL du webhook", "settings.save_discord": "Enregistrer Discord", "settings.gotify_help": "Créez une application dans Gotify, puis saisissez ici l'URL du serveur et le jeton de l'application.", "settings.server_url": "URL du serveur", "settings.application_token": "Jeton d'application", "settings.save_gotify": "Enregistrer Gotify", "settings.configured": "configuré", "settings.not_configured": "non configuré"},
|
||
} {
|
||
for key, value := range values {
|
||
messages[language][key] = value
|
||
}
|
||
}
|
||
}
|
||
|
||
type languageOption struct {
|
||
Code string
|
||
Display string
|
||
}
|
||
|
||
var languageOptions = []languageOption{
|
||
{Code: "fr", Display: "🇫🇷 Français"},
|
||
{Code: "en", Display: "🇬🇧 English"},
|
||
}
|
||
|
||
func supportedLanguageOptions() []languageOption { return languageOptions }
|
||
|
||
func validLanguage(value string) bool {
|
||
for _, option := range languageOptions {
|
||
if option.Code == value {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func negotiateLanguage(header string) string {
|
||
type candidate struct {
|
||
language string
|
||
quality float64
|
||
order int
|
||
}
|
||
var candidates []candidate
|
||
for order, part := range strings.Split(header, ",") {
|
||
fields := strings.Split(strings.TrimSpace(part), ";")
|
||
language := strings.ToLower(strings.Split(strings.TrimSpace(fields[0]), "-")[0])
|
||
quality := 1.0
|
||
for _, field := range fields[1:] {
|
||
field = strings.TrimSpace(field)
|
||
if strings.HasPrefix(field, "q=") {
|
||
if value, err := strconv.ParseFloat(strings.TrimPrefix(field, "q="), 64); err == nil {
|
||
quality = value
|
||
}
|
||
}
|
||
}
|
||
if validLanguage(language) && quality > 0 {
|
||
candidates = append(candidates, candidate{language, quality, order})
|
||
}
|
||
}
|
||
sort.SliceStable(candidates, func(i, j int) bool { return candidates[i].quality > candidates[j].quality })
|
||
if len(candidates) > 0 {
|
||
return candidates[0].language
|
||
}
|
||
return defaultLanguage
|
||
}
|
||
|
||
func localized(language, key string) string {
|
||
if !validLanguage(language) {
|
||
language = defaultLanguage
|
||
}
|
||
if value := messages[language][key]; value != "" {
|
||
return value
|
||
}
|
||
if value := messages[defaultLanguage][key]; value != "" {
|
||
return value
|
||
}
|
||
return key
|
||
}
|
||
|
||
func (d pageData) Msg(key string) string { return localized(d.Language, key) }
|