-다국어(i18n) 리소스를 별도 저장소로 분리하고 자동 검증·배포까지 붙여본 경험-
1. 들어가며
제가 담당했던 관광지 콘텐츠 서비스는 한국어를 기준으로 영어와 우즈베크어(라틴/키릴 두 표기), 카라칼파크어까지 총 네 개 언어, 다섯 개 언어팩을 지원합니다.
화면의 버튼 문구부터 안내 메시지, 유효성 검사 문구까지 전체 텍스트가 언어별 JSON 번역 파일에 의존하고 있습니다.
이 글에서는 프론트엔드 코드 안에서 관리하던 이 번역 파일들을 별도 저장소로 분리하고, 번역 누락을 자동으로 걸러내는 검증 스크립트와 배포 스크립트를 만든 경험을 정리했습니다.
2. 기존 방식의 한계
분리하기 전에는 프론트엔드 프로젝트 안에 언어별 JSON 파일을 두고, 문구 수정 요청이 올 때마다 기준 언어 파일을 고친 뒤 나머지 네 개 언어 파일에도 같은 키를 하나씩 추가하는 방식으로 작업했습니다.
이 방식에는 크게 세 가지 문제가 있었습니다.
-
문구 하나를 고치는 가벼운 작업도 프론트엔드 전체를 다시 빌드하고 배포해야 반영되었습니다.
-
오타 하나 고치는 데도 CI 빌드가 끝날 때까지 기다려야 했습니다.
-
언어별로 키를 손으로 옮기다 보니 특정 언어에만 키가 빠지는 실수가 잦았고, 그 언어를 쓰는 사용자 화면에만 번역 키 값이 그대로 노출되거나 빈 문자열이 출력되는 문제가 반복됐습니다.
-
이런 누락을 걸러내는 검증 절차가 없어서, 배포 후 QA나 실제 사용자가 발견하기 전까지는 문제를 알 방법이 없었습니다.
-
우즈베크어는 라틴 표기와 키릴 표기를 별도 파일로 관리하다 보니, 두 표기 사이에서도 키가 어긋나는 경우가 특히 잦았습니다.
3. 개선 방향: 언어팩 저장소 분리
번역 리소스는 정적 파일이라 애플리케이션 코드와 배포 주기를 굳이 같이 가져갈 필요가 없다는 점에 착안해, 번역 JSON만 따로 관리하는 저장소를 만들었습니다.
실제 서비스 인프라에서는 정적 파일을 서빙하는 Kubernetes nginx Pod가 있어, 이 Pod 안의 JSON 파일만 갱신하면 프론트엔드 재배포 없이 다음 요청부터 바로 새 번역이 반영됩니다.
저장소로 분리하니 검증 로직을 붙이기도 훨씬 수월해졌습니다.
지원 언어와 구조는 아래와 같습니다.
|
언어 코드 |
언어 |
비고 |
|---|---|---|
|
ko-KR |
한국어 |
기준 언어(base language) |
|
en-US |
영어 |
- |
|
uz-Latn / uz-Cyrl |
우즈베크어(라틴/키릴 표기) |
표기 체계별로 별도 관리 |
|
kaa-Latn |
카라칼파크어(라틴 표기) |
- |
각 언어 폴더는 화면 영역별로 common, admin, validation, user 네 개 파일로 나누었습니다.
하나의 큰 파일을 여러 사람이 동시에 고치면서 생기는 충돌을 줄이고, 문제가 생겼을 때 어느 영역인지 바로 알 수 있게 하기 위해서입니다.
4. 번역 키 자동 검증: validate.js
Node.js 내장 모듈(fs, path)만으로 검증 스크립트를 작성했습니다.
핵심은 한국어를 기준으로 삼아 나머지 언어 파일의 키 집합을 비교하는 것인데, JSON이 button.save처럼 중첩 구조를 가지므로 이를 점(dot) 표기 경로로 평탄화(flatten)한 뒤 비교합니다.
function collectKeys(obj, prefix = "") {
const keys = [];
Object.keys(obj).forEach((key) => {
const currentPath = prefix ? `${prefix}.${key}` : key;
if (typeof obj[key] === "object" && obj[key] !== null) {
keys.push(...collectKeys(obj[key], currentPath));
} else {
keys.push(currentPath);
}
});
return keys;
}
기준 언어에는 있지만 대상 언어에 없는 키는 Error, 반대로 대상 언어에만 있는 키는 Warning으로 구분했습니다.
화면이 깨지는 진짜 문제인 Error가 하나라도 있으면 process.exit(1)로 종료 코드를 반환하도록 했는데, 이 스크립트가 배포 스크립트의 첫 단계로 호출되기 때문에 여기서 비정상 종료되면 이후 배포 단계 자체가 실행되지 않습니다.
번역이 누락된 상태로는 배포가 물리적으로 불가능해지는 구조입니다.
5. 자동 배포 파이프라인
배포는 검증 → tar 압축 → nginx Pod 자동 탐지 → kubectl cp로 Pod에 복사 → Pod 내부에서 압축 해제 및 정리, 이렇게 다섯 단계로 이루어집니다.
팀에 macOS와 Windows 사용자가 섞여 있어 bash용과 PowerShell용 스크립트를 각각 만들었고, 둘 다 항상 같은 순서로 동작하도록 맞췄습니다.
Pod 이름은 재배포될 때마다 바뀌기 때문에 하드코딩하지 않고 매번 조회하도록 했습니다.
$POD = kubectl get pod -n $NAMESPACE --no-headers |
Where-Object { $_ -match "^nginx" } |
ForEach-Object { ($_ -split "\s+")[0] } |
Select-Object -First 1
참고로 macOS에서 tar를 실행하면 ._* 형태의 메타데이터 파일이 함께 생성되는데, 이 파일이 Pod 안에 그대로 남으면 정적 디렉터리가 지저분해집니다.
bash 스크립트에서는 COPYFILE_DISABLE=1 환경 변수와 --exclude 옵션으로 이를 제거하지만, Windows의 tar는 애초에 이런 메타파일을 만들지 않기 때문에 PowerShell 스크립트에는 해당 로직이 없습니다.
두 스크립트를 하나로 억지로 합치지 않고, 플랫폼별로 실제로 필요한 처리만 남긴 것도 각 환경에서 직접 실행해 보고 확인한 뒤 내린 선택이었습니다.
트러블슈팅: Windows에서만 실패하던 배포
처음에는 Pod 내부에서 실행할 여러 셸 명령(이동, 압축 해제, 삭제, 목록 출력)을 하나의 here-string으로 묶어 kubectl exec에 통째로 전달했습니다.
# 최초 버전 — 여러 명령을 하나의 here-string으로 묶어 전달
$cleanupScript = @"
cd $NGINX_HTML
tar -xvf $TAR_NAME
rm $TAR_NAME
"@
kubectl exec -n $NAMESPACE $POD -- /bin/sh -c $cleanupScript
macOS bash에서는 이 방식이 문제없이 동작했지만, Windows PowerShell은 외부 프로세스(kubectl.exe)에 인자를 넘기는 방식이 달라 개행이 포함된 문자열이 중간에 잘리거나 줄 단위로 쪼개졌고, 그 결과 Pod 안에서 명령이 절반만 실행되는 오류가 발생했습니다.
원인을 파악한 뒤에는 여러 명령을 하나로 묶는 방식 자체를 포기하고, kubectl exec 호출을 명령 단위로 분리했습니다.
# 수정 버전 — 명령을 kubectl exec 호출 단위로 분리
kubectl exec -n $NAMESPACE $POD -- rm "${NGINX_HTML}/locales"
kubectl exec -n $NAMESPACE $POD -- tar -xvf "${NGINX_HTML}/${TAR_NAME}" -C $NGINX_HTML
kubectl exec -n $NAMESPACE $POD -- rm "${NGINX_HTML}/${TAR_NAME}"
명령을 개별 호출로 나누자 PowerShell이 인자를 잘라내는 문제 자체가 사라졌습니다.
호출 횟수는 네 번으로 늘었지만 Pod 하나에 대한 로컬 조작이라 비용은 무시할 수준이었고, 이 경험으로 크로스플랫폼 스크립트는 두 환경에서 직접 실행해 보기 전까지는 완성된 게 아니라는 점을 다시 느꼈습니다.
6. 도입 효과와 마치며
저장소를 분리하고 자동화를 붙인 뒤로는 문구 수정이 프론트엔드 배포와 완전히 분리되어 스크립트 한 번으로 몇 초 안에 반영되고, 번역 누락은 배포 전에 기계적으로 걸러져 특정 언어권 사용자만 깨진 화면을 보는 일이 사라졌습니다.
또한 macOS든 Windows든 동일한 절차로 배포할 수 있게 되어 배포 방법을 아는 사람이 한 명에게 몰리는 부담도 줄었습니다.
화려한 기술을 새로 도입한 작업은 아니었습니다.
백 줄 남짓한 검증 스크립트와 kubectl, tar 같은 익숙한 도구를 조합한 것이 전부였지만, 실제로 반복되던 문제(재배포 부담, 번역 누락, 플랫폼별 스크립트 오류)를 정확히 짚어서 해결했다는 점에서 의미가 있었습니다.
특히 Windows에서만 재현되던 kubectl exec 인자 전달 문제처럼 직접 실행해봐야만 드러나는 부분들을 하나씩 해결하면서, 다음에는 GitLab CI에 검증 단계를 연결해 병합 전에 자동으로 걸러지도록 개선해보고 싶습니다.
zero