Django项目的多语言支持

引用

https://testdriven.io/blog/multiple-languages-in-django/

Django是一个非常流行和强大的Python Web框架,它提供了很多内置的功能,让开发者可以快速地构建高性能、安全、可扩展的网站。其中一个功能就是国际化和本地化,也就是让网站能够根据用户的语言和地区偏好,显示不同的内容和格式。这样,网站就可以覆盖更多的用户群体,提高用户体验和满意度。

在这篇博客中,我将介绍如何使用Django的国际化和本地化功能,以及一些第三方库,来实现多语言网站的开发。我将以一个简单的博客网站为例,展示如何让它支持英语、法语、西班牙语、越南语、波兰语、泰语、韩语、日语、简体中文和繁体中文这十种语言。我将分为以下几个步骤:

  • 基本设置
  • 模型和视图的国际化
  • 模板的国际化
  • URL的国际化
  • 静态文件的国际化
  • 语言切换
  • 使用第三方库

基本设置

首先,我们需要在项目的settings.py文件中,做一些基本的设置,来启用国际化和本地化功能,以及指定支持的语言和默认语言。具体的设置如下:

    # 基本设置

    LANGUAGE_CODE = 'zh-hans' # 默认语言,这里我设置为简体中文
    #PARLER_DEFAULT_LANGUAGE_CODE = 'zh-hans' # 这是一个第三方库的设置,后面会介绍

    TIME_ZONE = 'UTC' # 时区,这里我设置为UTC,你可以根据需要修改

    USE_I18N = True # 启用国际化功能

    USE_L10N = True # 启用本地化功能

    USE_TZ = True # 启用时区支持

    # https://github.com/django/django/blob/main/django/conf/global_settings.py#L56
    LANGUAGES = ( # 指定支持的语言,这里我设置了十种语言,你可以根据需要增加或删除
        ('en', ('English')),
        ('fr', ('Français')),
        ('es', ('Español')),
        ('vi', ('Tiếng việt')),
        ('pl', ('Polskie')),
        ('th', ('ไทย')),
        ('ko', ('한국어')),
        ('ja', ('日本語')),
        ("zh-hans", ("简体中文")),
        ("zh-hant", ("繁體中文"))
    )

    LOCALE_PATHS = [ # 指定存放翻译文件的路径,这里我设置为项目根目录下的locale文件夹
        os.path.join(os.path.dirname(os.path.dirname(__file__)), "locale")
    ]

模型和视图的国际化

接下来,我们需要在模型和视图中,标记需要翻译的字符串,这样Django就可以根据用户的语言,显示相应的翻译。在Python文件中,我们可以使用gettext_lazy这个函数,来标记需要翻译的字符串,它会在运行时才进行翻译,而不是在导入时。我们需要在文件开头,导入这个函数,如下:

    # 导入gettext_lazy函数
    from django.utils.translation import gettext_lazy as _

然后,我们就可以在模型和视图中,使用这个函数,来标记需要翻译的字符串,例如:

# 模型的国际化
from django.db import models
from django.utils.translation import gettext_lazy as _

class Post(models.Model): # 定义一个博客文章的模型
    title = models.CharField(_("title"), max_length=200) # 标记标题字段需要翻译
    content = models.TextField(_("content")) # 标记内容字段需要翻译
    created_at = models.DateTimeField(_("created at"), auto_now_add=True) # 标记创建时间字段需要翻译
    updated_at = models.DateTimeField(_("updated at"), auto_now=True) # 标记更新时间字段需要翻译

    def __str__(self):
        return self.title

    class Meta: # 定义模型的元数据
        verbose_name = _("post") # 标记模型的名称需要翻译
        verbose_name_plural = _("posts") # 标记模型的复数形式需要翻译

# 视图的国际化
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _

from .models import Post

def index(request): # 定义一个首页视图
    posts = Post.objects.all() # 获取所有的文章
    context = {
        "posts": posts,
        "title": _("My Blog"), # 标记标题需要翻译
        "welcome": _("Welcome to my blog!") # 标记欢迎语需要翻译
    }
    return render(request, "index.html", context) # 渲染模板

模板的国际化

在模板中,我们不能直接使用gettext_lazy这个函数,而是使用{% trans %}和{% blocktrans %}这两个模板标签,来标记需要翻译的字符串。使用这两个模板标签前,需要在模板的最开始处,加载i18n模板库,如下:

    <!-- 加载i18n模板库 -->
    {% load i18n %}

然后,我们就可以在模板中,使用{% trans %}和{% blocktrans %}这两个模板标签,来标记需要翻译的字符串,例如:

    <!-- 模板的国际化 -->
    {% load i18n %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{ title }}</title>
    </head>
    <body>
        <h1>{{ title }}</h1>
        <p>{% trans "This is a simple blog website that supports multiple languages." %}</p> <!-- 标记这句话需要翻译 -->
        <p>{{ welcome }}</p>
        <ul>
            {% for post in posts %}
            <li>
                <h2>{{ post.title }}</h2>
                <p>{{ post.content }}</p>
                <p>{% blocktrans %}Created at {{ post.created_at }}{% endblocktrans %}</p> <!-- 标记这句话需要翻译,注意变量要放在里面 -->
                <p>{% blocktrans %}Updated at {{ post.updated_at }}{% endblocktrans %}</p> <!-- 标记这句话需要翻译,注意变量要放在里面 -->
            </li>
            {% endfor %}
        </ul>
    </body>
    </html>

URL的国际化

为了让URL也能根据用户的语言,显示不同的路径,我们需要在项目的urls.py文件中,做一些修改,来启用URL的国际化。具体的修改如下:

    # URL的国际化
    from django.conf.urls.i18n import i18n_patterns # 导入i18n_patterns函数
    from django.contrib import admin
    from django.urls import path, include

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('rosetta/', include('rosetta.urls')), # 这是一个第三方库的URL,后面会介绍
    ]

    urlpatterns += i18n_patterns( # 使用i18n_patterns函数来添加语言前缀
        path('', include('blog.urls')),
        prefix_default_language=False # 这个参数表示是否给默认语言也添加前缀,这里我设置为False,表示不添加
    )

静态文件的国际化

有时候,我们的静态文件,如图片,也需要根据用户的语言,显示不同的内容。例如,我们可能想要在首页上显示一个欢迎图片,但是图片上的文字需要和用户的语言一致。为了实现这个功能,我们可以使用一个第三方库django-statici18n。它可以根据我们的语言设置,生成不同的静态文件,并且在模板中自动选择正确的文件。

为了使用django-statici18n,我们需要先安装它,然后在settings.py文件中,添加一些设置,如下:

    # 静态文件的国际化
    # 安装django-statici18n
    pip install django-statici18n

    # settings.py
    INSTALLED_APPS = [
        ...
        'statici18n', # 添加statici18n应用
    ]

    # 配置静态文件的存放路径
    STATIC_URL = '/static/'
    STATIC_ROOT = os.path.join(BASE_DIR, 'static')

    # 配置静态文件的国际化
    STATICI18N_ROOT = os.path.join(BASE_DIR, 'blog', 'static') # 指定静态文件的源路径
    STATICI18N_OUTPUT_DIR = 'jsi18n' # 指定静态文件的输出目录

然后,我们需要在blog/static目录下,创建一个images目录,用来存放不同语言的图片。我们可以使用一个在线工具,如https://www.canva.com/zh_cn/,来制作图片,并保存为png格式。我们需要为每种语言制作一个图片,并命名为welcome-语言代码.png,例如,英语的图片命名为welcome-en.png,法语的图片命名为welcome-fr.png等。

接下来,我们需要在blog/static目录下,创建一个js目录,用来存放一个JavaScript文件,用于在模板中选择正确的图片。我们可以命名为welcome.js,并写入以下内容:

    // welcome.js
    // 获取当前语言
    var language = document.documentElement.lang;

    // 获取图片元素
    var image = document.getElementById("welcome-image");

    // 根据语言设置图片的源路径
    image.src = "/static/images/welcome-" + language + ".png";

最后,我们需要在模板中,引入这个JavaScript文件,并添加一个图片元素,如下:

<!-- 模板中引入静态文件的国际化 -->
{% load i18n %}
{% load static %}
{% load statici18n %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <!-- 引入静态文件的国际化 -->
    <script src="{% statici18n LANGUAGE_CODE %}"></script>
    <script src="{% static 'js/welcome.js' %}"></script>
</head>
<body>
    <h1>{{ title }}</h1>
    <p>{% trans "This is a simple blog website that supports multiple languages." %}</p>
    <p>{{ welcome }}</p>
    <!-- 添加一个图片元素 -->
    <img id="welcome-image" alt="{% trans 'Welcome image' %}">
    <ul>
        {% for post in posts %}
        <li>
            <h2>{{ post.title }}</h2>
            <p>{{ post.content }}</p>
            <p>{% blocktrans %}Created at {{ post.created_at }}{% endblocktrans %}</p>
            <p>{% blocktrans %}Updated at {{ post.updated_at }}{% endblocktrans %}</p>
        </li>
        {% endfor %}
    </ul>
</body>
</html>

这样,我们就完成了静态文件的国际化。我们可以运行以下命令,来收集静态文件,并查看效果:

    (env)$ python manage.py collectstatic
    (env)$ python manage.py runserver

语言切换

我们会在首页上添加一个语言列表,让用户可以选择不同的语言。我们会使用一些模板标签,如{% get_current_language %}, {% get_available_languages %}, {% get_language_info_list %}等,来获取和显示语言信息。我们还会使用一个自定义的过滤器,url_remove_language,来移除URL中的语言前缀,以便在切换语言时不会跳转到首页。

为了实现语言切换功能,我们需要在blog/templates目录下,创建一个base.html文件,用来作为所有模板的基础模板。我们可以在这个文件中,添加一个语言列表,如下:

<!-- base.html -->
{% load i18n %}
{% load static %}
{% load statici18n %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    <!-- 引入静态文件的国际化 -->
    <script src="{% statici18n LANGUAGE_CODE %}"></script>
    <script src="{% static 'js/welcome.js' %}"></script>
</head>
<body>
    <!-- 添加一个语言列表 -->
    <ul>
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}[^1^][1]
        {% for language in languages %}
        <li>
            <a href="/{{ language.code }}{{ request.get_full_path|url_remove_language }}" {% if language.code == LANGUAGE_CODE %}style="font-weight:bold"{% endif %}>
                {{ language.name_local }}
            </a>
        </li>
        {% endfor %}
    </ul>
    {% block content %}{% endblock %}
</body>
</html>

注意,我们使用了一个自定义的过滤器,url_remove_language,来移除URL中的语言前缀,以便在切换语言时不会跳转到首页。为了创建这个过滤器,我们需要在blog目录下,创建一个templatetags目录,并在其中创建一个custom_filters.py文件,用来存放自定义的过滤器。我们可以在这个文件中,写入以下内容:

# custom_filters.py
import re
from django import template

register = template.Library()

# 自定义语言过滤器
@register.filter(name="url_remove_language")
def url_remove_language(arg):
    pattern = r'^/[a-z]{2}(-[a-z]{2,4})?/'
    return re.sub(pattern, '/', arg)

然后,我们需要在index.html文件中,继承base.html文件,并修改一些内容,如下:

<!-- index.html -->
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% block title %}
{{ title }}
{% endblock %}
{% block content %}
    <h1>{{ title }}</h1>
    <p>{% trans "This is a simple blog website that supports multiple languages." %}</p>
    <p>{{ welcome }}</p>
    <!-- 添加一个图片元素 -->
    <img id="welcome-image" alt="{% trans 'Welcome image' %}">
    <ul>
        {% for post in posts %}
        <li>
            <h2>{{ post.title }}</h2>
            <p>{{ post.content }}</p>
            <p>{% blocktrans %}Created at {{ post.created_at }}{% endblocktrans %}</p>
            <p>{% blocktrans %}Updated at {{ post.updated_at }}{% endblocktrans %}</p>
        </li>
        {% endfor %}
    </ul>
{% endblock %}

这样,我们就完成了语言切换功能。我们可以刷新网页,看到不同的语言选项,并且可以在不同的语言之间切换。

使用第三方库

除了Django自带的国际化和本地化功能,我们还可以使用一些第三方库,来增强我们的多语言网站的开发。在这篇博客中,我们介绍了两个第三方库,分别是Rosetta和django-parler。

Rosetta

Rosetta是一个可以让你在Django管理站点的同一个界面中编辑翻译的库。它可以让你轻松地编辑.po文件,并且会自动更新编译后的翻译文件,无需手动运行命令。它还可以让你使用Google翻译或Microsoft翻译来自动翻译字符串,节省你的时间和精力。

为了使用Rosetta,我们需要先安装它,然后在settings.py文件中,添加一些设置,如下:

# 安装Rosetta
pip install django-rosetta

# settings.py
INSTALLED_APPS = [
    ...
    'rosetta', # 添加rosetta应用
]

# 配置Rosetta
ROSETTA_WSGI_AUTO_RELOAD = True # 设置为True,表示在保存翻译后,自动重载WSGI服务器
ROSETTA_UWSGI_AUTO_RELOAD = True # 设置为True,表示在保存翻译后,自动重载uWSGI服务器
ROSETTA_GOOGLE_TRANSLATE = True # 设置为True,表示启用Google翻译
ROSETTA_GOOGLE_TRANSLATE_API_KEY = 'your-api-key' # 设置Google翻译的API密钥,你需要先申请一个
# ROSETTA_MICROSOFT_TRANSLATOR = True # 设置为True,表示启用Microsoft翻译
# ROSETTA_MICROSOFT_TRANSLATOR_API_KEY = 'your-api-key' # 设置Microsoft翻译的API密钥,你需要先申请一个

然后,我们需要在项目的urls.py文件中,添加一个URL,如下:

# urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('rosetta/', include('rosetta.urls')), # 添加rosetta的URL
]

接下来,我们就可以在浏览器中,访问http://127.0.0.1:8000/rosetta/,看到Rosetta的界面。我们可以在这里选择不同的语言,编辑和保存翻译,以及使用自动翻译功能。

django-parler

django-parler是一个可以让你翻译模型的库。由于Django本身不支持翻译模型,所以我们需要使用这个库。它会为每个包含翻译的模型创建一个单独的数据库表。这个表包含所有的翻译字段,以及一个外键来链接原始对象。django-parler提供了一个TranslatableModel模型类和一个TranslatedFields包装器来翻译模型字段。2它还提供了一个TranslatableAdmin类来在Django管理站点中管理翻译。

为了使用django-parler,我们需要先安装它,然后在settings.py文件中,添加一些设置,如下:

# 安装django-parler
pip install django-parler

# settings.py
INSTALLED_APPS = [
    ...
    'parler', # 添加parler应用
]

# 配置parler
PARLER_LANGUAGES = {
    None: (
        {'code': 'en',},
        {'code': 'fr',},
        {'code': 'es',},
        {'code': 'vi',},
        {'code': 'pl',},
        {'code': 'th',},
        {'code': 'ko',},
        {'code': 'ja',},
        {'code': 'zh-hans',},
        {'code': 'zh-hant',},
    ),
    'default': {
        'fallback': 'zh-hans',             # 默认的回退语言,这里我设置为简体中文
        'hide_untranslated': False,   # 是否隐藏未翻译的内容,这里我设置为False,表示显示
    }
}

然后,我们需要在模型中,使用django-parler提供的类和包装器,来翻译模型字段,如下:

# 模型的翻译
from django.db import models
from django.utils.translation import gettext_lazy as _
from parler.models import TranslatableModel, TranslatedFields # 导入django-parler提供的类和包装器

class Post(TranslatableModel): # 继承TranslatableModel类
    translations = TranslatedFields( # 使用TranslatedFields包装器来定义翻译字段
        title = models.CharField(_("title"), max_length=200), # 标记标题字段需要翻译
        content = models.TextField(_("content")), # 标记内容字段需要翻译
    )
    created_at = models.DateTimeField(_("created at"), auto_now_add=True) # 不需要翻译的字段,直接定义在模型中
    updated_at = models.DateTimeField(_("updated at"), auto_now=True) # 不需要翻译的字段,直接定义在模型中

    def __str__(self):
        return self.title

    class Meta: # 定义模型的元数据
        verbose_name = _("post") # 标记模型的名称需要翻译
        verbose_name_plural = _("posts") # 标记模型的复数形式需要翻译

最后,我们需要在admin.py文件中,使用django-parler提供的类,来注册模型,并管理翻译,如下:

# 管理站点的翻译
from django.contrib import admin
from parler.admin import TranslatableAdmin # 导入django-parler提供的类
from .models import Post

@admin.register(Post)
class PostAdmin(TranslatableAdmin): # 继承TranslatableAdmin类
    list_display = ('title', 'created_at', 'updated_at') # 定义列表显示的字段
    list_filter = ('translations__language_code',) # 定义列表过滤的字段,注意要加上translations__前缀
    search_fields = ('translations__title', 'translations__content') # 定义搜索的字段,注意要加上translations__前缀

这样,我们就完成了模型的翻译。我们可以运行以下命令,来创建和更新数据库表,并查看效果:

(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
(env)$ python manage.py createsuperuser
(env)$ python manage.py runserver

注意事项

  • 繁体中文的local文件夹名称 zh_Hant, 如果是zh_hant好像不工作。

评论

还没有人评论,抢个沙发吧...

Viagle Blog

欢迎来到我的个人博客网站