]> WPIA git - motion.git/commitdiff
add: make app multilanguage with two languages: EN + DE
authorINOPIAE <m.maengel@inopiae.de>
Wed, 18 Mar 2020 11:07:00 +0000 (12:07 +0100)
committerINOPIAE <m.maengel@inopiae.de>
Sun, 15 Nov 2020 03:17:15 +0000 (04:17 +0100)
Change-Id: I96713d380fedc4b6ff3424785cc10e554ce3fd7b

config.py.example
motion.py
requirements.txt
templates/base.html
templates/index.html
templates/motion.html
templates/proxy.html
templates/single_motion.html
tests/test_motion.py
translations/babel.cfg [new file with mode: 0644]

index 83cb5fb83a499aa7a98ca328062b4d291aa8aa88..56ce4bc6c275e21445c0f4bb3b31d1e5f8e83458 100644 (file)
@@ -18,3 +18,8 @@ MAX_PROXY=2  # user is allowed to hold up to MAX_PROXY votes
 DURATION={'hostname':[3, 7, 14]} # duration period for motions
 
 #DEBUGUSER={'hostname':'username/create:* vote:*'} # remove # at beginning of line to use local debuguser
 DURATION={'hostname':[3, 7, 14]} # duration period for motions
 
 #DEBUGUSER={'hostname':'username/create:* vote:*'} # remove # at beginning of line to use local debuguser
+
+LANGUAGES = {
+    'en': 'English',
+    'de': 'Deutsch'
+}
index 9e70c991dcd1fbe4354e7b9845749ad419ac964c..9326152ae8d99b9e96cea21005b0e2d5e35db293 100644 (file)
--- a/motion.py
+++ b/motion.py
@@ -3,11 +3,13 @@ from flask import Flask
 from flask import render_template, redirect
 from flask import request
 from functools import wraps
 from flask import render_template, redirect
 from flask import request
 from functools import wraps
+from flask_babel import Babel, gettext
 import postgresql
 import filters
 from flaskext.markdown import Markdown
 from markdown.extensions import Extension
 from datetime import date, time, datetime
 import postgresql
 import filters
 from flaskext.markdown import Markdown
 from markdown.extensions import Extension
 from datetime import date, time, datetime
+import gettext
 
 def get_db():
     db = getattr(g, '_database', None)
 
 def get_db():
     db = getattr(g, '_database', None)
@@ -18,6 +20,8 @@ def get_db():
 
 app = Flask(__name__)
 app.register_blueprint(filters.blueprint)
 
 app = Flask(__name__)
 app.register_blueprint(filters.blueprint)
+babel = Babel(app)
+gettext.install('motion')
 
 class EscapeHtml(Extension):
     def extendMarkdown(self, md, md_globals):
 
 class EscapeHtml(Extension):
     def extendMarkdown(self, md, md_globals):
@@ -55,6 +59,10 @@ debuguser = ConfigProxy("DEBUGUSER")
 
 max_proxy=app.config.get("MAX_PROXY")
 
 
 max_proxy=app.config.get("MAX_PROXY")
 
+@babel.localeselector
+def get_locale():
+    return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
+
 @app.before_request
 def lookup_user():
     global prefix
 @app.before_request
 def lookup_user():
     global prefix
@@ -248,18 +256,18 @@ def rel_redirect(loc):
 def put_motion():
     cat=request.form.get("category", "")
     if cat not in get_allowed_cats("create"):
 def put_motion():
     cat=request.form.get("category", "")
     if cat not in get_allowed_cats("create"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     time = int(request.form.get("days", "3"));
     if time not in times.per_host:
     time = int(request.form.get("days", "3"));
     if time not in times.per_host:
-        return "Error, invalid length", 400
+        return _('Error, invalid length'), 400
     title=request.form.get("title", "")
     title=title.strip()
     if title =='':
     title=request.form.get("title", "")
     title=title.strip()
     if title =='':
-        return "Error, missing title", 400
+        return _('Error, missing title'), 400
     content=request.form.get("content", "")
     content=content.strip()
     if content =='':
     content=request.form.get("content", "")
     content=content.strip()
     if content =='':
-        return "Error, missing content", 400
+        return _('Error, missing content'), 400
 
     db = get_db()
     with db.xact():
 
     db = get_db()
     with db.xact():
@@ -285,14 +293,14 @@ def validate_motion_access(privilege):
             with db.xact():
                 rv = db.prepare("SELECT id, type, deadline < CURRENT_TIMESTAMP AS expired, canceled FROM motion WHERE identifier=$1 AND host=$2")(motion, request.host);
                 if len(rv) == 0:
             with db.xact():
                 rv = db.prepare("SELECT id, type, deadline < CURRENT_TIMESTAMP AS expired, canceled FROM motion WHERE identifier=$1 AND host=$2")(motion, request.host);
                 if len(rv) == 0:
-                    return "Error, Not found", 404
+                    return _('Error, Not found'), 404
                 id = rv[0].get("id")
                 if not may(privilege, rv[0].get("type")):
                 id = rv[0].get("id")
                 if not may(privilege, rv[0].get("type")):
-                    return "Forbidden", 403
+                    return _('Forbidden'), 403
                 if rv[0].get("canceled") is not None:
                 if rv[0].get("canceled") is not None:
-                    return "Error, motion was canceled", 403
+                    return _('Error, motion was canceled'), 403
                 if rv[0].get("expired"):
                 if rv[0].get("expired"):
-                    return "Error, out of time", 403
+                    return _('Error, out of time'), 403
             return f(motion, id)
         decorated_function.__name__ = f.__name__
         return decorated_function
             return f(motion, id)
         decorated_function.__name__ = f.__name__
         return decorated_function
@@ -311,7 +319,7 @@ def validate_motion_access_vote(privilege):
 @validate_motion_access('cancel')
 def cancel_motion(motion, id):
     if request.form.get("reason", "none") == "none":
 @validate_motion_access('cancel')
 def cancel_motion(motion, id):
     if request.form.get("reason", "none") == "none":
-        return "Error, form requires reason", 500
+        return _('Error, form requires reason'), 500
     rv = get_db().prepare("UPDATE motion SET canceled=CURRENT_TIMESTAMP, cancelation_reason=$1, canceled_by=$2 WHERE identifier=$3 AND host=$4 AND canceled is NULL")(request.form.get("reason", ""), g.voter, motion, request.host)
     return motion_edited(motion)
 
     rv = get_db().prepare("UPDATE motion SET canceled=CURRENT_TIMESTAMP, cancelation_reason=$1, canceled_by=$2 WHERE identifier=$3 AND host=$4 AND canceled is NULL")(request.form.get("reason", ""), g.voter, motion, request.host)
     return motion_edited(motion)
 
@@ -330,7 +338,7 @@ def show_motion(motion):
                          + "WHERE motion.identifier=$1 AND motion.host=$3")
     resultmotion = p(motion, g.voter, request.host)
     if len(resultmotion) == 0:
                          + "WHERE motion.identifier=$1 AND motion.host=$3")
     resultmotion = p(motion, g.voter, request.host)
     if len(resultmotion) == 0:
-        return "Error, Not found", 404
+        return _('Error, Not found'), 404
 
     p = get_db().prepare("SELECT voter.email FROM vote INNER JOIN voter ON vote.proxy_id = voter.id WHERE vote.motion_id=$1 AND vote.voter_id=$2 AND vote.proxy_id <> vote.voter_id")
     resultproxyname = p(resultmotion[0][0], g.voter)
 
     p = get_db().prepare("SELECT voter.email FROM vote INNER JOIN voter ON vote.proxy_id = voter.id WHERE vote.motion_id=$1 AND vote.voter_id=$2 AND vote.proxy_id <> vote.voter_id")
     resultproxyname = p(resultmotion[0][0], g.voter)
@@ -359,7 +367,7 @@ def vote(motion, voter, id):
     if (voterid != g.voter):
         rv = db.prepare("SELECT voter_id FROM proxy WHERE proxy.revoked IS NULL AND proxy.proxy_id = $1 AND proxy.voter_id = $2")(g.voter, voterid);
         if len(rv) == 0:
     if (voterid != g.voter):
         rv = db.prepare("SELECT voter_id FROM proxy WHERE proxy.revoked IS NULL AND proxy.proxy_id = $1 AND proxy.voter_id = $2")(g.voter, voterid);
         if len(rv) == 0:
-            return "Error, proxy not found.", 400
+            return _('Error, proxy not found.'), 400
 
     p = db.prepare("SELECT * FROM vote WHERE motion_id = $1 AND voter_id = $2")
     rv = p(id, voterid)
 
     p = db.prepare("SELECT * FROM vote WHERE motion_id = $1 AND voter_id = $2")
     rv = p(id, voterid)
@@ -372,39 +380,39 @@ def vote(motion, voter, id):
 @app.route("/proxy")
 def proxy():
     if not may_admin("proxyadmin"):
 @app.route("/proxy")
 def proxy():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"))
 
 @app.route("/proxy/add", methods=['POST'])
 def add_proxy():
     if not may_admin("proxyadmin"):
     return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"))
 
 @app.route("/proxy/add", methods=['POST'])
 def add_proxy():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     voter=request.form.get("voter", "")
     proxy=request.form.get("proxy", "")
     if voter == proxy :
     voter=request.form.get("voter", "")
     proxy=request.form.get("proxy", "")
     if voter == proxy :
-        return "Error, voter equals proxy.", 400
+        return _('Error, voter equals proxy.'), 400
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(voter);
     if len(rv) == 0:
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(voter);
     if len(rv) == 0:
-        return "Error, voter not found.", 400
+        return _('Error, voter not found.'), 400
     voterid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(proxy);
     if len(rv) == 0:
     voterid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM voter WHERE email=$1")(proxy);
     if len(rv) == 0:
-        return "Error, proxy not found.", 400
+        return _('Error, proxy not found.'), 400
     proxyid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM proxy WHERE voter_id=$1 AND revoked is NULL")(voterid);
     if len(rv) != 0:
     proxyid = rv[0].get("id")
     rv = get_db().prepare("SELECT id FROM proxy WHERE voter_id=$1 AND revoked is NULL")(voterid);
     if len(rv) != 0:
-        return "Error, proxy allready given.", 400
+        return _('Error, proxy allready given.'), 400
     rv = get_db().prepare("SELECT COUNT(id) as c FROM proxy WHERE proxy_id=$1 AND revoked is NULL GROUP BY proxy_id")(proxyid);
     if len(rv) != 0:
         if rv[0].get("c") >= max_proxy:
     rv = get_db().prepare("SELECT COUNT(id) as c FROM proxy WHERE proxy_id=$1 AND revoked is NULL GROUP BY proxy_id")(proxyid);
     if len(rv) != 0:
         if rv[0].get("c") >= max_proxy:
-            return "Error, Max proxy for '" + proxy + "' reached.", 400
+            return "Error, Max proxy for '%s' reached." % (proxy), 400
     rv = get_db().prepare("INSERT INTO proxy(voter_id, proxy_id, granted_by) VALUES ($1,$2,$3)")(voterid, proxyid, g.voter)
     return rel_redirect("/proxy")
 
 @app.route("/proxy/revoke", methods=['POST'])
 def revoke_proxy():
     if not may_admin("proxyadmin"):
     rv = get_db().prepare("INSERT INTO proxy(voter_id, proxy_id, granted_by) VALUES ($1,$2,$3)")(voterid, proxyid, g.voter)
     return rel_redirect("/proxy")
 
 @app.route("/proxy/revoke", methods=['POST'])
 def revoke_proxy():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     id=request.form.get("id", "")
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE id=$2")(g.voter, int(id))
     return rel_redirect("/proxy")
     id=request.form.get("id", "")
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE id=$2")(g.voter, int(id))
     return rel_redirect("/proxy")
@@ -412,7 +420,7 @@ def revoke_proxy():
 @app.route("/proxy/revokeall", methods=['POST'])
 def revoke_proxy_all():
     if not may_admin("proxyadmin"):
 @app.route("/proxy/revokeall", methods=['POST'])
 def revoke_proxy_all():
     if not may_admin("proxyadmin"):
-        return "Forbidden", 403
+        return _('Forbidden'), 403
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE revoked IS NULL")(g.voter)
     return rel_redirect("/proxy")
 
     rv = get_db().prepare("UPDATE proxy SET revoked=CURRENT_TIMESTAMP, revoked_by=$1 WHERE revoked IS NULL")(g.voter)
     return rel_redirect("/proxy")
 
index 0713e61650a17e769757df9b9d33dad7e936d675..f2f536ce0260d7a32996d300f1248e7b05161c81 100644 (file)
@@ -1,5 +1,6 @@
 click==6.7
 Flask==0.12.2
 click==6.7
 Flask==0.12.2
+Flask-Babel==1.0.0
 itsdangerous==0.24
 Jinja2==2.10
 MarkupSafe==1.0
 itsdangerous==0.24
 Jinja2==2.10
 MarkupSafe==1.0
index c65a9484050021a70375e67986bfd032d2bc767d..b777e335276ffbafb7a61a39469555e3132c4fb6 100644 (file)
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
 <!DOCTYPE html>
 <html>
 <head>
-<title>{% block title %}Motion list{% endblock %}</title>
+<title>{% block title %}{{_('Motion list')}}{% endblock %}</title>
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
 <style type="text/css">
 .form-inline .motion {
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
 <style type="text/css">
 .form-inline .motion {
@@ -58,27 +58,27 @@ form {
 </head>
 <body>
 <nav class="navbar navbar-expand-lg navbar-light bg-light">
 </head>
 <body>
 <nav class="navbar navbar-expand-lg navbar-light bg-light">
-  <a class="navbar-brand" href="../">{{'Motion list'}}</a>
+  <a class="navbar-brand" href="../">{{_('Motion list')}}</a>
   <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
     <span class="navbar-toggler-icon"></span>
   </button>
    <div class="collapse navbar-collapse" id="navbarNavDropdown">
     <ul class="navbar-nav">
       <li class="nav-item">
   <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
     <span class="navbar-toggler-icon"></span>
   </button>
    <div class="collapse navbar-collapse" id="navbarNavDropdown">
     <ul class="navbar-nav">
       <li class="nav-item">
-        <a class="nav-link" href="/">{{'Home'}}</a>
+        <a class="nav-link" href="/">{{_('Home')}}</a>
       </li>
 {%- if may_proxyadmin %}
       <li class="nav-item">
       </li>
 {%- if may_proxyadmin %}
       <li class="nav-item">
-        <a class="nav-link" href="/proxy">{{'Proxy management'}}</a>
+        <a class="nav-link" href="/proxy">{{_('Proxy management')}}</a>
       </li>
 {%- endif %}
       <li class="nav-item">
       </li>
 {%- endif %}
       <li class="nav-item">
-        <a class="nav-link">{{'User'}}: {{g.user}}
+        <a class="nav-link">{{_('User')}}: {{g.user}}
 {%- if g.proxies_given %}
 {%- if g.proxies_given %}
-         <br/>proxy granted to: {{g.proxies_given}}
+         <br/>{{_('proxy granted to')}}: {{g.proxies_given}}
 {%- endif %}
 {%- if g.proxies_received %}
 {%- endif %}
 {%- if g.proxies_received %}
-         <br/>holds proxy of: {{g.proxies_received}}
+         <br/>{{_('holds proxy of')}}: {{g.proxies_received}}
 {%- endif %}
         </a>
       </li>
 {%- endif %}
         </a>
       </li>
@@ -90,9 +90,9 @@ form {
 <!-- Footer -->
   <footer class="page-footer">
     <div class="footer-copyright text-center py-3">
 <!-- Footer -->
   <footer class="page-footer">
     <div class="footer-copyright text-center py-3">
-      <p>&copy; {{footer.version_year}} Copyright: <a href="{{footer.copyright_link}}">{{footer.copyright_name}}</a> 
-      | <a href="{{footer.imprint_link}}">Imprint</a> 
-      | <a href="{{footer.dataprotection_link}}">Data protection</a></p>
+      <p>&copy; {{footer.version_year}} {{_('Copyright')}}: <a href="{{footer.copyright_link}}">{{footer.copyright_name}}</a> 
+      | <a href="{{footer.imprint_link}}">{{_('Imprint')}}</a> 
+      | <a href="{{footer.dataprotection_link}}">{{_('Data protection')}}</a></p>
     </div>
   </footer>
 </body>
     </div>
   </footer>
 </body>
index 16adecc2a9127fda861e405ce655b348eccc9ec7..e3d1bad1e823c1cc35c9c0fab3893d7799605480 100644 (file)
@@ -2,10 +2,12 @@
 {% block body %}
 <div class="container">
 {%- if categories|length != 0 %}
 {% block body %}
 <div class="container">
 {%- if categories|length != 0 %}
+<div class="card">
+</div>
 <form action="/motion" method="POST" class="form-inline">
 <div class="motion card">
   <div class="motion-title card-heading alert-light from-group">
 <form action="/motion" method="POST" class="form-inline">
 <div class="motion card">
   <div class="motion-title card-heading alert-light from-group">
-    <input class="form-control motion-title-input" placeholder="Motion title" type="text" name="title" id="title" required="yes">
+    <input class="form-control motion-title-input" placeholder="{{_('Motion title')}}" type="text" name="title" id="title" required="yes">
     {%- if categories|length == 1 %}
     <input type="text" class="float form-control" maxwidth="10" disabled value="{{categories[0]}}">
     <input type="hidden" name="category" value="{{categories[0]}}">
     {%- if categories|length == 1 %}
     <input type="text" class="float form-control" maxwidth="10" disabled value="{{categories[0]}}">
     <input type="hidden" name="category" value="{{categories[0]}}">
     </select>
   </div>
   <div class="card-body">
     </select>
   </div>
   <div class="card-body">
-    <textarea class="form-control" placeholder="Motion content" name="content" rows="8"></textarea><br>
-    Editing note: Markdown is used formatting.<br>
-    To add a line break add two lines, to enter a link use [text](https://domain.tld/link)<br>
-    <button class="btn btn-primary" type="submit">Submit Motion</button>
+    <textarea class="form-control" placeholder="{{_('Motion content')}}" name="content" rows="8"></textarea><br>
+    {{_('Editing note: Markdown is used formatting.')}}<br>
+    {{_('To add a line break add two lines, to enter a link use [text](https//domain.tld/link)')}}<br>
+    <button class="btn btn-primary" type="submit">{{_('Submit Motion')}}</button>
   </div>
 </div>
 </form>
 {%- endif %}
 {%- if prev %}
 {%- if prev == -1 %}
   </div>
 </div>
 </form>
 {%- endif %}
 {%- if prev %}
 {%- if prev == -1 %}
-<a href="/" class="btn btn-primary">Prev</a>
+<a href="/" class="btn btn-primary">{{_('Prev')}}</a>
 {%- else %}
 {%- else %}
-<a href="/?start={{ prev }}" class="btn btn-primary">Prev</a>
+<a href="/?start={{ prev }}" class="btn btn-primary">{{_('Prev')}}</a>
 {%- endif %}
 {%- endif %}
 {%- for motion in motions %}
 {% include 'motion.html' %}
 {%- endfor %}
 {%- if more %}
 {%- endif %}
 {%- endif %}
 {%- for motion in motions %}
 {% include 'motion.html' %}
 {%- endfor %}
 {%- if more %}
-<a href="/?start={{ more }}" class="btn btn-primary">Next</a>
+<a href="/?start={{ more }}" class="btn btn-primary">{{_('Next')}}</a>
 {%- endif %}
 </div>
 {%- endblock %}
 {%- endif %}
 </div>
 {%- endblock %}
index a363c0e75ef433ede3469058369d949995595b50..10440594230f7c34a095319f39c8c19001a7c3ec 100644 (file)
@@ -5,32 +5,33 @@
 {%- elif motion.yes is defined %}{% if motion.yes != None and motion.no != None and motion.yes > motion.no %} alert-success{% else %} alert-danger{% endif %}
 {%- else %} bg-light{%- endif -%}
 ">
 {%- elif motion.yes is defined %}{% if motion.yes != None and motion.no != None and motion.yes > motion.no %} alert-success{% else %} alert-danger{% endif %}
 {%- else %} bg-light{%- endif -%}
 ">
-    <span class="title-text">{{motion.name}}</span> ({{ 'Running' if motion.running else ('Canceled' if motion.canceled != None else 'Finished') }})
+    <span class="title-text">{{motion.name}}</span> ({{ _('Running') if motion.running else (_('Canceled') if motion.canceled != None else _('Finished')) }})
     <span class="motion-type">{{motion.type}}</span>
     <div># {{motion.identifier}}
 {%- if singlemotion == False %}
     <span class="motion-type">{{motion.type}}</span>
     <div># {{motion.identifier}}
 {%- if singlemotion == False %}
-    <a class="btn btn-primary" href="/motion/{{motion.identifier}}" role="button">{{ 'Vote' if motion.running else 'Result' }}</a>
+    <a class="btn btn-primary" href="/motion/{{motion.identifier}}" role="button">{{ _('Vote') if motion.running else _('Result') }}</a>
 {%- endif %}
     </div>
     <div class="date">
 {%- endif %}
     </div>
     <div class="date">
-      <div>Proposed: {{motion.posed|timestamp}} (UTC) by {{motion.poser}}</div>
+      <div>{{_('Proposed')}}: {{motion.posed|timestamp}} (UTC) {{_('by')}} {{motion.poser}}</div>
 {%- if motion.canceled != None %}
 {%- if motion.canceled != None %}
-      <div>Canceled: {{motion.canceled|timestamp}} (UTC) by {{motion.canceler}}</div></div>
+      <div>{{_('Canceled')}}: {{motion.canceled|timestamp}} (UTC) {{_('by')}} {{motion.canceler}}</div>
 {%- else %}
 {%- else %}
-      <div>Votes until: {{motion.deadline|timestamp}} (UTC)</div></div>
+      <div>{{_('Votes until')}}: {{motion.deadline|timestamp}} (UTC)</div>
 {%- endif %}
 {%- endif %}
+     </div>
   </div>
   <div class="card-body">
     <p>{{motion.content|markdown}}</p>
 {%- if motion.yes or motion.no or motion.abstain %}
     <p>
 {%- for vote in ['yes', 'no', 'abstain'] %}
   </div>
   <div class="card-body">
     <p>{{motion.content|markdown}}</p>
 {%- if motion.yes or motion.no or motion.abstain %}
     <p>
 {%- for vote in ['yes', 'no', 'abstain'] %}
-{{vote|capitalize}} <span class="badge badge-pill badge-secondary">{{motion[vote]}}</span><br>
+{{_(vote)|capitalize}} <span class="badge badge-pill badge-secondary">{{motion[vote]}}</span><br>
 {%- endfor %}
     </p>
 {%- endif %}
 {%- if motion.canceled != None %}
 {%- endfor %}
     </p>
 {%- endif %}
 {%- if motion.canceled != None %}
-    <p>Cancelation reason: {{motion.cancelation_reason}}</p>
+    <p>{{_('Cancelation reason')}}: {{motion.cancelation_reason}}</p>
 {%- endif %}
   </div>
 {%- block content %}{% endblock %}
 {%- endif %}
   </div>
 {%- block content %}{% endblock %}
index d81214c5d2b15fdfcd84584d5d4cbf8f6b422190..259606e6441e67001daeeac7f5e6da8d3bef2aaf 100644 (file)
@@ -4,8 +4,8 @@
 <form action="/proxy/add" method="POST">
   <table>
   <tr>
 <form action="/proxy/add" method="POST">
   <table>
   <tr>
-    <td>Voter</td>
-    <td>Proxy</td>
+    <td>{{_('Voter')}}</td>
+    <td>{{_('Proxy')}}</td>
     <td></td>
   </tr>
   <tr>
     <td></td>
   </tr>
   <tr>
@@ -24,7 +24,7 @@
       </select>
     </td>
     <td>
       </select>
     </td>
     <td>
-      <button type="submit" class="btn btn-primary">Add</button>
+      <button type="submit" class="btn btn-primary">{{_('Add')}}</button>
     </td>
   </tr>
   </table>
     </td>
   </tr>
   </table>
 <form action="/proxy/revoke" method="POST">
 <div class="motion card" id="votes">
   <div class="card-heading text-white bg-info">
 <form action="/proxy/revoke" method="POST">
 <div class="motion card" id="votes">
   <div class="card-heading text-white bg-info">
-    Granted Proxies
+    {{_('Granted Proxies')}}
   </div>
   <div class="card-body">
     <table>
       <thead>
   </div>
   <div class="card-body">
     <table>
       <thead>
-        <th>Voter</th>
-        <th>Proxy</th>
+        <th>{{_('Voter')}}</th>
+        <th>{{_('Proxy')}}</th>
         <th></th>
       </thead>
       {%- for row in proxies %}
       <tr>
         <td>{{row.voter_email}}</td>
         <td>{{row.proxy_email}}</td>
         <th></th>
       </thead>
       {%- for row in proxies %}
       <tr>
         <td>{{row.voter_email}}</td>
         <td>{{row.proxy_email}}</td>
-        <td><button type="submit" class="btn btn-danger" name="id" value="{{row.id}}">Revoke</button></td>
+        <td><button type="submit" class="btn btn-danger" name="id" value="{{row.id}}">{{_('Revoke')}}</button></td>
       </tr>
       {%- endfor %}
     </table>
       </tr>
       {%- endfor %}
     </table>
@@ -55,7 +55,7 @@
 </form>
 {%- endif %}
 <form action="/proxy/revokeall" method="POST">
 </form>
 {%- endif %}
 <form action="/proxy/revokeall" method="POST">
-  <button type="submit" class="btn btn-danger">Revoke ALL</button>
+  <button type="submit" class="btn btn-danger">{{_('Revoke all')}}</button>
 </form>
 </div>
 {%- endblock %}
\ No newline at end of file
 </form>
 </div>
 {%- endblock %}
\ No newline at end of file
index c53e4b51307bb67b66d5b7079662fa89bc8c8c29..4c853eebbd022e296030f1deb95336f38f5aa077 100644 (file)
@@ -1,17 +1,17 @@
 {% extends "base.html" %}
 {% block title -%}
 {% extends "base.html" %}
 {% block title -%}
-Motion: {{motion.name}}
+{{_('Motion')}}: {{motion.name}}
 {%- endblock %}
 {% block body %}
 {%- include 'motion.html' %}
 {%- if votes %}
 <div class="motion card" id="votes">
   <div class="card-heading text-white bg-info">
 {%- endblock %}
 {% block body %}
 {%- include 'motion.html' %}
 {%- if votes %}
 <div class="motion card" id="votes">
   <div class="card-heading text-white bg-info">
-    Motion Votes
+    {{_('Motion Votes')}}
   </div>
   <div class="card-body">
     {%- for row in votes %}
   </div>
   <div class="card-body">
     {%- for row in votes %}
-    <div>{{row.email}}: {{row.result}}{%- if row.proxyemail %} : given by {{row.proxyemail}}{%- endif %}</div>
+    <div>{{row.email}}: {{row.result}}{%- if row.proxyemail %} : {_('given by')}} {{row.proxyemail}}{%- endif %}</div>
     {%- endfor %}
   </div>
 </div>
     {%- endfor %}
   </div>
 </div>
@@ -22,20 +22,20 @@ Motion: {{motion.name}}
 {%- if may_vote %}
 <div class="panel panel-info" id="votes">
   <div class="panel-body">
 {%- if may_vote %}
 <div class="panel panel-info" id="votes">
   <div class="panel-body">
-<h3>My vote</h3>
+<h3>{{_('My vote')}}</h3>
 {%- if proxyname %}
 {%- if proxyname %}
-Given by {{proxyname[0][0]}}
+{{_('Given by')}} {{proxyname[0][0]}}
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{g.voter}}" method="POST">
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{g.voter}}" method="POST">
-{%- for vote in ['yes','no','abstain'] %}
-<button type="submit" class="btn btn-{{ 'success' if vote == motion.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{vote}}</button>
+{%- for vote in ['yes', 'no', 'abstain'] %}
+<button type="submit" class="btn btn-{{ 'success' if vote == motion.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{_(vote)}}</button>
 {%- endfor %}
 </form>
 
 {%- for p in proxyvote %}
 {%- endfor %}
 </form>
 
 {%- for p in proxyvote %}
-<h3>Vote for {{p.email}}</h3>
+<h3>{{_('Vote for')}} {{p.email}}</h3>
 {%- if p.owneremail and p.result%}
 {%- if p.owneremail and p.result%}
-Voted by {{p.owneremail}}
+{{_('Voted by')}} {{p.owneremail}}
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
 {%- for vote in ['yes','no','abstain'] %}
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
 {%- for vote in ['yes','no','abstain'] %}
@@ -47,18 +47,17 @@ Voted by {{p.owneremail}}
 
 {%- if may_cancel %}
 <form action="/motion/{{motion.identifier}}/cancel" method="POST" class="form-inline">
 
 {%- if may_cancel %}
 <form action="/motion/{{motion.identifier}}/cancel" method="POST" class="form-inline">
-<input type="text" placeholder="cancelation reason" name="reason" class="form-control" required="yes">
-<button type="submit" class="btn btn-danger" name="cancel" value="cancel" id="cancel">Cancel</button></br>
+<input type="text" placeholder="{{_('Cancelation reason')}}" name="reason" class="form-control" required="yes">
+<button type="submit" class="btn btn-danger" name="cancel" value="cancel" id="cancel">{{_('Cancel')}}</button></br>
 </form>
 {%- endif %}
 {%- if may_finish %}
 <form action="/motion/{{motion.identifier}}/finish" method="POST" class="form-inline">
 </form>
 {%- endif %}
 {%- if may_finish %}
 <form action="/motion/{{motion.identifier}}/finish" method="POST" class="form-inline">
-<button type="submit" class="btn btn-danger" name="finish" value="finish" id="finish">Finish</button></br>
+<button type="submit" class="btn btn-danger" name="finish" value="finish" id="finish">{{_('Finish')}}</button></br>
 </form>
 {%- endif %}
 </form>
 {%- endif %}
-
 {%- endif %}
 {%- endif %}
-<a href="/?start={{motion.id}}#motion-{{motion.id}}" class="btn btn-primary">Back</a>
+<a href="/?start={{motion.id}}#motion-{{motion.id}}" class="btn btn-primary">{{_('Back')}}</a>
 {%- endblock %}
   </div>
 </div>
 {%- endblock %}
   </div>
 </div>
index 0bee29420e123a31029dcc6922b3d1c9103a71d7..e500639bc3a006d52fec1fbcb001ce3c2dc6b80e 100644 (file)
@@ -107,41 +107,39 @@ class GeneralTests(BasicTest):
     def test_basic_results_data(self):
         result = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
         testtext= '<div class="motion card" id="motion-3">\n  <div class="motion-title card-heading alert-warning">'\
     def test_basic_results_data(self):
         result = self.app.get('/', environ_base={'USER_ROLES': user}, follow_redirects=True)
         testtext= '<div class="motion card" id="motion-3">\n  <div class="motion-title card-heading alert-warning">'\
-            + '\n    <span class=\"title-text\">Motion C</span> (Canceled)\n    <span class=\"motion-type\">group1</span>'\
+            + '\n    <span class="title-text">Motion C</span> (Canceled)\n    <span class="motion-type">group1</span>'\
             + '\n    <div># g1.20200402.003'\
             + '\n    <a class="btn btn-primary" href="/motion/g1.20200402.003" role="button">Result</a>'\
             + '\n    <div># g1.20200402.003'\
             + '\n    <a class="btn btn-primary" href="/motion/g1.20200402.003" role="button">Result</a>'\
-            + '\n    </div>'\
-            + '\n    <div class=\"date\">\n      <div>Proposed: 2020-04-02 21:47:24 (UTC) by User A</div>'\
-            + '\n      <div>Canceled: 2020-04-03 21:48:24 (UTC) by User A</div></div>\n  </div>'\
-            + '\n  <div class=\"card-body\">\n    <p><p>A third motion</p></p>'\
-            + '\n    <p>\nYes <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
-            + '\nNo <span class=\"badge badge-pill badge-secondary\">0</span><br>'\
-            + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n    </p>'\
-            + '\n    <p>Cancelation reason: Entered with wrong text</p>\n  </div>\n</div>'
+            + '\n    </div>\n    <div class="date">'\
+            + '\n      <div>Proposed: 2020-04-02 21:47:24 (UTC) by User A</div>'\
+            + '\n      <div>Canceled: 2020-04-03 21:48:24 (UTC) by User A</div>\n     </div>\n  </div>'\
+            + '\n  <div class="card-body">\n    <p><p>A third motion</p></p>'\
+            + '\n    <p>\nYes <span class="badge badge-pill badge-secondary">1</span><br>'\
+            + '\nNo <span class="badge badge-pill badge-secondary">0</span><br>'\
+            + '\nAbstain <span class="badge badge-pill badge-secondary">0</span><br>\n    </p>'\
+            + '\n    <p>Cancelation reason: Entered with wrong text</p>\n  </div>\n</div>\n'
         self.assertIn(str.encode(testtext), result.data)
         testtext= '<div class="motion card" id="motion-2">\n  <div class="motion-title card-heading alert-danger">'\
         self.assertIn(str.encode(testtext), result.data)
         testtext= '<div class="motion card" id="motion-2">\n  <div class="motion-title card-heading alert-danger">'\
-            + '\n    <span class=\"title-text\">Motion B</span> (Finished)\n    <span class=\"motion-type\">group1</span>'\
+            + '\n    <span class="title-text">Motion B</span> (Finished)\n    <span class="motion-type">group1</span>'\
             + '\n    <div># g1.20200402.002'\
             + '\n    <a class="btn btn-primary" href="/motion/g1.20200402.002" role="button">Result</a>'\
             + '\n    <div># g1.20200402.002'\
             + '\n    <a class="btn btn-primary" href="/motion/g1.20200402.002" role="button">Result</a>'\
-            + '\n    </div>'\
-            + '\n    <div class=\"date\">\n      <div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>'\
-            + '\n      <div>Votes until: 2020-04-04 21:41:26 (UTC)</div></div>\n  </div>'\
-            + '\n  <div class=\"card-body\">\n    <p><p>A second motion</p></p>'\
-            + '\n    <p>\nYes <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
-            + '\nNo <span class=\"badge badge-pill badge-secondary\">2</span><br>'\
-            + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n    </p>\n  </div>\n</div>\n'
-        self.assertIn(str.encode(testtext), result.data)
-        testtext= '<div class=\"motion card\" id=\"motion-1\">\n  <div class=\"motion-title card-heading alert-success\">'\
-            + '\n    <span class=\"title-text\">Motion A</span> (Finished)\n    <span class=\"motion-type\">group1</span>'\
+            + '\n    </div>\n    <div class="date">\n      <div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>'\
+            + '\n      <div>Votes until: 2020-04-04 21:41:26 (UTC)</div>\n     </div>\n  </div>'\
+            + '\n  <div class="card-body">\n    <p><p>A second motion</p></p>\n    <p>'\
+            + '\nYes <span class="badge badge-pill badge-secondary">1</span><br>'\
+            + '\nNo <span class="badge badge-pill badge-secondary">2</span><br>'\
+            + '\nAbstain <span class="badge badge-pill badge-secondary">0</span><br>\n    </p>\n  </div>\n</div>\n'
+        self.assertIn(str.encode(testtext), result.data)
+        testtext= '<div class="motion card" id="motion-1">\n  <div class="motion-title card-heading alert-success">'\
+            + '\n    <span class="title-text">Motion A</span> (Finished)\n    <span class="motion-type">group1</span>'\
             + '\n    <div># g1.20200402.001'\
             + '\n    <a class="btn btn-primary" href="/motion/g1.20200402.001" role="button">Result</a>'\
             + '\n    <div># g1.20200402.001'\
             + '\n    <a class="btn btn-primary" href="/motion/g1.20200402.001" role="button">Result</a>'\
-            + '\n    </div>'\
-            + '\n    <div class=\"date">\n      <div>Proposed: 2020-04-02 21:40:33 (UTC) by User A</div>'\
-            + '\n      <div>Votes until: 2020-04-02 21:40:33 (UTC)</div></div>\n  </div>'\
-            + '\n  <div class=\"card-body\">\n    <p><p>My special motion</p></p>'\
-            + '\n    <p>\nYes <span class=\"badge badge-pill badge-secondary\">2</span><br>'\
-            + '\nNo <span class=\"badge badge-pill badge-secondary\">1</span><br>'\
-            + '\nAbstain <span class=\"badge badge-pill badge-secondary\">0</span><br>\n    </p>\n  </div>\n</div>\n</div>'
+            + '\n    </div>\n    <div class="date">\n      <div>Proposed: 2020-04-02 21:40:33 (UTC) by User A</div>'\
+            + '\n      <div>Votes until: 2020-04-02 21:40:33 (UTC)</div>\n     </div>\n  </div>'\
+            + '\n  <div class="card-body">\n    <p><p>My special motion</p></p>\n    <p>'\
+            + '\nYes <span class="badge badge-pill badge-secondary">2</span><br>'\
+            + '\nNo <span class="badge badge-pill badge-secondary">1</span><br>'\
+            + '\nAbstain <span class="badge badge-pill badge-secondary">0</span><br>\n    </p>\n  </div>\n</div>\n'
         self.assertIn(str.encode(testtext), result.data)
         testtext= 'Proxy management'
         self.assertNotIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
         testtext= 'Proxy management'
         self.assertNotIn(str.encode(testtext), result.data)
@@ -347,8 +345,8 @@ class VoterTests(BasicTest):
     def test_see_old_vote(self):
         motion='g1.20200402.002'
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
     def test_see_old_vote(self):
         motion='g1.20200402.002'
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
-        testtext= '<div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>\n      <div>Votes until: 2020-04-04 21:41:26 (UTC)</div></div>'\
-            + '\n  </div>\n  <div class="card-body">\n    <p><p>A second motion</p></p>\n  </div>\n</div>'\
+        testtext= '<div>Proposed: 2020-04-02 21:41:26 (UTC) by User A</div>\n      <div>Votes until: 2020-04-04 21:41:26 (UTC)</div>'\
+            + '\n     </div>\n  </div>\n  <div class="card-body">\n    <p><p>A second motion</p></p>\n  </div>\n</div>'\
             + '\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
         self.assertIn(str.encode(testtext), result.data)
 
             + '\n<a href="/?start=2#motion-2" class="btn btn-primary">Back</a>'
         self.assertIn(str.encode(testtext), result.data)
 
diff --git a/translations/babel.cfg b/translations/babel.cfg
new file mode 100644 (file)
index 0000000..cd2157f
--- /dev/null
@@ -0,0 +1,4 @@
+[python: **.py]
+[jinja2: **/templates/**.html]
+encoding = utf-8
+extensions=jinja2.ext.autoescape,jinja2.ext.with_