2
0

support i18n

This commit is contained in:
Stefan Bühler 2022-04-07 13:26:52 +02:00
parent 99db542326
commit 71e7fe7b09
4 changed files with 98 additions and 1 deletions

View File

@ -1,6 +1,6 @@
# python Captive Portal # python Captive Portal
### Installation ## Installation
Either clone repository (and install dependencies either through distribution or as virtualenv with `./setup-venv.sh`) or install as package. Either clone repository (and install dependencies either through distribution or as virtualenv with `./setup-venv.sh`) or install as package.
@ -8,3 +8,12 @@ Either clone repository (and install dependencies either through distribution or
pipx install https://github.tik.uni-stuttgart.de/NKS/python-capport pipx install https://github.tik.uni-stuttgart.de/NKS/python-capport
In production put a reverse proxy in front of the local web ui (on 127.0.0.1:8000), and handle `/static` path either to `src/capport/api/static/` or your customized version of static files.
## Customization
Create `custom/templates` and put customized templates (from `src/capport/api/templates`) there.
Create `i18n/<langcode>` folders to put localized templates into.
Requests with a `setlang=<langcode>` query parameter will set the language and try to store the choice in a session cookie.

View File

@ -5,4 +5,5 @@ app = MyQuartApp(__name__)
__import__('capport.api.setup') __import__('capport.api.setup')
__import__('capport.api.lang')
__import__('capport.api.views') __import__('capport.api.views')

View File

@ -18,6 +18,26 @@ class DispatchingJinjaLoader(quart.templating.DispatchingJinjaLoader):
def __init__(self, app: MyQuartApp) -> None: def __init__(self, app: MyQuartApp) -> None:
super().__init__(app) super().__init__(app)
def get_source(
self, environment: jinja2.Environment, template: str
) -> typing.Tuple[str, typing.Optional[str], typing.Optional[typing.Callable[[], bool]]]:
"""Returns the template source from the environment.
This considers the loaders on the :attr:`app` and blueprints.
"""
langs: typing.List[str] = quart.g.langs
for loader in self._loaders():
for lang in langs:
try:
return loader.get_source(environment, os.path.join('i18n', lang, template))
except jinja2.TemplateNotFound:
continue
try:
return loader.get_source(environment, template)
except jinja2.TemplateNotFound:
continue
raise jinja2.TemplateNotFound(template)
def _loaders(self) -> typing.Generator[jinja2.BaseLoader, None, None]: def _loaders(self) -> typing.Generator[jinja2.BaseLoader, None, None]:
if self.app.custom_loader: if self.app.custom_loader:
yield self.app.custom_loader yield self.app.custom_loader

67
src/capport/api/lang.py Normal file
View File

@ -0,0 +1,67 @@
from __future__ import annotations
import re
import typing
import quart
from .app import app
_VALID_LANGUAGE_NAMES = re.compile(r'^[-a-z0-9_]+$')
def parse_accept_language(value: str) -> typing.List[str]:
value = value.strip()
if not value or value == '*':
return []
tuples = []
for entry in value.split(','):
attrs = entry.split(';')
name = attrs.pop(0).strip().lower()
q = 1.0
for attr in attrs:
if not '=' in attr: continue
key, value = attr.split('=', maxsplit=1)
if key.strip().lower() == 'q':
try:
q = float(value.strip())
except ValueError:
q = 0.0
if q >= 0.0:
tuples.append((q, name))
tuples.sort()
have = set()
result = []
for (_q, name) in tuples:
if name in have: continue
if name == '*': break
have.add(name)
if _VALID_LANGUAGE_NAMES.match(name):
result.append(name)
short_name = name.split('-', maxsplit=1)[0].split('_', maxsplit=1)[0]
if not short_name or short_name in have: continue
have.add(short_name)
result.append(short_name)
return result
@app.before_request
def detect_language():
g = quart.g
r = quart.request
s = quart.session
lang = r.args.get('setlang')
if lang:
lang = lang.strip().lower()
if lang and _VALID_LANGUAGE_NAMES.match(lang):
s = quart.session
s['lang'] = lang
g.langs = [lang]
return
lang = s.get('lang')
if lang:
lang = lang.strip().lower()
if lang and _VALID_LANGUAGE_NAMES.match(lang):
g.langs = [lang]
return
acc_lang = ','.join(r.headers.get_all('Accept-Language'))
g.langs = parse_accept_language(acc_lang)