跳转至

Django多语言支持

1 开启I18N支持

USE_I18N = True
USE_L10N = True

2 设置

  • 1. 设置支持语言

# 语言选项
LANGUAGES = (
    ('zh-hans', '简体中文'),
    ('zh-hant', '繁体中文'),
    ('en', '英语'),
    ('es', '西班牙语'),
    ('bn', '孟加拉语'),
    ('id', '印地语'),
    ('pt', '葡萄牙语'),
    ('ru', '俄语'),
    ('ja', '日语'),
    ('de', '德语'),
    ('ko', '韩语'),
    ('fr', '法语'),
    ('ar', '阿拉伯语'),
)
  • 2. 设置默认语言

# 汉语 上海时区
LANGUAGE_CODE = 'zh-hans'
  • 3. 添加LocaleMiddlewareMIDDLEWARE

LocaleMiddleware必须在Session之后,因为语言区域来源于session中的设置,必须在CommonMiddleware之前,因为CommonMiddleware需要选择一个语言去反查url。

MIDDLEWARE = [
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
]
  • 4. 设置翻译文件存储位置

LOCALE_PATHS = (
    os.path.join(BASE_DIR, "locale/"),
)

3 模板翻译

  • 1. 简单翻译语法

  • 所有待翻译html必须加入{% load i18n %}标签,即使是被include的父html里加载过该标签,被include的html依旧需要加载。

  • 单独文字 {% trans "我是待翻译文字" %}

  • 区块文字

    {% blocktrans with year=product.publish_time|date:'Y' %}
        {{ year }}年上市
    {% endblocktrans %}
    
  • 2. 运行django-admin makemessages --all生成待翻译的django.po文件。

  • 3. 翻译django.po文件

安装django-rosetta插件,即可在admin中看到rosetta面板,进入即可对生成的待翻译的django.po文件进行翻译,无需再在代码中翻译。

或者直接在代码中打开django.po文件进行翻译。

  • 4. 运行django-admin compilemessages命令编译翻译好的django.po文件以生成django.mo文件。

  • 5. 复数翻译

{% blocktrans count news.view_times as news_view_times %}
    查看次数:{{ news_view_times }}
{% plural %}
    查看次数:{{ news_view_times }}
{% endblocktrans %}

一个未发现原因的Bug,0会被识别为复数,暂未找到解决方法,可见此处

4 内容翻译

from django.db import models
from hvad.models import TranslatableModel, TranslatedFields


class Demo(TranslatableModel):
    translations = TranslatedFields(
        # 标题
        title=models.CharField(max_length=100,
                               verbose_name=u'标题'),
    )

    class Meta:
        verbose_name = 'Demo'
        verbose_name_plural = 'Demo'
        ordering = ('-id',)

    def trans_title(self):
        return self.lazy_translation_getter('title', str(self.id))

    def __str__(self):
        title = self.trans_title()
        return title if title else ''
  • 3. forms.py

from django import forms
from hvad.admin import TranslatableModelForm

from .models import *


class DemoForm(TranslatableModelForm):

    def __init__(self, *args, **kwargs):
        super(ExhibitForm, self).__init__(*args, **kwargs)
        self.fields['title'].widget.attrs.update({'class': 'form-control'})

    class Meta:
        model = Exhibit
        fields = ['title']
  • 4. 如果使用django rest framework,不使用请忽略本部分。

serializers.py

from rest_framework import serializers
from hvad.contrib.restframework import TranslatableModelSerializer

from .models import *

class DemoSerializer(TranslatableModelSerializer):

    class Meta:
        model = Demo
        fields = ['title']
  • 5. 全部语言列表

class DemoAllListView(ListView):
    template_name = 'demo/list_all.html'
    context_object_name = 'demo_list'
    paginate_by = settings.PAGE_NUM

    def get_queryset(self):
        return Demo.objects.language('all').order_by('-id')
  • 6. 当前语言列表

class DemoListView(ListView):
    queryset = Demo.objects.language().order_by('-id')
    template_name = 'demo/list_lang.html'
    context_object_name = 'demo_list'
    paginate_by = settings.PAGE_NUM
  • 7. 创建当前语言的对象

class DemoCreateView(CreateView):
    form_class = DemoForm
    template_name = 'demo/create.html'
    success_url = reverse_lazy('demo_list')
  • 8. 查看当前语言的对象详情

class DemoDetailView(DetailView):
    queryset = Demo.objects.language().all()
    slug_field = 'id'
    template_name = 'demo/detail.html'
  • 9. 路由中设置了i18n_patterns时,当前获取到的{{demo.title}}既是对应语言的title。

5 url设置

from django.conf.urls.i18n import i18n_patterns

urlpatterns += i18n_patterns(
    url(r'^', include('web.urls')),
    prefix_default_language=False,
)

# 设置rosetta支持
if 'rosetta' in settings.INSTALLED_APPS:
    urlpatterns += [
        url(r'^rosetta/', include('rosetta.urls')),
    ]

prefix_default_languageFalse时,默认语言不再出现在url中。

6 可直接切换当前url语言的标签

web/templatetags/translate_url.py :

from django.template import Library
from django.urls.exceptions import Resolver404
from django.core.urlresolvers import resolve, reverse
from django.utils.translation import activate, get_language
from urllib.parse import urlencode

register = Library()


@register.simple_tag(takes_context=True)
def translate_url(context, lang=None, *args, **kwargs):
    """
    将当前页面url转换至其他语言的url
    用法: {% translate_url language.code %}
    """
    # request 对象
    request = context['request']
    # 当前语言环境
    cur_language = get_language()
    try:
        # 根据当前url检索url别名
        url_parts = resolve(request.path)
        # 激活欲转换的语言环境
        activate(lang)
        # 根据url别名检索出对应语言环境的url
        url = reverse(url_parts.url_name, args=url_parts.args, kwargs=url_parts.kwargs)
        # 保持request parameters不丢失
        parameters = urlencode(request.GET, doseq=True)
        if parameters:
            url = '{}?{}'.format(url, parameters)
    except Resolver404:
        # 未检索到则返回主页
        url = '/'
    finally:
        # 还原至当前语言环境
        activate(cur_language)
    return url

使用方法:

navbar.html:

{% load translate_url %}
<div class="head_bg">
    <div class="container">
        <div class="row">
            <div class="nav_language_select">
                {% get_language_info_list for current_languages as languages %}
                    <div class="col-md-2 col-sm-3 col-xs-5 text-right nav_language_div">
                    <div class="nav_language_div2">
                        {% get_language_info_list for current_languages as languages %}
                        {% for language in languages %}
                            {% if not forloop.first %}
                                <span>/</span>
                            {% endif %}
                            {% ifequal LANGUAGE_CODE language.code %}
                                <a href="{% translate_url language.code %}" class='nav_color_active'>
                                    {{ language.name_local }}
                                </a>
                            {% else %}
                                <a href="{% translate_url language.code %}">
                                    {{ language.name_local }}
                                </a>
                            {% endifequal %}
                        {% endfor %}
                    </div>
            </div>
        </div>
    </div>
</div>

Q&A

  • Q:运行 django-admin makemessages --all 命令后提示can't find msguniq. make sure you have gnu gettext tools 0.15 or newer installed.(window7 django1.11.1)

A:前往此处下载软件包解压,并设置其子bin目录至环境变量。

  • Q:运行 django-admin compilemessages 命令后,django提示ValueError: plural forms expression could be dangerous

A:修改生成的django.po文件中的"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"为"Plural-Forms: nplurals=1; plural=0;\n"即可。

  • Q:zh_Hans命名问题

A:setting中LANGUAGE_CODE = 'zh_Hans',locale中文件夹名称未zh_Hans,其余地方用zh-hans,如:

LANGUAGES = (
    ('en', 'English'),
    ('zh-hans', 'Chinese'),
)

具体原因参见下列 lang2locale 函数,做了如下操作:Basically, for our Chinese language codes, the - is replaced with a _, and the first letter of the second word is capitalized(只在后一个单词长度大于2的时候转换).

def to_locale(language, to_lower=False):
    """
    Turns a language name (en-us) into a locale name (en_US). If 'to_lower' is
    True, the last component is lower-cased (en_us).
    """
    p = language.find('-')
    if p >= 0:
        if to_lower:
            return language[:p].lower() + '_' + language[p + 1:].lower()
        else:
            # Get correct locale for sr-latn
            if len(language[p + 1:]) > 2:
                return language[:p].lower() + '_' + language[p + 1].upper() + language[p + 2:].lower()
            return language[:p].lower() + '_' + language[p + 1:].upper()
    else:
        return language.lower()
  • Q:加入django-rosetta后出现如2中描述的问题

A:找到rosetta安装目录,按2中解决方法修改rosetta/locale/en/LC_MESSAGE/django.po文件即可。

  • Q:需要只生成某个app的locale文件

A:需要进入该app目录后再执行makemessages命令,makemessages命令会生成当前执行命令的文件夹及子文件夹中的所有locale文件。所以在根目录执行makemessages命令会出现CommandError: Unable to find a locale path to store translations for file Porject\__init__.py的错误。

  • Q:翻译后的内容不实时生效

A:重启服务器翻译内容才能生效

相关知识

  1. Django决定当前语言的顺序(使用LocaleMiddleware的情况下,未使用则优先使用LANGUAGE_CODE):
    使用i18_patterns时,将优先匹配url中的语言代码。
    没有语言代码,使用session中的LANGUAGE_SESSION_KEY。
    没有session,检索cookie中的django_language参数,该参数使用LANGUAGE_COOKIE_NAME设置。
    没有cookie,使用http请求头中的Accept-Language。
    没有Accept-Language请求头,则使用系统默认设置的LANGUAGE_CODE。
  1. locale namelanguage code不同,locale name是混合了语言与城市的简写,语言部分总是小写,城市部分总是大写,用下划线连接,如it,deAT,es,ptBR等;language总是小写,用破则号连接,如it,de-at,es,ot-br。

  2. django-admin compilemessages --settings=path.to.settings 只编译设置中设置的LOCALE_PATHS中的语言文件。

  3. Django默认假设用户使用的可翻译的项目的基础语言为英文,不使用英文使用英文做为基础语言时(详见此处),需要注意: gettext 方法为原始语言只提供了两种复数形式,所以你需要为其他复数形式不同于英语的语言提供复数形式规则。当其他英文的子语言被使用并且英文翻译缺失时,返回的语言不会是LANGUAGE_CODE里面的语言,而是其原始语言。例如,一个英文用户访问一个西班牙语作为默认语言的网站,其原始字符是使用俄语写的,那么他将看到的是俄语而不是西班牙语。

  4. 使用 USE_L10N = True 可以启用formatting system,根据语言区域对页面显示格式做出个性化适配。

  5. Internationalization,i 和 n 之间有 18 个字母,简称 I18N。本地化 -- localization, l 和 n 之间有 10 个字母,简称 L10N。国际化意味着 Web产品有适用于任何地方的潜力,针对程序开发人员;本地化则是指使一个国际化的程序为了在某个特定地区使用而进行实际翻译的过程,针对翻译人员而言。