Babel locale

This commit is contained in:
Emily 2019-05-12 15:04:25 +01:00
parent 08e68e35b8
commit 426d1fe531
19 changed files with 195 additions and 103 deletions

3
babel.cfg Normal file
View File

@ -0,0 +1,3 @@
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View File

@ -1,8 +0,0 @@
from flask import render_template
from objects import glob
@glob.app.context_processor
def locale():
def _locale(lang, key):
return glob.langs[lang][key] if key in glob.langs[lang] else key
return dict(locale = _locale)

View File

@ -6,14 +6,14 @@ from flask_login import UserMixin
from objects import glob from objects import glob
class BillForm(Form): class BillForm(Form):
payment_to = StringField("lbl_to", [validators.DataRequired()]) payment_to = StringField("Payment to", [validators.DataRequired()])
description = TextAreaField("lbl_desc", render_kw = { description = TextAreaField("Description", render_kw = {
"cols": 55, "cols": 55,
"rows": 8 "rows": 8
}) })
sum = DecimalField("lbl_sum") sum = DecimalField("Sum")
kid = IntegerField("lbl_id") kid = IntegerField("KID")
date_due = DateField("lbl_date") date_due = DateField("Date due")
class LoginForm(Form): class LoginForm(Form):
email = StringField("Email", [ email = StringField("Email", [

View File

@ -1,8 +0,0 @@
{
"lbl_to": "Payment to",
"lbl_desc": "Description",
"lbl_sum": "Sum",
"lbl_id": "KID",
"lbl_date": "Date due",
"lbl_status": "Payment status"
}

View File

@ -1,4 +0,0 @@
{
"add": "Add",
"close": "Close"
}

View File

@ -1,7 +0,0 @@
{
"lnk_dashboard": "Dashboard",
"lnk_bills": "Bills",
"lnk_receipts": "Receipts",
"lnk_warranties": "Warranties",
"ttl_economical": "Economical"
}

View File

@ -1,8 +0,0 @@
{
"lbl_to": "Betaling til",
"lbl_desc": "Tekst",
"lbl_sum": "Sum",
"lbl_id": "KID",
"lbl_date": "Forfallsdato",
"lbl_status": "Betalingsstatus"
}

View File

@ -1,4 +0,0 @@
{
"add": "Legg til",
"close": "Lukk"
}

View File

@ -1,7 +0,0 @@
{
"lnk_dashboard": "Dashbord",
"lnk_bills": "Regninger",
"lnk_receipts": "Kvitteringer",
"lnk_warranties": "Garantier",
"ttl_economical": "Økonomisk"
}

17
localizer.py Normal file
View File

@ -0,0 +1,17 @@
from flask import g, request, session
from flask_babel import Babel
from objects import glob
babel = Babel(glob.app)
LANGUAGES = {
"en": "English",
"no": "Norwegian"
}
@babel.localeselector
def get_locale():
if request.args.get("lang"):
session["lang"] = request.args.get("lang") if request.args.get("lang") in LANGUAGES.keys() else "en"
return session.get("lang", "en")

View File

@ -5,8 +5,9 @@ from objects import glob # Global sharing of python objects in a manageable way
glob.app = Flask(__name__) glob.app = Flask(__name__)
glob.app.secret_key = "E2FGrJXLtOxPh70Q" glob.app.secret_key = "E2FGrJXLtOxPh70Q"
import localizer # Initialize localization (Babel)
import routes # All flask app routes import routes # All flask app routes
import filters # All flask app filters # import filters # All flask app filters
if glob.config["git"]["auto_pull_and_restart"]: # Only used on the VPS (Do not enable in config) if glob.config["git"]["auto_pull_and_restart"]: # Only used on the VPS (Do not enable in config)
@glob.app.route(glob.config["git"]["webhook_endpoint"], methods = ["POST"]) @glob.app.route(glob.config["git"]["webhook_endpoint"], methods = ["POST"])

View File

@ -10,8 +10,6 @@ import bcrypt
app = None # main.py -> Flask App app = None # main.py -> Flask App
sql_conn = None sql_conn = None
langs = {}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Global variables that initializes on first load of module # Global variables that initializes on first load of module
@ -22,19 +20,6 @@ if not os.path.isfile("config.json"):
with open("config.json", "r") as f: with open("config.json", "r") as f:
config = json.load(f) config = json.load(f)
path = "locale"
for l in os.listdir(path):
langs[l] = {}
_path = "%s/%s" % (path, l)
for f in os.listdir(_path):
_f = "%s/%s" % (_path, f)
with open(_f, "r", encoding = "UTF-8") as j:
pnd = json.load(j)
for k in pnd.keys():
if k in langs[l]:
raise Exception("Duplicate localization entries found in file %s" % _f)
langs[l] = {**langs[l], **pnd}
def make_sql_connection(): def make_sql_connection():
return mysql.connector.connect(**config["mysql"]) return mysql.connector.connect(**config["mysql"])

View File

@ -6,6 +6,8 @@ from forms.login import LoginForm, RegisterForm, BillForm, User, register_accoun
from objects import glob # Global sharing of python objects in a manageable way from objects import glob # Global sharing of python objects in a manageable way
from flask_babel import gettext
login_manager = flask_login.LoginManager() login_manager = flask_login.LoginManager()
login_manager.init_app(glob.app) login_manager.init_app(glob.app)
login_manager.login_view = "login" login_manager.login_view = "login"
@ -65,7 +67,7 @@ def receipts():
@glob.app.route("/login", methods = ["GET", "POST"]) @glob.app.route("/login", methods = ["GET", "POST"])
def login(): def login():
if flask_login.current_user.is_authenticated: if flask_login.current_user.is_authenticated:
flash("Already logged in", "info") flash(gettext("Already logged in"), "info")
return redirect(url_for("dashboard")) return redirect(url_for("dashboard"))
form = LoginForm(request.form) form = LoginForm(request.form)
@ -73,20 +75,20 @@ def login():
try: try:
user = User((form.email.data, form.password.data)) user = User((form.email.data, form.password.data))
except Exception as e: except Exception as e:
flash(str(e), "danger") flash(gettext(str(e)), "danger")
return render_template("login.html", form=form) return render_template("login.html", form=form)
flask_login.login_user(user) flask_login.login_user(user)
logged_in_users.append(user) logged_in_users.append(user)
flash("Logged in", "success") flash(gettext("Logged in"), "success")
return redirect(url_for("dashboard")) return redirect(url_for("dashboard"))
return render_template("login.html", form=form) return render_template("login.html", form=form)
@glob.app.route("/register", methods = ["GET", "POST"]) @glob.app.route("/register", methods = ["GET", "POST"])
def register(): def register():
if flask_login.current_user.is_authenticated: if flask_login.current_user.is_authenticated:
flash("Already logged in", "info") flash(gettext("Already logged in"), "info")
return redirect(url_for("dashboard")) return redirect(url_for("dashboard"))
form = RegisterForm(request.form) form = RegisterForm(request.form)
@ -94,10 +96,10 @@ def register():
try: try:
register_account(form.email.data, form.password.data, form.firstname.data, form.surname.data) register_account(form.email.data, form.password.data, form.firstname.data, form.surname.data)
except Exception as e: except Exception as e:
flash(str(e), "danger") flash(gettext(str(e)), "danger")
return render_template("register.html", form=form) return render_template("register.html", form=form)
flash("User registered", "success") flash(gettext("User registered"), "success")
return redirect(url_for("login")) return redirect(url_for("login"))
return render_template("register.html", form=form) return render_template("register.html", form=form)
@ -105,12 +107,12 @@ def register():
@flask_login.login_required @flask_login.login_required
def logout(): def logout():
flask_login.logout_user() flask_login.logout_user()
flash("Logged out", "success") flash(gettext("Logged out"), "success")
return redirect(url_for("login")) return redirect(url_for("login"))
@glob.app.errorhandler(401) @glob.app.errorhandler(401)
def unauthorized_handler_err(): def unauthorized_handler_err():
flash("Login is required", "danger") flash(gettext("Login is required"), "danger")
unauthorized_handler() unauthorized_handler()
@login_manager.user_loader @login_manager.user_loader
@ -122,4 +124,3 @@ def load_user(uuid):
@login_manager.unauthorized_handler @login_manager.unauthorized_handler
def unauthorized_handler(): def unauthorized_handler():
return redirect(url_for("login")) return redirect(url_for("login"))

View File

@ -1,12 +1,10 @@
{% set LANG = "no" %} <!-- TODO: Dynamic lang swap -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
{% include 'layout/includes/boot-head.html' %} {% include 'layout/includes/boot-head.html' %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/custom.css') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/custom.css') }}">
{% if title %} {% if title %}
<title>Husstanden - {{ locale(LANG, title) }}</title> <title>Husstanden - {{ _(title) | title }}</title>
{% else %} {% else %}
<title>Husstanden</title> <title>Husstanden</title>
{% endif %} {% endif %}

View File

@ -105,29 +105,29 @@
<div class="flex-column"> <div class="flex-column">
<div> <!-- Collection --> <div> <!-- Collection -->
<a class="item" href="{{ url_for('dashboard') }}"> <a class="item" href="{{ url_for('dashboard') }}">
<i class="far fa-calendar-alt"></i><span>{{ locale(LANG, "lnk_dashboard") }}</span> <i class="far fa-calendar-alt"></i><span>{{ _("Dashboard") }}</span>
</a> </a>
</div> </div>
<div> <div>
<h4>{{ locale(LANG, "ttl_economical") }}</h4> <h4>{{ _("Economical") }}</h4>
</div> </div>
<div> <div>
<a class="item" href="{{ url_for('bills') }}"> <a class="item" href="{{ url_for('bills') }}">
<i class="fas fa-money-check-alt"></i><span>{{ locale(LANG, "lnk_bills") }}</span> <i class="fas fa-money-check-alt"></i><span>{{ _("Bills") }}</span>
</a> </a>
</div> </div>
<div> <div>
<a class="item" onclick="toggleCategory(this)"> <a class="item" onclick="toggleCategory(this)">
<i class="far fa-list-alt"></i><span>{{ locale(LANG, "lnk_receipts") }}</span> <i class="far fa-list-alt"></i><span>{{ _("Receipts") }}</span>
<i class="fas fa-chevron-right"></i> <i class="fas fa-chevron-right"></i>
</a> </a>
<div class="downtab hidden"> <div class="downtab hidden">
<div class="flex-column"> <div class="flex-column">
<div class="page"> <div class="page">
<a href="{{ url_for('receipts') }}">{{ locale(LANG, "lnk_receipts") }}</a> <a href="{{ url_for('receipts') }}">{{ _("Receipts") }}</a>
</div> </div>
<div class="page"> <div class="page">
<a href="{{ url_for('warranties') }}">{{ locale(LANG, "lnk_warranties") }}</a> <a href="{{ url_for('warranties') }}">{{ _("Warranties") }}</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<style> <style>
.rndblock { .rndblock {
border-radius: 100%; border-radius: 100%;
background: #506EE4; background: #506EE4;
background-position: center; background-position: center;
width: 40px; width: 40px;
height: 40px; height: 40px;
@ -15,11 +15,24 @@
cursor: pointer; cursor: pointer;
} }
.languk { .lang-icon {
background-position: left;
background-size: contain;
width: 32px;
height: 24px;
color: white;
text-align: center;
font-size: 24px;
vertical-align: middle;
display: table-cell;
}
.lang-en {
background-image: url("{{ url_for('static', filename='const/img/flags/gb.svg') }}"); background-image: url("{{ url_for('static', filename='const/img/flags/gb.svg') }}");
} }
.langno { .lang-no {
background-image: url("{{ url_for('static', filename='const/img/flags/no.svg') }}"); background-image: url("{{ url_for('static', filename='const/img/flags/no.svg') }}");
} }
@ -34,7 +47,7 @@
</style> </style>
<nav class="navbar topnav" style="margin-bottom: 10px;"> <nav class="navbar topnav" style="margin-bottom: 10px;">
<h3>{{ locale(LANG, title) }}</h3> <h3>{{ _(title) | title }}</h3>
<div> <div>
{% with messages = get_flashed_messages(with_categories = true) %} {% with messages = get_flashed_messages(with_categories = true) %}
{% if messages %} {% if messages %}
@ -49,7 +62,25 @@
</div> </div>
<div class="my-2 my-lg-0 d-flex icon-buttons"> <div class="my-2 my-lg-0 d-flex icon-buttons">
<div class="col"> <div class="col">
<div class="rndblock languk"></div> <div class="rndblock lang-{{ session.lang }}" data-toggle="dropdown" aria-expanded="false"></div>
<ul class="dropdown-menu">
<li>
<div class="row">
<a class="col d-flex m-1" href="?lang=en">
<div class="lang-icon lang-en"></div>
<div class="flex-grow-1"></div>
<span>{{ _("english") | title }}</span>
<div class="flex-grow-1"></div>
</a>
<a class="col d-flex m-1" href="?lang=no">
<div class="lang-icon lang-no"></div>
<div class="flex-grow-1"></div>
<span>{{ _("norwegian") | title }}</span>
<div class="flex-grow-1"></div>
</a>
</div>
</li>
</ul>
</div> </div>
<div class="col"> <div class="col">
<div class="rndblock"> <div class="rndblock">
@ -64,7 +95,7 @@
<li> <li>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<a href="{{ url_for('logout') }}">Sign out</a> <a href="{{ url_for('logout') }}">{{ _("Sign out") }}</a>
</div> </div>
</div> </div>
</li> </li>

View File

@ -1,4 +1,4 @@
{% set title = "lnk_bills" %} {% set title = "Bills" %}
{% extends "layout/dash.html" %} {% extends "layout/dash.html" %}
@ -12,7 +12,7 @@
<div class="container module"> <div class="container module">
<button type="button" class="btn btn-primary" style="margin:10px;" data-toggle="modal" data-target="#myModal">{{ locale(LANG, "add") }}</button> <button type="button" class="btn btn-primary" style="margin:10px;" data-toggle="modal" data-target="#myModal">{{ _("Add") }}</button>
<div class="modal fade" id="myModal" role="dialog"> <div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<!-- Modal content--> <!-- Modal content-->
@ -23,7 +23,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
{% macro render_field(field) %} {% macro render_field(field) %}
<dt><label for="{{ field.label.field_id }}">{{ locale(LANG, field.label.text) }}</label> <dt><label for="{{ field.label.field_id }}">{{ _(field.label.text) }}</label>
<dd>{{ field(**kwargs)|safe }} <dd>{{ field(**kwargs)|safe }}
{% if field.errors %} {% if field.errors %}
<ul class=errors> <ul class=errors>
@ -42,11 +42,11 @@
{{ render_field(form.kid) }} {{ render_field(form.kid) }}
{{ render_field(form.date_due) }} {{ render_field(form.date_due) }}
</dl> </dl>
<input type=submit value="{{ locale(LANG, 'add') }}"> <input type=submit value="{{ _('Add') }}">
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ locale(LANG, "close") }}</button> <button type="button" class="btn btn-default" data-dismiss="modal">{{ _("Close") }}</button>
</div> </div>
</div> </div>
</div> </div>
@ -55,12 +55,12 @@
<table class="table"> <table class="table">
<thead class="thead-light"> <thead class="thead-light">
<tr> <tr>
<th scope="col">{{ locale(LANG, "lbl_to") }}</th> <th scope="col">{{ _("Payment to") }}</th>
<th scope="col">{{ locale(LANG, "lbl_desc") }}</th> <th scope="col">{{ _("Description") }}</th>
<th scope="col" style="width: 120px">{{ locale(LANG, "lbl_sum") }}</th> <th scope="col" style="width: 120px">{{ _("Sum") }}</th>
<th scope="col" style="width: 220px">{{ locale(LANG, "lbl_id") }}</th> <th scope="col" style="width: 220px">{{ _("KID") }}</th>
<th scope="col" style="width: 120px">{{ locale(LANG, "lbl_date") }}</th> <th scope="col" style="width: 120px">{{ _("Date due") }}</th>
<th scope="col" style="width: 150px">{{ locale(LANG, "lbl_status") }}</th> <th scope="col" style="width: 150px">{{ _("Payment status") }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -1,4 +1,4 @@
{% set title = "lnk_dashboard" %} {% set title = "Dashboard" %}
{% extends "layout/dash.html" %} {% extends "layout/dash.html" %}

View File

@ -0,0 +1,102 @@
# Norwegian text and messages
#
msgid ""
msgstr ""
"Project-Id-Version: 0.4\n"
"Report-Msgid-Bugs-To: noreply@osufx.com\n"
"POT-Creation-Date: 2019-05-12 15:57+0200\n"
"PO-Revision-Date: 2015-05-12 09:47+1000\n"
"Last-Translator: Emily Steinsvik <emily@osufx.com>\n"
"Language: no\n"
"Language-Team: no <emily@osufx.com>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
#: routes.py:70 routes.py:91
msgid "Already logged in"
msgstr ""
#: routes.py:84
msgid "Logged in"
msgstr "Logget inn"
#: routes.py:102
msgid "User registered"
msgstr "Bruker registrert"
#: routes.py:110
msgid "Logged out"
msgstr "Logget ut"
#: routes.py:115
msgid "Login is required"
msgstr "Innlogging kreves"
#: templates/layout/includes/side_nav.html:108
msgid "Dashboard"
msgstr "Dashbord"
#: templates/layout/includes/side_nav.html:112
msgid "Economical"
msgstr "Økonomisk"
#: templates/layout/includes/side_nav.html:116
msgid "Bills"
msgstr "Regninger"
#: templates/layout/includes/side_nav.html:121
#: templates/layout/includes/side_nav.html:127
msgid "Receipts"
msgstr "Kvitteringer"
#: templates/layout/includes/side_nav.html:130
msgid "Warranties"
msgstr "Garantier"
#: templates/layout/includes/top_nav.html:72
msgid "english"
msgstr "engelsk"
#: templates/layout/includes/top_nav.html:78
msgid "norwegian"
msgstr "norsk"
#: templates/layout/includes/top_nav.html:98
msgid "Sign out"
msgstr "Logg ut"
#: templates/pages/bills.html:15 templates/pages/bills.html:45
msgid "Add"
msgstr "Legg til"
#: templates/pages/bills.html:49
msgid "Close"
msgstr "Lukk"
#: templates/pages/bills.html:58
msgid "Payment to"
msgstr "Betaling til"
#: templates/pages/bills.html:59
msgid "Description"
msgstr "Tekst"
#: templates/pages/bills.html:60
msgid "Sum"
msgstr "Sum"
#: templates/pages/bills.html:61
msgid "KID"
msgstr "KID"
#: templates/pages/bills.html:62
msgid "Date due"
msgstr "Forfallsdato"
#: templates/pages/bills.html:63
msgid "Payment status"
msgstr "Betalingsstatus"