Skip to content
Snippets Groups Projects
Commit 2536d4d3 authored by Markus Willman's avatar Markus Willman
Browse files

localization support

parent 02205718
No related branches found
No related tags found
No related merge requests found
Showing with 192 additions and 108 deletions
...@@ -13,3 +13,7 @@ data/gtfs.zip ...@@ -13,3 +13,7 @@ data/gtfs.zip
*.pyc *.pyc
*~ *~
\#*\# \#*\#
# gettext
*.pot
*.mo
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* text=auto * text=auto
# These should have unix like line endings # These should have unix like line endings
*.po text eol=lf
*.sh text eol=lf *.sh text eol=lf
*.gradle text eol=lf *.gradle text eol=lf
gradlew text eol=lf gradlew text eol=lf
......
...@@ -10,3 +10,7 @@ data/gtfs.zip ...@@ -10,3 +10,7 @@ data/gtfs.zip
*.pyc *.pyc
*~ *~
\#*\# \#*\#
# gettext
*.pot
*.mo
...@@ -39,7 +39,8 @@ RUN pip --no-cache-dir install -r /opt/services/smartbusstop/system-requirements ...@@ -39,7 +39,8 @@ RUN pip --no-cache-dir install -r /opt/services/smartbusstop/system-requirements
&& rm -f /opt/services/smartbusstop/requirements.txt \ && rm -f /opt/services/smartbusstop/requirements.txt \
&& mkdir -p /opt/services/smartbusstop/data/gtfs \ && mkdir -p /opt/services/smartbusstop/data/gtfs \
&& unzip /opt/services/smartbusstop/data/gtfs.zip -d /opt/services/smartbusstop/data/gtfs \ && unzip /opt/services/smartbusstop/data/gtfs.zip -d /opt/services/smartbusstop/data/gtfs \
&& rm /opt/services/smartbusstop/data/gtfs.zip && rm /opt/services/smartbusstop/data/gtfs.zip \
&& /opt/services/smartbusstop/scripts/babel_compile.sh
EXPOSE 80 EXPOSE 80
STOPSIGNAL SIGTERM STOPSIGNAL SIGTERM
......
[python: **/server/**.py]
encoding=utf-8
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_,server.templating.CustomFunctionsExtension
encoding=utf-8
slient=False
#!/bin/sh
pybabel compile -d ./translations
#!/bin/sh
if [ "$#" -ne 1 ]; then
echo "Usage: $0 language_code" >&2
exit 1
fi
PYTHONPATH='.' pybabel extract -F babel.cfg -k lazy_gettext -o translations/messages.pot .
pybabel init -i translations/messages.pot -d ./translations -l $1
rm translations/messages.pot
#!/bin/sh
PYTHONPATH='.' pybabel extract -F babel.cfg -k lazy_gettext -o translations/messages.pot .
pybabel update -i translations/messages.pot -d ./translations
rm translations/messages.pot
# coding=utf-8 # coding=utf-8
import optparse, os, pytz, six, math, sys, re import optparse, os, math, sys, re
import dateutil.parser
from flask import Flask, render_template, abort, url_for, redirect, send_from_directory, jsonify, request from flask import Flask, render_template, abort, url_for, redirect, send_from_directory, jsonify, request, g
from flask_babel import Babel
from werkzeug.contrib.profiler import ProfilerMiddleware from werkzeug.contrib.profiler import ProfilerMiddleware
from datetime import timedelta from datetime import timedelta
from dateutil.tz import tzlocal
from .staticVariables import TEMPLATE_DIR, STATIC_DIR, DATA_DIR, DEFAULT_STOP, DEFAULT_HOST, DEFAULT_PORT, APP_VERSION from .staticVariables import TEMPLATE_DIR, STATIC_DIR, DATA_DIR, DEFAULT_STOP, DEFAULT_HOST, DEFAULT_PORT, APP_VERSION, AVAILABLE_LANGUAGES, TRANSLATION_DIR
from .dataHandler import loadJson, formatSeconds, hasTemplate, hasData, CustomJsonEncoder from .dataHandler import loadJson, hasTemplate, hasData, CustomJsonEncoder
from .gtfs import GtfsHandler from .gtfs import GtfsHandler
from .locator import FoliLocator from .locator import FoliLocator
app = Flask(__name__, template_folder = TEMPLATE_DIR, static_folder = STATIC_DIR) app = Flask(__name__, template_folder = TEMPLATE_DIR, static_folder = STATIC_DIR)
app.json_encoder = CustomJsonEncoder app.json_encoder = CustomJsonEncoder
# pybabel is picky when extracting strings from jinja templates, to put it nicely,so this "extension" is used so pybabel can also use it
app.jinja_env.add_extension('server.templating.CustomFunctionsExtension')
app.config['BABEL_TRANSLATION_DIRECTORIES'] = TRANSLATION_DIR # undocumented Flask-Babel config option
# For localization Flask-BabelEx might be better for choice performance (they are api compatible, sans setting custom translation path)
# - Flask-Babel loads the translation catalog every request
babel = Babel(app)
gtfs = GtfsHandler('gtfs', dir = DATA_DIR, defer = (sys.path[0] == '' and __package__ != None)) gtfs = GtfsHandler('gtfs', dir = DATA_DIR, defer = (sys.path[0] == '' and __package__ != None))
locator = FoliLocator(endpointURI = 'http://data.foli.fi/siri/vm', userAgent = APP_VERSION, pollFreq = 3) locator = FoliLocator(endpointURI = 'http://data.foli.fi/siri/vm', userAgent = APP_VERSION, pollFreq = 3)
...@@ -81,38 +88,57 @@ def afterRequestHandler(response): ...@@ -81,38 +88,57 @@ def afterRequestHandler(response):
response.headers['Content-Security-Policy'] = "default-src='self'" response.headers['Content-Security-Policy'] = "default-src='self'"
return response return response
@app.url_value_preprocessor
def pullUrlGlobals(endpoint, values):
if endpoint == 'static' or values == None:
return
# stopId is esentially a global routing argument, its absence is an exception
# these are snake_case because they also act as template variables by default
g.stop_id = values.pop('stopId', None)
# keep ref and lang query string arguments in outbuound urls, sanitized, for now retain language as query argument as opposed to path segment also
g.referer = values.pop('ref', request.args.get('ref', default = None, type = str))
if not g.referer or not re.match("^[\w\d_-]+$", g.referer):
g.referer = None
else:
# ref can include unique indentifier after the display keyword, for logging
g.is_kiosk = g.referer.startswith('kiosk')
g.lang_code = values.pop('lang', request.args.get('lang', default = None, type = str))
if not g.lang_code or not re.match("^[\w\d_-]+$", g.lang_code):
g.lang_code = getLocale()
@app.url_defaults @app.url_defaults
def urlDefaults(endpoint, values): def urlDefaults(endpoint, values):
if endpoint == 'static': if endpoint == 'static' or values == None:
return return
# keep ref and lang query string arguments in outbuound urls, sanitized, for now retain language as query argument as opposed to path segment also # inject stop id if the endpoint is expecting it and it is not present
# FIXME: stopId could also be handled here, more info: http://flask.pocoo.org/docs/0.12/patterns/urlprocessors/ if g.stop_id and not 'stopId' in values and app.url_map.is_endpoint_expecting(endpoint, 'stopId'):
referer = request.args.get('ref', None) values['stopId'] = g.stop_id
if referer and re.match("^[\w\d_-]+$", referer):
values['ref'] = referer
# todo: actually make use of this with Flask-Babel
lang = request.args.get('lang', None)
if lang and re.match("^[\w\d_-]+$", lang):
values['lang'] = lang
@app.context_processor
def utilityProcessor():
# using snake_case for consistency with built in template functions
return dict(
load_json = loadJson,
format_seconds = formatSeconds
)
@app.template_filter('date') if g.referer and not 'ref' in values:
def dateFilter(value, format = '%b %d %H:%M', tz = None): values['ref'] = g.referer
if isinstance(value, six.string_types):
value = dateutil.parser.parse(value)
if tz == None:
return value.strftime(format)
return value.astimezone(pytz.timezone(tz)).strftime(format) if g.lang_code and not 'lang' in values and g.lang_code != request.accept_languages.best_match(AVAILABLE_LANGUAGES):
values['lang'] = g.lang_code
@babel.localeselector
def getLocale():
lang = getattr(g, 'lang_code', None)
if lang is not None and lang in AVAILABLE_LANGUAGES:
return lang
return request.accept_languages.best_match(AVAILABLE_LANGUAGES)
@babel.timezoneselector
def getTimezone():
stopId = getattr(g, 'stop_id', None)
if stopId is not None:
return gtfs.getStopTimeZone(stopId)
return tzlocal().tzname()
# if we don't have a stop code in URL redirect to DEFAULT_STOP for now # if we don't have a stop code in URL redirect to DEFAULT_STOP for now
@app.route('/') @app.route('/')
...@@ -128,7 +154,7 @@ def sendRootFile(): ...@@ -128,7 +154,7 @@ def sendRootFile():
# catch all route for any views, automatically loads data into template context # catch all route for any views, automatically loads data into template context
@app.route('/<stopId>/') @app.route('/<stopId>/')
@app.route('/<stopId>/<path:path>') @app.route('/<stopId>/<path:path>')
def view(stopId, path = 'index.html'): def view(path = 'index.html'):
if '.html' not in path: if '.html' not in path:
path = path + '.html' path = path + '.html'
...@@ -141,35 +167,27 @@ def view(stopId, path = 'index.html'): ...@@ -141,35 +167,27 @@ def view(stopId, path = 'index.html'):
dataFile = path.replace('.html', '.json') dataFile = path.replace('.html', '.json')
data = None data = None
if hasData(stopId + os.sep + dataFile): if hasData(g.stop_id + os.sep + dataFile):
data = loadJson(stopId + os.sep + dataFile) data = loadJson(g.stop_id + os.sep + dataFile)
elif hasData(dataFile): elif hasData(dataFile):
data = loadJson(dataFile) data = loadJson(dataFile)
# snake_case because these are template variables if data == None:
if data != None: data = {}
data["stop_id"] = stopId
else:
data = {"stop_id": stopId}
data["stop_info"] = gtfs.getStop(stopId) # snake_case because these are template variables
data["stop_info"] = gtfs.getStop(g.stop_id)
if data["stop_info"]: if data["stop_info"]:
data.update({ data.update(getViewData(request, g.stop_id, os.path.splitext(path)[0]))
# ref can include unique indentifier after the display keyword, for logging
'is_kiosk': request.args.get('ref', default = '', type = str).startswith('kiosk'),
'lang': request.args.get('lang', 'en')
})
data.update(getViewData(request, stopId, os.path.splitext(path)[0]))
return render_template(path, **data) return render_template(path, **data)
abort(404) abort(404)
# URI: /api/v1/info/<stopId>/ # URI: /api/v1/info/<stopId>/
@app.route('/api/v<int:version>/info/<stopId>/') @app.route('/api/v<int:version>/info/<stopId>/')
def apiStopInfo(version, stopId): def apiStopInfo(version):
if int(version) == 1: if int(version) == 1:
data = gtfs.getStop(stopId) data = gtfs.getStop(g.stop_id)
if data: if data:
return jsonify(data) return jsonify(data)
...@@ -179,10 +197,10 @@ def apiStopInfo(version, stopId): ...@@ -179,10 +197,10 @@ def apiStopInfo(version, stopId):
# URI: /api/v1/arrivals/<stopId>/[?limit=<n>] # URI: /api/v1/arrivals/<stopId>/[?limit=<n>]
@app.route('/api/v<int:version>/arrivals/<stopId>/') @app.route('/api/v<int:version>/arrivals/<stopId>/')
def apiNextTrips(version, stopId): def apiNextTrips(version):
if int(version) == 1: if int(version) == 1:
# see comments in gtfs.py about accuracy and potential use with SIRI SM endpoint # see comments in gtfs.py about accuracy and potential use with SIRI SM endpoint
data = gtfs.getNextTrips(stopId, limit = request.args.get('limit', default = 5, type = int)) data = gtfs.getNextTrips(g.stop_id, limit = request.args.get('limit', default = 5, type = int))
if data: if data:
return jsonify(data) return jsonify(data)
...@@ -194,10 +212,10 @@ def apiNextTrips(version, stopId): ...@@ -194,10 +212,10 @@ def apiNextTrips(version, stopId):
# URI: /api/v1/routes/<stopId>[/<type>] # URI: /api/v1/routes/<stopId>[/<type>]
@app.route('/api/v<int:version>/routes/<stopId>/') @app.route('/api/v<int:version>/routes/<stopId>/')
@app.route('/api/v<int:version>/routes/<stopId>/<type>/') @app.route('/api/v<int:version>/routes/<stopId>/<type>/')
def apiDayRoutes(version, stopId, type = 'full'): def apiDayRoutes(version, type = 'full'):
if int(version) == 1: if int(version) == 1:
asList = True if type == 'list' else False asList = True if type == 'list' else False
data = gtfs.getDaysRoutes(stopId = stopId, asList = asList) data = gtfs.getDaysRoutes(stopId = g.stop_id, asList = asList)
if data: if data:
return jsonify(data) return jsonify(data)
...@@ -208,10 +226,10 @@ def apiDayRoutes(version, stopId, type = 'full'): ...@@ -208,10 +226,10 @@ def apiDayRoutes(version, stopId, type = 'full'):
# URI: /api/v1/trips/<stopId>/[?offset=<n>&limit=<m>] # URI: /api/v1/trips/<stopId>/[?offset=<n>&limit=<m>]
@app.route('/api/v<int:version>/trips/<stopId>/') @app.route('/api/v<int:version>/trips/<stopId>/')
def apiDayTrips(version, stopId): def apiDayTrips(version):
if int(version) == 1: if int(version) == 1:
data = gtfs.getDaysTrips( data = gtfs.getDaysTrips(
stopId = stopId, stopId = g.stop_id,
offset = request.args.get('offset', default = 0, type = int), offset = request.args.get('offset', default = 0, type = int),
limit = request.args.get('limit', default = -1, type = int) limit = request.args.get('limit', default = -1, type = int)
) )
...@@ -226,9 +244,9 @@ def apiDayTrips(version, stopId): ...@@ -226,9 +244,9 @@ def apiDayTrips(version, stopId):
# URI: /api/v1/trip/<tripSlug>/ # URI: /api/v1/trip/<tripSlug>/
# tripSlug: stopId.index (get from trips or arrivals endpoint) # tripSlug: stopId.index (get from trips or arrivals endpoint)
@app.route('/api/v<int:version>/trip/<stopId>.<int:index>/') @app.route('/api/v<int:version>/trip/<stopId>.<int:index>/')
def apiStopTrip(version, stopId, index): def apiStopTrip(version, index):
if int(version) == 1: if int(version) == 1:
data = gtfs.getStopTrip(stopId, index) data = gtfs.getStopTrip(g.stop_id, index)
if data: if data:
return jsonify(data) return jsonify(data)
...@@ -240,15 +258,15 @@ def apiStopTrip(version, stopId, index): ...@@ -240,15 +258,15 @@ def apiStopTrip(version, stopId, index):
@app.route('/api/v<int:version>/locator/') @app.route('/api/v<int:version>/locator/')
@app.route('/api/v<int:version>/locator/<stopId>/') @app.route('/api/v<int:version>/locator/<stopId>/')
@app.route('/api/v<int:version>/locator/<stopId>/<type>/') @app.route('/api/v<int:version>/locator/<stopId>/<type>/')
def apiLocatorService(version, stopId = 'raw', type = 'routed'): def apiLocatorService(version, type = 'routed'):
if int(version) == 1: if int(version) == 1:
lastModified = data = None lastModified = data = None
if stopId == 'raw': if g.stop_id == 'raw' or not g.stop_id:
lastModified, data = locator.getLastResponse() lastModified, data = locator.getLastResponse()
elif type == 'routed': elif type == 'routed':
lastModified, data = gtfs.locateRoutedVehicles(stopId) lastModified, data = gtfs.locateRoutedVehicles(g.stop_id)
elif type == 'nearby': elif type == 'nearby':
lastModified, data = gtfs.locateNearbyVehicles(stopId, request.args.get('distance', default = 5000, type = int)) lastModified, data = gtfs.locateNearbyVehicles(g.stop_id, request.args.get('distance', default = 5000, type = int))
if data: if data:
response = jsonify(data) response = jsonify(data)
...@@ -263,7 +281,7 @@ def apiLocatorService(version, stopId = 'raw', type = 'routed'): ...@@ -263,7 +281,7 @@ def apiLocatorService(version, stopId = 'raw', type = 'routed'):
# URI: /api/v1/data/[<stopId>/]<path>.json # URI: /api/v1/data/[<stopId>/]<path>.json
@app.route('/api/v<int:version>/data/<path:path>.json') @app.route('/api/v<int:version>/data/<path:path>.json')
@app.route('/api/v<int:version>/data/<stopId>/<path:path>.json') @app.route('/api/v<int:version>/data/<stopId>/<path:path>.json')
def apiDataFile(version, path, stopId = None): def apiDataFile(version, path):
dataFile = path + '.json' dataFile = path + '.json'
# todo: use nginx sendfile when available # todo: use nginx sendfile when available
...@@ -274,8 +292,8 @@ def apiDataFile(version, path, stopId = None): ...@@ -274,8 +292,8 @@ def apiDataFile(version, path, stopId = None):
} }
# send_from_directory allows clientside caching by default, which is fine since these are static files # send_from_directory allows clientside caching by default, which is fine since these are static files
if stopId != None and hasData(stopId + os.sep + dataFile): if g.stop_id != None and hasData(g.stop_id + os.sep + dataFile):
return send_from_directory(DATA_DIR, stopId + os.sep + dataFile, **options) return send_from_directory(DATA_DIR, g.stop_id + os.sep + dataFile, **options)
if hasData(dataFile): if hasData(dataFile):
return send_from_directory(DATA_DIR, dataFile, **options) return send_from_directory(DATA_DIR, dataFile, **options)
......
# coding=utf-8 # coding=utf-8
import os, json import os, json, sys
from flask.json import JSONEncoder from flask.json import JSONEncoder
from datetime import date from datetime import date
from speaklater import is_lazy_string
from .staticVariables import DATA_DIR, TEMPLATE_DIR from .staticVariables import DATA_DIR, TEMPLATE_DIR
# http://flask.pocoo.org/snippets/119/ # http://flask.pocoo.org/snippets/119/
# more useful date format # more useful date format, babel support
class CustomJsonEncoder(JSONEncoder): class CustomJsonEncoder(JSONEncoder):
def default(self, object): def default(self, object):
try: try:
...@@ -20,7 +21,14 @@ class CustomJsonEncoder(JSONEncoder): ...@@ -20,7 +21,14 @@ class CustomJsonEncoder(JSONEncoder):
else: else:
return list(iterable) return list(iterable)
return JSONEncoder.default(self, object) # lazy strings support
if is_lazy_string(object):
if sys.version_info[0] < 3:
return unicode(object)
else:
return str(object)
return super(CustomJsonEncoder, self).default(object)
# helper for formatting seconds # helper for formatting seconds
def formatSeconds(seconds, signed = True): def formatSeconds(seconds, signed = True):
......
...@@ -11,8 +11,12 @@ DEFAULT_PORT = 5000 ...@@ -11,8 +11,12 @@ DEFAULT_PORT = 5000
TEMPLATE_DIR = os.path.normpath(os.path.join(os.getcwd(), 'templates')) TEMPLATE_DIR = os.path.normpath(os.path.join(os.getcwd(), 'templates'))
STATIC_DIR = os.path.normpath(os.path.join(os.getcwd(), 'static')) STATIC_DIR = os.path.normpath(os.path.join(os.getcwd(), 'static'))
DATA_DIR = os.path.normpath(os.path.join(os.getcwd(), 'data')) DATA_DIR = os.path.normpath(os.path.join(os.getcwd(), 'data'))
TRANSLATION_DIR = os.path.normpath(os.path.join(os.getcwd(), 'translations'))
# for now, have one static bus stop in the system and use this id to fetch information about it # for now, have one static bus stop in the system and use this id to fetch information about it
# bus stop ids are strings (can be like T4 etc) # bus stop ids are strings (can be like T4 etc)
# - can be specified in the URI # - can be specified in the URI
DEFAULT_STOP = 'T35' DEFAULT_STOP = 'T35'
# list of valid languages
AVAILABLE_LANGUAGES = ['en', 'fi']
# coding=utf-8
from jinja2.ext import Extension
from .dataHandler import formatSeconds, loadJson
# this exist purely for the benefit of pybabel
# normally you would add functions through flask
class CustomFunctionsExtension(Extension):
def __init__(self, environment):
Extension.__init__(self, environment)
environment.globals['load_json'] = loadJson
environment.globals['format_seconds'] = formatSeconds
environment.filters['formatseconds'] = formatSeconds
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{% set title = 'Hello ' + scope %} {% set title = 'Hello ' + scope %}
{% set sticky_banners = true %} {% set sticky_banners = true %}
{% set localized = false %}
{# {#
Two quick examples, note that load_json is a non standard function that should be Two quick examples, note that load_json is a non standard function that should be
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{% set title = 'Föli Info' %} {% set title = 'Föli Info' %}
{% set sticky_banners = true %} {% set sticky_banners = true %}
{% set localized = false %}
{% block content %} {% block content %}
<!-- the use of the grid here has some scaling issues on mobile device --> <!-- the use of the grid here has some scaling issues on mobile device -->
......
{% extends 'internal/base.html' %} {% extends 'internal/base.html' %}
{% set title = 'Home' %} {% set title = _('Home') %}
{% set sticky_banners = false %} {% set sticky_banners = false %}
{% set localized = true %}
{# the minus sign in super() call is for whitespace control #} {# the minus sign in super() call is for whitespace control #}
{% block scripts %} {% block scripts %}
...@@ -18,17 +19,17 @@ ...@@ -18,17 +19,17 @@
<div class="row mb-4"> <div class="row mb-4">
<div class="col-md-5 offset-md-1 mb-4 mb-md-0"> <div class="col-md-5 offset-md-1 mb-4 mb-md-0">
<div class="card menu-card bg-foli text-center box-shadow"> <div class="card menu-card bg-foli text-center box-shadow">
<img class="card-img-top" src="{{ util.static_url('img/map_placeholder.png') }}" alt="Card image"> <img class="card-img-top" src="{{ util.static_url('img/map_placeholder.png') }}" alt="{{ _('Card Image') }}">
<div class="card-body"> <div class="card-body">
<a role="button" href="{{ util.view_url('map') }}" class="btn btn-lg btn-primary">Live Map</a> <a role="button" href="{{ util.view_url('map') }}" class="btn btn-lg btn-primary">{{ _('Live Map') }}</a>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
<div class="card menu-card bg-foli text-center box-shadow"> <div class="card menu-card bg-foli text-center box-shadow">
<img class="card-img-top" src="{{ util.static_url('img/timetables_placeholder.png') }}" alt="Card image"> <img class="card-img-top" src="{{ util.static_url('img/timetables_placeholder.png') }}" alt="{{ _('Card Image') }}">
<div class="card-body"> <div class="card-body">
<a role="button" href="{{ util.view_url('timetables') }}" class="btn btn-lg btn-primary">Timetables</a> <a role="button" href="{{ util.view_url('timetables') }}" class="btn btn-lg btn-primary">{{ _('Timetables') }}</a>
</div> </div>
</div> </div>
</div> </div>
...@@ -37,21 +38,21 @@ ...@@ -37,21 +38,21 @@
<div class="col-md-5 offset-md-1 mb-4 mb-md-0"> <div class="col-md-5 offset-md-1 mb-4 mb-md-0">
<!-- this particular button variation has some responsiveness issues --> <!-- this particular button variation has some responsiveness issues -->
<div class="card menu-card text-center bg-white border-foli text-foli box-shadow"> <div class="card menu-card text-center bg-white border-foli text-foli box-shadow">
<img class="card-img" src="{{ util.static_url('img/news_placeholder.png') }}" alt="Card image"> <img class="card-img" src="{{ util.static_url('img/news_placeholder.png') }}" alt="{{ _('Card Image') }}">
<div class="card-img-overlay"> <div class="card-img-overlay">
<h4 class="card-title">Info</h4> <h4 class="card-title">{{ _('Info') }}</h4>
<p class="card-text">Wheather, Social Media, Events</p> <p class="card-text">{{ _('Wheather, Social Media, Events') }}</p>
<a role="button" href="{{ util.view_url('info') }}" class="btn btn-lg btn-primary">View</a> <a role="button" href="{{ util.view_url('info') }}" class="btn btn-lg btn-primary">{{ _('View') }}</a>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
<div class="card menu-card text-center bg-white border-foli text-foli box-shadow"> <div class="card menu-card text-center bg-white border-foli text-foli box-shadow">
<img class="card-img" src="{{ util.static_url('img/foli_placeholder.png') }}" alt="Card image"> <img class="card-img" src="{{ util.static_url('img/foli_placeholder.png') }}" alt="{{ _('Card Image') }}">
<div class="card-img-overlay"> <div class="card-img-overlay">
<h4 class="card-title js-hide">Föli</h4> <h4 class="card-title js-hide">{{ _('Föli') }}</h4>
<p class="card-text js-hide">Information about Föli services</p> <p class="card-text js-hide">{{ _('Information about Föli services') }}</p>
<a role="button" href="{{ util.view_url('foli') }}" class="btn btn-lg btn-primary js-hide">View</a> <a role="button" href="{{ util.view_url('foli') }}" class="btn btn-lg btn-primary js-hide">{{ _('View') }}</a>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{% set title = 'Info' %} {% set title = 'Info' %}
{% set sticky_banners = false %} {% set sticky_banners = false %}
{% set localized = true %}
{% block content %} {% block content %}
<!-- TODO: created to prevent accidental 404 during presentations --> <!-- TODO: created to prevent accidental 404 during presentations -->
......
{%- import 'internal/macros.html' as util with context -%} {%- import 'internal/macros.html' as util with context -%}
<!doctype html> <!doctype html>
<html lang="en"> <html lang="{{ g.lang_code }}">
<head> <head>
{% block head %} {% block head %}
<title>{% block title %}Smart Bus Stop{% if title %} - {{ title }}{% endif %}{% endblock title %}</title> <title>{% block title %}Smart Bus Stop{% if title %} - {{ title }}{% endif %}{% endblock title %}</title>
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<!-- contains a föli logo, a header text and clock --> <!-- contains a föli logo, a header text and clock -->
<div class="d-flex flex-row justify-content-between bg-foli contentHeader"> <div class="d-flex flex-row justify-content-between bg-foli contentHeader">
<div id="foliLogo"><a href="{{ util.home_url() }}" class="sr-only">Home (Föli logo)</a></div> <div id="foliLogo"><a href="{{ util.home_url() }}" class="sr-only">{{ _('Logo') }}</a></div>
<h1 id="headerText">{{ title }}</h1> <h1 id="headerText">{{ title }}</h1>
<h1 id="headerClock" class="d-none d-sm-block">00:00</h1> <h1 id="headerClock" class="d-none d-sm-block">00:00</h1>
</div> </div>
...@@ -51,19 +51,19 @@ ...@@ -51,19 +51,19 @@
<div class="modal-dialog modal-lg vertical-align-center" role="document"> <div class="modal-dialog modal-lg vertical-align-center" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title text-foli" id="externalLinkModalLabel">Opening external content</h5> <h5 class="modal-title text-foli" id="externalLinkModalLabel">{{ _('Opening external content') }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>This content is available on an external site in the following address:</p> <p>{{ _('This content is available on an external site in the following address:') }}</p>
<code class="d-flex justify-content-center externalLinkURI"></code> <code class="d-flex justify-content-center externalLinkURI"></code>
<p>Please scan the below code to access it on your personal device:<p> <p>{{ _('Please scan the below code to access it on your personal device:') }}<p>
<div class="d-flex justify-content-center externalLinkQRCode"></div> <div class="d-flex justify-content-center externalLinkQRCode"></div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary" data-dismiss="modal">{{ _('Close') }}</button>
</div> </div>
</div> </div>
</div> </div>
...@@ -76,19 +76,19 @@ ...@@ -76,19 +76,19 @@
<nav class="navbar navbar-expand-lg navbar-dark bg-dark justify-content-between flex-grow pr-5 pr-lg-3"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark justify-content-between flex-grow pr-5 pr-lg-3">
<ul class="navbar-nav mr-auto pr-lg-5"> <ul class="navbar-nav mr-auto pr-lg-5">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="#">Terms</a> <a class="nav-link" href="#">{{ _('Terms') }}</a>
</li> </li>
</ul> </ul>
<ul class="navbar-nav mr-auto"> <ul class="navbar-nav mr-auto">
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" id="homeLink" href="{{ util.home_url() }}"><i class="fa fa-home" aria-hidden="true"></i> Home <span class="sr-only">(current)</span></a> <a class="nav-link" id="homeLink" href="{{ util.home_url() }}"><i class="fa fa-home" aria-hidden="true"></i> {{ _('Home') }}</a>
</li> </li>
</ul> </ul>
<ul class="navbar-nav d-none d-lg-flex mr-0"> <ul class="navbar-nav d-none d-lg-flex mr-0">
{# TODO: request.url or request.base_url could be used here, but then we have to deal with duplicate get arguments #} {# TODO: request.url or request.base_url could be used here, but then we have to deal with duplicate get arguments #}
<li><a href="{{ util.home_url(lang = 'en') }}"><div class="lang lang-en rounded border border-foli m-1"><span class="sr-only">English</span></div></a></li> <li><a href="{{ util.home_url(lang = 'en') }}"><div class="lang lang-en rounded border border-foli m-1"><span class="sr-only">{{ _('English') }}</span></div></a></li>
<li><a href="{{ util.home_url(lang = 'fi') }}"><div class="lang lang-fi rounded border border-foli m-1"><span class="sr-only">Finnish</span></div></a></li> <li><a href="{{ util.home_url(lang = 'fi') }}"><div class="lang lang-fi rounded border border-foli m-1"><span class="sr-only">{{ _('Finnish') }}</span></div></a></li>
<!-- <li><a href="{{ util.home_url(lang = 'se') }}"><div class="lang lang-se rounded border border-foli m-1"><span class="sr-only">Swedish</span></div></a></li> --> <!-- <li><a href="{{ util.home_url(lang = 'se') }}"><div class="lang lang-se rounded border border-foli m-1"><span class="sr-only">{{ _('Swedish') }}</span></div></a></li> -->
</ul> </ul>
</nav> </nav>
{% endblock footer %} {% endblock footer %}
...@@ -120,7 +120,7 @@ ...@@ -120,7 +120,7 @@
// TODO: disable when not running on the display // TODO: disable when not running on the display
$.fn.disableExternalLinks = function(force = false) { $.fn.disableExternalLinks = function(force = false) {
// for some extra security the first condition is resolved when templates are compiled // for some extra security the first condition is resolved when templates are compiled
if ({{ (not is_kiosk)|tojson|safe }} && !force) if ({{ (not g.is_kiosk)|tojson|safe }} && !force)
return this; return this;
this.find('a[href^="http"]:not([href*="' + location.hostname + '"])').on('click', function (e) { this.find('a[href^="http"]:not([href*="' + location.hostname + '"])').on('click', function (e) {
...@@ -157,7 +157,7 @@ ...@@ -157,7 +157,7 @@
}, 1000); }, 1000);
// disable the right click context menu (the condition is resolved at template compile time) // disable the right click context menu (the condition is resolved at template compile time)
$(document).contextmenu(function() { return {{ (not is_kiosk)|tojson|safe }}; }); $(document).contextmenu(function() { return {{ (not g.is_kiosk)|tojson|safe }}; });
}); });
</script> </script>
{% endblock scripts %} {% endblock scripts %}
......
{# this file contains helper macros, not intended for rendering but import, imported as util in main templates. Note the whitespace control #} {# this file contains helper macros, not intended for rendering but import, imported as util in main templates. Note the whitespace control #}
{% macro view_url(view_name, context = stop_id) -%} {% macro view_url(view_name, context = g.stop_id) -%}
{{ url_for('view', path = view_name, stopId = context, _external = True) }} {{ url_for('view', path = view_name, stopId = context, _external = True) }}
{%- endmacro %} {%- endmacro %}
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
{{ url_for('static', filename = filename, _external = True) }} {{ url_for('static', filename = filename, _external = True) }}
{%- endmacro %} {%- endmacro %}
{% macro home_url(context = stop_id, lang = None) -%} {% macro home_url(context = g.stop_id, lang = None) -%}
{% if not lang -%} {% if not lang -%}
{{ url_for('view', path = '', stopId = context, _external = True) }} {{ url_for('view', path = '', stopId = context, _external = True) }}
{%- else -%} {%- else -%}
...@@ -16,16 +16,17 @@ ...@@ -16,16 +16,17 @@
{%- endif %} {%- endif %}
{%- endmacro %} {%- endmacro %}
{% macro time_short(time, context = stop_info) -%} {# For format string documentation, see: http://babel.pocoo.org/en/latest/dates.html#pattern-syntax #}
{{ time | date(format = '%H:%M', tz = context.stop_timezone) }} {% macro time_short(time) -%}
{{ time | datetimeformat(format = 'HH:hh') }}
{%- endmacro %} {%- endmacro %}
{% macro time_long(time, context = stop_info) -%} {% macro time_long(time) -%}
{{ time | date(format = '%H:%M:%S', tz = context.stop_timezone) }} {{ time | timeformat(format = 'HH:hh:ss') }}
{%- endmacro %} {%- endmacro %}
{% macro date(time, context = stop_info) -%} {% macro date(time) -%}
{{ time | date(tz = context.stop_timezone) }} {{ time | datetimeformat }}
{%- endmacro %} {%- endmacro %}
{% macro stop_name(full_name = True, context = stop_info) -%} {% macro stop_name(full_name = True, context = stop_info) -%}
...@@ -46,15 +47,15 @@ ...@@ -46,15 +47,15 @@
<nav> <nav>
<ul class="pagination pagination-lg pagination-foli justify-content-center"> <ul class="pagination pagination-lg pagination-foli justify-content-center">
{% if context.prev_url -%} {% if context.prev_url -%}
<li class="page-item previous"><a class="page-link" href="{{ context.prev_url }}">Previous</a></li> <li class="page-item previous"><a class="page-link" href="{{ context.prev_url }}">{{ _('Previous') }}</a></li>
{%- else -%} {%- else -%}
<li class="page-item previous disabled"><span class="page-link">Previous</span></li> <li class="page-item previous disabled"><span class="page-link">{{ _('Previous') }}</span></li>
{%- endif %} {%- endif %}
<li class="page-item counts disabled"><span class="page-link">{{ context.on_page }} / {{ context.page_count }}</span></li> <li class="page-item counts disabled"><span class="page-link">{{ context.on_page }} / {{ context.page_count }}</span></li>
{% if context.next_url -%} {% if context.next_url -%}
<li class="page-item next"><a class="page-link" href="{{ context.next_url }}">Next</a></li> <li class="page-item next"><a class="page-link" href="{{ context.next_url }}">{{ _('Next') }}</a></li>
{%- else -%} {%- else -%}
<li class="page-item next disabled"><span class="page-link">Next</span></li> <li class="page-item next disabled"><span class="page-link">{{ _('Next') }}</span></li>
{%- endif %} {%- endif %}
</ul> </ul>
</nav> </nav>
......
{% extends 'internal/base.html' %} {% extends 'internal/base.html' %}
{% set title = 'Live Map' %} {% set title = _('Live Map') %}
{% set sticky_banners = false %} {% set sticky_banners = false %}
{% set localized = true %}
{% block head %} {% block head %}
{{- super() }} {{- super() }}
...@@ -14,7 +15,7 @@ ...@@ -14,7 +15,7 @@
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<h2 class="text-center text-foli content-spaced-lg text-shadow">Live Bus Traffic Nearby</h2> <h2 class="text-center text-foli content-spaced-lg text-shadow">{{ _('Live Bus Traffic Nearby') }}</h2>
</div> </div>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment