I have one server with flask application instance and have several domain which mapped to this server by DNS.
My site must support several languages by host and pref
It's in the official doc: http://flask.pocoo.org/docs/patterns/urlprocessors/ (This is basically the same answer as Matthew Scragg's).
My own solution:
from flask import Flask, g, render_template, redirect, request
app = Flask(__name__)
default_language = 'en'
language_urls = {
'en': 'mysite.com',
'fr': 'mysite.com/fr',
'ru': 'mysite.ru',
'by': 'mysite.ru/by',
}
languages = ','.join(language_urls.keys())
def get_language_by_request(request_host, request_path):
'''
Looking bad, but work.
I cab't use request.view_args there,
because this can't detect language for 404 pages
like mysite.com/fr/unknown-page
'''
request_host_path = request_host + request_path
request_paths = request_host_path.split('/', 2)
if (len(request_paths) > 1 and request_paths[1] in language_urls.keys()):
request_language_prefix = request_paths[1]
return request_language_prefix
for language, url in language_urls.items():
host_prefix = url.split('/')
if len(host_prefix) == 1:
host, = host_prefix
if request_host == host:
return language
return default_language
def get_language_url_parameter_value(language, request_host):
host_prefix = language_urls[language]
if host_prefix == request_host:
return None
return language
def get_redirection_url_by_request(request_host, request_path, request_url):
'''
Looking bad, but work.
I cab't use request.view_args there,
because this can't detect language for 404 pages
like mysite.com/fr/unknown-page
'''
request_host_path = request_host + request_path
request_paths = request_host_path.split('/', 2)
request_language_prefix = None
if (len(request_paths) > 1 and request_paths[1] in language_urls.keys()):
request_language_prefix = request_paths[1]
hosts = []
for language, url in language_urls.items():
host_prefix = url.split('/')
if len(host_prefix) == 1:
host, = host_prefix
language_prefix = None
else:
host, language_prefix = host_prefix
if request_host == host and request_language_prefix == language_prefix:
return None
hosts.append(host)
if request_host not in hosts:
return None
if request_language_prefix:
request_host_prefix = request_host + '/' + request_language_prefix
host_prefix = language_urls[request_language_prefix]
return request_url.replace(request_host_prefix, host_prefix)
return None
@app.url_defaults
def set_language_in_url(endpoint, values):
if '_lang' not in values and hasattr(g, 'language_url_value'):
values['_lang'] = g.language_url_value
@app.url_value_preprocessor
def get_language_from_url(endpoint, values):
g.language = get_language_by_request(request.host, request.path)
g.language_url_value = get_language_url_parameter_value(g.language, request.host)
if values and '_lang' in values:
del values['_lang']
@app.before_request
def check_language_redirection():
redirection_url = get_redirection_url_by_request(request.host, request.path, request.url)
return redirect(redirection_url) if redirection_url else None
@app.route('/')
@app.route('/<any(%s):_lang>/' % languages)
def home():
return render_template('home.html')
@app.route('/other/')
@app.route('/<any(%s):_lang>/other/' % languages)
def other():
return render_template('other.html')
I don't use blueprints there because I also use flask-login
and I can't set several login pages with different languages for each blueprint. For example if page required login, flask redirect me to login page and I must update language for this page. Also login pages can't be as mysite.com/login
, mysite.com/fr/login
and etc without several redirections.
UPD: I can't use request.view_args
for detect language or redirection, because on this case I can't detect language for error pages as mysite.com/fr/wrong-page-there
(can't detect endpoint
and view_args
). To avoid this problem I can use hask: add url rule as /<lang_code>/<path:path>
and raise 404 error there.
I worked on something similar few months back. I modified it a bit and pushed to github. You can do what codegeek suggested if you are unable to make your templates language neutral. With this method you can cut down on the template files needed.
https://github.com/scragg0x/Flask-Localisation-Example
mysite.py
from flask import Flask, Blueprint, g, redirect, request
app = Flask(__name__)
mod = Blueprint('mysite', __name__, url_prefix='/<lang_code>')
sites = {
'mysite.com': 'en',
'myothersite.com': 'fr'
}
@app.url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', g.lang_code)
@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
url = request.url.split('/', 3)
g.lang_code = sites[url[2]]
@mod.url_defaults
def add_language_code(endpoint, values):
values.setdefault('lang_code', g.lang_code)
@mod.url_value_preprocessor
def pull_lang_code(endpoint, values):
g.lang_code = values.pop('lang_code')
@app.route('/')
@mod.route('/')
def index():
# Use g.lang_code to pull localized data for template
return 'lang = %s' % g.lang_code
app.register_blueprint(mod)
tests.py
import os
import unittest
import re
import requests
import urllib2
import json
from mysite import app
class MySiteTestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['SERVER_NAME'] = 'mysite.com'
self.domain = 'http://mysite.com/'
self.app = app.test_client()
def tearDown(self):
pass
def test_en_index(self):
rv = self.app.get('/en/', self.domain)
self.assertEqual(rv.data, 'lang = en')
print self.domain, rv.data
def test_fr_index(self):
rv = self.app.get('/fr/', self.domain)
self.assertEqual(rv.data, 'lang = fr')
print self.domain, rv.data
def test_default(self):
rv = self.app.get('/', self.domain)
self.assertEqual(rv.data, 'lang = en')
print self.domain, rv.data
class MyOtherSiteTestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['SERVER_NAME'] = 'myothersite.com'
self.domain = 'http://myothersite.com/'
self.app = app.test_client()
def tearDown(self):
pass
def test_en_index(self):
rv = self.app.get('/en/', self.domain)
self.assertEqual(rv.data, 'lang = en')
print self.domain, rv.data
def test_fr_index(self):
rv = self.app.get('/fr/', self.domain)
self.assertEqual(rv.data, 'lang = fr')
print self.domain, rv.data
def test_default(self):
rv = self.app.get('/', self.domain)
self.assertEqual(rv.data, 'lang = fr')
print self.domain, rv.data
if __name__ == '__main__':
unittest.main()
Disclaimer: This code is not tested. I am just giving you a ballpark idea of how to approach this.
I suggest you use blueprints in combination with an extension like Flask-Babel. For example, you can do something like:
views.py
mysitebp = Blueprint('mysitebp',__name__)
Then in your application package (usually __init__.py
) , you can do:
__init__.py
from mysite.views import mysitebp
app = Flask(__name__)
app.register_blueprint(mysitebp,url_prefix='/en/',template_folder='en')
app.register_blueprint(mysitebp,url_prefix='/fr',template_folder='fr')
..and so on
Your directory structure could look like:
mysite/
__init__.py
views.py
templates/
base.html
404.html
en/
en.html
fr/
french.html
Flask-Babel would help you translate the 404.html etc.