Add language selection feature and internationalization support

This commit is contained in:
Eduardo Silva 2025-04-14 15:42:12 -03:00
parent 701f957642
commit e430580aba
18 changed files with 261 additions and 107 deletions

0
intl_tools/__init__.py Normal file
View File

1
intl_tools/admin.py Normal file
View File

@ -0,0 +1 @@
# Register your models here.

6
intl_tools/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class IntlToolsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'intl_tools'

23
intl_tools/forms.py Normal file
View File

@ -0,0 +1,23 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Column, Layout, Row, Submit
from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _
class LanguageForm(forms.Form):
language = forms.ChoiceField(
choices=settings.LANGUAGES,
label=_("Language"),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_method = 'post'
self.helper.layout = Layout(
Row(
Column('language', css_class='col-md-6'),
),
Submit('submit', _("Change Language"), css_class='btn btn-primary')
)

View File

1
intl_tools/models.py Normal file
View File

@ -0,0 +1 @@
# Create your models here.

1
intl_tools/tests.py Normal file
View File

@ -0,0 +1 @@
# Create your tests here.

25
intl_tools/views.py Normal file
View File

@ -0,0 +1,25 @@
from django.conf import settings
from django.shortcuts import redirect, render
from django.utils import translation
from .forms import LanguageForm
def view_change_language(request):
if request.method == 'POST':
form = LanguageForm(request.POST)
if form.is_valid():
language = form.cleaned_data['language']
translation.activate(language)
request.session['django_language'] = language
next_url = '/'
response = redirect(next_url)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, language)
return response
else:
form = LanguageForm(initial={'language': translation.get_language()})
if request.user.is_authenticated:
return render(request, 'generic_form.html', {'form': form})
else:
return render(request, 'generic_form_guest.html', {'form': form})

Binary file not shown.

View File

@ -0,0 +1,87 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-04-14 15:34-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: intl_tools/forms.py:11 templates/accounts/login.html:36
msgid "Language"
msgstr "Idioma"
#: intl_tools/forms.py:22 templates/base.html:80
msgid "Change Language"
msgstr "Alterar Idioma"
#: templates/accounts/login.html:14
msgid "Username"
msgstr "Usuário"
#: templates/accounts/login.html:23
msgid "Password"
msgstr "Senha"
#: templates/accounts/login.html:32
msgid "Login"
msgstr "Acessar"
#: templates/accounts/logout.html:11
msgid "You have been successfully logged out."
msgstr "Você foi desconectado com sucesso."
#: templates/accounts/logout.html:14
msgid "Login again"
msgstr "Acessar novamente"
#: templates/base.html:112
msgid "Status"
msgstr "Estado"
#: templates/base.html:158
msgid "User Manager"
msgstr "Configurar Usuários"
#: templates/base.html:176
msgid "VPN Invite"
msgstr "Convite para VPN"
#: templates/base.html:254
msgid "Update Required"
msgstr "Atualização Necessária"
#: templates/base.html:256
msgid ""
"Your WireGuard settings have been modified. To apply these changes, please "
"update the configuration and reload the WireGuard service."
msgstr ""
"Suas configurações do WireGuard foram modificadas. Para aplicar essas "
"mudanças, atualize a configuração e recarregue o serviço WireGuard."
#: templates/base.html:265
msgid "Update and restart service"
msgstr "Atualizar e reiniciar o serviço"
#: templates/base.html:273
msgid "Update and reload service"
msgstr "Atualizar e recarregar o serviço"
#: templates/base.html:286
msgid "Update Available"
msgstr "Atualização Disponível"
#: templates/base.html:288
msgid "Version"
msgstr "Versão"

View File

@ -1,62 +1,50 @@
{% extends "base_login.html" %}
{% load i18n %}
{% block content %}
<div class="register-box">
<div class="register-logo">
<a href="/"><b>wireguard-webadmin</b></a>
</div>
<div class="card">
<div class="card-body register-card-body">
<form action="/accounts/login/" method="post">
{% csrf_token %}
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="{% trans 'Username' %}" name="username">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user"></span>
</div>
</div>
</div>
<div class="register-box">
<div class="register-logo">
<a href="/"><b>wireguard-webadmin</b></a>
<div class="input-group mb-3">
<input type="password" class="form-control" placeholder="{% trans 'Password' %}" name="password">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<button type="submit" class="btn btn-primary btn-block">{% trans 'Login' %}</button>
</div>
<div class="col-md-6">
<a class="btn btn-outline-primary btn-block" href="/change_language/">
{% trans 'Language' %}
</a>
</div>
</div>
</form>
</div>
</div>
</div>
<div class="card">
<div class="card-body register-card-body">
<form action="/accounts/login/" method="post">
{% csrf_token %}
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="username" name="username">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-user"></span>
</div>
</div>
</div>
<div class="input-group mb-3">
<input type="password" class="form-control" placeholder="Password" name="password">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-8">
</div>
<div class="col-4">
<button type="submit" class="btn btn-primary btn-block">Login</button>
</div>
</div>
</form>
</div>
<!-- /.form-box -->
</div><!-- /.card -->
</div>
<!-- /.register-box -->
<script>
document.querySelector('input[name="username"]').addEventListener('input', function() {
this.value = this.value.toLowerCase().replace(/\s/g, '');
});
</script>
{% endblock %}
```
<script>
document.querySelector('input[name="username"]').addEventListener('input', function() {
this.value = this.value.toLowerCase().replace(/\s/g, '');
});
</script>
{% endblock %}

View File

@ -1,35 +1,20 @@
{% extends "base_login.html" %}
{% load i18n %}
{% block content %}
<div class="register-box">
<div class="register-logo">
<a href="/"><b>wireguard-webadmin</b></a>
</div>
<div class="card">
<div class="card-body register-card-body">
<p class="login-box-msg">You have been successfully logged out.</p>
<div class="row">
<div class="col-12">
<a href="/accounts/login/" class="btn btn-primary btn-block">Login again</a>
<div class="register-box">
<div class="register-logo">
<a href="/"><b>wireguard-webadmin</b></a>
</div>
<div class="card">
<div class="card-body register-card-body">
<p class="login-box-msg">{% trans 'You have been successfully logged out.' %}</p>
<div class="row">
<div class="col-12">
<a href="/accounts/login/" class="btn btn-primary btn-block">{% trans 'Login again' %}</a>
</div>
</div>
</div>
</div>
</div>
<!-- /.form-box -->
</div><!-- /.card -->
</div>
<!-- /.register-box -->
{% endblock %}
```
</div>
</div>
{% endblock %}

View File

@ -1,5 +1,7 @@
<!DOCTYPE html>
<html lang="en">
{% load i18n %}
{% get_current_language as CURRENT_LANGUAGE %}
<html lang="{{ CURRENT_LANGUAGE }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -70,13 +72,16 @@
<li class="nav-item">
<a class="nav-link" data-widget="pushmenu" href="#" role="button"><i class="fas fa-bars"></i></a>
</li>
<li class="nav-item d-none d-sm-inline-block">
<a href="/status/" class="nav-link">Status</a>
</li>
</ul>
<!-- Right navbar links -->
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a href="/change_language/" class="nav-link" title="{% trans 'Change Language' %}">
<i class="fas fa-language"></i>
</a>
</li>
<li class="nav-item">
<a href="/accounts/logout/" class="nav-link">
<i class="fas fa-sign-out-alt"></i>
@ -104,7 +109,7 @@
<a href="/status/" class="nav-link {% if '/status/' in request.path %}active{% endif %}">
<i class="fas fa-tachometer-alt nav-icon"></i>
<p>
Status
{% trans 'Status' %}
</p>
</a>
</li>
@ -150,7 +155,7 @@
<a href="/user/list/" class="nav-link {% if '/user/' in request.path %}active{% endif %}">
<i class="fas fa-users nav-icon"></i>
<p>
User Manager
{% trans 'User Manager' %}
</p>
</a>
</li>
@ -159,7 +164,7 @@
<a href="/server/manage/" class="nav-link {% if '/server/' in request.path %}active{% endif %}">
<i class="fas fa-cogs nav-icon"></i>
<p>
Server Settings
WireGuard
</p>
</a>
</li>
@ -168,7 +173,7 @@
<a href="/vpn_invite/" class="nav-link {% if '/vpn_invite/' in request.path %}active{% endif %}">
<i class="fas fa-share-square nav-icon"></i>
<p>
VPN Invite
{% trans 'VPN Invite' %}
</p>
</a>
</li>
@ -246,9 +251,9 @@
<div class="container-fluid">
{% if pending_changes_warning %}
<div class="alert alert-warning" role="alert">
<h4 class="alert-heading">Update Required</h4>
<h4 class="alert-heading">{% trans 'Update Required' %}</h4>
<p>
Your WireGuard settings have been modified. To apply these changes, please update the configuration and reload the WireGuard service.
{% trans 'Your WireGuard settings have been modified. To apply these changes, please update the configuration and reload the WireGuard service.' %}
</p>
<p>
<a
@ -257,7 +262,7 @@
{% else %}
href="#" class="btn btn-secondary disabled"
{% endif %}
>Update and restart service</a>
>{% trans 'Update and restart service' %}</a>
<a
{% if user_acl.enable_reload %}
@ -265,7 +270,7 @@
{% else %}
href="#" class="btn btn-secondary disabled"
{% endif %}
>Update and reload service</a>
>{% trans 'Update and reload service' %}</a>
</p>
</div>
@ -278,9 +283,9 @@
</div>
<!-- /.content-wrapper -->
<footer class="main-footer">
{% if webadmin_version.update_available %}<a class='btn btn-sm btn-danger' id="btn_update_changelog">Update Available</a>{% else %}wireguard-webadmin {% endif %}
{% if webadmin_version.update_available %}<a class='btn btn-sm btn-danger' id="btn_update_changelog">{% trans 'Update Available' %}</a>{% else %}wireguard-webadmin {% endif %}
<div class="float-right d-none d-sm-inline-block">
<b>Version</b> {{ webadmin_version.current_version }}
<b>{% trans 'Version' %}</b> {{ webadmin_version.current_version }}
</div>
</footer>

View File

@ -1,5 +1,7 @@
<!DOCTYPE html>
<html lang="en">
{% load i18n %}
{% get_current_language as CURRENT_LANGUAGE %}
<html lang="{{ CURRENT_LANGUAGE }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">

View File

@ -0,0 +1,17 @@
{% extends "base_login.html" %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="register-box">
<div class="register-logo">
<a href="/"><b>wireguard-webadmin</b></a>
</div>
<div class="card">
<div class="card-body register-card-body">
{% csrf_token %}
{% crispy form %}
</div>
</div>
</div>
{% endblock %}

3
update_messages.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
django-admin makemessages -a --ignore=.venv/*
django-admin compilemessages --ignore=.venv/*

View File

@ -10,6 +10,7 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
@ -38,6 +39,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'crispy_forms',
'crispy_bootstrap4',
'intl_tools',
'wireguard',
'user_manager',
'wireguard_tools',
@ -49,6 +51,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
@ -113,17 +116,22 @@ AUTH_PASSWORD_VALIDATORS = [
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
LANGUAGES = [
('pt-br', 'Português'),
('en', 'English'),
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
STATIC_URL = 'static/'
STATIC_ROOT = '/app_static_files/'

View File

@ -26,6 +26,7 @@ from dns.views import view_apply_dns_config, view_manage_dns_settings, view_mana
from firewall.views import manage_firewall_rule, manage_redirect_rule, view_firewall_migration_required, \
view_firewall_rule_list, view_generate_iptables_script, view_manage_firewall_settings, view_redirect_rule_list, \
view_reset_firewall
from intl_tools.views import view_change_language
from user_manager.views import view_manage_user, view_peer_group_list, view_peer_group_manage, view_user_list
from vpn_invite.views import view_email_settings, view_vpn_invite_list, view_vpn_invite_settings
from vpn_invite_public.views import view_public_vpn_invite
@ -85,4 +86,5 @@ urlpatterns = [
path('vpn_invite/smtp_settings/', view_email_settings, name='email_settings'),
path('invite/', view_public_vpn_invite, name='public_vpn_invite'),
path('invite/download_config/', download_config_or_qrcode, name='download_config_or_qrcode'),
path('change_language/', view_change_language, name='change_language'),
]