글로벌 서비스를 개발하다 보면, 다양한 언어를 지원하는 다국어화(i18n)은 필수 기능입니다.
하지만 프로젝트가 커질수록 번역 작업은 단순한 텍스트 변경을 넘어 팀 간의 협업 문제로 이어질 때가 많습니다.
이번 글에서는 피처링코 Product팀이 겪었던 문제 상황과 이를 해결하기 위해 Google Sheets와 기타 도구들을 활용해 협업 프로세스를 개선했던 과정을 공유하려고 합니다.
기존 방식과 문제 상황
피처링코 프론트엔드는 Next.js와 next-i18next를 사용해 다국어화를 구현했습니다. 초기에는 JSON 파일로 번역 데이터를 관리했죠.
📂 public
┣ 📂 locales
┃ ┗ 📂 ko
┃ ┗ 📜 common.json // 한국어 common 다국어화 파일
┃ ┗ 📜 campaign.json // 한국어 campaign 다국어화 파일
┃ ┗ 📂 ja
┃ ┗ 📜 common.json // 일본어 common 다국어화 파일
┃ ┗ 📜 campaign.json // 일본어 campaign 다국어화 파일
{
"title": "다국어화 작업 효율화하기",
"description": "Google Sheets로 번역 데이터를 관리하고 동기화하자"
}
번역 데이터는 useTranslation 훅을 사용해 컴포넌트에서 로드했습니다.
const Page = () => {
const { t } = useTranslation('common');
return <h1>{t('title')}</h1>; // 다국어화 작업 효율화하기
};
export const getServerSideProps = async (ctx) => ({
props: {
...(await serverSideTranslations(ctx.locale ?? 'ko', ['common'])),
},
});
export default Page;
하지만 프로젝트가 확장되고, 팀원들이 늘어나면서 예상치 못한 문제들이 터져 나왔습니다.
문제점 1. 작은 변경에도 복잡한 절차
오탈자 하나를 수정하려 해도 복잡한 과정을 거쳐야 했습니다.
번역가 : 디자이너님, 이 버튼에 있는 텍스트가 틀렸어요. ‘저장하기’라고 되어 있어야 하는데 ‘저장’으로만 나오네요.
디자이너 : 아, 그런가요? 일단 개발자님께 전달드릴게요.
( 몇 시간 뒤… )
디자이너 : 개발자님, 여기 번역가님이 발견한 부분인데, ‘저장’을 ‘저장하기’로 바꿔달래요.
개발자 : 음… 잠시만요. JSON 파일에서 키를 확인해 봐야겠네요.
( 잠시 후 )
아, button.save 키를 수정하면 될 것 같아요. 수정 후 배포하면 알려드릴게요.
( 배포 후… )
개발자 : 디자이너님 배포완료 됐어요.
디자이너 : 번역가님, 수정 완료됐어요. 다시 한번 부탁드려요.
번역가 : 확인해 보니까 그 옆에 있는 취소하기도 취소로 나오네요. 이것도 바꿔야될 것 같아요.
( 전체 프로세스 반복 )
문제점 2. 두 명의 개발자가 같은 JSON 키를 사용한 상황
병렬로 작업하던 두 개발자가 서로 다른 페이지에서 동일한 키를 사용해 충돌이 발생했습니다.
개발자 A : 이번에 A 페이지에 header.title 키를 추가했어요. “A”라고 뜨게 했습니다.
개발자 B : 어? 저도 B 페이지에서 header.title 키를 썼는데요. “B”이라고 나오게 했습니다.
개발자 A : 그럼 지금 A 페이지에서 “B”으로 뜨나요?
개발자 B : 네,,, 이거 수정하려면 서로 다른 키로 나눠야겠네요. 충돌난 키를 다 찾아봐야겠어요!
문제점 3. 하나의 키가 중복 사용되어 변경이 어려운 상황
번역가 : 개발자님, button.submit을 ‘제출’ 대신 ‘저장’으로 바꿀 수 있을까요?
개발자 : 그 키가 여러 곳에서 쓰여서요. 다른 페이지에도 영향이 갈 텐데, 새 키로 분리해야겠어요.
해결책 : Google Sheets와 자동화 스크립트
이러한 문제들을 해결하기 위해 Google Sheets를 도입하기로 결정했습니다.
왜 Google Sheets를 선택했나요?
-
빠른 도입 가능 : 별도의 시스템 구축 없이 바로 사용 할 수 있었습니다.
-
실시간 협업 가능 : 번역가와 디자이너가 동시에 데이터를 작업할 수 있었습니다.
-
JSON과의 유사성 : Sheets 데이터를 JSON으로 쉽게 변환할 수 있었습니다.
Google Sheets와 JSON 데이터를 매핑하기
JSON depth의 확장성과 가독성, 그리고 사용성을 위해 key와 해당 key의 depth를 분리하여 관리했습니다.
ko |
ja |
en |
key |
key1 |
key2 |
key3 |
key4 |
---|---|---|---|---|---|---|---|
|
|
|
=CONCATENATE(key1, key2, key3, …) |
|
|
|
|
위처럼 Google Sheets에 데이터를 관리하면, Google Sheets와 JSON파일 양방향으로 손쉽게 변환할 수 있었습니다.
사용 예시
{
"key": "value",
"key1": "value1",
"key2": {
"key3": "value3",
"key4": "value4",
"key5": {
"key6": "value6"
}
}
}
위와 같은 JSON 파일들은 아래와 같은 Google Sheets로 나타낼 수 있었죠.
ko |
ja |
en |
key |
key1 |
key2 |
key3 |
key4 |
---|---|---|---|---|---|---|---|
value |
value-ja |
value-en |
key |
key |
|
|
|
|
|
|
key1 |
key1 |
|
|
|
|
|
|
key2.key3 |
key2 |
key3 |
|
|
|
|
|
key2.key4 |
key2 |
key4 |
|
|
|
|
|
key2.key5.key6 |
key2 |
key5 |
key6 |
|
자동화 스크립트
Google Sheets와 JSON 파일 간의 동기화를 쉽게 하기 위해, 데이터를 JSON 파일로 변환하는 스크립트를 작성했습니다.
1. IMPORT 필수 모듈
- GoogleSpreadsheet, JWT, fs, path
2. SET 기본 설정
- Google API 인증 정보 (i18n-key.json)
- Google Spreadsheet ID (SHEET_ID)
- 명령행 인자로 가져온 sheet 목록 (args)
3. CONFIGURE JWT 인증
- JWT 객체 생성 (Google API 인증용)
4. INITIALIZE Google Spreadsheet 객체
- GoogleSpreadsheet(SHEET_ID, JWT)
5. DEFINE createJsonBySheet 함수
- INPUT: Sheet 객체, Sheet 인덱스(optional)
- FETCH rows (Sheet 데이터 행 가져오기)
- GET headerValues (첫 번째 행의 헤더 정보)
- DETERMINE 언어 코드(langCodes)
- 'key' 이전의 컬럼들
- LOOP through langCodes (언어별 처리)
- CHECK: 해당 언어 폴더가 없으면 생성
- CREATE JSON 파일 경로 및 이름
- INITIALIZE JSON 데이터 객체
- LOOP through rows (행 데이터를 JSON으로 변환)
- Split key를 기준으로 계층적 JSON 구조 생성
- WRITE JSON 데이터를 파일에 저장
6. DEFINE run 함수
- LOAD Google Sheet 정보
- IF: 특정 시트 이름(args) 제공
- LOOP through args (Sheet 이름으로 특정 시트 처리)
- ELSE: 모든 시트 처리
- LOOP through 모든 시트 (Sheet 인덱스로 반복)
- CALL createJsonBySheet 함수
7. EXECUTE run 함수
- Google Sheet 데이터를 JSON으로 변환 후 저장
명령어로 간단히 실행하기
스크립트를 작성한 후, 아래와 같은 명령어 한 줄로 데이터를 변환하고 업데이트할 수 있게 되었습니다:
node scripts/i18n.js -> 모든 sheet 업데이트

모든 Sheet 업데이트
node scripts/i18n.js common -> common sheet만 업데이트

“common” Sheet만 업데이트
추가 개선점
Google Sheets와 자동화 스크립트를 도입한 이후에도 협업 과정이나 효율적인 개발 과정을 위해 다음과 같은 개선 사항들을 적용했습니다.
tF 함수로 상수 데이터 관리
프론트엔드 개발에서는 다양한 설정, 변수, 카테고리를 상수 파일(constant.ts)로 관리합니다.
하지만 next-i18next의 useTranslation 훅은 상수 파일 내에서 직접 사용할 수 없었고, i18n.t를 직접 import하여 사용하려 했으나, 타입 체크가 제공되지 않아 불편했습니다.
tF함수 정의
tF는 i18n.t를 감싸고, 런타임에서 번역 데이터를 호출할 수 있도록 돕는 함수입니다.
import { type i18n as i18nn } from 'i18next';
import { i18n } from 'next-i18next';
type Param = Exclude<Parameters<i18nn['t']>[0], string | string[] | TemplateStringsArray>[number];
export const tF = (param: Param, options?: Record<string, string>) =>
(() => i18n?.t(param, options)) as () => string;
tF를 사용하면 상수처럼 보이지만, 실제로는 런타임에서 호출 가능한 형태로 데이터를 관리할 수 있습니다.
사용 사례 : 설정 목록 데이터 관리
import { tF } from '@utils/i18n';
export const settingList = [
{ name: tF('common:settings.common'), path: '.../workspace_setting' },
{ name: tF('common:settings.team'), path: '.../team_members_management' },
{ name: tF('common:settings.membership'), path: '.../membership_management' },
] as const;
컴포넌트 내 사용
<ul>
{settingList.map((item, idx) => (
<li key={idx}>{item.name()}</li> // item.name() 호출 시 번역된 값을 반환
))}
</ul>
next-i18next의 번역 파일(JSON)이 빌드 타임에는 로드되지 않고, 런타임에 로드되는 특성 때문에 상수번역데이터를 불러와 호출 .() 을 해줘야 사용할 수 있었습니다.
VSCode 확장 프로그램(i18n Ally) 도입
JSON 키를 관리하거나, 특정 키가 어디서 사용되고 있는지 추적하는 데 어려움이 있었습니다.
특히 <Trans /> 컴포넌트나 tF 함수처럼 키를 감싸는 방식의 코드를 지원하지 않아 불편했습니다.
i18n Ally 도입
i18n Ally는 JSON 파일의 키와 번역 데이터를 시각적으로 관리하고, 코드 내 키 사용 여부를 추적할 수 있도록 돕는 VSCode 확장 프로그램입니다.
{
"i18n-ally.localesPaths": [
"packages/apps/public/locales",
"..."
],
"i18n-ally.enabledFrameworks": [
"react-i18next"
],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.keystyle": "nested",
"i18n-ally.defaultNamespace": "common",
"i18n-ally.annotationMaxLength": 1000,
"i18n-ally.regex.usageMatchAppend": [
"tF\\([\\'\"]({key})[\\'\"]\\)",
"i18nKey\\=\\{[\\'\"]({key})[\\'\"]\\}",
],
}

도입 후, 팀원들의 격렬한 반응이 이어졌습니다.

Confluence 가이드로 협업 환경 조성
프로세스를 개선하기 전까지 번역 키와 데이터의 관리 주체는 프론트엔드 개발자에게만 있었습니다.
이로 인해 번역가와 디자이너는 작업에 능동적으로 참여하기 어려웠고, PM이나 다른 개발자들도 번역 작업이나 오류 수정을 지원하기 쉽지 않았습니다.
가이드 문서 작성
우리는 모든 직군이 다국어화(i18n)를 이해하고 작업에 참여할 수 있도록 Confluence 가이드 문서를 작성했습니다.
-
i18n 개념
-
JSON 파일 관리 방법
-
Google Sheets 작성법
-
번역 키 작성 규칙
-
프론트엔드 개발 적용 방식
-
Bold 텍스트, 변수 포함 문장, UI 요소 포함 텍스트 처리 방법
-
…
등에 대한 가이드 문서를 작성해, 개발자뿐만 아니라 디자이너, 번역가, PM도 i18n을 이해하고 번역 데이터를 작성할 수 있도록 했습니다.
이 가이드를 통해 번역가와 디자이너는 번역 데이터를 직접 작성하고, 필요 시 Google Sheets에서 수정 후 개발자에게 요청할 수 있게 되었습니다.
또한, 프론트엔드 개발자가 아닌 다른 개발자들도 가이드를 참고해 번역 데이터를 수정하거나 다국어화를 도울 수 있는 환경이 조성되었습니다.
Google Sheets 도입 후
Google Sheets를 활용해 번역 프로세스를 개선한 후, 피처링코 팀의 커뮤니케이션은 명확하고 간결해졌습니다. 번역 데이터 관리가 체계화되면서 번역가, 디자이너, 개발자 모두 작업 효율을 크게 높일 수 있었습니다.
단방향 통신으로 간소화된 작업 흐름
이제 번역가나 디자이너가 Google Sheets에서 직접 텍스트를 수정하고, 단방향으로 개발자에게 반영 및 배포 요청을 할 수 있게 되었습니다.
번역가 : 개발자님, Google Sheets에서 텍스트를 “저장하기”로 바꿨어요. 다른 변경 사항도 같이 수정했습니다!
개발자 : 확인했습니다! 업데이트 진행 후 배포 후에 알려드릴게요!
node scripts/…
( 배포 중 )
“수정 완료했습니다!”
이 단순화된 흐름 덕분에 팀원 간 커뮤니케이션 비용이 크게 줄어들었습니다.
실시간 키 값 확인으로 충돌 방지
Google Sheets를 통해 개발자 각자가 사용하는 키 값을 실시간으로 확인할 수 있게 되었습니다.
이로 인해 중복 키 사용 문제가 대폭 줄어들었습니다.
개발자 A : 이번에 “header.title” 키를 “A”로 설정해야겠다.
개발자 B : 어? “header.title” 키가 이미 “A”로 되어있네? 그럼 내 건 “B.header.title”로 바꿔야겠다.
이처럼 실시간 확인이 가능해지면서 개발자 간의 키 충돌 문제가 자연스럽게 해결되었습니다.
의미와 페이지별 다른 키 값 사용
Confluence 가이드에 정의된 키 값 컨벤션을 참고하여, 같은 단어라도 페이지나 문맥에 따라 키를 분리해 관리할 수 있었습니다.
-
form.button.submit: 회원가입 폼에서 사용되는 “저장하기”
-
payment.button.submit: 결제 폼에서 사용되는 “저장하기” → “구매하기”
-
feedback.button.submit: 피드백 폼에서 사용되는 “저장하기”
번역가 : 결제 페이지에서만 “저장하기”를 “구매하기”로 바꾸고 싶은데 괜찮을까요?
개발자 : 바로 반영하겠습니다!
이렇게 키 값을 정확한 컨벤션으로 분리하면서 번역가와 디자이너가 특정 문맥에 맞는 UX Writing을 조정하거나, 다른 페이지에서 사용하는 번역을 구분해 독립적으로 관리할 수 있었습니다.
마무리
Google Sheets, 자동화 스크립트, tF 함수, VSCode 확장 프로그램, 그리고 Confluence 가이드를 통해 저희팀은 다국어화 작업의 효율성을 크게 개선할 수 있었습니다.
이 경험은 일본 버전의 피처링코를 단 2개월 만에 성공적으로 런칭하는 데 큰 도움이 되었습니다.
아직 개선해야 할 부분도 남아있습니다. 번역 데이터 변경 이력 관리, 초기 키 자동 생성, 언어별 UI 테스트 자동화 등 앞으로도 꾸준히 발전시켜 나갈 계획입니다.
혹시 여러분은 다국어화를 어떻게 관리하고 계신가요? 더 나은 방법이 있다면 댓글로 공유해 주세요. 😊