support i18n
This commit is contained in:
		
							
								
								
									
										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)
 | 
				
			||||||
		Reference in New Issue
	
	Block a user