Django


2935 浏览 5 years, 2 months

12 国际化 i18n

版权声明: 转载请注明出处 http://www.codingsoho.com/

国际化 i18n

Django 官方教程:https://docs.djangoproject.com/en/1.8/#internationalization-and-localization

Django 支持国际化,多语言。Django的国际化是默认开启的,如果您不需要国际化支持,那么您可以在您的设置文件中设置 USE_I18N = False,那么Django会进行一些优化,不加载国际化支持机制。

NOTE: 18表示Internationlization这个单词首字母I和结尾字母N之间的字母有18个。I18N就是Internationlization的意思。

设置

支持国际化,我们要完成下面的操作

在settings.py文件中设置

MIDDLEWARE_CLASSES = (
    ...
    'django.middleware.locale.LocaleMiddleware',
)

这个是国际化文件存放的位置

语言相关的配置里, USE_I18N = True这个默认是True,不要改成False

TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True

默认的code是en-us,但是代码里好像是en,所以我一般会把它改为en

LANGUAGE_CODE = 'en'

添加语言列表

LANGUAGES = (
    ('en', ('English')),
    ('zh-cn', ('中文简体')),
    ('zh-tw', ('中文繁體')),
)

:不同的版本里,这些code可能会不一样,新版本里的简体中文就是zh-hans

或者

LANGUAGES = (
    ('en-us', ugettext('English')),
    ('zh-cn', ugettext('Chinese Simple')),
    ('zh-tw', ugettext('Chinese taiwan')),
)

设置翻译文件存放位置

#翻译文件所在目录,需要手工创建
LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale'),
)

添加模板处理器

TEMPLATE_CONTEXT_PROCESSORS = (
    ...
    "django.core.context_processors.i18n",
)

# django 1.11

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                'django.template.context_processors.i18n', 
            ],
        },
    },
]

接下来,我们就可以通过命令生成需要翻译的文件:

django-admin.py makemessages -l zh_CN
django-admin.py makemessages -l zh-tw

Django 1.9 及以上版本要改成

python manage.py makemessages -l zh_hans
python manage.py makemessages -l zh_hant

手工翻译 locale 中的 django.po

...
#: .\tutorial\models.py:23
msgid "created at"
msgstr "创建于"

#: .\tutorial\models.py:24
msgid "updated at"
msgstr "更新于"

...

手工翻译 locale 中的文本后,我们需要编译一下,这样翻译才会生效

django-admin.py compilemessages

或者

python manage.py compilemessages -l zh_hans

如果翻译不生效,请检查你的语言包的文件夹是不是有 中划线,请用下划线代替它。
比如 zh-hans 改成 zh_hans

语言的翻译可以下载翻译编辑工具poedit

其他工具 gettext-0.17-win32-setup.exe

语言的名字

通过django.utils.translation.get_language_info函数可以获取当前版本支持的语言

具体的定义在django.conf.locale.__init__.py里的LANG_INFO表中

LANG_INFO = {
    'en': {
        'bidi': False,
        'code': 'en',
        'name': 'English',
        'name_local': 'English',
    },
    'zh-cn': {
        'fallback': ['zh-hans'],
    },
    'zh-hans': {
        'bidi': False,
        'code': 'zh-hans',
        'name': 'Simplified Chinese',
        'name_local': '绠€浣撲腑鏂?,
    },
    'zh-hant': {
        'bidi': False,
        'code': 'zh-hant',
        'name': 'Traditional Chinese',
        'name_local': '绻侀珨涓枃',
    },
    'zh-hk': {
        'fallback': ['zh-hant'],
    },
    'zh-tw': {
        'fallback': ['zh-hant'],
    },

在setting文件定义相关的语言

LANGUAGES = (
    ('en', ('English')),
    ('zh-cn', ('中文简体')),
    ('zh-tw', ('中文繁體')),
)

默认生成的文件里,使用的是en-us,原因不明,但是这会导致语言切换失败

LANGUAGE_CODE = 'en-us'

LANGUAGES的定义在global_settings.py文件里

# Languages we provide translations for, out of the box.
LANGUAGES = [
    ('en', gettext_noop('English')),
    ('zh-hans', gettext_noop('Simplified Chinese')),
    ('zh-hant', gettext_noop('Traditional Chinese')),
......

代码中标记

  • In views, mark translatable strings using the ugettext function (usually imported as _) 视图中,通过ugettext函数
  • In templates, mark translatable strings using the trans template tag for strings that do not contain variables, and blocktrans for strings that do 模板中,通过transblocktrans模板标签
  • In forms and models, mark translatable strings using the ugettext_lazy function (usually imported as _) 在表单或模型中,通过ugettext_lazy函数

model的定义

from django.utils.translation import ugettext_lazy as _
class MyAbstractUser(AbstractBaseUser, PermissionsMixin):
    first_name = models.CharField(_('first name'), max_length=30, blank=True)

view的定义

先导入translation库,并简化命名为_

from django.utils.translation import ugettext as _

在每个需i18n的字符串前加入_

from django.utils.translation import ugettext as _

class DailyInspectionListView(ListView): 
    def dispatch(self, request, *args, **kwargs):
        request.breadcrumbs([
            (_("Home"),reverse("home", kwargs={})),
            (_('Daily Inspection'),request.path_info),
        ])
        return super(DailyInspectionListView, self).dispatch(request,args,kwargs)   

模板中的定义

添加“{% load i18n %}”,然后就可以使用trans标记

<button type='submit' class='btn btn-default btn-block'>{% trans 'Log in' %}</button>

如果你只想标记字符串而想以后再翻译,可以添加noop选项:

<button type='submit' class='btn btn-default btn-block'>{% trans 'Log in' noop %}</button>

trans标记只能翻译字符串,不能使用变量,如果有变量需要翻译,那么需要使用{% blocktrans %}标记

{% blocktrans %}Log in site {{ site_name }}{% endblocktrans %}

另外RequestContext对象有三个针对翻译的变量LANGUAGES,LANGUAGE_CODELANGUAGE_BIDI,分别表示语言列表,当前用户语言的偏好,和语言的书写方式:

LANGUAGES 是一系列元组组成的列表,每个元组的第一个元素是语言代码,第二个元素是用该语言表示的语言名称。
LANGUAGE_CODE是以字符串表示的当前用户偏好语言(例如, en-us )。(详见 Django 如何确定语言偏好。)
LANGUAGE_BIDI是当前语言的书写方式。若设为 True,则该语言书写方向为从右到左(如希伯来语和阿拉伯语);若设为 False,则该语言书写方向为从左到右(如英语、法语和德语)。

还有另一种方式,也能达到在模板中使用上面三个变量的目的,如下代码:

{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_current_language_bidi as LANGUAGE_BIDI %}

这些值其实不需要特别去取,在前面的middleware的process_request里面已经赋值了

语言切换

添加前端

定义一个templatetag,添加文件langs.py

# -*- coding:utf-8 -*-
from django import template
from django.utils.translation import get_language_info
from django.conf import settings

LANGUAGES = []
for lang_code in settings.LANGUAGES_SUPPORTED:
    LANGUAGES.append(get_language_info(lang_code))

register = template.Library()

@register.inclusion_tag('languages_select_part.html')
def language_select(default):
    return {'languages':LANGUAGES, 'default':default}

在setting.py里添加LANGUAGES_SUPPORTED

LANGUAGES_SUPPORTED = ('en', 'zh-cn',)

模板languages_select_part.html的定义如下:

{% if languages %}
<form id="language-select-form" method="post" action="{% url django.views.i18n.set_language %}">{% csrf_token %}
<select class="dropdown" onchange="this.form.submit();" name="language">
{% for lang in languages %}<option value="{{ lang.code }}" {% if lang.code == default %}selected="selected"{% endif %}>{{ lang.name_local }}</option>
{% endfor %}</select>
</form>{% endif %}

那么在网站的模板中,就可以通过如下代码将该下拉框添加到页面中:

{% load langs %}
{% language_select LANGUAGE_CODE %}

官方文档的实现如下,它用了get_current_languageget_available_languagesget_language_info_list来获取系统language相关的信息

https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#the-set-language-redirect-view

{% load i18n %}

<form action="{% url 'set_language' %}" method="post">{% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}" />
    <select name="language">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go" />
</form>
后端的实现
path不带language code

添加url路由

url(r'^setlang/$', 'django.views.i18n.set_language', name = 'setlang'),
后端的原理

django.views.i18n.set_language该函数会完成语言转换和next设定

def set_language(request):
    """
    Redirect to a given url while setting the chosen language in the
    session or cookie. The url and the language code need to be
    specified in the request parameters.

    Since this view changes how the user will see the rest of the site, it must
    only be accessed as a POST request. If called as a GET request, it will
    redirect to the page in the request (the 'next' parameter) without changing
    any state.
    """
    next = request.POST.get('next', request.GET.get('next'))
    if not is_safe_url(url=next, host=request.get_host()):
        next = request.META.get('HTTP_REFERER')
        if not is_safe_url(url=next, host=request.get_host()):
            next = '/'
    response = http.HttpResponseRedirect(next)
    if request.method == 'POST':
        lang_code = request.POST.get('language', None)
        if lang_code and check_for_language(lang_code):
            if hasattr(request, 'session'):
                request.session[LANGUAGE_SESSION_KEY] = lang_code
            else:
                response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code,
                                    max_age=settings.LANGUAGE_COOKIE_AGE,
                                    path=settings.LANGUAGE_COOKIE_PATH,
                                    domain=settings.LANGUAGE_COOKIE_DOMAIN)
    return response

语言的激活在middleware中完成

django.middleware.locale.py

class LocaleMiddleware(object):
    def process_request(self, request):
        check_path = self.is_language_prefix_patterns_used()
        language = translation.get_language_from_request(
            request, check_path=check_path)
        translation.activate(language)
        request.LANGUAGE_CODE = translation.get_language()

django.utils.translation.trans_real.py

获取language的信息的顺序

  • path \en..
  • LANGUAGE_SESSION_KEY
  • settings.LANGUAGE_COOKIE_NAME
def get_language_from_request(request, check_path=False):
    """
    Analyzes the request to find what language the user wants the system to
    show. Only languages listed in settings.LANGUAGES are taken into account.
    If the user requests a sublanguage where we have a main language, we send
    out the main language.

    If check_path is True, the URL path prefix will be checked for a language
    code, otherwise this is skipped for backwards compatibility.
    """
    if check_path:
        lang_code = get_language_from_path(request.path_info)
        if lang_code is not None:
            return lang_code
    supported_lang_codes = get_languages()
    if hasattr(request, 'session'):
        lang_code = request.session.get(LANGUAGE_SESSION_KEY)
        if lang_code in supported_lang_codes and lang_code is not None and check_for_language(lang_code):
            return lang_code
    lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)
    try:
        return get_supported_language_variant(lang_code)
    except LookupError:
        pass
    accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
    for accept_lang, unused in parse_accept_lang_header(accept):
        if accept_lang == '*':
            break
        if not language_code_re.search(accept_lang):
            continue
        try:
            return get_supported_language_variant(accept_lang)
        except LookupError:
            continue
    try:
        return get_supported_language_variant(settings.LANGUAGE_CODE)
    except LookupError:
        return settings.LANGUAGE_CODE
path带language code
from django.conf.urls.i18n import i18n_patterns
urlpatterns += i18n_patterns('',
)

如果要全部使用

from django.conf.urls.i18n import i18n_patterns
urlpatterns = i18n_patterns('',
)

To enable change lang url requests add (make sure this is outside i18n_patterns

(r'^i18n/', include('django.conf.urls.i18n'))
)

url的形式如下: http://127.0.0.1:8000/en/inspection/dailyinspection

切换的时候碰到一点问题,从set_language函数里可以看到,next url首先是从next里面获取,如果没有,则从request.META.get('HTTP_REFERER')里面。

如果不设next,那么request.META.get('HTTP_REFERER')重定向的还是当前的页面(en),切换不成功

在form里设置next,如下

{% load langs %}

{% if languages %}
<form class="navbar-form navbar-left" id="language-select-form" method="post" action="{% url 'setlang' %}">{% csrf_token %}
    <select class="dropdown form-control" onchange="this.form.submit();" name="language">
        {% for lang in languages %}
        <option value="{{ lang.code }}" {% if lang.code == default %}selected="selected"{% endif %}>{{ lang.name_local }}</option>
        {% endfor %}
    </select>
    <input name="next" type="hidden" value="{{ request.get_full_path|strip_lang }}" >
</form>
{% endif %}
@register.filter(name="strip_lang")
def strip_lang(value):
    lang = translation.get_language()
    print lang, value
    strip_url = '/%s' % value.lstrip('/%s/' % lang) 
    return strip_url

则重定向到http://127.0.0.1:8000/inspection/dailyinspection,这个又是不合法的url,因为url pattern已经改成了i18n_patterns

尝试在middleware中修改next,系统报错这个字典是immutable

if request.method == "POST":
    lang = request.POST.get("language", None)
    if lang:
        next_url = request.POST.get('next',None)
        if next_url:
            next_url_modified = '/%s%s' % (lang, next_url)
            request.POST.update({'next': next_url_modified})

最后用了修改set_language的方法,照着系统的方法修改了一下

url(r'^setlang/$', 'zakkabag.views.set_language', name='setlang'),

def set_language(request):
    next = request.POST.get('next', request.GET.get('next'))
    if not is_safe_url(url=next, host=request.get_host()):
        next = request.META.get('HTTP_REFERER', None)
        if not is_safe_url(url=next, host=request.get_host()):
            next = reverse("home", kwargs={}) 
    response = http.HttpResponseRedirect(next)
    lang_code = request.POST.get('language', None) if request.method == 'POST' else request.GET.get('language', None)
    if lang_code and check_for_language(lang_code):
        if hasattr(request, 'session'):
            request.session[LANGUAGE_SESSION_KEY] = lang_code
        else:
            response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code,
                max_age=settings.LANGUAGE_COOKIE_AGE,
                path=settings.LANGUAGE_COOKIE_PATH,
                domain=settings.LANGUAGE_COOKIE_DOMAIN)

    return response
get方式切换语言

模仿后台

def set_language(request):
    next = request.REQUEST.get('next', None)
    if not next:
        next = request.META.get('HTTP_REFERER', None)
    if not next:
        next = reverse("home", kwargs={}) 
    response = http.HttpResponseRedirect(next)
    if request.method == 'GET':
        lang_code = request.GET.get('language', None)
        if lang_code and check_for_language(lang_code):
            if hasattr(request, 'session'):
                request.session[LANGUAGE_SESSION_KEY] = lang_code
            else:
                response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
    return response

前台

           <li><a href="{% url 'set_language' %}?language='zh-cn'&next={{request.path}}">chinese</a></li>
           <li><a href="{% url 'set_language' %}?language='en'&next={{request.path}}">english</a></li>  

但是check_for_language这个函数总是失败,实际上还没有进入该函数,在它的wrapper就返回False了

前台也可以用下面方式

<ul class="lang-switch">
    {% get_language_info_list for LANGUAGES as languages %}
      {% for language in languages %}
      {% language lang_code %}
      <li><a href="#" rel="{{ language.code }}">{{ language.code|upper }}</a></li>
      {% endlanguage %}
      {% endfor %}
</ul>

GET方式没有成功,后面再看吧

问题汇总

No translation file found for domain 'django'

检查.gitignore文件,看看是不是忽略了.mo文件,把这个注释掉就好了

django.core.exceptions.ImproperlyConfigured: LANGUAGE_CODE "zh-cn" must have a matching entry in LANGUAGES

https://docs.djangoproject.com/en/1.8/ref/settings/

参考