React Native 청구서 화면의 하드코딩 문구를 i18n으로 모듈화한 과정
React Native 관리자 앱의 청구서 문구를 도메인별 한국어·영어 리소스로 분리하고 react-i18next 번역 키로 단계적으로 전환한 과정을 정리합니다.
관리자 앱의 청구서 화면에는 제목, 버튼, 안내 문구, 상태와 단위가 여러 컴포넌트에 직접 작성돼 있었습니다. 한국어만 제공할 때는 빠르게 구현할 수 있지만, 언어를 추가하거나 같은 용어를 바꿀 때 관련 화면을 일일이 찾아 수정해야 합니다.
청구서 등록과 상세, 금액 수정, 전송, 면제·삭제 흐름을 대상으로 하드코딩 문구를 번역 리소스로 옮기고, 화면에서는 react-i18next의 번역 키를 사용하도록 정비했습니다.
화면에 흩어진 문구가 변경 비용을 키웠습니다
하드코딩된 문자열은 컴포넌트가 문구의 실제 값까지 책임지게 만듭니다. 같은 의미의 문구가 조금씩 다르게 쓰일 수 있고, 번역 누락 여부도 전체 화면을 열어 보기 전에는 알기 어렵습니다.
이번 작업에서는 먼저 번역 리소스를 화면 도메인별 파일로 나눴습니다.
아래 코드 예시는 설명을 위해 식별자와 운영 설정값을 일반화했습니다.
translations/
├── ko/
│ ├── common.json
│ ├── components.json
│ └── screen.invoice.json
└── en/
├── common.json
├── components.json
└── screen.invoice.json
하나의 거대한 언어 파일에 모든 문구를 넣는 대신 공통 문구와 청구서 도메인의 문구를 분리했습니다. 청구서 화면을 수정할 때 확인해야 할 리소스 범위가 명확해졌습니다.
청구서의 사용자 행동을 기준으로 키를 묶었습니다
screen.invoice.json 안에서도 화면 이름만 나열하지 않고 사용자가 수행하는 기능 단위로 구조를 나눴습니다.
{
"components": {
"editAmount": {
"title": "금액 수정",
"discountAmount": "할인 금액",
"paymentAmount": "결제 금액",
"edit": "수정"
},
"sendInvoice": {
"title": "청구서 전송",
"student": "학생",
"parent": "보호자",
"send": "전송",
"successMessage": "청구서가 전송되었습니다."
}
}
}
같은 구조를 영어 리소스에도 맞췄습니다. 키의 경로가 같기 때문에 화면 코드는 언어별 실제 문구를 알 필요가 없습니다.
컴포넌트는 문구 대신 번역 키를 요청합니다
화면에서는 useTranslation으로 t 함수를 가져오고, 직접 작성한 문구를 키 조회로 교체했습니다.
const { t } = useTranslation();
<ExampleSheet title={t("screen.invoice.components.sendInvoice.title")}>
<ActionButton>
{t("screen.invoice.components.sendInvoice.send")}
</ActionButton>
</ExampleSheet>
이제 컴포넌트는 제목이 한국어인지 영어인지 판단하지 않습니다. 현재 선택된 언어와 리소스를 i18n 계층이 결정합니다.
문구가 화면에 도달하는 흐름은 다음과 같습니다.
flowchart LR A[사용자가 언어 선택] --> B[현재 언어 저장] B --> C[같은 번역 키 조회] C --> D[언어별 리소스 선택] D --> E[청구서 화면에 문구 표시]
핵심은 화면과 언어 데이터를 분리해 언어가 늘어나도 컴포넌트의 동작 코드는 바꾸지 않는 것입니다.
금액과 날짜는 값까지 번역 문자열로 만들지 않았습니다
청구서에는 금액, 인원, 날짜처럼 동적으로 바뀌는 값이 많습니다. 이런 값을 문자열 연결로 만들면 언어마다 다른 어순과 단위 위치를 처리하기 어렵습니다.
번역 리소스에는 자리표시자와 공통 단위 키를 두고 실제 값만 전달했습니다.
t("common.amount", {
amount: amount.toLocaleString(),
});
리소스 안에서는 $t(common.unit.currency)처럼 공통 단위를 참조했습니다. 같은 단위를 여러 화면에서 중복 정의하지 않고, 언어별 표현을 한곳에서 관리할 수 있습니다.
청구서 흐름을 단계적으로 옮겼습니다
청구서 모듈 전체를 한 커밋에서 바꾸지 않고 변경 범위를 나눴습니다.
- i18n 초기화와 언어 상태 저장 구조를 구성했습니다.
- 한국어·영어 리소스를 화면 도메인별 파일로 분리했습니다.
- 청구서 등록, 금액 수정, 전송, 면제 사유 컴포넌트에 번역 키를 적용했습니다.
- 메모 기록, 수납일, 청구서 삭제·면제와 상세 화면까지 범위를 확장했습니다.
- 공통 단위와 날짜 형식을 재사용하고 레거시 중복 리소스를 정리했습니다.
단계를 나눈 이유는 번역 리소스와 화면 코드가 동시에 크게 바뀌면 누락과 잘못된 키를 찾기 어려워지기 때문입니다. 리소스 추가 → 화면 적용 → 공통화 → 레거시 제거 순서로 진행해 각 변경의 책임을 구분했습니다.
키 구조도 유지보수 대상입니다
도메인별 리소스로 분리하면 파일의 책임은 선명해지지만 키가 지나치게 깊어질 수 있습니다. 반대로 공통 키를 과도하게 사용하면 문맥이 다른 단어가 하나의 번역에 묶입니다.
청구서 문구는 다음 기준으로 나눴습니다.
- 여러 도메인에서 같은 의미로 쓰는 단위와 버튼은 공통 리소스 사용
- 청구서에서만 쓰는 상태와 안내는
screen.bill에 유지 - 화면마다 문맥이 달라지는 표현은 억지로 하나의 키로 합치지 않음
- 한국어와 영어 리소스의 키 구조를 동일하게 유지
기존 키 이름의 오탈자를 바로 고치지 않은 구간도 있습니다. 사용 중인 모든 참조를 함께 바꾸지 않으면 런타임에서 키가 그대로 노출될 수 있기 때문에, 별도의 안전한 변경 단위로 다루는 편이 낫다고 판단했습니다.
확인된 결과와 범위
i18n 기반 구성과 모듈별 한국어·영어 리소스를 마련하고, 청구서 관련 화면에 useTranslation을 적용했습니다. 공통 단위·날짜 처리와 레거시 파일 정리도 함께 반영했습니다.
이번에 다국어화를 적용한 범위는 청구서 모듈과 함께 수정한 주요 화면 및 공통 리소스입니다.
정리
이번 작업은 한국어 문구를 영어로 한 번 번역한 것이 아니라, 청구서 UI와 언어 리소스의 책임을 분리한 구조 개선이었습니다.
- 언어 리소스를 화면 도메인별로 나눴습니다.
- 하드코딩 문구를
t함수와 번역 키로 교체했습니다. - 동적 값은 자리표시자로 전달하고 공통 단위와 날짜 형식을 재사용했습니다.
- 적용 범위를 단계적으로 넓힌 뒤 중복 리소스를 정리했습니다.
그 결과 언어를 추가하거나 청구서 용어를 수정할 때 화면 컴포넌트를 직접 고치는 범위를 줄이고, 누락된 번역을 리소스 구조에서 추적할 수 있는 기반을 만들었습니다.