Adding url_for() links to Jinja templates of a Flask multilanguage website
In a multilanguage website with multilanguage slugs we can no longer use <a href="{{ url_for('pages.about') }}">{{ _('About') }}</a>.
The problem: url_for() in Jinja templates
@pages_blueprint.route('/contact')
def contact():
return page_view('contact')
@pages_blueprint.route('/about')
def about():
return page_view('about')
@pages_blueprint.route('/terms-of-use')
def terms_of_use():
return page_view('terms-of-use')
@pages_blueprint.route('/privacy-policy')
def privacy_policy():
return page_view('privacy-policy')
I removed these lines so the only remaining endpoint for pages would be the page_view function:
@pages_blueprint.route('/<slug>', methods=['GET', 'POST'])
def page_view(slug):
...
Then the pages stopped working ... of course. At some places in the templates I still used url_for() to generate the link to a page. For example, the 'Read more' was still specified as:
<a href="{{ url_for('pages.about') }}">{{ _('Read more') }}</a>
And on the Register page there was the link:
* {{ _('See our') }} <a href="{{ url_for('pages.privacy_policy') }}" class="link">{{ _('privacy policy') }}</a> {{ _('how we process and protect your personal data.') }}
Both failed with the famous:
werkzeug.routing.BuildError: Could not build url for endpoint ...
Nothing special and yes it was expected, but how to solve this and more important, how to solve this in a multilanguage way?
The solution: a nested dict for every language selected
The solution is not very difficult. As I mentioned in previous posts we have two tables:
ContentItem
ContentItemTranslation
ContentItem contains a 'common' name. For example the About page has a common name 'About' and then the ContentItemTranslation records contain the translated page title, for the Dutch language this is 'Over ons' and the derived slug is 'over-ons'. To be able to supply the proper data to the url_for() function we need to create a dictionary with the following structure:
{
'About': { 'title': title, 'slug': slug },
'Privacy policy': { 'title': title, 'slug': slug },
...
}
Then we need the context processor to get this dictionary into the template, here I use app_template_slug as the dictionary name. In the Jinja template we can use this in the following way:
* {{ _('See our') }} <a href="{{ url_for('pages.page_view', slug=app_template_slug['Privacy policy']['slug']) }}" class="link">{{ app_template_slug['Privacy policy']['title'] }}</a> {{ _('how we process and protect your personal data.') }}
We use the 'common' name to get the proper title and slug, so nothing language dependent here. As with other functionalities I created the object that takes cares of the generation of the nested dictionary in the create_app function.
Links / credits
Multilanguage fallback revisited and a page footer with multilanguage links
/en/blog/multilanguage-fallback-revisited-and-a-page-footer-with-multilanguage-links
Refining multilanguage: adding language fallback as an option
/en/blog/refining-multilanguage-adding-language-fallback-as-an-option
Read more
Flask Jinja2 Multilanguage
Recent
- Hiding database UUID primary keys of your web application
- Don't Repeat Yourself (DRY) with Jinja2
- SQLAlchemy, PostgreSQL, maximum number of rows per user
- Show the values in SQLAlchemy dynamic filters
- Secure data transfer with Public Key encryption and pyNaCl
- rqlite: a high-availability and distributed SQLite alternative
Most viewed
- Using Python's pyOpenSSL to verify SSL certificates downloaded from a host
- Using UUIDs instead of Integer Autoincrement Primary Keys with SQLAlchemy and MariaDb
- Connect to a service on a Docker host from a Docker container
- Using PyInstaller and Cython to create a Python executable
- SQLAlchemy: Using Cascade Deletes to delete related objects
- Flask RESTful API request parameter validation with Marshmallow schemas