mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-03-21 17:13:26 +01:00
Remove language hardcoding form html and make it dynamic.
This commit is contained in:
@@ -187,6 +187,9 @@ Documentation and deployment guidance in security tooling is never "done", and e
|
||||
|
||||
If you see a clearer way to describe installation steps, safer container defaults, better reverse-proxy examples, SELinux improvements, or a more practical demo environment, please contribute. Small improvements (typos, wording, examples) are just as valuable as code changes.
|
||||
|
||||
Want to add a new UI language? Copy `internal/locales/en.json`, translate all values, save it as `internal/locales/<lang>.json`, and open a pull request.
|
||||
Please use a proper lowercase locale short code for `<lang>` (for example `ch`, `ch_de`, `es`, or `pt_br`).
|
||||
|
||||
|
||||
See [`CONTRIBUTING.md`](https://github.com/swissmakers/fail2ban-ui/blob/main/CONTRIBUTING.md) for more info.
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "Català",
|
||||
"page.title": "Tauler de Fail2ban UI",
|
||||
"nav.dashboard": "Tauler",
|
||||
"nav.filter_debug": "Depuració de Filtres",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "Deutsch",
|
||||
"page.title": "Fail2ban UI Dashboard",
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.filter_debug": "Filter-Debug",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "Schwiizerdütsch",
|
||||
"page.title": "Fail2ban UI Dashboard",
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.filter_debug": "Filter Debug",
|
||||
@@ -207,12 +208,12 @@
|
||||
"filter_debug.no_matches": "Ke Übereinstimmige gfunde.",
|
||||
"settings.title": "Istellige",
|
||||
"settings.general": "Allgemeini Istellige",
|
||||
"settings.language": "Sprach",
|
||||
"settings.language": "Dini Sprach",
|
||||
"settings.server_port": "Server-Port",
|
||||
"settings.server_port_placeholder": "z.B. 8080",
|
||||
"settings.port_env_set": "Port wird über d PORT-Umgebigsvariable gsetzt:",
|
||||
"settings.port_env_hint": "Um de Port über d Weboberflächi z ändere, entferne d PORT-Umgebigsvariable und start de Container neu.",
|
||||
"settings.port_restart_hint": "⚠️ Port-Änderige erfordere ä Neustart vom Container, zum wirksam z werde.",
|
||||
"settings.port_restart_hint": "⚠️ Zum übernäh vo Port-Änderige mues dr Container neugstartet wärde.",
|
||||
"settings.enable_debug": "Debug-Modus aktivierä",
|
||||
"settings.enable_console": "Konsolenusgab aktivierä",
|
||||
"settings.console.title": "Konsolenusgab",
|
||||
@@ -221,12 +222,12 @@
|
||||
"settings.alert": "Alarm-Istellige",
|
||||
"settings.callback_url": "Fail2ban Callback-URL",
|
||||
"settings.callback_url_placeholder": "http://127.0.0.1:8080",
|
||||
"settings.callback_url_hint": "Diä URL wird vo aune Fail2Ban-Instanze brucht, zum Ban-Payloads a Fail2Ban UI z sende. Für lokali Installatione bruchts dr gliich Port wie z Fail2Ban UI (z.B. http://127.0.0.1:8080). Für Reverse-Proxy-Setups sött dr TLS-verschlüssleti Endpunkt wenn müglech brücht wärde (auso z.B. https://fail2ban.example.com).",
|
||||
"settings.callback_url_hint": "Z Callback wird brucht, zum Ban-/Unban-Payloads wo uf dim remote Fail2Ban passiere as Fail2Ban UI z sende. Für lokali Installatione chasch dr gliich Port wie z Fail2Ban UI (z.B. http://127.0.0.1:8080) näh. Für Reverse-Proxy-Setups sött dr TLS-verschlüssleti Endpunkt wenn müglech brücht wärde (auso z.B. https://fail2ban.example.com).",
|
||||
"settings.callback_url_env_set": "Callback-URL wird über d CALLBACK_URL-Umgebigsvariable gsetzt:",
|
||||
"settings.callback_url_env_hint": "Um d Callback-URL über d Weboberflächi z ändere, entfern d CALLBACK_URL-Umgebigsvariable und start dr Container neu.",
|
||||
"settings.callback_secret": "Fail2ban Callback-URL Secret",
|
||||
"settings.callback_secret_placeholder": "Automatisch generierts 42-Zeiche-Secret",
|
||||
"settings.callback_secret.description": "Zur Authentifizierig vo Ban-Callbacks. Wird outomatisch id Fail2ban-Action-Konfiguration abegschribe.",
|
||||
"settings.callback_secret.description": "Zur Authentifizierig vo de Callbacks. (Wird outomatisch id remote Fail2ban-Action-Konf synchronisiert.)",
|
||||
"settings.destination_email": "Ziiu-Email (Alarmempfänger)",
|
||||
"settings.destination_email_placeholder": "alerts@swissmakers.ch",
|
||||
"settings.alert_countries": "Alarm-Länder",
|
||||
@@ -361,7 +362,7 @@
|
||||
"settings.advanced.integration": "Integration",
|
||||
"settings.advanced.integration_none": "Integration uswähle",
|
||||
"settings.advanced.integration_hint": "Wähl d Firewall oder Appliance, wo diä permanenti Sperreg sött dürefüehre.",
|
||||
"settings.advanced.mikrotik.note": "Gib dr SSH-Zuegriff uf di Mikrotik-Router a und d Address-Lischte, wo d'Sperrige ihtreit wärde.",
|
||||
"settings.advanced.mikrotik.note": "Dr SSH-Zuegang für di Mikrotik-Router, um d sperr-Addresslischtene, outomatisch z pflege.",
|
||||
"settings.advanced.mikrotik.host": "Host",
|
||||
"settings.advanced.mikrotik.port": "Port",
|
||||
"settings.advanced.mikrotik.username": "SSH-Benutzername",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "English",
|
||||
"page.title": "Fail2ban UI Dashboard",
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.filter_debug": "Filter Debug",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "Español",
|
||||
"page.title": "Panel de control Fail2ban UI",
|
||||
"nav.dashboard": "Panel de control",
|
||||
"nav.filter_debug": "Depuración de filtros",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "Français",
|
||||
"page.title": "Tableau de bord Fail2ban UI",
|
||||
"nav.dashboard": "Tableau de bord",
|
||||
"nav.filter_debug": "Débogage des filtres",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"meta.language_name": "Italiano",
|
||||
"page.title": "Cruscotto Fail2ban UI",
|
||||
"nav.dashboard": "Cruscotto",
|
||||
"nav.filter_debug": "Debug Filtro",
|
||||
|
||||
@@ -74,6 +74,11 @@ type emailDetail struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
type localeOption struct {
|
||||
Code string
|
||||
Label string
|
||||
}
|
||||
|
||||
type githubReleaseResponse struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
@@ -1579,6 +1584,7 @@ func shouldAlertForCountry(country string, alertCountries []string) bool {
|
||||
func renderIndexPage(c *gin.Context) {
|
||||
disableExternalIP := os.Getenv("DISABLE_EXTERNAL_IP_LOOKUP") == "true" || os.Getenv("DISABLE_EXTERNAL_IP_LOOKUP") == "1"
|
||||
autoDark := os.Getenv("AUTODARK") == "true" || os.Getenv("AUTODARK") == "1"
|
||||
languageOptions := listLocaleOptions()
|
||||
|
||||
// Checks if OIDC is enabled and skip login page setting
|
||||
oidcEnabled := auth.IsEnabled()
|
||||
@@ -1600,11 +1606,71 @@ func renderIndexPage(c *gin.Context) {
|
||||
"updateCheckEnabled": updateCheckEnabled,
|
||||
"disableExternalIP": disableExternalIP,
|
||||
"autoDark": autoDark,
|
||||
"languageOptions": languageOptions,
|
||||
"oidcEnabled": oidcEnabled,
|
||||
"skipLoginPage": skipLoginPage,
|
||||
})
|
||||
}
|
||||
|
||||
func listLocaleOptions() []localeOption {
|
||||
localeDir := "./internal/locales"
|
||||
if _, container := os.LookupEnv("CONTAINER"); container {
|
||||
localeDir = "/app/locales"
|
||||
}
|
||||
entries, err := os.ReadDir(localeDir)
|
||||
if err != nil {
|
||||
return []localeOption{{Code: "en", Label: "en"}}
|
||||
}
|
||||
options := make([]localeOption, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
name := entry.Name()
|
||||
if filepath.Ext(name) != ".json" {
|
||||
continue
|
||||
}
|
||||
code := strings.TrimSuffix(name, ".json")
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
label := localeLabelFromFile(filepath.Join(localeDir, name), code)
|
||||
options = append(options, localeOption{
|
||||
Code: code,
|
||||
Label: label,
|
||||
})
|
||||
}
|
||||
if len(options) == 0 {
|
||||
return []localeOption{{Code: "en", Label: "en"}}
|
||||
}
|
||||
sort.Slice(options, func(i, j int) bool {
|
||||
if options[i].Code == "en" {
|
||||
return true
|
||||
}
|
||||
if options[j].Code == "en" {
|
||||
return false
|
||||
}
|
||||
return strings.ToLower(options[i].Label) < strings.ToLower(options[j].Label)
|
||||
})
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func localeLabelFromFile(path, fallback string) string {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
var translations map[string]string
|
||||
if err := json.Unmarshal(data, &translations); err != nil {
|
||||
return fallback
|
||||
}
|
||||
if label, ok := translations["meta.language_name"]; ok && strings.TrimSpace(label) != "" {
|
||||
return label
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Version
|
||||
// =========================================================================
|
||||
|
||||
@@ -280,12 +280,9 @@
|
||||
<div class="mb-4">
|
||||
<label for="languageSelect" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="settings.language">Language</label>
|
||||
<select id="languageSelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<option value="en">English</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="it">Italiano</option>
|
||||
<option value="de_ch">Schwiizerdütsch</option>
|
||||
{{range .languageOptions}}
|
||||
<option value="{{.Code}}">{{.Label}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
|
||||
Reference in New Issue
Block a user