diff --git a/README.md b/README.md index de72aa8..72be632 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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. @@ -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 +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/` folders to put localized templates into. + +Requests with a `setlang=` query parameter will set the language and try to store the choice in a session cookie. diff --git a/src/capport/api/app.py b/src/capport/api/app.py index b9e144e..70ab89e 100644 --- a/src/capport/api/app.py +++ b/src/capport/api/app.py @@ -5,4 +5,5 @@ app = MyQuartApp(__name__) __import__('capport.api.setup') +__import__('capport.api.lang') __import__('capport.api.views') diff --git a/src/capport/api/app_cls.py b/src/capport/api/app_cls.py index f7e8220..e33e2b5 100644 --- a/src/capport/api/app_cls.py +++ b/src/capport/api/app_cls.py @@ -18,6 +18,26 @@ class DispatchingJinjaLoader(quart.templating.DispatchingJinjaLoader): def __init__(self, app: MyQuartApp) -> None: 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]: if self.app.custom_loader: yield self.app.custom_loader diff --git a/src/capport/api/lang.py b/src/capport/api/lang.py new file mode 100644 index 0000000..0789b6c --- /dev/null +++ b/src/capport/api/lang.py @@ -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)