Python flask web application with multilanguages s

2020-02-11 09:23发布

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 prefix:

mysite.com - english
mysite.com/fr - franch
mysite.ru - russian
mysite.ru/by - belarusian
localhost or other unknown host without language prefix - default language (english)

I implemented it with double route registration /endpoint and /<lang>/endpoint and reloaded url_for function and it work, but now I must implement custom error pages for abort function:

mysite.com/wrong-url-there - mysite.com/404.html (english)
mysite.com/fr/wrong-url-there - mysite.com/fr/404.html (franch)
mysite.ru/wrong-url-there - mysite.ru/404.html (russian)
mysite.ru/by/wrong-url-there - mysite.ru/by/404.html (belorusian)

And I don't see solution for this. I think my implementation bad and I must improve it. I think I must create one instance of application for each site language root with predefined language for it or use blueprint, but I don't find solution for me yet.

Is anybody can give me advice how resolve this url multilanguages support with flask or wsgi or nginx?

4条回答
再贱就再见
2楼-- · 2020-02-11 09:49

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.

查看更多
迷人小祖宗
3楼-- · 2020-02-11 09:52

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
4楼-- · 2020-02-11 10:00

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()
查看更多
家丑人穷心不美
5楼-- · 2020-02-11 10:01

It's in the official doc: http://flask.pocoo.org/docs/patterns/urlprocessors/ (This is basically the same answer as Matthew Scragg's).

查看更多
登录 后发表回答