I've just created the basic Pyramid "hello world" template project and added i18n support. I'm using Python 3.5 and Chameleon templates (.pt
) with gettext.
I can currently change the language through the .ini
file.
Now I would like to make it dynamic and read the language code from the URL. So URLs are changed to /<language code>/page/{possible params}
so for example /fi/home
. I don't want to add {language}
to existing routes/views so that the language code parameter is hidden and views don't know anything about it except when creating links to other pages in templates/views.
EDIT: Here's my attempt using tweens mentioned by Mikko Ohtamaa:
Added to __init__.py
:
config.add_tween('myapp.tweens.LocalizerTween')
tweens.py
:
import logging
from pyramid.registry import Registry
from pyramid.request import Request
log = logging.getLogger(__name__)
class LocalizerTween(object):
"""
Set translator based on URL
"""
def __init__(self, handler, registry: Registry):
self.handler = handler
self.registry = registry
def __call__(self, request: Request):
if request.path.count("/") > 1 and len(request.path) > 3:
request.locale_name = request.path[1:].split("/", 1)[0]
else:
# Redirect to default language
from pyramid.settings import aslist
import pyramid.httpexceptions as exc
raise exc.HTTPFound("/" + aslist(request.registry.settings['pyramid.default_locale_name'])[0] + "/")
newpath = request.path[1:]
newpath = newpath[newpath.find("/"):]
log.debug("new path: %s", newpath)
request.path = newpath
response = self.handler(request)
return response
Redirect to default language gives exception:
pyramid.httpexceptions.HTTPFound: The resource was found at
Trying to set new path gives:
AttributeError: can't set attribute
If I comment out request.path = newpath
and go to /fi/
and /en/
I get 404 page in correct language.
Here is an example solution, strictly limited to the scope of language aware paths (no localization bindings, etc):
"""Self-contained language aware path routing example for Pyramid."""
from urllib.parse import urlunparse
from urllib.parse import urlparse
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
from pyramid.request import Request
from pyramid.response import Response
def redirect_to_default_language(request: Request):
"""A view that redirects path language-free URLs to the default language URLs.
E.g. /greeter/foobar -> /en/greeter/foobar
"""
default_language = request.registry.settings["default_language"]
parts = urlparse(request.url)
new_path = "/{}{}".format(default_language, parts.path)
new_parts = [parts[0], parts[1], new_path, parts[3], parts[4], parts[5]]
language_redirected_url = urlunparse(new_parts)
return HTTPFound(language_redirected_url)
def add_localized_route(config, name, pattern, factory=None, pregenerator=None, **kw):
"""Create path language aware routing paths.
Each route will have /{lang}/ prefix added to them.
Optionally, if default language is set, we'll create redirect from an URL without language path component to the URL with the language path component.
"""
orig_factory = factory
def wrapper_factory(request):
lang = request.matchdict['lang']
# determine if this is a supported lang and convert it to a locale,
# likely defaulting to your default language if the requested one is
# not supported by your app
request.path_lang = lang
if orig_factory:
return orig_factory(request)
orig_pregenerator = pregenerator
def wrapper_pregenerator(request, elements, kw):
if 'lang' not in kw:
# not quite right but figure out how to convert request._LOCALE_ back into a language url
kw['lang'] = request.path_lang
if orig_pregenerator:
return orig_pregenerator(elements, kw)
return elements, kw
if pattern.startswith('/'):
new_pattern = pattern[1:]
else:
new_pattern = pattern
new_pattern = '/{lang}/' + new_pattern
# Language-aware URL routed
config.add_route(name, new_pattern, factory=wrapper_factory, pregenerator=wrapper_pregenerator, **kw)
# Add redirect to the default language routes
if config.registry.settings.get("default_language"):
# TODO: This works only for the most simplest routes
fallback_route_name = name + "_language_redirect_fallback"
config.add_route(fallback_route_name, pattern)
config.add_view(redirect_to_default_language, route_name=fallback_route_name)
def home(request):
"""Example of language aware parameterless routing."""
if request.path_lang == "fi":
msg = 'Hyvää päivää!'
else:
msg = 'Hello sir'
# This will use current language
# and automatically populate /{lang}/ matchdict
# as in wrapper_pregenerator()
another_url = request.route_url("greeter", name="mikko")
text = """{}
Also see {}
""".format(msg, another_url)
return Response(text)
def greeter(request):
"""Example of language aware matchdict routing."""
name = request.matchdict["name"]
if request.path_lang == "fi":
return Response('Mitä kuuluu {}?'.format(name))
else:
return Response('How are you {}?'.format(name))
if __name__ == '__main__':
config = Configurator()
# Map all /lang/ free URLs to this language
config.registry.settings["default_language"] = "en"
# Set up config.add_localized_route
config.add_directive('add_localized_route', add_localized_route)
# Parameterless routing
# This will create
# - /
# - /fi/
# - /en/
# patterns
config.add_localized_route('home', '/')
config.add_view(home, route_name='home')
# Match dict routing
# This will create
# - /greet/mikko
# - /en/greet/mikko
# - /fi/greet/mikko
# patterns
config.add_localized_route('greeter', '/greet/{name}')
config.add_view(greeter, route_name='greeter')
# Run the web server
app = config.make_wsgi_app()
server = make_server('0.0.0.0', 8080, app)
server.serve_forever()