Django에서 gettext vs gettext_lazy 헷갈림 끝내기 (평가 시점으로 이해하기)
Django i18n을 쓰다 보면 gettext()와 gettext_lazy() 중 뭘 써야 하는지 매번 흔들립니다. 대부분의 혼란은 “둘의 차이”를 용어로 외우려 해서 생깁니다.
핵심은 딱 하나입니다.
gettext는 지금 번역한다 (즉시 평가, eager)gettext_lazy는 나중에 번역한다 (지연 평가, lazy)
이 “평가 시점” 하나로 거의 모든 케이스가 정리됩니다.
번역이 “언제” 결정되는지가 왜 중요할까?
Django는 보통 요청(request)마다 언어가 바뀝니다.

- 미들웨어가 요청을 보고
activate("ko")/activate("en")같은 걸로 현재 스레드/컨텍스트의 언어를 활성화 - 템플릿 렌더링, 폼 렌더링, admin 화면 렌더링 시점에 그 언어로 번역이 되어야 함
즉, 문자열을 “언제” 번역해버리느냐가 결과를 갈라요.
gettext() : “지금” 번역해 문자열을 반환
from django.utils.translation import gettext as _
def view(request):
message = _("Welcome") # 이 줄이 실행되는 순간의 활성 언어로 번역됨
return HttpResponse(message)
- 함수/뷰 내부처럼 요청 처리 중(runtime) 에 호출하면 대체로 기대한 대로 동작합니다.
- 하지만 모듈 import 시점에 호출하면 문제가 생깁니다.
흔한 함정: 모듈 상수에서 gettext() 쓰기
# app/constants.py
from django.utils.translation import gettext as _
WELCOME = _("Welcome") # ❌ 서버 시작/모듈 import 시점의 언어로 "고정"될 수 있음
이 경우 WELCOME는 “나중에” 언어가 바뀌어도 이미 번역된 문자열로 고정돼버립니다(특히 import가 한 번만 일어나는 환경에서).
gettext_lazy() : “나중에” 번역될 프록시(Proxy)를 반환
from django.utils.translation import gettext_lazy as _
WELCOME = _("Welcome") # ✅ 실제 문자열이 아니라, 필요할 때 번역되는 객체
gettext_lazy()가 돌려주는 건 보통 “lazy object(프록시)”입니다.
- 폼/템플릿/admin이 값을 문자열로 렌더링하는 시점에
- 그때 활성화된 언어로 번역됩니다.
한 줄 요약: “렌더링 시점에 언어가 정해지는 곳”에서는 lazy가 정답인 경우가 많다.
어디에 뭘 쓰면 되나: 실전 규칙
1) “지금 당장 화면/응답을 만들고 있다” → gettext
- 뷰/서비스 로직에서 즉시 문자열을 만들어 응답하거나 로그를 찍는 경우
from django.utils.translation import gettext as _
def signup_done(request):
return JsonResponse({"message": _("Signup completed.")})
2) “클래스 속성/모델 메타/폼 정의처럼, import 시점에 평가될 수 있다” → gettext_lazy
- 모델의
verbose_name,help_text - 폼 필드의
label,help_text - DRF serializer 필드의
label(비슷한 이유) - admin의
list_display설명 등 “정의는 미리, 렌더링은 나중”인 곳
from django.db import models
from django.utils.translation import gettext_lazy as _
class Article(models.Model):
title = models.CharField(_("title"), max_length=100) # verbose_name 위치
status = models.CharField(
_("status"),
max_length=20,
choices=[
("draft", _("Draft")),
("published", _("Published")),
],
help_text=_("Visibility of the article."),
)
3) “모듈 레벨 상수/choices 같은 재사용 값” → 보통 gettext_lazy
- 재사용되는 상수는 특히 lazy로 두는 게 안전합니다.
from django.utils.translation import gettext_lazy as _
STATUS_CHOICES = [
("draft", _("Draft")),
("published", _("Published")),
]
4) “외부 시스템으로 보내는 문자열(로그/서드파티 API/헤더 등)” → gettext 또는 lazy를 강제 평가
lazy 객체를 그대로 넘기면 예상치 못한 타입/직렬화 문제를 만들 수 있습니다.
from django.utils.translation import gettext_lazy as _
from django.utils.encoding import force_str
msg = _("Welcome")
logger.info(force_str(msg)) # ✅ 실제 문자열로 변환해서 사용
5) “문자열 합치기/포매팅이 포함된다” → lazy 전용 도구도 고려
lazy 메시지에 f-string/str.format()을 섞으면 평가 시점이 꼬일 수 있습니다. Django는 이를 위해 format_lazy를 제공합니다.
from django.utils.translation import gettext_lazy as _
from django.utils.text import format_lazy
title = format_lazy("{}: {}", _("Error"), _("Invalid token"))
또는 파이썬 % 포맷을 쓰면 번역 문자열 관리가 편합니다.
from django.utils.translation import gettext as _
message = _("Hello, %(name)s!") % {"name": user.username}
가장 많이 하는 실수 3가지
실수 1) 모듈 import 시점에 gettext()로 “고정 번역” 만들어버리기
- “상수/choices”가 있으면 우선 lazy를 의심하세요.
실수 2) lazy 객체를 JSON 직렬화/로그에 그대로 넣기
force_str()로 문자열로 바꾼 뒤 사용하세요.
실수 3) f-string으로 번역 문자열을 조립하기
# ❌ 비추천
_("Hello") + f" {user.username}"
- 번역 단위가 쪼개지고(언어마다 어순이 달라짐), 평가 시점도 더 복잡해집니다.
- 번역 문자열 안에서 변수 치환하세요.
# ✅ 추천
_("Hello, %(name)s!") % {"name": user.username}
헷갈림을 줄이는 Tips
헷갈림을 줄이는 가장 효과적인 방법은 파일 성격에 따라 _의 의미를 통일하는 겁니다.
models.py,forms.py,admin.py같이 “정의가 먼저인 파일”:from django.utils.translation import gettext_lazy as _views.py,services.py같이 “실행이 먼저인 파일”:from django.utils.translation import gettext as _
이렇게 하면 “여기서는 lazy가 기본”이라는 기준이 생겨서 실수가 줄어듭니다.
간단 정리
- 즉시 평가가 안전한 곳(런타임 로직):
gettext - 나중에 렌더링될 것(정의/메타/상수/choices/라벨):
gettext_lazy - 외부로 나가는 문자열: 필요하면
force_str - lazy + 포매팅:
format_lazy또는 번역 문자열 내부에서 치환
이 기준만 잡히면 “개념이 정립되지 않아서 헷갈리는” 상태에서 벗어나, 거의 자동으로 선택할 수 있습니다.
관련 글 링크
- gettext_lazy를 JSON 키에 쓰면 생기는 문제와 해결 방법
댓글이 없습니다.