[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-taler-merchant-demos] 18/18: merge torsten-redesign branch, imple
From: |
gnunet |
Subject: |
[taler-taler-merchant-demos] 18/18: merge torsten-redesign branch, implement i18n support |
Date: |
Sat, 10 Oct 2020 22:55:52 +0200 |
This is an automated email from the git hooks/post-receive script.
grothoff pushed a commit to branch master
in repository taler-merchant-demos.
commit 2e665813a44988bfd906c0fab773f82652047841
Merge: ad45c1d 58af977
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sat Oct 10 22:55:31 2020 +0200
merge torsten-redesign branch, implement i18n support
.gitignore | 3 +-
Makefile | 41 +++++-
README.md | 33 ++++-
setup.py | 1 +
talermerchantdemos/.gitignore | 6 +
talermerchantdemos/.vscode/launch.json | 15 +++
talermerchantdemos/blog/articles/scrap1_11.html | 1 -
talermerchantdemos/blog/blog.py | 139 ++++++++++++++++-----
talermerchantdemos/blog/content.py | 13 +-
.../blog/templates/article_frame.html | 13 --
.../blog/templates/article_frame.html.j2 | 15 +++
.../blog/templates/article_refunded.html | 10 --
.../blog/templates/article_refunded.html.j2 | 16 +++
talermerchantdemos/blog/templates/base.html | 79 ------------
talermerchantdemos/blog/templates/base.html.j2 | 119 ++++++++++++++++++
.../blog/templates/confirm_refund.html | 19 ---
.../blog/templates/confirm_refund.html.j2 | 22 ++++
talermerchantdemos/blog/templates/error.html | 22 ----
talermerchantdemos/blog/templates/error.html.j2 | 24 ++++
talermerchantdemos/blog/templates/index.html | 40 ------
talermerchantdemos/blog/templates/show_refund.html | 28 -----
talermerchantdemos/static/__init__.py | 1 +
talermerchantdemos/static/{demo.css => demo.scss} | 27 ++--
talermerchantdemos/static/navbar.css | 75 +++++++++++
talermerchantdemos/static/navbar.css.map | 1 +
talermerchantdemos/static/navbar.scss | 103 +++++++++++++++
talermerchantdemos/static/{pure.css => pure.scss} | 2 +-
.../{taler-fallback.css => taler-fallback.scss} | 0
28 files changed, 606 insertions(+), 262 deletions(-)
diff --cc .gitignore
index bee8a64,8e95770..92ed544
--- a/.gitignore
+++ b/.gitignore
@@@ -1,1 -1,3 +1,2 @@@
- __pycache__
+ talermerchantdemos/static/*.css
-
+ talermerchantdemos/static/navbar.css.map
diff --cc Makefile
index d40e2f4,eefac0d..0cea1c3
--- a/Makefile
+++ b/Makefile
@@@ -38,3 -38,27 +38,42 @@@ dist
.PHONY: pretty
pretty:
yapf -r -i talerblog/
+
++# i18n
++extract:
++# Note: Flask-BabelEx expects 'translations/' for the dirname,
++# even though GNU gettext typically uses 'locale/'
++ mkdir -p translations/
++ pybabel extract -F babel.cfg -o translations/messages.pot .
++# Add new language as follows:
++# pybabel init -i locale/messages.pot -d locale/ -l de
++
++compile:
++ pybabel compile -d translations/
++
++update: extract
++ pybabel update -i translations/messages.pot -d translations/
++
+ # SASS/SCSS
+
+ sass-setup:
+ @echo "This is the initial sass-installation/setup script."
+ @echo "This setup must run as root, on a machine that has NPM
installed!"
+ @echo "If your password is requested (for escalation), please enter it."
+ sudo npm install -g sass
+
+ scss-setup: sass-setup
+
+ sass-build:
+ @echo "Warning: If Sass/Scss is not installed, please run \`make
sass-setup\` first!"
+ @echo "This script will only convert files inside /static"
+ sass talermerchantdemos/static:talermerchantdemos/static
+
+ scss-build: sass-build
+
+ sass-autobuild:
+ @echo "Warning: If Sass/Scss is not installed, please run \`make
sass-setup\` first!"
+ @echo "This script will automatically build sass/scss files in the
static directory!"
+ sass --watch talermerchantdemos/static:talermerchantdemos/static
+
+ scss-autobuild: sass-autobuild
diff --cc README.md
index 953ec69,d221946..c9f3e7f
--- a/README.md
+++ b/README.md
@@@ -6,9 -10,9 +10,9 @@@ Step 1: `cd` into the directory:<br/
> ```$ cd taler-merchant-demos```
<br/>
--Step 2: Ensure Python3.5 or above is installed using a command like:<br/>
++Step 2: Ensure Python 3.5 or above and Flask with the Babel extension are
installed using a command like:<br/>
--> ```$ sudo apt install python3.8 -y```
++> ```$ sudo apt install python3.8 python3-flask-babel -y```
<br/>
Step 3: Ensure Python3 Pip is installed:<br/>
@@@ -16,7 -20,7 +20,7 @@@
> ```$ sudo apt install python3-pip -y```
<br/>
--Step 4: configure it using:<br/>
++Step 4: configure this package using:<br/>
> ```$ ./configure --destination=local```
@@@ -27,14 -31,24 +31,28 @@@ Step 5: Install UWSGI<br
<br/>
Step 6: Install LXML
- *NOTE: DO NOT INSTALL USING PIP2 (on my system, that is what the pip command
uses) - INSTALL IT USING PIP3*
+ *NOTE: DO NOT INSTALL USING PIP2 (on my system, that is what the pip command
uses) - INSTALL IT USING PIP3*<br>
> ```$ pip3 install lxml```
+
+ <br/>
+
+ Step 7: Install NPM<br>
+
+ > ```$ sudo apt install npm; sudo npm install -g npm node```
+
<br>
+ Step 8: Install scss<br>
+ > ```$ make sass-install```
+
++This will the NPM implementation of sass. If you have a Ruby
++tool called 'sass' installed, this build will NOT work.
++
++
## Quick Install for the dependencies
Here's one command to automatically install all dependencies at once:
- > ```$ sudo apt install python3.8 python3-pip -y; pip3 install lxml uwsgi;
./configure --destination=local```
+ > ```$ sudo apt install python3.8 python3-pip npm -y; pip3 install lxml
uwsgi; ./configure --destination=local; sudo npm install -g npm node;make
sass-install```
## Configuring the demo
*This is just how I did it, and not the main method of doing it*
@@@ -72,40 -84,11 +90,45 @@@ Step 3: Configure the config
To apply changes, use
> ```$ make install```
+ <br>
+
+ To apply ***SCSS*** changes, use
+ > ```$ make scss-build```
+
## Running the program
To start the server, use the following command:<br>
-> ```$ taler-merchant-demos --serve-http blog```
+> ```$ taler-merchant-demos blog```
+
+## More configuration options.
+This makes the blog speak UWSGI over TCP:
+> ```
+> [frontends]
+> backend_apikey = "ApiKey Sandbox"
+> backend = https://backend.test.taler.net/
+>
+> [taler]
+> currency = TESTKUDOS
+>
+> [blog]
+> serve = uwsgi
+> uwsgi_serve = tcp
+> uwsgi_port = XZY
+
+> ```
+<br>
+
+This makes the blog speak UWSGI over unix domain socket:
+> ```
+> [frontends]
+> backend_apikey = "ApiKey Sandbox"
+> backend = https://backend.test.taler.net/
+>
+> [taler]
+> currency = TESTKUDOS
+>
+> [blog]
+> serve = uwsgi
+> uwsgi_serve = unix
+> uwsgi_unixpath = "/tmp/blog.uwsgi"
+> uwsgi_unixpath_mode = XZY
+> ```
diff --cc setup.py
index deeba73,deeba73..af37dc7
--- a/setup.py
+++ b/setup.py
@@@ -20,6 -20,6 +20,7 @@@ setup(name='talermerchantdemos'
"static/*.svg",
# Blog files
"blog/templates/*.html",
++ "blog/templates/*.j2",
"blog/articles/*",
"blog/data/*",
# Donation files
diff --cc talermerchantdemos/blog/blog.py
index d6cd3fc,865605b..c8315e5
--- a/talermerchantdemos/blog/blog.py
+++ b/talermerchantdemos/blog/blog.py
@@@ -1,6 -1,6 +1,6 @@@
##
--# This file is part of GNU taler.
--# Copyright (C) 2014-2017 INRIA
++# This file is part of GNU Taler.
++# Copyright (C) 2014-2020 Taler Systems SA
#
# TALER is free software; you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
Software
@@@ -24,6 -24,6 +24,11 @@@ import tracebac
import uuid
import base64
import flask
++from flask import request
++from flask_babel import Babel
++from flask_babel import refresh
++from flask_babel import force_locale
++from flask_babel import gettext
import time
import sys
from urllib.parse import urljoin, urlencode, urlparse
@@@ -85,6 -54,6 +90,34 @@@ ARTICLE_AMOUNT = CURRENCY + ":0.5
BACKEND_URL = urljoin(BACKEND_BASE_URL, "instances/blog/")
app.config.from_object(__name__)
++babel = Babel(app)
++
++print("Using translations from:")
++print(list(babel.translation_directories))
++translations = [str(translation) for translation in babel.list_translations()]
++translations.append('en')
++print("Operating with the following translations available:")
++print(translations)
++
++
++##
++# Helper function used inside Jinja2 logic to create a links
++# to the current page but in a different language. Used to
++# implement the "Language" menu.
++#
++def self_localized(lang):
++ """
++ Return URL for the current page in another locale.
++ """
++ path = request.path
++ # path must have the form "/$LANG/$STUFF"
++ parts = path.split('/', 2)
++ if (2 >= len(parts)):
++ # Totally unexpected path format, do not localize
++ return path
++ return "/" + lang + "/" + parts[2]
++
++app.jinja_env.globals.update(self_localized=self_localized)
##
@@@ -107,7 -76,7 +140,7 @@@ def utility_processor()
# @param abort_status_code status code to return along the response.
# @param params _kw_ arguments to passed verbatim to the templating engine.
def err_abort(abort_status_code, **params):
-- t = flask.render_template("templates/error.html", **params)
++ t = flask.render_template("templates/error.html.j2", **params)
flask.abort(flask.make_response(t, abort_status_code))
@@@ -120,7 -89,7 +153,7 @@@
@app.errorhandler(Exception)
def internal_error(e):
return flask.render_template(
-- "templates/error.html", message="Internal error",
stack=traceback.format_exc()
++ "templates/error.html.j2", message=gettext("Internal error"),
stack=traceback.format_exc()
)
@@@ -130,8 -99,19 +163,29 @@@
# @return response object of the index page.
@app.route("/")
def index():
- supported = ['en', 'de' ]
+ default = 'en'
- target = flask.request.accept_languages.best_match(supported, default)
++ target = flask.request.accept_languages.best_match(translations, default)
+ return flask.redirect("/" + target + "/", code=302)
+
++@babel.localeselector
++def get_locale():
++ parts = request.path.split('/', 2)
++ if (2 >= len(parts)):
++ # Totally unexpected path format, do not localize
++ return "en"
++ lang = parts[1]
++ if lang in translations:
++ return lang
++ return "en"
++
+ ##
+ # Serve the main index page for a particular language.
+ #
+ # @return response object of the index page.
+ @app.route("/<lang>/")
+ def start(lang):
return flask.render_template(
- "templates/index.html", merchant_currency=CURRENCY,
articles=ARTICLES.values()
- "templates/index.html", lang=lang, merchant_currency=CURRENCY,
articles=ARTICLES.values()
++ "templates/index.html.j2", lang=lang, merchant_currency=CURRENCY,
articles=ARTICLES.values()
)
@@@ -144,16 -124,11 +198,16 @@@ def confirm_refund(lang, order_id)
order_status = pay_status.get("order_status")
if order_status != "paid":
err_abort(
- 400, message="can't refund unpaid article",
+ 400, message="Cannot refund unpaid article",
)
article_name = pay_status["contract_terms"]["extra"]["article_name"]
+
+ if not refundable(pay_status):
+ return flask.render_template(
- "templates/error.html", message="Item not refundable (anymore)"
++ "templates/error.html.j2", message=gettext("Article is not
anymore refundable")
+ )
return flask.render_template(
-- "templates/confirm_refund.html", article_name=article_name,
order_id=order_id
++ "templates/confirm_refund.html.j2", article_name=article_name,
order_id=order_id
)
@@@ -219,34 -188,31 +272,53 @@@ def render_article(article_name, data,
err_abort(404, message=m)
# the order_id is needed for refunds
return flask.render_template(
-- "templates/article_frame.html",
++ "templates/article_frame.html.j2",
article_file=get_article_file(article_info),
article_name=article_name,
order_id=order_id,
+ refundable=refundable
)
+##
+# Setup a fresh order with the backend.
+#
+# @param article_name which article the order is for
+# @param lang which language to use
+#
+def post_order(article_name,lang):
+ order = dict(
+ amount=ARTICLE_AMOUNT,
+ extra=dict(article_name=article_name,lang=lang),
+ fulfillment_url=flask.request.base_url,
+ summary="Essay: " + article_name.replace("_", " "),
+ # 10 minutes time for a refund
+ wire_transfer_deadline=dict(t_ms=1000 * int(time.time() + 15 * 30)),
+ )
+ order_resp = backend_post(
+ BACKEND_URL,
+ "private/orders",
+ dict(order=order, refund_delay=dict(d_ms=1000 * 120)))
+ return order_resp
+
+ ##
+ # Setup a fresh order with the backend.
+ #
+ # @param article_name which article the order is for
+ # @param lang which language to use
+ #
+ def post_order(article_name,lang):
+ order = dict(
+ amount=ARTICLE_AMOUNT,
+ extra=dict(article_name=article_name,lang=lang),
+ fulfillment_url=flask.request.base_url,
+ summary="Essay: " + article_name.replace("_", " "),
+ # 10 minutes time for a refund
+ refund_deadline=dict(t_ms=1000 * int(time.time() + 10 * 30)),
+ wire_transfer_deadline=dict(t_ms=1000 * int(time.time() + 15 * 30)),
+ )
+ order_resp = backend_post(BACKEND_URL, "private/orders",
dict(order=order))
+ return order_resp
+
##
# Trigger a article purchase. The logic follows the main steps:
@@@ -308,39 -274,30 +379,49 @@@ def article(article_name, lang=None, da
if order_status == "paid":
refunded = pay_status["refunded"]
- if refunded:
+ if refunded:
- return flask.render_template(
- "templates/article_refunded.html",
+ return flask.render_template(
- "templates/article_refunded.html",
++ "templates/article_refunded.html.j2",
article_name=article_name,
order_id=order_id,
)
- return render_article(article_name, data, order_id,
refundable(pay_status))
- else:
- response = render_article(article_name, data, order_id)
- response.set_cookie("order_id", order_id,
path=urllib.parse.quote(f"/essay/{article_name}"))
- response.set_cookie("order_id", order_id,
path=urllib.parse.quote(f"/{lang}/essay/{article_name}"))
- return response
- else:
- # Check if the customer came on this page via the
- # re-purchase detection mechanism
- ai = pay_status.get("already_paid_order_id")
- au = pay_status.get("already_paid_fulfillment_url")
- if ai is not None and au is not None:
- response = flask.redirect(au)
- response.set_cookie("order_id", ai,
path=urllib.parse.quote(f"/essay/{article_name}"))
- return response
++ response = render_article(article_name, data, order_id,
refundable(pay_status))
++ response.set_cookie(
++ "order_id", order_id,
path=urllib.parse.quote(f"/essay/{article_name}")
++ )
++ response.set_cookie(
++ "order_id", order_id,
path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
++ )
++ return response
+
+ # Check if the customer came on this page via the
+ # re-purchase detection mechanism
+ ai = pay_status.get("already_paid_order_id")
+ au = pay_status.get("already_paid_fulfillment_url")
+ if ai is not None and au is not None:
+ response = flask.redirect(au)
+ response.set_cookie(
+ "order_id", ai, path=urllib.parse.quote(f"/essay/{article_name}")
+ )
+ return response
# Redirect the browser to a page where the wallet can
# run the payment protocol.
response = flask.redirect(pay_status["order_status_url"])
- response.set_cookie("order_id", order_id,
path=urllib.parse.quote(f"/essay/{article_name}"))
- response.set_cookie("order_id", order_id,
path=urllib.parse.quote(f"/{lang}/essay/{article_name}"))
+ response.set_cookie(
+ "order_id", order_id,
path=urllib.parse.quote(f"/essay/{article_name}")
+ )
++ response.set_cookie(
++ "order_id", order_id,
path=urllib.parse.quote(f"/{lang}/essay/{article_name}")
++ )
return response
+
+@app.errorhandler(500)
+def handler(e):
+ return flask.render_template(
- "templates/error.html", message="Internal server error")
++ "templates/error.html.j2", message=gettext("Internal server error"))
+
+@app.errorhandler(404)
+def handler(e):
+ return flask.render_template(
- "templates/error.html", message="Page not found")
++ "templates/error.html.j2", message=gettext("Page not found"))
diff --cc talermerchantdemos/blog/templates/article_frame.html
index a5050d3,8c2b6d4..0000000
deleted file mode 100644,100644
--- a/talermerchantdemos/blog/templates/article_frame.html
+++ /dev/null
@@@ -1,13 -1,11 +1,0 @@@
--{% extends "templates/base.html" %}
--{% block main %}
--{% include "articles/" + article_file %}
--
- {% if refundable %}
--<hr>
--<p>
- You don't like this article? <a href="{{ url_for('confirm_refund',
order_id=order_id) }}">Get a refund</a> within
- the first hour after buying it.
- You did not like this article?
- <a href="{{ url_for('confirm_refund', lang='en', order_id=order_id) }}">Get
a refund</a>
- within the first hour after buying it.
--</p>
- {% endif %}
-
--{% endblock main %}
diff --cc talermerchantdemos/blog/templates/article_frame.html.j2
index 0000000,0000000..a878e95
new file mode 100644
--- /dev/null
+++ b/talermerchantdemos/blog/templates/article_frame.html.j2
@@@ -1,0 -1,0 +1,15 @@@
++{% extends "templates/base.html.j2" %}
++{% block main %}
++{% include "articles/" + article_file %}
++
++{% if refundable %}
++<hr>
++<p>
++ {{
++ gettext("You did not like this article?") +
++ gettext("You can <a href="{url}">request a refund</a> within the first
hour after buying it.").format(url=url_for('confirm_refund', lang='en',
order_id=order_id)
++ }}
++</p>
++{% endif %}
++
++{% endblock main %}
diff --cc talermerchantdemos/blog/templates/article_refunded.html
index 95c4a6b,95c4a6b..0000000
deleted file mode 100644,100644
--- a/talermerchantdemos/blog/templates/article_refunded.html
+++ /dev/null
@@@ -1,10 -1,10 +1,0 @@@
--{% extends "templates/base.html" %}
--{% block main %}
--
--<h2>Refunded</h2>
--
--<p>Your payment (order ID <tt>{{ order_id }}<tt>) for the article "{{
article_name }}" has been refunded.</p>
--
--<p>You won't be able to view it until you pay for it again.</p>
--
--{% endblock main %}
diff --cc talermerchantdemos/blog/templates/article_refunded.html.j2
index 0000000,0000000..34e0a0b
new file mode 100644
--- /dev/null
+++ b/talermerchantdemos/blog/templates/article_refunded.html.j2
@@@ -1,0 -1,0 +1,16 @@@
++{% extends "templates/base.html.j2" %}
++{% block main %}
++
++<h2>{{ gettext("Refunded") }}</h2>
++
++<p>
++{{
++ gettext("Your payment (order ID <tt>{order}<tt>) for the article
"{article}" has been refunded.").format(order=order_id,article=article_name)
++}}
++</p>
++
++<p>
++{{ gettext("You won't be able to view it until you pay for it again.") }}
++</p>
++
++{% endblock main %}
diff --cc talermerchantdemos/blog/templates/base.html.j2
index 0000000,824cf9d..58ce857
mode 000000,100644..100644
--- a/talermerchantdemos/blog/templates/base.html.j2
+++ b/talermerchantdemos/blog/templates/base.html.j2
@@@ -1,0 -1,120 +1,119 @@@
+ <!DOCTYPE html>
-<!--
++<!--
+ This file is part of GNU TALER.
- Copyright (C) 2014, 2015, 2016 INRIA
++ Copyright (C) 2014, 2015, 2016, 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Lesser General Public License as published by the Free
Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR
+ A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
+
+ You should have received a copy of the GNU Lesser General Public License
along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ -->
+
+ <html>
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ {% block meta %}{% endblock %}
- <title>Taler Essay Shop Demo</title>
++ <title>{{ gettext("Taler Essay Shop Demo") }}</title>
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static',
filename='pure.css') }}" />
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static',
filename='demo.css') }}" />
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static',
filename='navbar.css') }}" />
+ <style>
+ .warn {
+ background-color: #aa393977;
+ padding: 1em;
+ }
+ @keyframes hoveranim {
+ from {left:0;}
+ to {left:1vw;}
+ }
+ @keyframes hoveranimrevert {
+ from {left:1vw;}
+ to {left:0;}
+ }
+ .notice {
+ border-radius: 1em;
+ background: #0333;
+ border-left: 0.3em solid #033;
+ padding-left: 1em;
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ margin-top: 2em;
+ margin-bottom: 2em;
+ }
+ .notice {
+ position: relative;
+ left: 0;
+ animation-name: hoveranimrevert;
+ animation-duration: 1s;
+ }
+ .notice:hover {
+ left: 1vw;
+ animation-name: hoveranim;
+ animation-duration: 1s;
+ }
+ #main a:link, #main a:visited, #main a:hover, #main a:active {
+ color: black;
+ }
+ </style>
-
++
+ {% block styles %}{% endblock %}
+ {% block scripts %}{% endblock %}
+ </head>
+
+ <body>
+ <header class="demobar" style="display: flex; flex-direction: column;">
+ <h1><span class="tt adorn-brackets">Taler Demo</span></h1>
+ <h1><span class="it"><a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG')
}}">Shop</a></span></h1>
- <p>On this page you can buy articles using an imaginary currency (for
now).
- The articles are chapters from Richard Stallman's book "Free
Software, Free Society",
- which is also
- <a
href="http://shop.fsf.org/product/free-software-free-society-2/">published by
the FSF</a>
- and available gratis at <a href="http://www.gnu.org/">gnu.org</a>.
++ <p>{{
++ gettext("On this page you can buy articles using an imaginary
currency.") + "<br>" +
++ gettext("The articles are chapters from Richard Stallman's book
"Free Software, Free Society".") + "<br>" +
++ gettext('The book is <a href="{shop}">published by the FSF</a> and
available gratis at <a
href="{gnu}">gnu.org</a>.').format(shop="https://shop.fsf.org/product/free-software-free-society-2",
gnu="https://www.gnu.org")
++ }}
+ </p>
+ </header>
+ <div style="display:flex; flex-direction: column;" class="navcontainer">
+ <nav class="demolist">
- <a href="{{ env('TALER_ENV_URL_INTRO', '#') }}">Introduction</a>
- <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">Bank</a>
- <a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}"
class="active">Essay Shop</a>
- <a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#')
}}">Donations</a>
- <a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#')
}}">Tipping/Survey</a>
- <a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#') }}">Back-office</a>
++ <a href="{{ env('TALER_ENV_URL_INTRO', '#')
}}">{{gettext("Introduction")}}</a>
++ <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">{{gettext("Bank")}}</a>
++ <a href="{{ env('TALER_ENV_URL_MERCHANT_BLOG', '#') }}"
class="active">{{gettext("Essay Shop")}}</a>
++ <a href="{{ env('TALER_ENV_URL_MERCHANT_DONATIONS', '#')
}}">{{gettext("Donations")}}</a>
++ <a href="{{ env('TALER_ENV_URL_MERCHANT_SURVEY', '#')
}}">{{gettext("Tipping/Survey")}}</a>
++ <!-- a href="{{ env('TALER_ENV_URL_BACKOFFICE', '#')
}}">{{gettext("Back-office")}}</a -->
+ <span class="right">
- Language
++ {{ gettext("English [en]") }}
+ <!-- <input type="checkbox"> -->
+ <div class="nav">
+ <br>
+ <!--<hr style="width: 100%;">-->
- <a href="{{ '#LANG_EN_LANGEND' }}" class="navbtn">EN</a><br>
- <a href="{{ '#LANG_DE_LANGEND' }}" class="navbtn">DE</a><br>
- <a href="{{ '#LANG_FR_LANGEND' }}" class="navbtn">FR</a><br>
- <a href="{{ '#LANG_IT_LANGEND' }}" class="navbtn">IT</a><br>
- <a href="{{ '#LANG_JP_LANGEND' }}" class="navbtn">JP</a>
- <!-- lang strings structured as such to make replacing them with
code using an ide's replace function easy -->
++ {% if lang != 'en' %}
++ <a href="{{ self_localized('en') }}" class="navbtn">English
[en]</a><br>
++ {% endif %}
++ {% if lang != 'de' %}
++ <a href="{{ self_localized('de') }}" class="navbtn">Deutsch
[de]</a><br>
++ {% endif %}
+ </div>
+ </span>
+ </nav>
+ </div>
+ <!-- <input type="checkbox" class="r"><label>test</label> -->
+
+ <section id="main" class="content">
+ {% block main %}
+ This is the main content of the page.
+ {% endblock %}
+ <hr />
+ <div>
- <p>You can learn more about Taler on our main <a
href="https://taler.net">website</a>.</p>
++ <p>{{ gettext('You can learn more about Taler on our main <a
href="{site}">website</a>.').format(site="https://taler.net/") }}</p>
+ <div style="flex-grow:1"></div>
+ <p>Copyright © 2014—2020 Taler Systems SA</p>
+ </div>
+ </section>
+ </body>
+ </html>
-
diff --cc talermerchantdemos/blog/templates/confirm_refund.html
index 10aaa74,c4773d3..0000000
deleted file mode 100644,100644
--- a/talermerchantdemos/blog/templates/confirm_refund.html
+++ /dev/null
@@@ -1,19 -1,19 +1,0 @@@
--{% extends "templates/base.html" %}
--{% block main %}
-- <h1>Refund Article?</h1>
--
-- <p>
- Do you want to get a refund for the article <em>{{ article_name }}</em>?
After you've requested a refund,
- Do you want to get a refund for the article <em>{{ article_name }}</em>?
After you have requested a refund,
-- you won't be able to read the article anymore.
-- </p>
--
-- <p>
- You will only be able to receive the refund on the same wallet that
you've used to pay
- You will only be able to receive the refund on the same wallet that you
have used to pay
-- for this article originally.
-- </p>
--
-- <form action="{{ url_for('refund', order_id=order_id) }}" method="POST">
-- <input type="text" name="article_name" value={{ article_name}} hidden>
-- <input type="submit" value="Request refund">
-- </form>
--{% endblock main %}
diff --cc talermerchantdemos/blog/templates/confirm_refund.html.j2
index 0000000,0000000..09f3730
new file mode 100644
--- /dev/null
+++ b/talermerchantdemos/blog/templates/confirm_refund.html.j2
@@@ -1,0 -1,0 +1,22 @@@
++{% extends "templates/base.html.j2" %}
++{% block main %}
++ <h1>{{ gettext("Confirm refund request for article"))</h1>
++
++ <p>
++ {{
++ gettext("Do you want to get a refund for the article
<em>{name}</em>?").format(name=article_name) +
++ gettext("After you have requested a refund, you won't be able to read
the article anymore.")
++ }}
++ </p>
++
++ <p>
++ {{
++ gettext ("You will only be able to receive the refund on the same
wallet that you have used to pay for this article originally.")
++ }}
++ </p>
++
++ <form action="{{ url_for('refund', order_id=order_id) }}" method="POST">
++ <input type="text" name="article_name" value={{ article_name}} hidden>
++ <input type="submit" value="Request refund">
++ </form>
++{% endblock main %}
diff --cc talermerchantdemos/blog/templates/error.html
index 0d4bd02,0d4bd02..0000000
deleted file mode 100644,100644
--- a/talermerchantdemos/blog/templates/error.html
+++ /dev/null
@@@ -1,22 -1,22 +1,0 @@@
--{% extends "templates/base.html" %}
--{% block main %}
-- <h1>An Error Occurred</h1>
--
-- <p>{{ message }}</p>
--
-- {% if status_code %}
-- <p>The backend returned status code {{ status_code }}.</p>
-- {% endif %}
--
-- {% if json %}
-- <p>Backend Response:</p>
-- <pre>{{ json }}</pre>
-- {% endif %}
--
-- {% if stack %}
-- <p>Stack trace:</p>
-- <pre>
-- {{ stack }}
-- </pre>
-- {% endif %}
--{% endblock main %}
diff --cc talermerchantdemos/blog/templates/error.html.j2
index 0000000,0000000..ffc2e1f
new file mode 100644
--- /dev/null
+++ b/talermerchantdemos/blog/templates/error.html.j2
@@@ -1,0 -1,0 +1,24 @@@
++{% extends "templates/base.html.j2" %}
++{% block main %}
++ <h1>{{ gettext("Error encountered") }}</h1>
++
++ <p>{{ message }}</p>
++
++ {% if status_code %}
++ <p>
++ {{ gettext ("The backend returned status code
{code}.").format(code=status_code) }}.
++ </p>
++ {% endif %}
++
++ {% if json %}
++ <p>{{gettext("Backend response:")}}</p>
++ <pre>{{ json }}</pre>
++ {% endif %}
++
++ {% if stack %}
++ <p>{{gettext("Stack trace:")}}</p>
++ <pre>
++ {{ stack }}
++ </pre>
++ {% endif %}
++{% endblock main %}
diff --cc talermerchantdemos/blog/templates/index.html
index 0159779,e139bd2..0000000
deleted file mode 100644,100644
--- a/talermerchantdemos/blog/templates/index.html
+++ /dev/null
@@@ -1,40 -1,41 +1,0 @@@
--{% extends "templates/base.html" %}
--{% block main %}
-- <h1>Essay Shop: Free Software, Free Society</h1>
-- <div style="font-size: smaller;">
-- <p>This is the second edition of <cite>Free Software, Free Society:
Selected Essays of Richard M. Stallman.</cite><br>
-- Free Software Foundation<br>
-- 51 Franklin Street, Fifth Floor<br>
-- Boston, MA 02110-1335
-- <br>
-- Copyright © 2002, 2010 Free Software Foundation, Inc.
-- </p>
--
-- <p>Verbatim copying and distribution of this entire book are permitted
-- worldwide, without royalty, in any medium, provided this notice is
-- preserved. Permission is granted to copy and distribute translations
-- of this book from the original English into another language provided
-- the translation has been approved by the Free Software Foundation and
-- the copyright notice and this permission notice are preserved on all
-- copies.
-- </p>
-- <p>ISBN 978-0-9831592-0-9</p>
-- </div>
--
-- <h2>Chapters</h2>
-- <div>
-- Click on an individual chapter to to purchase it. You can
- get free, virtual money to buy articles on this page at the <a href="{{
env('TALER_ENV_URL_BANK', '#') }}">bank</a>.
- get free, virtual money to buy articles on this page at the
- <a href="{{ env('TALER_ENV_URL_BANK', '#') }}">bank</a>.
-- </div>
--
-- <div>
-- {% for article in articles %}
-- <div class="notice">
- <h3><a href="{{ url_for('article', article_name=article.slug)
}}">{{article.title}}</a></h3>
- <p>{{ article.teaser|safe }} <a href="{{ url_for('article',
article_name=article.slug) }}">(Pay to read more...)</a></p>
- <h3><a href="{{ url_for('article', lang=article.lang,
article_name=article.slug) }}" class="articleTitle">{{article.title}}</a></h3>
- <p>{{ article.teaser|safe }} <a href="{{ url_for('article',
lang=article.lang, article_name=article.slug) }}">(Pay to read more...)</a></p>
-- </div>
-- {% else %}
-- <em>(No articles available)</em>
-- {% endfor %}
-- </div>
--{% endblock main %}
diff --cc talermerchantdemos/blog/templates/show_refund.html
index 913b6a5,913b6a5..0000000
deleted file mode 100644,100644
--- a/talermerchantdemos/blog/templates/show_refund.html
+++ /dev/null
@@@ -1,28 -1,28 +1,0 @@@
--{% extends "templates/base.html" %}
--
--{% block main %}
--
--<h1>Refund</h1>
--
--<div class="taler-installed-hide">
-- <p>
-- Looks like your browser doesn't support GNU Taler payments. You can try
-- installing a <a href="https://taler.net/en/wallet.html">wallet browser
extension</a>.
-- </p>
--</div>
--
--<div>
--
-- <p>
-- You can use this QR code to get a refund with your mobile wallet:
-- </p>
--
-- {{ qrcode_svg | safe }}
--
-- <p>
-- Click <a href="{{ taler_refund_uri }}">this link</a> to open your system's
Taler wallet if it exists.
-- </p>
--
--</div>
--
--{% endblock main %}
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-taler-merchant-demos] 02/18: add license, (continued)
- [taler-taler-merchant-demos] 02/18: add license, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 04/18: fixed thing in readme, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 01/18: Re-Designed blog, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 06/18: removed a br to make it look better, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 11/18: fixed scss complaining, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 10/18: stuff?, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 05/18: language switcher now works, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 07/18: towards supporting language switching, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 15/18: Merge branch 'torsten-redesign' of git+ssh://git.taler.net/taler-merchant-demos into torsten-redesign, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 13/18: gitignore changes: add newline towards eof, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 18/18: merge torsten-redesign branch, implement i18n support,
gnunet <=
- [taler-taler-merchant-demos] 17/18: fix logic to match spec changes of #6616, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 08/18: fix merge issue, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 16/18: front-port Dold patch from master, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 09/18: Moved css to scss (See readme for build instructions), gnunet, 2020/10/10
- [taler-taler-merchant-demos] 12/18: gitignore changes, gnunet, 2020/10/10
- [taler-taler-merchant-demos] 14/18: add logic for repurchase detection, adjusting order_id if provided, gnunet, 2020/10/10