diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-10-10 22:55:31 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-10-10 22:55:31 +0200 |
commit | 2e665813a44988bfd906c0fab773f82652047841 (patch) | |
tree | abb09bb226ec904d23592edbc8372634f5b7a31b /talermerchantdemos/blog/blog.py | |
parent | ad45c1d0d6152d6eddde39881461a3472f799ab7 (diff) | |
parent | 58af97724593e6d9cf423035f46da8f88c29526c (diff) | |
download | taler-merchant-demos-2e665813a44988bfd906c0fab773f82652047841.tar.gz taler-merchant-demos-2e665813a44988bfd906c0fab773f82652047841.tar.bz2 taler-merchant-demos-2e665813a44988bfd906c0fab773f82652047841.zip |
merge torsten-redesign branch, implement i18n support
Diffstat (limited to 'talermerchantdemos/blog/blog.py')
-rw-r--r-- | talermerchantdemos/blog/blog.py | 139 |
1 files changed, 110 insertions, 29 deletions
diff --git a/talermerchantdemos/blog/blog.py b/talermerchantdemos/blog/blog.py index d6cd3fc..c8315e5 100644 --- a/talermerchantdemos/blog/blog.py +++ b/talermerchantdemos/blog/blog.py @@ -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,11 @@ import traceback 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 +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 +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,23 +153,44 @@ def err_abort(abort_status_code, **params): @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() ) ## -# Serve the main index page. +# Serve the main index page, redirecting to /<lang>/ # # @return response object of the index page. @app.route("/") def index(): + default = 'en' + 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.j2", lang=lang, merchant_currency=CURRENCY, articles=ARTICLES.values() ) -@app.route("/confirm-refund/<order_id>", methods=["GET"]) -def confirm_refund(order_id): +@app.route("/<lang>/confirm-refund/<order_id>", methods=["GET"]) +def confirm_refund(lang, order_id): session_id = flask.session.get("session_id", "") pay_status = backend_get( BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id) @@ -144,16 +198,16 @@ def confirm_refund(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 ) @@ -163,16 +217,15 @@ def confirm_refund(order_id): # # @param order_id the order ID of the transaction to refund. # @return the following errors (named by HTTP response code): -# - 400: no article was asked to be refunded! -# - 401: the refund was asked on a non-payed article. -# - 500: the backend was unable to give response. -# Or, in the successful case, a redirection to the -# "refund URL" is returned; then the wallet will run -# the refund protocol in a transparent way. +# - 400: order unknown +# - 402: the refund was asked on an unpaid article. +# - 302: in the successful case, a redirection to the +# "refund URL" is returned; then the wallet will run +# the refund protocol in a transparent way. @app.route("/refund/<order_id>", methods=["POST"]) -def refund(order_id): +def refund(lang, order_id): if not order_id: - return flask.jsonify(dict(error="Aborting refund: article not payed")), 401 + return flask.jsonify(dict(error="Aborting refund: order unknown")), 400 session_id = flask.session.get("session_id", "") pay_status = backend_get( BACKEND_URL, f"private/orders/{order_id}", params=dict(session_id=session_id) @@ -219,7 +272,7 @@ def render_article(article_name, data, order_id, refundable): 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, @@ -247,6 +300,25 @@ def post_order(article_name,lang): 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: @@ -267,9 +339,10 @@ def post_order(article_name,lang): # In the successful case, either the article is returned, or # the browser gets redirected to a page where the wallet can # send the payment. -@app.route("/essay/<article_name>") +@app.route("/<lang>/essay/<article_name>") +@app.route("/<lang>/essay/<article_name>/data/<data>") @app.route("/essay/<article_name>/data/<data>") -def article(article_name, data=None): +def article(article_name, lang=None, data=None): # We use an explicit session ID so that each payment (or payment replay) is # bound to a browser. This forces re-play and prevents sharing the article # by just sharing the URL. @@ -279,8 +352,6 @@ def article(article_name, data=None): if not session_id: session_id = flask.session["session_id"] = str(uuid.uuid4()) order_id = None - # Temporary workaround... - lang="en" ## # First-timer; generate order first. if not order_id: @@ -309,12 +380,19 @@ def article(article_name, data=None): if order_status == "paid": refunded = pay_status["refunded"] if refunded: - return flask.render_template( - "templates/article_refunded.html", + return flask.render_template( + "templates/article_refunded.html.j2", article_name=article_name, order_id=order_id, ) - return render_article(article_name, data, order_id, refundable(pay_status)) + 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 @@ -333,14 +411,17 @@ def article(article_name, data=None): 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")) |