How can I choose the language, using Flask + Babel? - python

Now I'm developing a project, which should support two languages: English, as default, and Russian. It's pretty easy to do, using HTTP_ACCEPT_LANGUAGE header, the code is bellow:
babel = Babel(app)
#babel.localeselector
def get_locale():
return request.accept_languages.best_match(app.config["LANGUAGES"].keys())
Languages are hardcoded in application config file:
LANGUAGES = {
'en': 'English',
'ru': 'Russian'
}
But I also want to add a button, like Switch language to English. What is the best practice to realise it?

This is the solution I came across:
First you set a route that will handle the language change and will store the selected language on the session:
#app.route('/language/<language>')
def set_language(language=None):
session['language'] = language
return redirect(url_for('index'))
Secondly, you have to modify a little the code you have to get the selected language from the session:
#babel.localeselector
def get_locale():
# if the user has set up the language manually it will be stored in the session,
# so we use the locale from the user settings
try:
language = session['language']
except KeyError:
language = None
if language is not None:
return language
return request.accept_languages.best_match(app.config['LANGUAGES'].keys())
You have also to be able to access the CURRENT_LANGUAGE from the templates, so you can inject it:
#app.context_processor
def inject_conf_var():
return dict(
AVAILABLE_LANGUAGES=app.config['LANGUAGES'],
CURRENT_LANGUAGE=session.get('language',request.accept_languages.best_match(app.config['LANGUAGES'].keys())))
Finally, on the template you can choose the the language you want:
{% for language in AVAILABLE_LANGUAGES.items() %}
{% if CURRENT_LANGUAGE == language[0] %}
{{ language[1] }}
{% else %}
<a href="{{ url_for('set_language', language=language[0]) }}" >{{ language[1] }}</a>
{% endif %}
{% endfor %}
Application config.py includes the following constant:
LANGUAGES = {
'en': 'English',
'es': 'Spanish'
}
Hope this helps!

Related

How to change language of flask babel on a button click?

I have the function in Flask, which returns the website in english. Moreover, i want to be able to use german in the website at a button push from html. How can I change the language at a button push from english to german and from german to english? Also, is it possible to use the function get_locale only at call, not running automatically?
#babel.localeselector
def get_locale():
return 'en'
At first You need to store the address at the main app file:
#app.route('/language=<language>')
def set_language(language=None):
session['language'] = language
return redirect(url_for('home'))
Than You need to change to the same addess to get_locale function in main app file:
#babel.localeselector
def get_locale():
if request.args.get('language'):
session['language'] = request.args.get('language')
return session.get('language', 'en')
To access current language from template:
app.config['LANGUAGES'] = {
'en': 'English',
'ge': 'German',
}
app.secret_key = "super secret key"
#app.context_processor
def inject_conf_var():
return dict(AVAILABLE_LANGUAGES=app.config['LANGUAGES'], CURRENT_LANGUAGE=session.get('language', request.accept_languages.best_match(app.config['LANGUAGES'].keys())))
Here the template file:
{% for language in AVAILABLE_LANGUAGES.items() %}
{% if CURRENT_LANGUAGE == language[0] %}
{{ language[1] }}
{% else %}
<a href="{{ url_for('set_language', language=language[0]) }}" >{{ language[1] }}</a>
{% endif %}
{% endfor %}

Django multilingual site

I am trying to create a multilingual website, which would get content from DB based on selected language. For that I use i18n_patterns and so I can get the selected language easily using get_language() in the view.
Right now, my models look like this (simplified):
class Article(models.Model):
author = models.CharField("Author", max_length=255)
class ArticleText(models.Model):
id = models.OneToOneField(Article, on_delete=models.CASCADE, primary_key=True)
en = models.TextField("English")
fr = models.TextField("French")
My idea (based on research here on SO) was that I would store the article's text content elsewhere, linking it's primary key to the actual Article object's primary key.
The Article model contains just general info (publish date, author, etc.), which is the same for all languages. However, I am not really sure how to access the data from my template. Right now I use:
context["articles"] = models.Content.objects.select_related("id")
I can then access the various fields in my template using
{% for article in articles %}
<h3>{{ article.author }} - {{ article.fr }}</h3>
{% endfor %}
However, I want the language ({{ article.$LANGUAGE }}) to change based on the selected language. Something like
{{ article.$get_current_language }} --> {{ article.fr }}
I guess I could do the following in the template:
{% if get_current_language == en %}
{{ article.en }}
{% elif get_current_language == fr %}
{{ article.fr }}
{% endif %}
But I think it's ugly to do this in the template - I'd rather do this in the view and just pass it into to the template. Unfortunately, I don't have any idea on how to pass all the article objects and their relative texts in the correct language, and in a way that I would be able to for-loop through them.
Am I overthinking things and should I just use the if-block in the template, or is there a better way to do this?
This seems a good use case for writing a custom template filter which will return the appropriate article version given the current language.
For detailed code explanation about how you can write a custom filter, see Django docs: Writing custom template tags and filters.
And here's a code sample to get you started:
from django import template
from django.template.defaultfilters import stringfilter
register = template.Library()
#register.filter
def localize(value, arg):
"""Returns appropriate language version
of the given article.
value: the article
arg: the current language code
"""
article = value
lang = arg
try:
# `getattr` will call `article.en` or `article.fr`
# depending on the value of `lang`
text = getattr(article, lang)
except AttributeError:
# if this code runs, this means that
# the `article` doesn't have a version
# for the given `lang`
# so, return a default version
text = article.en
return text
Now, you need to create a directory called templatetags inside your app and create a file in the directory and put this code in that file. The detailed instructions can be found in the Code layout section of the docs.
When you've read the docs about where to put the above code, this is how you can use this filter:
{% get_current_language as LANG %}
{{ article|localize:LANG }}

Flask-Babel Multiple Language URL Routing

I am creating a multi-language site (EN and FR) and I need it to toggle back and forth on click if the user so chooses. I am using Flask-Babel and the translations and toggle are working correctly on click, however, I need the URLs to be translated as well. I have currently wrapped my URL routes like so, with both the English and French URLs:
#main.route('/accueil')
#main.route('/home')
def index():
return render('index.html', {})
#main.route('/a-propos-de-nous')
#main.route('/about-us')
def about():
return render('about.html', {})
The rest of the code that is grabbing the language and toggling is as follows:
app = Flask(__name__, static_folder=settings.STATIC_ROOT)
main = Blueprint('main', __name__, url_prefix='/language/<lang_code>')
#app.url_defaults
def set_language_code(endpoint, values):
if 'lang_code' in values or not session['lang_code']:
return
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
values['lang_code'] = session['lang_code']
#app.url_value_preprocessor
def get_lang_code(endpoint, values):
if values is not None:
session['lang_code'] = values.pop('lang_code', None)
#app.before_request
def ensure_lang_support():
lang_code = session['lang_code']
if lang_code and lang_code not in app.config['SUPPORTED_LANGUAGES'].keys():
return abort(404)
#babel.localeselector
def get_locale():
if session.get('lang_code') is None:
session['lang_code'] = request.accept_languages.best_match(app.config['SUPPORTED_LANGUAGES'].keys())
return session['lang_code']
The template looks like this where the user clicks on the link to change languages:
{% if session['lang_code']=='en' %}
{% set new_lang_code='fr' %}
{% else %}
{% set new_lang_code='en' %}
{% endif %}
<li>{{ _('Fr') }}</li>
As I have little experience with Python/Flask...I am struggling with the best way to switch to the translated URL. How would I go about doing this? Any information would be appreciated. Thanks in advance.
I have found a solution! I had to add endpoints to the URL routes like so:
#main.route('accueil', endpoint="index_fr")
#main.route('home', endpoint="index_en")
def index():
return render('index.html', {})
#main.route('a-propos-de-nous', endpoint="about_fr")
#main.route('about-us', endpoint="about_en")
def about():
return render('about.html', {})
This allowed me to use Babel to translate the URL endpoints like it did for the rest of the text, and grab the correct URL ending along with the language code from the session. The toggle works like this now:
{% if session['lang_code']=='en' %}
{% set new_lang_code='fr' %}
{% else %}
{% set new_lang_code='en' %}
{% endif %}
<li>{{ _('Fr') }}</li>

Django - Show/hide urls in base html dependant on user group?

I want to try and show or hide urls in my navigation page based on user group.
currently i am adding to the top of every view and sending through a value to the template and checking against that, but this doesnt seem too efficient, also if a view does not require auth and that value is not set, will that break the template?
is there a better way to do this?
like a global.py where i could check and set then use it in any template? or something completely different altogether?
view.py
Authorised_user = ''
if request.user.is_authenticated():
Authorised_user = 'IT'
#login_required
def index(request):
return render(request, 'service/index.html', {
'Authorised': Authorised_user,
})
template.html
{% if Authorised == 'IT' or Authorised =='Netwworks' %}
Link
{% endif %}
i do have the user groups in django admin
Based on Get user group in a template
Create user_tags.py / group_tags.py at an appropriate place. e.g. auth_extra/templatetags/user_tags.py
from django import template
register = template.Library()
#register.filter('in_group')
def in_group(user, group_name):
return user.groups.filter(name=group_name).exists()
Then in your template:
{% load user_tags %}
{% if request.user|in_group:"IT"%}
IT only link
{% endif %}
{% if request.user|in_group:"Netwworks"%}
Netwworks only link
{% endif %}
Easiest way around this for me was https://stackoverflow.com/a/17087532/8326187.
Here you don't have to create a custom template tag.
{% if request.user.groups.all.0.name == "groupname" %}
...
{% endif %}
You need to create context_processors.py and create a function say
def foo():
Authorised_user = ''
if request.user.is_authenticated():
Authorised_user = 'IT'
Then in setttings
TEMPLATE_CONTEXT_PROCESSORS = ("path_to_context_processor.foo")
this way you can use foo variable in all the templates without explicitly defining in all the views.
You can also have a look here:https://rubayeet.wordpress.com/2009/10/31/django-how-to-make-a-variable-available-in-all-templates/

Django SEO app, integrating external data like 'Best {{ product }} of the year'

I am trying to integrate external data in my metatags. What I want to achieve - by entering in Title field (in admin) "Details for {{ product.name }}" I would like to get to get automaticaly "Details for Hadron Collider" as a result in a browser. I have this in my template
{% load seo %}
{% get_metadata for product as metadata %}
{{ metadata.title }}
and I am passing Product object to the template, but what I get as a result is unchanged "Details for {{ product.name }}", so {{ value }} won't be populated / parsed? I have read the docs http://django-seo.readthedocs.org/en/latest/reference/administrators.html, it looks so simple
If you would like to reference the relevant model instance, you type the name of the model surrounded by two braces. For example Buy {{ product }} today!. You can go further and reference different fields from the relevant instance, for example By {{ product.name }} today!.
Though it doesn't work for me. Please, help. Probably I am doing something wrong.
Ok, after days of depression :) I solved it, at least for my project and currently only for Models, but for ModelInstance and Views it's almost I will make it later, it has the same problem in my case. I am quite new to Django, so a better solution may exist, I found this one.
I use Django 1.6.4, Python 2.7, multilingual project (Django Transmeta for model translation)
The problems:
1. variable substitution in models, model instances and views doesn't work. So, if we pass an object to DjangoSEO it won't substitute e.g. {{ product }}, {{ product.name }} variables. According to documentation it should. The problem, for Model is in backends.py, ModelBackend class. Function _resolve_value doesn't pass object to the function _resolve, which is supposed to populate meta tags with object properties. Here are the functions with slight changes that work for me.
def _resolve_value(self, name):
value = super(ModelMetadataBase, self)._resolve_value(name)
try:
return _resolve(value, self._content_object)
except AttributeError:
return value
def _resolve(value, model_instance=None, context=None):
""" Resolves any template references in the given value. """
if isinstance(value, basestring) and "{" in value:
if context is None:
context = Context()
if model_instance is not None:
context[model_instance.__class__.__name__.lower()] = model_instance
t = Template(value)
value = t.render(context)
return value
Also, in the file base.py, function get_linked_metadata we have to attach our object to Metadata instances like this:
if ModelMetadata is not None:
try:
model_md = ModelMetadata.objects.get(_content_type=content_type, _language=language)
except ModelMetadata.DoesNotExist:
model_md = ModelMetadata(_content_type=content_type, _language=language)
model_md._content_object = obj
instances.append(model_md)
And the problem 2 - DjangoSEO was using one Metadata instance for any language, even with option use_i18n = True. So, add _language=language as mentioned above.
Django SEO app is quite good, it has everything SEO needs (if it works :), so there is no reason to reinvent the wheel.
You need to wrap your variables in html tags.
{% load seo %}
{% get_metadata for product as metadata %}
<html>
<head>
<title>{{ metadata.title}}</title>
</head>
<body></body>
</html>

Categories