Skip to main content

Django i18n: 국제화 및 번역 가이드

settings.py부터 프로덕션까지: Django의 번역 시스템을 구성하고, PO 파일을 작성하며, 복수형을 처리하고, AI로 번역을 자동화해요.

1

설치 및 문자열 추출

Django에는 i18n 프레임워크가 내장되어 있어 활성화하기만 하면 돼요. 지능형 로케일 폴백을 위해 django-locale-chain을 설치한 다음, makemessages를 사용해 Python 코드와 템플릿에서 번역 가능한 문자열을 추출하여 PO 파일에 저장해요.

Django는 내부적으로 GNU gettext를 사용해요. makemessages 명령은 코드에서 gettext 호출과 템플릿 태그를 검색한 후 .po 파일을 생성해요. 번역을 마치면 compilemessages가 런타임에서 빠르게 조회할 수 있도록 이를 바이너리 .mo 파일로 변환해요.
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

설정 및 미들웨어 구성

settings.py에서 USE_I18N = True로 설정하고, 지원할 LANGUAGES 목록을 정의한 다음, MIDDLEWARE 스택에 LocaleMiddleware를 추가하여 국제화를 활성화해요. LocaleMiddleware는 URL 접두사, 세션, 쿠키 또는 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는 SessionMiddleware 뒤에(세션을 읽기 때문이에요), CommonMiddleware 앞에 와야 해요. 순서가 잘못되면 언어 감지가 아무런 오류 없이 실패하여 모든 사용자에게 기본 LANGUAGE_CODE가 표시돼요.

URL 국제화

i18n_patterns()를 사용하면 활성 언어 코드를 URL 앞에 자동으로 추가할 수 있어요. 언어마다 고유한 URL 네임스페이스(/en/about/, /de/about/)가 생기므로 SEO에 유리하고, 사용자가 특정 언어의 링크를 공유할 수 있어요.

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

번역할 문자열 표시

Django는 두 가지 주요 번역 함수를 제공해요. 요청 시 평가되는 문자열에는 gettext() (별칭 _())를, 임포트 시 평가되는 문자열에는 gettext_lazy()를 사용해요. 템플릿에서는 {% trans %} 및 {% blocktrans %} 태그를 사용해요.

뷰에서 사용(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}")

템플릿에서 사용

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>

모델 및 양식에서 사용

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
모델 필드, 양식 레이블 또는 클래스 수준 속성에서 gettext_lazy() 대신 gettext()를 사용하면 요청마다 번역되지 않고 임포트 시점(서버가 시작될 때)에 한 번만 번역돼요. 번역은 임포트 시 활성화된 언어, 즉 일반적으로 기본 LANGUAGE_CODE로 고정돼요.

PO 파일 형식

makemessages를 실행하면 Django가 언어별로 .po(Portable Object) 파일을 생성해요. 이 파일에는 msgid/msgstr 쌍이 들어 있어요. msgstr 값을 번역한 후 compilemessages를 실행하면 Django가 런타임에 읽는 바이너리 .mo 파일이 생성돼요.

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

복수형 및 변수 처리

Django는 GNU gettext의 복수형 규칙을 따르는 ngettext()로 복수형을 처리해요. 언어마다 복수형의 개수와 올바른 형식을 선택하는 공식이 정해져 있어요. PO 파일에서는 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}
gettext 호출 안에서는 Python f-string이나 .format()을 절대 사용하지 마세요. 번역 추출기가 이를 파싱할 수 없어요. 예전 방식의 %() 서식을 사용하세요: _('Hello, %(name)s!') % {'name': name}. 이렇게 하면 번역가도 PO 편집기에서 변수 플레이스홀더를 확인할 수 있어요.

번역 품질 자동화

i18n-validate로 출시 전에 누락된 키와 손상된 플레이스홀더를 찾아요. 실제 번역이 준비되기 전에 i18n-pseudo의 의사 번역으로 UI를 테스트해요.

흔히 발생하는 문제

gettext()와 gettext_lazy()의 혼동

gettext_lazy()를 _라는 이름으로 임포트하여 모델 필드, 양식 레이블, 임포트 시 평가되는 모든 클래스 수준 속성에 사용해요. 뷰 함수와 요청 시 실행되는 코드에는 gettext()를 사용해요. 두 함수를 혼동하면 번역이 서버 시작 시의 언어로 고정돼요.

compilemessages 실행 누락

Django는 .po 파일이 아니라 .mo(컴파일된) 파일을 읽어요. .po 파일을 편집한 뒤 'python manage.py compilemessages'를 실행하지 않으면 변경 사항이 표시되지 않아요. 배포 스크립트에 compilemessages를 추가하세요.

LOCALE_PATHS 미설정

LOCALE_PATHS가 비어 있거나 잘못된 디렉터리를 가리키면 makemessages가 엉뚱한 위치에 .po 파일을 만들고, Django가 런타임에 해당 파일을 찾지 못해요. 항상 LOCALE_PATHS = [BASE_DIR / 'locale']로 설정하고 디렉터리가 존재하는지 확인하세요.

URL에 i18n_patterns 누락

i18n_patterns()가 없으면 URL에 언어 접두사가 없어 Django가 언어 감지를 쿠키와 헤더에만 의존해요. 언어별 URL이 없어 SEO에 불리하고 URL을 통한 언어 전환도 작동하지 않아요. 사용자에게 표시되는 URL 패턴을 i18n_patterns()로 감싸세요.

django-locale-chain을 활용한 지능형 로케일 폴백

Django의 번역 시스템은 지역 변형이 없을 때 LANGUAGE_CODE로 바로 폴백해요. pt-PT 번역이 있어도 pt-BR 사용자에게 영어가 표시돼요. django-locale-chain은 gettext 폴백 체인을 설치하여 이 문제를 해결해요. pt-BR은 pt-PT, pt 순으로 시도한 후 기본 언어로 폴백해요.

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"]})

권장 프로젝트 구조

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

지금 i18n Agent 사용해 보기

번역 파일을 여기에 드롭

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

또는 클릭하여 파일 선택

대상 언어

가입 불필요즉시 견적

django-locale-chain을 활용한 로케일 폴백

pt-BR 같은 지역 로케일에 번역 키가 없으면 Django는 상위 로케일 pt를 먼저 확인하지 않고 템플릿 언어로 바로 이동해요.

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',
    ...
]

지원되는 프레임워크 전체 목록과 75개의 기본 제공 체인은 로케일 폴백 가이드에서 확인하세요. Learn more →

자주 묻는 질문