]> WPIA git - motion.git/commitdiff
upd: add language switch to navigation
authorINOPIAE <m.maengel@inopiae.de>
Fri, 24 Jul 2020 03:44:36 +0000 (05:44 +0200)
committerINOPIAE <m.maengel@inopiae.de>
Sun, 29 Nov 2020 15:07:14 +0000 (16:07 +0100)
Change-Id: I6be2927531964caa94f1a68cce0027985b8775b9

.gitignore
babel.cfg [deleted file]
motion.py
templates/base.html
templates/motion.html
templates/single_motion.html
tests/test_motion.py
translations/README.md

index cc946fcf5ee38fcf10010d92c29984fe5c38b71d..1b9d93c37830a382949691bf6a41935b2dbd4df6 100644 (file)
@@ -11,3 +11,6 @@
 # emacs
 \#*\#
 *~
 # emacs
 \#*\#
 *~
+messages.pot
+messages.mo
+messages.po
diff --git a/babel.cfg b/babel.cfg
deleted file mode 100644 (file)
index cd2157f..0000000
--- a/babel.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-[python: **.py]
-[jinja2: **/templates/**.html]
-encoding = utf-8
-extensions=jinja2.ext.autoescape,jinja2.ext.with_
index 9326152ae8d99b9e96cea21005b0e2d5e35db293..25c863c06d12ac24636e901f3e519a04818c3ae6 100644 (file)
--- a/motion.py
+++ b/motion.py
@@ -9,6 +9,7 @@ import filters
 from flaskext.markdown import Markdown
 from markdown.extensions import Extension
 from datetime import date, time, datetime
 from flaskext.markdown import Markdown
 from markdown.extensions import Extension
 from datetime import date, time, datetime
+from flask_language import Language, current_language
 import gettext
 
 def get_db():
 import gettext
 
 def get_db():
@@ -21,6 +22,7 @@ def get_db():
 app = Flask(__name__)
 app.register_blueprint(filters.blueprint)
 babel = Babel(app)
 app = Flask(__name__)
 app.register_blueprint(filters.blueprint)
 babel = Babel(app)
+lang = Language(app)
 gettext.install('motion')
 
 class EscapeHtml(Extension):
 gettext.install('motion')
 
 class EscapeHtml(Extension):
@@ -61,7 +63,21 @@ max_proxy=app.config.get("MAX_PROXY")
 
 @babel.localeselector
 def get_locale():
 
 @babel.localeselector
 def get_locale():
-    return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
+    return str(current_language)
+
+@lang.allowed_languages
+def get_allowed_languages():
+    return app.config['LANGUAGES'].keys()
+
+@lang.default_language
+def get_default_language():
+    return 'en'
+
+def get_languages():
+    return app.config['LANGUAGES']
+
+# Manually add vote options to the translation strings. They are used as keys in loops.
+TRANSLATION_STRINGS={_('yes'), _('no'), _('abstain')}
 
 @app.before_request
 def lookup_user():
 
 @app.before_request
 def lookup_user():
@@ -85,11 +101,11 @@ def lookup_user():
         roles = env.get("ROLES")
 
     if user is None:
         roles = env.get("ROLES")
 
     if user is None:
-        return "Server misconfigured", 500
+        return _('Server misconfigured'), 500
     roles = roles.split(" ")
 
     if user == "<invalid>":
     roles = roles.split(" ")
 
     if user == "<invalid>":
-        return "Access denied", 403;
+        return _('Access denied'), 403;
 
     db = get_db()
     with db.xact():
 
     db = get_db()
     with db.xact():
@@ -245,7 +261,7 @@ def main():
         else:
             prev = -1
     return render_template('index.html', motions=rv[:10], more=rv[10]["id"] if len(rv) == 11 else None, times=times.per_host, prev=prev,
         else:
             prev = -1
     return render_template('index.html', motions=rv[:10], more=rv[10]["id"] if len(rv) == 11 else None, times=times.per_host, prev=prev,
-                           categories=get_allowed_cats("create"), singlemotion=False, may_proxyadmin=may_admin("proxyadmin"))
+                           categories=get_allowed_cats("create"), singlemotion=False, may_proxyadmin=may_admin("proxyadmin"), languages=get_languages())
 
 def rel_redirect(loc):
     r = redirect(loc)
 
 def rel_redirect(loc):
     r = redirect(loc)
@@ -354,7 +370,7 @@ def show_motion(motion):
     if may("audit", resultmotion[0].get("type")) and not resultmotion[0].get("running") and not resultmotion[0].get("canceled"):
         votes = get_db().prepare("SELECT vote.result, voter.email FROM vote INNER JOIN voter ON voter.id = vote.voter_id WHERE vote.motion_id=$1")(resultmotion[0].get("id"));
         votes = get_db().prepare("SELECT vote.result, voter.email, CASE voter.email WHEN proxy.email THEN NULL ELSE proxy.email END as proxyemail FROM vote INNER JOIN voter ON voter.id = vote.voter_id INNER JOIN voter as proxy ON proxy.id = vote.proxy_id WHERE vote.motion_id=$1")(resultmotion[0].get("id"));
     if may("audit", resultmotion[0].get("type")) and not resultmotion[0].get("running") and not resultmotion[0].get("canceled"):
         votes = get_db().prepare("SELECT vote.result, voter.email FROM vote INNER JOIN voter ON voter.id = vote.voter_id WHERE vote.motion_id=$1")(resultmotion[0].get("id"));
         votes = get_db().prepare("SELECT vote.result, voter.email, CASE voter.email WHEN proxy.email THEN NULL ELSE proxy.email END as proxyemail FROM vote INNER JOIN voter ON voter.id = vote.voter_id INNER JOIN voter as proxy ON proxy.id = vote.proxy_id WHERE vote.motion_id=$1")(resultmotion[0].get("id"));
-    return render_template('single_motion.html', motion=resultmotion[0], may_vote=may("vote", resultmotion[0].get("type")), may_cancel=may("cancel", resultmotion[0].get("type")), votes=votes, proxyvote=resultproxyvote, proxyname=resultproxyname)
+    return render_template('single_motion.html', motion=resultmotion[0], may_vote=may("vote", resultmotion[0].get("type")), may_cancel=may("cancel", resultmotion[0].get("type")), votes=votes, proxyvote=resultproxyvote, proxyname=resultproxyname, languages=get_languages())
 
 @app.route("/motion/<string:motion>/vote/<string:voter>", methods=['POST'])
 @validate_motion_access_vote('vote')
 
 @app.route("/motion/<string:motion>/vote/<string:voter>", methods=['POST'])
 @validate_motion_access_vote('vote')
@@ -381,7 +397,7 @@ def vote(motion, voter, id):
 def proxy():
     if not may_admin("proxyadmin"):
         return _('Forbidden'), 403
 def proxy():
     if not may_admin("proxyadmin"):
         return _('Forbidden'), 403
-    return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"))
+    return render_template('proxy.html', voters=get_voters(), proxies=get_all_proxies(), may_proxyadmin=may_admin("proxyadmin"), languages=get_languages())
 
 @app.route("/proxy/add", methods=['POST'])
 def add_proxy():
 
 @app.route("/proxy/add", methods=['POST'])
 def add_proxy():
@@ -405,7 +421,7 @@ def add_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:
     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 '%s' reached." % (proxy), 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")
 
     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")
 
@@ -424,3 +440,7 @@ def revoke_proxy_all():
     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")
 
+@app.route("/language/<string:language>")
+def set_language(language):
+    lang.change_language(language)
+    return rel_redirect("/")
index b777e335276ffbafb7a61a39469555e3132c4fb6..936e1cb007fc6ac3ab11aff8a5690201a95143dc 100644 (file)
@@ -3,6 +3,8 @@
 <head>
 <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">
 <head>
 <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">
+<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
+<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
 <style type="text/css">
 .form-inline .motion {
   width: 100%;
 <style type="text/css">
 .form-inline .motion {
   width: 100%;
@@ -82,6 +84,16 @@ form {
 {%- endif %}
         </a>
       </li>
 {%- endif %}
         </a>
       </li>
+      <li class="nav-item dropdown">
+        <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+          {{_('Language')}}
+        </a>
+        <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
+         {%- for k, v in languages.items() %}
+           <a class="dropdown-item" href="/language/{{k}}">{{v}}</a>
+         {%- endfor %}
+        </div>
+      </li>
     </ul>
   </div>
 </nav>
     </ul>
   </div>
 </nav>
index 10440594230f7c34a095319f39c8c19001a7c3ec..edc637a28e82d3dee6aa3f4713c690e914c319ff 100644 (file)
 {%- endif %}
     </div>
     <div class="date">
 {%- endif %}
     </div>
     <div class="date">
-      <div>{{_('Proposed')}}: {{motion.posed|timestamp}} (UTC) {{_('by')}} {{motion.poser}}</div>
+      <div>{{_('Proposed')}}: {{_('%(dt)s (UTC) by %(user)s', dt=motion.posed|timestamp, user=motion.poser)}}</div>
 {%- if motion.canceled != None %}
 {%- if motion.canceled != None %}
-      <div>{{_('Canceled')}}: {{motion.canceled|timestamp}} (UTC) {{_('by')}} {{motion.canceler}}</div>
+      <div>{{_('Canceled')}}: {{_('%(dt)s (UTC) by %(user)s', dt=motion.canceled|timestamp, user=motion.poser)}}</div>
 {%- else %}
 {%- else %}
-      <div>{{_('Votes until')}}: {{motion.deadline|timestamp}} (UTC)</div>
+      <div>{{_('Votes until')}}: {{_('%(dt)s (UTC)', dt=motion.deadline|timestamp)}}</div>
 {%- endif %}
      </div>
   </div>
 {%- endif %}
      </div>
   </div>
index 4c853eebbd022e296030f1deb95336f38f5aa077..61611adf1468151d0485800717371d9eb1aaa031 100644 (file)
   <div class="panel-body">
 <h3>{{_('My vote')}}</h3>
 {%- if proxyname %}
   <div class="panel-body">
 <h3>{{_('My vote')}}</h3>
 {%- if proxyname %}
-{{_('Given by')}} {{proxyname[0][0]}}
+{{_('Given by %(pn)s', pn=proxyname[0][0])}}
+
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{g.voter}}" method="POST">
 {%- for vote in ['yes', 'no', 'abstain'] %}
 {%- 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>
+<button type="submit" class="btn btn-{{ 'success' if vote == motion.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{_(vote)|capitalize}}</button>
 {%- endfor %}
 </form>
 
 {%- for p in proxyvote %}
 {%- endfor %}
 </form>
 
 {%- for p in proxyvote %}
-<h3>{{_('Vote for')}} {{p.email}}</h3>
+<h3>{{_('Vote for %(email)s', email=p.email)}}</h3>
 {%- if p.owneremail and p.result%}
 {%- if p.owneremail and p.result%}
-{{_('Voted by')}} {{p.owneremail}}
+{{_('Voted by %(email)s', email=p.owneremail)}}
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
 {%- endif %}
 <form action="/motion/{{motion.identifier}}/vote/{{p.voter_id}}" method="POST">
-{%- for vote in ['yes','no','abstain'] %}
-<button type="submit" class="btn btn-{{ 'success' if vote == p.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 == p.result else 'primary' }}" name="vote" value="{{vote}}" id="vote-{{vote}}">{{_(vote)|capitalize}}</button>
 {%- endfor %}
 </form>
 {%- endfor %}
 {%- endfor %}
 </form>
 {%- endfor %}
index e500639bc3a006d52fec1fbcb001ce3c2dc6b80e..0399df04fc955922eae2510351774e556135cfb6 100644 (file)
@@ -234,11 +234,11 @@ class VoterTests(BasicTest):
         result = self.app.get('/', environ_base={'USER_ROLES': user})
         resulttext=self.buildResultText('A fourth motion', 1, 0, 0)
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         result = self.app.get('/', environ_base={'USER_ROLES': user})
         resulttext=self.buildResultText('A fourth motion', 1, 0, 0)
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
-        testtext= 'class=\"btn btn-success\" name=\"vote\" value="yes" id="vote-yes">yes</button>'
+        testtext= 'class=\"btn btn-success\" name=\"vote\" value="yes" id="vote-yes">Yes</button>'
         self.assertIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
-        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"no\" id=\"vote-no\">no</button>'
+        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"no\" id=\"vote-no\">No</button>'
         self.assertIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
-        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">abstain</button>'
+        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">Abstain</button>'
         self.assertIn(str.encode(testtext), result.data)
 
     def test_vote_no(self):
         self.assertIn(str.encode(testtext), result.data)
 
     def test_vote_no(self):
@@ -249,11 +249,11 @@ class VoterTests(BasicTest):
         resulttext=self.buildResultText('A fourth motion', 0, 1, 0)
         self.assertIn(str.encode(resulttext), result.data)
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         resulttext=self.buildResultText('A fourth motion', 0, 1, 0)
         self.assertIn(str.encode(resulttext), result.data)
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
-        testtext= 'class="btn btn-primary" name="vote\" value=\"yes\" id=\"vote-yes\">yes</button>'
+        testtext= 'class="btn btn-primary" name="vote\" value=\"yes\" id=\"vote-yes\">Yes</button>'
         self.assertIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
-        testtext= 'class=\"btn btn-success\" name=\"vote\" value=\"no\" id=\"vote-no\">no</button>'
+        testtext= 'class=\"btn btn-success\" name=\"vote\" value=\"no\" id=\"vote-no\">No</button>'
         self.assertIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
-        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">abstain</button>'
+        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">Abstain</button>'
         self.assertIn(str.encode(testtext), result.data)
 
     def test_vote_abstain(self):
         self.assertIn(str.encode(testtext), result.data)
 
     def test_vote_abstain(self):
@@ -264,11 +264,11 @@ class VoterTests(BasicTest):
         resulttext=self.buildResultText('A fourth motion', 0, 0, 1)
         self.assertIn(str.encode(resulttext), result.data)
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         resulttext=self.buildResultText('A fourth motion', 0, 0, 1)
         self.assertIn(str.encode(resulttext), result.data)
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
-        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"yes\" id=\"vote-yes\">yes</button>'
+        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"yes\" id=\"vote-yes\">Yes</button>'
         self.assertIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
-        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"no\" id=\"vote-no\">no</button>'
+        testtext= 'class=\"btn btn-primary\" name=\"vote\" value=\"no\" id=\"vote-no\">No</button>'
         self.assertIn(str.encode(testtext), result.data)
         self.assertIn(str.encode(testtext), result.data)
-        testtext= 'class=\"btn btn-success\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">abstain</button>'
+        testtext= 'class=\"btn btn-success\" name=\"vote\" value=\"abstain\" id=\"vote-abstain\">Abstain</button>'
         self.assertIn(str.encode(testtext), result.data)
 
     def test_vote_change(self):
         self.assertIn(str.encode(testtext), result.data)
 
     def test_vote_change(self):
@@ -793,15 +793,15 @@ class ProxyVoteTests(BasicTest):
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         # own vote without change
         testtext= '<form action="/motion/g1.20200402.004/vote/4" method="POST">\n'\
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         # own vote without change
         testtext= '<form action="/motion/g1.20200402.004/vote/4" method="POST">\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">yes</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">no</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>'
+            + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">No</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>'
         self.assertIn(str.encode(testtext), result.data)
         # proxy vote with change
         testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
         self.assertIn(str.encode(testtext), result.data)
         # proxy vote with change
         testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
-            + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">yes</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">no</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>\n'
+            + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">No</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>\n'
         self.assertIn(str.encode(testtext), result.data)
         
         # User B view
         self.assertIn(str.encode(testtext), result.data)
         
         # User B view
@@ -809,9 +809,9 @@ class ProxyVoteTests(BasicTest):
         # own vote without change
         testtext= '<h3>My vote</h3>\nGiven by testuser\n'\
             + '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
         # own vote without change
         testtext= '<h3>My vote</h3>\nGiven by testuser\n'\
             + '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
-            + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">yes</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">no</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>'
+            + '<button type="submit" class="btn btn-success" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="no" id="vote-no">No</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>'
         self.assertIn(str.encode(testtext), result.data)
         
         # change vote
         self.assertIn(str.encode(testtext), result.data)
         
         # change vote
@@ -820,9 +820,9 @@ class ProxyVoteTests(BasicTest):
 
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
 
         result = self.app.get('/motion/' + motion, environ_base={'USER_ROLES': user}, follow_redirects=True)
         testtext= '<form action="/motion/g1.20200402.004/vote/2" method="POST">\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">yes</button>\n'\
-            + '<button type="submit" class="btn btn-success" name="vote" value="no" id="vote-no">no</button>\n'\
-            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">abstain</button>\n</form>\n'
+            + '<button type="submit" class="btn btn-primary" name="vote" value="yes" id="vote-yes">Yes</button>\n'\
+            + '<button type="submit" class="btn btn-success" name="vote" value="no" id="vote-no">No</button>\n'\
+            + '<button type="submit" class="btn btn-primary" name="vote" value="abstain" id="vote-abstain">Abstain</button>\n</form>\n'
         self.assertIn(str.encode(testtext), result.data)
 
     def test_proxy_vote_no_proxy(self):
         self.assertIn(str.encode(testtext), result.data)
 
     def test_proxy_vote_no_proxy(self):
index c4df27eaafb8ba27068208a15403a581df42f8a5..5ec4ffae6a8968e37e21604dbf68be65f2331be7 100644 (file)
@@ -1,5 +1,29 @@
 # Translation files for WPIA motion tool
 
 # Translation files for WPIA motion tool
 
+Add the needed translation files here.
+
 The software is translated via Transifex: https://www.transifex.com/wpia/motion/.
 
 The current files are also available in the motion-translations repository on our gitlab system https://git.ccs-baumann.de/wpia/motion-translations.
 The software is translated via Transifex: https://www.transifex.com/wpia/motion/.
 
 The current files are also available in the motion-translations repository on our gitlab system https://git.ccs-baumann.de/wpia/motion-translations.
+
+To extract the translation files use:
+
+```
+pybabel extract -F ./translations/babel.cfg -k _l -o ./translations/messages.pot --input-dirs=.
+```
+
+To create a language file use e.g. de
+
+```
+pybabel init -i ./translations/messages.pot -d translations -l de
+```
+
+The translation files are maintained in a repo and translated on Transifex [https://www.transifex.com/wpia/landingpage-1/dashboard/](https://www.transifex.com/wpia/landingpage-1/dashboard/).
+
+The translation file are stored in this files structure translations/XX/LC_MESSAGES/messages.po with XX as language code.
+
+To compile the translated text from *.po to *.mo use:
+
+```
+pybabel compile -f -d translations
+```