Skip to main content

Django i18n: Hướng dẫn quốc tế hóa và dịch thuật

Từ settings.py đến môi trường thực tế: cấu hình hệ thống dịch của Django, viết tệp PO, xử lý dạng số nhiều và tự động dịch bằng AI.

1

Cài đặt và trích xuất chuỗi

Django tích hợp sẵn framework i18n, bạn chỉ cần bật lên. Cài đặt django-locale-chain để dùng cơ chế dự phòng locale thông minh, rồi dùng makemessages để trích xuất các chuỗi có thể dịch từ mã Python và mẫu vào tệp PO.

Bên trong, Django sử dụng GNU gettext. Lệnh makemessages quét mã để tìm lời gọi gettext và thẻ mẫu, rồi tạo tệp .po. Sau khi dịch, compilemessages chuyển chúng thành tệp nhị phân .mo để tra cứu nhanh khi chạy.
Terminal
pip install django-locale-chain
Terminal
# Extract all translatable strings from Python and template files
python manage.py makemessages -l de -l ja -l es -l fr

# After translating .po files, compile to .mo (binary)
python manage.py compilemessages

# Project structure after running makemessages:
# locale/
# ├── de/
# │   └── LC_MESSAGES/
# │       ├── django.po    <-- translate this
# │       └── django.mo    <-- compiled (auto-generated)
# ├── ja/
# │   └── LC_MESSAGES/
# │       ├── django.po
# │       └── django.mo
# └── es/
#     └── LC_MESSAGES/
#         ├── django.po
#         └── django.mo
2

Cấu hình cài đặt và middleware

Bật quốc tế hóa trong settings.py bằng cách đặt USE_I18N = True, xác định danh sách LANGUAGES được hỗ trợ và thêm LocaleMiddleware vào ngăn xếp MIDDLEWARE. LocaleMiddleware phát hiện ngôn ngữ của người dùng qua tiền tố URL, phiên, cookie hoặc tiêu đề Accept-Language.

settings.py
# settings.py

from django.utils.translation import gettext_lazy as _

# Default language
LANGUAGE_CODE = 'en'

# Enable i18n
USE_I18N = True
USE_L10N = True

# Languages your site supports
LANGUAGES = [
    ('en', _('English')),
    ('de', _('German')),
    ('ja', _('Japanese')),
    ('es', _('Spanish')),
    ('fr', _('French')),
    ('pt-br', _('Brazilian Portuguese')),
]

# Where Django looks for .po files
LOCALE_PATHS = [
    BASE_DIR / 'locale',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',       # <-- enables i18n
    'locale_chain.middleware.LocaleChainMiddleware',    # <-- smart fallbacks
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
LocaleMiddleware phải nằm sau SessionMiddleware (vì đọc phiên) và trước CommonMiddleware. Nếu đặt sai thứ tự, việc phát hiện ngôn ngữ sẽ âm thầm thất bại và mọi người dùng đều thấy LANGUAGE_CODE mặc định.

Quốc tế hóa URL

Dùng i18n_patterns() để tự động thêm mã ngôn ngữ đang hoạt động vào đầu URL. Mỗi ngôn ngữ sẽ có không gian tên URL riêng (/en/about/, /de/about/), giúp SEO tốt hơn và cho phép người dùng chia sẻ liên kết theo ngôn ngữ.

urls.py
# urls.py
from django.conf.urls.i18n import i18n_patterns
from django.urls import path, include

urlpatterns = [
    # Non-localized URLs (API, admin, etc.)
    path('api/', include('api.urls')),
]

urlpatterns += i18n_patterns(
    # These get prefixed with the language code: /en/about/, /de/about/
    path('', include('myapp.urls')),
    path('admin/', admin.site.urls),
    prefix_default_language=False,  # Skip prefix for default language
)
3

Đánh dấu chuỗi cần dịch

Django cung cấp hai hàm dịch chính: gettext() (bí danh _()) cho chuỗi được đánh giá khi xử lý yêu cầu và gettext_lazy() cho chuỗi được đánh giá khi import. Trong mẫu, dùng các thẻ {% trans %} và {% blocktrans %}.

Trong view (mã Python)

views.py
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from django.http import HttpResponse

def greeting_view(request):
    # Simple translation
    welcome = _("Welcome to our site")

    # Translation with variables (Python string formatting)
    user_greeting = _("Hello, %(name)s!") % {"name": request.user.username}

    # Plurals
    count = request.user.order_set.count()
    order_text = ngettext(
        "You have %(count)d order.",
        "You have %(count)d orders.",
        count,
    ) % {"count": count}

    return HttpResponse(f"{welcome}<br>{user_greeting}<br>{order_text}")

Trong mẫu

templates/myapp/index.html
{# Load the i18n template tags #}
{% load i18n %}

{# Simple translation #}
<h1>{% trans "Welcome to our site" %}</h1>

{# Translation with variables #}
{% blocktrans with name=user.username %}
  Hello, {{ name }}!
{% endblocktrans %}

{# Plurals in templates #}
{% blocktrans count count=order_count %}
  You have {{ count }} order.
{% plural %}
  You have {{ count }} orders.
{% endblocktrans %}

{# Mark strings as translatable but don't output them (for attributes, etc.) #}
{% trans "Submit" as submit_label %}
<button type="submit">{{ submit_label }}</button>

Trong model và biểu mẫu

models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

class Product(models.Model):
    name = models.CharField(_("product name"), max_length=200)
    description = models.TextField(_("description"), blank=True)

    class Meta:
        verbose_name = _("product")
        verbose_name_plural = _("products")

    def __str__(self):
        return self.name

# IMPORTANT: Use gettext_lazy (_) for anything evaluated at import time:
# - Model field labels, verbose_name, help_text
# - Form field labels
# - Class-level attributes
# Use gettext for anything evaluated at request time:
# - View functions, template tags
Nếu dùng gettext() thay cho gettext_lazy() trong trường model, nhãn biểu mẫu hoặc thuộc tính cấp lớp, chuỗi chỉ được dịch một lần khi import (lúc máy chủ khởi động) thay vì theo từng yêu cầu. Bản dịch sẽ bị cố định theo ngôn ngữ đang hoạt động lúc import, thường là LANGUAGE_CODE mặc định.

Định dạng tệp PO

Sau khi chạy makemessages, Django tạo tệp .po (Portable Object) cho từng ngôn ngữ. Các tệp này chứa cặp msgid/msgstr. Dịch giá trị msgstr, rồi chạy compilemessages để tạo tệp nhị phân .mo mà Django đọc khi chạy.

locale/de/LC_MESSAGES/django.po
# locale/de/LC_MESSAGES/django.po

msgid "Welcome to our site"
msgstr "Willkommen auf unserer Seite"

msgid "Hello, %(name)s!"
msgstr "Hallo, %(name)s!"

#, python-format
msgid "You have %(count)d order."
msgid_plural "You have %(count)d orders."
msgstr[0] "Sie haben %(count)d Bestellung."
msgstr[1] "Sie haben %(count)d Bestellungen."

msgid "product name"
msgstr "Produktname"

msgid "description"
msgstr "Beschreibung"

msgid "product"
msgstr "Produkt"

msgid "products"
msgstr "Produkte"

msgid "Submit"
msgstr "Absenden"
4

Xử lý dạng số nhiều và biến

Django dùng ngettext() cho dạng số nhiều theo quy tắc số nhiều của GNU gettext. Mỗi ngôn ngữ xác định số dạng số nhiều và công thức chọn dạng phù hợp. Tệp PO khai báo thông tin này bằng tiêu đề Plural-Forms.

Plural forms by language
# English: 2 forms (singular, plural)
msgid "%(count)d item"
msgid_plural "%(count)d items"
msgstr[0] "%(count)d item"
msgstr[1] "%(count)d items"

# German: 2 forms (singular, plural)
msgstr[0] "%(count)d Artikel"
msgstr[1] "%(count)d Artikel"

# Russian: 3 forms (one, few, many)
msgstr[0] "%(count)d товар"        # 1 item
msgstr[1] "%(count)d товара"       # 2-4 items
msgstr[2] "%(count)d товаров"      # 5+ items

# Arabic: 6 forms (zero, one, two, few, many, other)
msgstr[0] "لا عناصر"               # 0
msgstr[1] "عنصر واحد"              # 1
msgstr[2] "عنصران"                 # 2
msgstr[3] "%(count)d عناصر"        # 3-10
msgstr[4] "%(count)d عنصرًا"       # 11-99
msgstr[5] "%(count)d عنصر"         # 100+

# Japanese: 1 form (no plural distinction)
msgstr[0] "%(count)d個のアイテム"

# In Python code, always use ngettext:
from django.utils.translation import ngettext

msg = ngettext(
    "%(count)d item",
    "%(count)d items",
    count,
) % {"count": count}
Không dùng f-string của Python hoặc .format() trong lời gọi gettext vì trình trích xuất bản dịch không thể phân tích chúng. Hãy dùng định dạng %() kiểu cũ: _('Hello, %(name)s!') % {'name': name}. Cách này cũng giúp người dịch thấy chữ giữ chỗ của biến trong trình biên tập PO.

Tự động kiểm soát chất lượng bản dịch

Dùng i18n-validate để phát hiện khóa thiếu và chữ giữ chỗ hỏng trước khi phát hành. Thử nghiệm UI bằng bản dịch giả lập qua i18n-pseudo trước khi có bản dịch thật.

Lỗi thường gặp

Nhầm lẫn giữa gettext() và gettext_lazy()

Dùng gettext_lazy() (được import dưới tên _) cho trường model, nhãn biểu mẫu và mọi thuộc tính cấp lớp được đánh giá khi import. Dùng gettext() cho hàm view và mã chạy khi xử lý yêu cầu. Dùng lẫn hai hàm sẽ cố định bản dịch theo ngôn ngữ khởi động của máy chủ.

Quên chạy compilemessages

Django đọc tệp .mo (đã biên dịch), không đọc tệp .po. Nếu bạn sửa tệp .po mà không chạy 'python manage.py compilemessages', thay đổi sẽ không xuất hiện. Hãy thêm compilemessages vào tập lệnh triển khai.

Chưa cấu hình LOCALE_PATHS

Nếu LOCALE_PATHS trống hoặc trỏ đến sai thư mục, makemessages sẽ tạo tệp .po ở sai nơi và Django không thể tìm thấy khi chạy. Luôn đặt LOCALE_PATHS = [BASE_DIR / 'locale'] và xác minh thư mục tồn tại.

URL thiếu i18n_patterns

Không có i18n_patterns(), URL sẽ không có tiền tố ngôn ngữ và Django chỉ dựa vào cookie/tiêu đề để phát hiện ngôn ngữ. Điều này gây bất lợi cho SEO (không có URL theo ngôn ngữ) và làm hỏng việc chuyển ngôn ngữ qua URL. Hãy bọc các mẫu URL dành cho người dùng bằng i18n_patterns().

Dự phòng locale thông minh với django-locale-chain

Hệ thống dịch của Django chuyển thẳng về LANGUAGE_CODE khi thiếu một biến thể khu vực. Người dùng pt-BR sẽ thấy tiếng Anh ngay cả khi bạn có bản dịch pt-PT. django-locale-chain khắc phục bằng chuỗi dự phòng gettext: pt-BR thử pt-PT, rồi pt trước khi trở về ngôn ngữ mặc định.

settings.py
# settings.py -- Smart fallback with django-locale-chain
# pip install django-locale-chain

MIDDLEWARE = [
    # ...
    'django.middleware.locale.LocaleMiddleware',
    'locale_chain.middleware.LocaleChainMiddleware',  # after LocaleMiddleware
    # ...
]

# That's it! 75 built-in fallback chains are now active:
# pt-BR user → tries pt-PT → tries pt → falls back to LANGUAGE_CODE
# es-MX user → tries es-419 → tries es → falls back to LANGUAGE_CODE
# fr-CA user → tries fr → falls back to LANGUAGE_CODE

# Optional: customize specific chains
LOCALE_FALLBACK_CHAINS = {
    "pt-BR": ["pt-PT", "pt"],
    "es-MX": ["es-419", "es"],
    "fr-CA": ["fr"],
}

# Or configure programmatically in AppConfig.ready():
from locale_chain import configure

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        configure(overrides={"zh-Hant-HK": ["zh-Hant-TW", "zh-Hant"]})

Cấu trúc dự án đề xuất

Project Structure
myproject/
├── myproject/
│   ├── settings.py          # i18n config, MIDDLEWARE, LANGUAGES
│   ├── urls.py              # i18n_patterns for URL prefixing
│   └── wsgi.py
├── myapp/
│   ├── models.py            # gettext_lazy for field labels
│   ├── views.py             # gettext for request-time strings
│   └── templates/
│       └── myapp/
│           └── index.html   # {% load i18n %}, {% trans %}, {% blocktrans %}
├── locale/                  # Created by makemessages
│   ├── de/
│   │   └── LC_MESSAGES/
│   │       ├── django.po    # German translations
│   │       └── django.mo    # Compiled binary
│   ├── ja/
│   │   └── LC_MESSAGES/
│   │       ├── django.po
│   │       └── django.mo
│   └── es/
│       └── LC_MESSAGES/
│           ├── django.po
│           └── django.mo
├── manage.py
└── requirements.txt

Dùng thử i18n Agent ngay

Thả tệp bản dịch của bạn vào đây

JSON, YAML, PO, XML, CSV, Markdown, Properties

hoặc nhấp để duyệt

Ngôn ngữ đích

Không cần đăng kýBáo giá tức thì

Dự phòng locale với django-locale-chain

Khi thiếu khóa dịch trong một locale khu vực như pt-BR, Django chuyển thẳng về ngôn ngữ mẫu thay vì kiểm tra locale cha pt trước.

Terminal
pip install django-locale-chain
Configuration
# settings.py
LOCALE_CHAINS = {
    'pt-BR': ['pt', 'es', 'en'],
    'zh-Hant-HK': ['zh-Hant', 'zh', 'en'],
}

MIDDLEWARE = [
    ...
    'django_locale_chain.middleware.LocaleChainMiddleware',
    ...
]

Xem Hướng dẫn dự phòng locale để biết danh sách đầy đủ các framework được hỗ trợ và 75 chuỗi tích hợp sẵn. Learn more →

Câu hỏi thường gặp