Django에서 gettext_lazy를 JSON Key로 쓸 때 생기는 뒷목 잡는 상황

결론의 한줄 정리 : JSON Key는 반드시 '진짜' 문자열이어야 한다 , 하지만 Django의 gettext_lazy가 반환하는 것은 사실 문자열이 아니라 __proxy__라는 객체이다.

1. 잘 돌아가던 코드가 왜 갑자기?

Django 개발 중 다국어 처리를 위해 gettext_lazy를 쓰는 건 일상적인 일이죠. 그런데 평소처럼 잘 쓰다가 어느 날 갑자기 JSON 응답(JsonResponse) 과정에서 직렬화(Serialization) 에러가 터질 때가 있습니다.

보통은 에러 로그를 봐도 __proxy__ 어쩌구 하는 알 수 없는 메시지만 뜨니 당황하기 마련인데요. 원인을 뜯어보면 의외로 단순한 곳에 범인이 있습니다.

infographic about gettext_lazy and JSON key problem

2. 범인은 "Key"에 있었다

결론부터 말하면, gettext_lazy 객체는 딕셔너리의 Value로 들어갈 땐 조용히 넘어가 주지만, Key로 들어가는 순간 문제를 일으킵니다.

구분 Value에 썼을 때 Key에 썼을 때
작동 여부 정상 (자동 str 변환됨) 에러 발생
이유 JSON 직렬화 시 자동 형변환 지원 JSON Key는 반드시 '진짜' 문자열이어야 함

3. 왜 이런 차이가 생길까?

gettext_lazy가 반환하는 건 진짜 String이 아니라 __proxy__라는 객체입니다. 이름 그대로 "나중에 필요할 때 번역해서 줄게"라고 약속만 해둔 상태죠.

Python의 json.dumps()나 Django의 JsonResponse는 딕셔너리의 값을 훑으면서 이 '약속'을 알아서 문자열로 풀어줍니다(Evaluation). 하지만 딕셔너리의 Key는 이야기가 다릅니다. JSON 표준상 Key는 무조건 문자열이어야 하는데, 이 과정에서 __proxy__ 객체가 자동으로 변환되지 않고 그대로 부딪히니 에러가 나는 겁니다.

# ✅ 문제 없음: Value는 알아서 str로 변환됩니다.
json.dumps({'language': _('Korean')})

# ❌ 에러: Key는 스스로 변환되지 못하고 거절당합니다.
json.dumps({_('Korean'): 'language'})

4. 어떻게 해결할까?

방법 1: 그냥 gettext 쓰기 (가장 깔끔)

Lazy 방식이 꼭 필요한 상황이 아니라면, 호출 즉시 문자열을 반환하는 gettext를 사용하는 게 가장 속 편합니다.

from django.utils.translation import gettext as _ 

LANGUAGE_MAP = {
    "en": _("English"),
    "ko": _("Korean"),
}
  • 참고: 다만 이 방식은 앱이 로드되는 시점에 번역이 결정되므로, 동적인 번역 환경이 중요하다면 주의가 필요합니다.

방법 2: 직렬화 직전에 str() 끼워넣기

이미 Lazy 객체를 광범위하게 쓰고 있다면, JSON으로 넘기기 바로 직전에 강제로 문자열 변환을 해줍니다.

# Key로 활용하기 전에 str()로 한 번 감싸서 '진짜 문자열'로 만듭니다.
lang_key = str(LANGUAGE_MAP.get(code))

방법 3: 번역의 주체를 클라이언트로 넘기기

만약 프론트를 Django의 템플릿을 사용하지 않고 별도의 프론트 클라이언트를 사용하는 방식이라면 (요즘은 이런 방식이 트렌드인지도 모르겠습니다만.), Django(혹은 DRF) 서버는 en, ko 같은 코드(Code)만 던져주고, 실제 화면에 뿌리는 텍스트는 프론트엔드(React, Vue 등)의 i18n 라이브러리에서 처리하는 방식입니다. 서버 로직이 가벼워진다는 장점이 있죠.


마치며

json.dumps() 와 gettext_lazy()와 함께 사용하던 중, "어제는 됐는데 오늘은 왜 안 되지?" 싶은 상황이 생겼다면, 아마 어제는 운 좋게 Value에만 썼고 오늘은 Key에 썼을 가능성이 높습니다.

gettext_lazy는 편리하지만, 결국 '진짜 문자열'이 아니라는 점을 늘 염두에 두어야 합니다. 특히 JSON을 다룰 때는 더더욱요. 비슷한 에러로 삽질하고 계신 분들께 도움이 되었길 바랍니다.