PySide6 다국어 전환 QTranslator 교체 후 retranslateUi로 즉시 적용, 문자열 중앙화 레시피
🌍 런타임에서 언어를 바꾸고 UI를 바로 갱신하는 가장 깔끔한 방법을 코드 관점으로 정리했습니다
데스크톱 앱을 배포해 보면 언어 선택이 단순한 옵션이 아니라 출시 시장을 넓히는 핵심이라는 걸 실감하게 됩니다.
특히 PySide(Qt for Python)에서는 번역 파일을 불러오고 교체하는 타이밍, 그리고 사용자에게 보이는 모든 문자열을 어떻게 한곳에서 관리하느냐가 유지보수 난이도를 좌우하죠.
이 글은 실제 현업에서 바로 적용할 수 있도록 언어 전환의 원리와 안전한 패턴을 차근차근 풀어냅니다.
불필요한 재시작 없이 언어를 바꾸자마자 메뉴, 대화상자, 상태바까지 자연스럽게 반영되도록 만드는 과정을 친근한 설명과 검증된 레시피로 안내합니다.
핵심은 QTranslator를 교체한 뒤 retranslateUi를 호출하여 텍스트를 즉시 갱신하고, 모든 사용자 노출 문자열을 중앙화하여 한 곳에서 관리하는 것입니다.
여기에 설치·제거 순서, 번역 파일 로딩 규칙, LanguageChange 이벤트와의 관계, 그리고 tr 및 QCoreApplication.translate 활용까지 더하면 흔들림 없는 다국어 기반이 완성됩니다.
글의 흐름은 개념→구현→안전장치 순으로 구성되어 있어 초보자도 따라 하며 동작 원리를 이해하도록 설계했습니다.
📋 목차
🔗 QTranslator 교체 후 retranslateUi 호출 원리
PySide(Qt for Python)에서 다국어 전환은 QTranslator를 애플리케이션에 설치·교체한 뒤, 각 위젯이 보유한 텍스트를 retranslateUi로 다시 주입하는 흐름으로 이뤄집니다.
QCoreApplication.installTranslator를 호출하면 Qt가 내부적으로 LanguageChange 이벤트를 전파하고, Qt Designer 기반 UI 클래스는 retranslateUi를 통해 문자열을 다시 설정합니다.
사용자 정의 위젯이나 수동으로 만든 화면은 직접 텍스트를 재설정하거나, 시그널을 연결해 갱신하도록 구성해야 안정적으로 동작합니다.
핵심은 번역 로딩, 트랜스레이터 교체, UI 재번역이 한 묶음으로 움직이도록 일관된 진입 함수를 갖추는 것입니다.
from PySide6.QtCore import QCoreApplication, QTranslator, QLocale
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QLabel
class MainWindow(QMainWindow):
def __init__(self, ui):
super().__init__()
self.ui = ui
self.ui.setupUi(self)
class Ui_MainWindow:
def setupUi(self, MainWindow):
MainWindow.setWindowTitle(self.tr("Demo"))
# 간단 예시용 위젯
self.label = QLabel(MainWindow)
self.label.setText(self.tr("Hello"))
self.label.move(20, 20)
self.btn = QPushButton(MainWindow)
self.btn.setText(self.tr("Change language"))
self.btn.move(20, 60)
def retranslateUi(self, MainWindow):
# 언어 변경 시 호출되어 텍스트를 다시 설정
MainWindow.setWindowTitle(self.tr("Demo"))
self.label.setText(self.tr("Hello"))
self.btn.setText(self.tr("Change language"))
def tr(self, text):
# Qt Designer가 생성하는 tr 래퍼와 유사한 컨텍스트 번역
return QCoreApplication.translate(self.__class__.__name__, text)
class I18nController:
def __init__(self, app, ui):
self.app = app
self.ui = ui
self.translator = None
def switch_language(self, locale_name: str):
# 1) 기존 번역기 제거
if self.translator:
self.app.removeTranslator(self.translator)
# 2) 새 번역기 로드
t = QTranslator()
# 예: translations 폴더에 app_ko.qm, app_en.qm
ok = t.load(f":/translations/app_{locale_name}.qm") or \
t.load(f"translations/app_{locale_name}.qm")
if ok:
self.app.installTranslator(t)
self.translator = t
else:
self.translator = None # 로드 실패 시 기본 언어
# 3) UI 재번역 (Designer 기반)
self.ui.retranslateUi(qt_main_window_reference)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
ui = Ui_MainWindow()
main = MainWindow(ui)
# MainWindow 참조를 컨트롤러에서 다시 쓰기 위해 전달
global qt_main_window_reference
qt_main_window_reference = main
i18n = I18nController(app, ui)
# 버튼 클릭으로 언어 전환
ui.btn.clicked.connect(lambda: i18n.switch_language("ko")) # "en", "ja" 등
main.show()
sys.exit(app.exec())
위 예시는 QTranslator 교체 → installTranslator → retranslateUi로 이어지는 전형적인 흐름을 보여줍니다.
Qt Designer가 생성한 클래스는 retranslateUi를 기본 제공하므로 이 함수를 언어 스위치 진입점에서 항상 호출하는 습관이 중요합니다.
직접 만든 위젯의 경우에도 동일한 패턴으로 재번역 함수를 두고, 컨트롤러가 일괄 호출하도록 구성하면 흔들림이 줄어듭니다.
💡 TIP: 메뉴, 툴바, 상태바 메시지도 retranslateUi에서 한 번에 설정하면 누락을 방지할 수 있습니다.
QAction 텍스트, QMenu 제목, QStatusBar 문구를 모두 같은 위치에서 재설정하세요.
- 🛠️번역 파일(.qm)을 언어 코드 규칙으로 정리: app_ko.qm, app_en.qm 등
- ⚙️installTranslator 이전에 기존 번역기는 반드시 removeTranslator로 제거
- 🔌설치 후에는 retranslateUi를 호출해 즉시 갱신
⚠️ 주의: setText를 여기저기 흩뿌려 두면 일부 위젯이 재번역에서 빠집니다.
문자열은 반드시 UI 클래스의 retranslateUi 또는 전용 재번역 함수로 중앙화하세요.
💬 핵심 공식: QTranslator 교체 → LanguageChange 전파 → retranslateUi로 텍스트 재주입.
이 흐름만 지켜도 런타임 다국어 전환은 안정적으로 구현됩니다.
🛠️ QTranslator 설치·제거와 번역 파일 로딩
언어 전환의 첫 단계는 올바른 번역기(QTranslator)를 로딩, 설치, 제거하는 것입니다.
PySide6에서는 QCoreApplication.installTranslator()와 removeTranslator()를 통해 번역기를 교체할 수 있습니다.
여기서 중요한 점은 ‘기존 번역기 제거 후 새 번역기 설치’라는 순서를 반드시 지켜야 한다는 것입니다.
그렇지 않으면 문자열 매핑이 꼬이거나, 다국어 전환이 일부만 반영되는 현상이 나타날 수 있습니다.
Qt의 번역 파일(.qm)은 Qt Linguist를 통해 생성되며, 일반적으로 app_언어코드.qm 형태로 관리합니다.
예를 들어 한국어는 app_ko.qm, 영어는 app_en.qm처럼 저장합니다.
이러한 규칙을 정해두면, 사용자가 선택한 언어 코드를 기반으로 동적으로 로드 경로를 구성할 수 있어 유지보수가 한결 수월해집니다.
def install_translator(app, lang_code):
translator = QTranslator()
path = f"translations/app_{lang_code}.qm"
if not translator.load(path):
print(f"⚠️ 번역 파일 {path}을(를) 찾을 수 없습니다.")
return False
app.installTranslator(translator)
return translator
def remove_translator(app, translator):
if translator:
app.removeTranslator(translator)
del translator
print("🔁 기존 번역기 제거 완료")
위 코드는 QTranslator의 생명주기를 관리하는 가장 기본적인 형태입니다.
설치와 제거를 별도로 정의해 두면 코드의 의도가 명확해지고, 다국어 전환 시점마다 중복 호출을 피할 수 있습니다.
특히 PySide에서는 번역기 객체의 생명주기를 명확히 관리하지 않으면 GC(가비지 컬렉션)에 의해 번역이 갑자기 해제되는 문제가 종종 발생합니다.
💡 TIP: QTranslator 인스턴스를 반드시 클래스 멤버 변수로 보관하세요.
지역 변수로 선언하면 함수 종료 시 해제되어 번역이 즉시 사라질 수 있습니다.
또한 QTranslator.load()는 절대경로뿐 아니라 리소스 파일(.qrc) 내부 경로도 지원합니다.
앱을 배포할 때 번역 파일을 외부에 두면 사용자가 실수로 삭제할 위험이 있으므로, 가능한 한 리소스 빌드 시스템에 포함시키는 것이 좋습니다.
💬 언어 전환은 단순한 텍스트 치환이 아니라 ‘번역 매핑 테이블 교체’ 과정입니다.
QTranslator 교체는 이 매핑 테이블을 전환하는 핵심 단계로, 올바른 설치·제거 순서가 안정성의 열쇠입니다.
- 📦번역 파일은 앱 실행 경로 또는 리소스 파일(qrc)에 포함
- 🔁기존 번역기 제거 → 새 번역기 설치 순서 유지
- 🧩QTranslator 객체는 클래스 속성으로 유지하여 가비지 컬렉션 방지
- 🧾번역 파일 이름 규칙을 명확히 정해 언어 코드 기반으로 자동 로드 가능
⚠️ 주의: installTranslator를 여러 번 호출하면 번역 매핑이 겹쳐 예기치 않은 문자열이 표시될 수 있습니다.
교체 시 반드시 removeTranslator로 기존 인스턴스를 제거한 뒤 새로 설치하세요.
⚙️ 문자열 중앙화 전략 tr와 translate 활용
PySide에서 다국어 전환을 제대로 구현하려면 모든 UI 문자열을 한곳에서 관리하는 구조가 필수입니다.
Qt는 이를 위해 tr()과 QCoreApplication.translate() 두 가지 방법을 제공합니다.
두 함수는 모두 .ts/.qm 번역 파일과 연결되며, Qt Linguist가 인식할 수 있도록 컨텍스트(Context)와 문자열 원문(Source text)을 함께 전달해야 합니다.
이러한 구조를 제대로 활용하면 전역 검색·번역 수정·일괄 갱신이 가능해지고, 문자열을 하드코딩할 때 발생하는 누락이나 불일치 문제를 방지할 수 있습니다.
💬 tr()은 클래스 내부에서, translate()는 외부 모듈에서 사용합니다.
컨텍스트를 일관되게 유지해야만 다국어 전환 시 충돌 없이 동작합니다.
# tr() 사용 예시
class Ui_MainWindow:
def setupUi(self, MainWindow):
MainWindow.setWindowTitle(self.tr("Settings"))
self.label.setText(self.tr("Language"))
self.saveButton.setText(self.tr("Save"))
# translate() 사용 예시 (클래스 외부)
from PySide6.QtCore import QCoreApplication
text = QCoreApplication.translate("Ui_MainWindow", "Language")
label.setText(text)
Qt Linguist는 각 문자열이 포함된 클래스 이름을 컨텍스트로 사용하므로, 클래스명 또는 모듈 구조가 바뀌면 번역 매핑이 깨질 수 있습니다.
따라서 tr()이나 translate()를 쓸 때는 컨텍스트명을 일정하게 유지하고, 문자열을 함수 내부가 아닌 별도의 관리 모듈로 모으는 것이 이상적입니다.
예를 들어 “messages.py” 같은 전용 파일을 만들어 모든 문자열을 상수로 정의하면, 나중에 UI 구조가 바뀌어도 번역 파일을 재생성할 필요가 없습니다.
# messages.py
class Messages:
APP_TITLE = "Demo Application"
MENU_FILE = "File"
MENU_EDIT = "Edit"
ACTION_SAVE = "Save"
STATUS_READY = "Ready"
# main.py
self.setWindowTitle(self.tr(Messages.APP_TITLE))
self.statusBar().showMessage(self.tr(Messages.STATUS_READY))
이처럼 문자열 중앙화는 단순한 관리 편의성을 넘어, 다국어 대응 시 가장 중요한 인프라입니다.
모든 UI 텍스트가 한 경로에서 관리되면, 번역자와 개발자가 서로 다른 파일을 편집할 필요 없이 버전 충돌 없이 협업할 수 있습니다.
또한 “일관된 컨텍스트 관리”는 Qt Linguist에서 문자열이 중복되는 문제를 근본적으로 줄여줍니다.
💡 TIP: 중앙화된 메시지 모듈은 테스트 자동화에도 유리합니다.
단일 소스에서 텍스트를 불러오므로, 변경 사항이 발생해도 UI 비교 테스트가 단순해집니다.
- 📁모든 문자열은 전용 모듈(messages.py 등)에 중앙화
- 💬Qt Linguist에서 인식 가능한 형태로 tr() 또는 translate() 사용
- 🧩컨텍스트명 변경 시 .ts 파일 재생성 필요 — 되도록 불변 유지
- 📦UI 구조 변경에도 번역 데이터는 동일하게 유지되도록 구조 설계
⚠️ 주의: UI 곳곳에서 직접 setText(“문자열”)을 호출하면, 나중에 번역이 전혀 반영되지 않습니다.
tr() 또는 translate()를 반드시 사용해 번역 가능 상태로 유지하세요.
🔌 런타임 언어 전환 UI 패턴과 시그널
런타임 언어 전환은 사용자가 설정 메뉴에서 언어를 바꾸면 애플리케이션이 즉시 모든 보이는 텍스트를 갱신하는 흐름을 말합니다.
핵심은 QTranslator 교체 이후에 전체 화면이 일관되게 retranslateUi를 실행하도록 연결하는 엔지니어링입니다.
Qt는 내부적으로 QEvent.LanguageChange를 발생시키며, 위젯의 changeEvent에서 이 이벤트를 받아 텍스트를 다시 설정할 수 있습니다.
또한 메인 윈도우에서 언어 전환 시그널을 내보내고, 모든 서브윈도우·다이얼로그가 이 시그널을 구독하여 자체 retranslateUi를 호출하면 누락 없이 갱신됩니다.
🔌 changeEvent로 자동 재번역 받기
from PySide6.QtCore import QEvent, QCoreApplication
from PySide6.QtWidgets import QMainWindow
class MainWindow(QMainWindow):
def __init__(self, ui):
super().__init__()
self.ui = ui
self.ui.setupUi(self)
def changeEvent(self, event):
if event.type() == QEvent.LanguageChange:
# Qt가 LanguageChange를 보낼 때 retranslateUi 호출
self.ui.retranslateUi(self)
super().changeEvent(event)
이 패턴은 Qt Designer 기반 UI에 특히 적합합니다.
메뉴, 액션, 상태바 문자열까지 retranslateUi에 모여 있다면, LanguageChange만 받아 재호출해도 전체 UI가 갱신됩니다.
🔗 중앙 I18n 컨트롤러와 시그널 브로드캐스트
from PySide6.QtCore import QObject, Signal, QTranslator
from PySide6.QtWidgets import QApplication
class I18nController(QObject):
languageChanged = Signal() # 모든 화면에 알리는 사용자 정의 시그널
def __init__(self, app: QApplication):
super().__init__()
self.app = app
self._translator = None
def switch(self, lang: str):
# 1) 기존 제거
if self._translator:
self.app.removeTranslator(self._translator)
# 2) 새 로드
t = QTranslator()
if t.load(f"translations/app_{lang}.qm"):
self.app.installTranslator(t)
self._translator = t
else:
self._translator = None
# 3) 커스텀 시그널로 재번역 요청
self.languageChanged.emit()
# 각 윈도우/다이얼로그 쪽
class SettingsDialog(QDialog):
def __init__(self, ui, i18n: I18nController):
super().__init__()
self.ui = ui
self.ui.setupUi(self)
i18n.languageChanged.connect(lambda: self.ui.retranslateUi(self))
LanguageChange 이벤트는 최상위 위젯들에 자동으로 도달하지만, 런타임에 생성된 다이얼로그나 커스텀 위젯이 많다면 누락이 발생하기 쉽습니다.
이때 중앙 컨트롤러가 언어 전환을 수행하고 languageChanged 시그널을 브로드캐스트하면, 모든 화면이 스스로 retranslateUi를 호출해 일관성이 보장됩니다.
🧩 메뉴·액션·상태바와 단축키, 툴팁까지 한 번에
메뉴 제목, QAction 텍스트, 단축키 표시, 툴팁, 상태바 메시지는 모두 사용자 노출 문자열입니다.
이들을 retranslateUi에 모아 두고, 언어 전환 시 단일 경로로 재설정하면 누락을 피할 수 있습니다.
| 대상 | 재설정 예시 |
|---|---|
| 메뉴/액션 | fileMenu.setTitle(self.tr(“File”)) actionSave.setText(self.tr(“Save”)) |
| 툴팁/상태바 | btn.setToolTip(self.tr(“Open settings”)) statusBar.showMessage(self.tr(“Ready”)) |
💎 핵심 포인트:
번역기는 한 번만 설치하고, 갱신은 각 화면의 changeEvent(LanguageChange)나 languageChanged 시그널을 통해 retranslateUi로 일괄 처리합니다.
- 🔁메인/다이얼로그 모두 changeEvent(LanguageChange)에서 retranslateUi 호출
- 📡중앙 I18nController.languageChanged 시그널로 누락 없는 브로드캐스트
- 🧱모든 사용자 노출 문자열은 retranslateUi에 중앙화
- 🧪언어 전환 후 주요 화면을 순회하며 시각 회귀 테스트 수행
⚠️ 주의: 번역기만 교체하고 retranslateUi를 누락하면 일부 위젯은 이전 텍스트를 계속 표시합니다.
특히 런타임에 생성된 팝업/컨텍스트 메뉴는 별도 재번역 호출이 필요합니다.
💡 흔한 오류와 디버깅 체크리스트
PySide에서 다국어 전환을 구현할 때 겪는 문제의 대부분은 번역기 교체 순서나 retranslateUi 호출 누락으로 발생합니다.
또한 QTranslator 객체의 생명주기를 제대로 관리하지 않으면, 가비지 컬렉션이 번역을 해제해 버리는 문제도 자주 등장합니다.
아래는 실제 프로젝트에서 자주 마주치는 오류 상황과 해결 팁을 정리한 실전 점검표입니다.
- 🚫언어 전환 후 UI 일부만 갱신됨 → retranslateUi 호출 누락 여부 확인
- 🔁removeTranslator()를 생략하면 중복 매핑으로 오작동 발생
- 🧩QTranslator 객체가 지역 변수로 선언되어 자동 해제되는 경우 → 클래스 속성으로 유지
- 🧱retranslateUi 내부에 setText가 누락되어 특정 위젯이 번역되지 않음
- ⚙️Qt Linguist로 .qm 파일을 갱신하지 않아 최신 문자열이 반영되지 않음
- 🧾컨텍스트명이 바뀌었는데 기존 .ts 파일을 그대로 사용해 번역이 무효화됨
- 🧪테스트 환경에서 언어 전환 후 GC가 번역 객체를 해제해 문자열이 영어로 복귀
- 🧰동적 위젯(런타임 생성)은 LanguageChange 이벤트를 자동으로 받지 못함 → 시그널 연결 필요
💬 PySide6 다국어 전환은 단순한 코드 조합이 아닌, 이벤트 순서와 객체 생명주기 관리의 문제입니다.
언어 변경 후 반응이 없다면, 먼저 QTranslator가 여전히 유효한지 확인하세요.
🧠 디버깅을 쉽게 하는 로깅 코드 추가
언어 전환 흐름을 추적하려면, 각 단계에서 어떤 번역기가 설치되어 있고 어느 시점에 retranslateUi가 호출되는지 로그로 남기는 것이 효과적입니다.
다음 예시는 간단한 디버그 로그를 추가하여 다국어 흐름을 추적하는 방법입니다.
def switch_language(app, translator, lang):
print(f"[I18N] 언어 전환 요청: {lang}")
if translator:
print("[I18N] 기존 번역기 제거")
app.removeTranslator(translator)
new_translator = QTranslator()
ok = new_translator.load(f"translations/app_{lang}.qm")
if ok:
app.installTranslator(new_translator)
print(f"[I18N] 새 번역기 로드 완료: app_{lang}.qm")
else:
print("[I18N] 번역 파일 로드 실패")
return new_translator
디버그 로그를 추가하면, 다국어 전환 시 어느 단계에서 실패했는지를 명확하게 확인할 수 있습니다.
특히 LanguageChange 이벤트 발생 여부를 로그로 남겨두면 이벤트 루프에서 신호가 제대로 순환되는지 검증할 수 있습니다.
💡 TIP: 다국어 전환 테스트는 실제 UI 클릭으로만 하지 말고 단위 테스트로도 자동화하세요.
언어 변경 후 윈도우 타이틀, 버튼 텍스트가 올바르게 변경되었는지 검증할 수 있습니다.
⚠️ 주의: .ts 파일 수정 후 .qm으로 다시 빌드하지 않으면 변경 내용이 반영되지 않습니다.
Qt Linguist 저장만으로는 런타임에 반영되지 않으니 반드시 lrelease 명령을 실행하세요.
💎 핵심 포인트:
문제가 생겼다면 항상 “QTranslator → installTranslator → retranslateUi” 흐름을 점검하세요.
이 세 가지가 정상적으로 이어지면 다국어 전환은 99% 성공합니다.
❓ 자주 묻는 질문 (FAQ)
언어를 바꿔도 일부 텍스트가 그대로인 이유는 무엇인가요?
모든 문자열을 retranslateUi 내부로 이동시키면 해결됩니다.
QTranslator 객체를 전역으로 두면 안 되나요?
다만 전역 변수보다 I18nController 같은 클래스 멤버로 관리하면 더 명확하고 유지보수에 유리합니다.
.ts와 .qm 파일의 차이는 무엇인가요?
Qt Linguist에서 편집 후 lrelease로 .qm 파일을 생성해야 합니다.
언어 전환 후 재시작 없이 즉시 반영하려면 어떻게 하나요?
QEvent.LanguageChange는 언제 발생하나요?
해당 이벤트는 모든 활성 위젯으로 전달됩니다.
언어 전환 시 QApplication을 재시작하는 건 좋은 방법인가요?
재시작 없이 retranslateUi() 호출만으로 충분히 전환할 수 있습니다.
재시작은 복잡한 상태 유지 문제를 야기합니다.
Qt Designer로 만든 UI 파일도 자동으로 번역되나요?
해당 함수는 모든 setText() 호출을 번역 가능 형태로 포함하므로, 언어 전환 시 별도 수정 없이 자동 반영됩니다.
언어별 폰트나 레이아웃 차이도 조정할 수 있나요?
changeEvent(LanguageChange) 안에서 언어별 조건문을 사용해 폰트, 정렬, 폭을 조정할 수 있습니다.
🧭 PySide6 다국어 전환의 완성: QTranslator와 retranslateUi의 조합
PySide6 다국어 전환의 핵심은 번역기(QTranslator)와 UI 재번역(retranslateUi)의 결합입니다.
이 두 요소를 정확히 제어하면 재시작 없이 즉시 언어가 바뀌는 자연스러운 다국어 환경을 구현할 수 있습니다.
번역기의 교체 순서, retranslateUi 호출 위치, 그리고 문자열 중앙화 전략만 잘 지켜도 유지보수성과 확장성이 크게 향상됩니다.
또한 언어별 리소스를 리소스 파일(.qrc)로 묶어 배포하면, 사용자 환경에 따라 자동으로 로드할 수 있고, 실수로 번역 파일이 누락되는 문제를 방지할 수 있습니다.
여기에 Qt Linguist로 정기적으로 번역을 갱신하고, I18nController로 언어 전환을 일괄 관리하면 글로벌 시장에서도 경쟁력 있는 앱을 유지할 수 있습니다.
결국 핵심은 “번역기 교체 → retranslateUi 호출 → 문자열 중앙화”라는 3단계 구조입니다.
이 공식만 지키면 PySide6 다국어 전환은 예측 가능하고, 깔끔하며, 안정적으로 작동합니다.
특히 다국어 지원을 미리 고려한 설계는 향후 새로운 언어 추가나 현지화 작업의 비용을 획기적으로 줄여줍니다.
🏷️ 관련 태그 : PySide6, QTranslator, retranslateUi, 다국어전환, QtforPython, LanguageChange, QtLinguist, 번역파일관리, UI중앙화, i18n전략