support i18n
This commit is contained in:
parent
99db542326
commit
71e7fe7b09
11
README.md
11
README.md
@ -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.
|
||||||
|
@ -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')
|
||||||
|
@ -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
67
src/capport/api/lang.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user