글로벌 서비스를 만드는 데 있어, 다양한 언어를 지원하는 다국어화(i18n)은 필수 기능입니다. 개발팀 입장에서는 i18n 라이브러리를 도입하고, JSON 파일로 각 언어의 텍스트를 관리하면 기본적인 구현은 끝난 것처럼 보이기도 합니다.
하지만 프로젝트가 커질수록 번역 작업은 단순한 텍스트 변경을 넘어 팀 간의 협업 문제로 이어지기 시작합니다.
“텍스트 하나 바꾸는 데 몇 명을 오가야 한다”, “같은 키가 다른 페이지에서도 사용되어 바꾸기 어렵다”, “디자이너가 문구를 바꾸고 싶어도 개발자 없으면 찾지도 못하는” 등의 불편들이 반복적으로 발생합니다.
저희 피처링코 Product팀도 일본 진출을 앞두고 빠르게 다국어를 지원해야 하는 상황에서 이런 문제들을 직면했습니다.
이제는 단순히 번역 문자열을 모아두는 수준이 아니라, 번역 데이터를 팀 전체가 이해하고 함께 작업할 수 있는 구조가 필요했습니다.
이 글에서는 Google Sheets를 중심에 둔 협업 프로세스를 어떻게 설계하고, 자동화를 통해 효율을 높였는지 그 과정을 공유하려고 합니다.
JSON 기반 번역 관리의 한계
초기에는 Next.js와 next-i18next를 활용해 다국어화를 구현했습니다. 번역 문자열은 public/locales 경로에 국가별 JSON 파일로 저장되었고, useTranslation() 훅을 통해 컴포넌트에서 호출하는 구조였습니다.
📂 public ┣ 📂 locales ┃ ┗ 📂 ko ┃ ┗ 📜 common.json // 한국어 common 다국어화 파일 ┃ ┗ 📜 campaign.json // 한국어 campaign 다국어화 파일 ┃ ┗ 📂 ja ┃ ┗ 📜 common.json // 일본어 common 다국어화 파일 ┃ ┗ 📜 campaign.json // 일본어 campaign 다국어화 파일
// JSON 예시 { "title": "다국어화 작업 효율화하기", "description": "Google Sheets로 번역 데이터를 관리하고 동기화하자" }
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. 작은 변경에도 복잡한 절차
오탈자 하나를 수정하는 작업조차도 번역가 -> 디자이너 -> 개발자 -> 배포 -> 확인하는 과정을 반복해야 했습니다.
단순히 텍스트 수정을 위해 많은 인원이 개입되었고, 실제 반영까지의 시간도 과도하게 길어졌습니다. 결국 팀 생산성을 저하시키는 요인이 되었습니다.
문제점 2. 동일한 키 사용 중복 문제
키 관리가 명확하게 이루어지지 않다 보니, 서로 다른 페이지를 개발하던 개발자들이 동일한 키를 사용해 충돌이 발생했습니다.
이는 배포 이후에 발견되는 경우가 종종있어, UI텍스트가 의도치 않게 바뀌는 문제가 발생했습니다.
문제점 3. 하나의 키가 재사용되며 변경의 어려움
동일한 버튼이라도 상황에 따라 다른 UX Writing이 필요한 경우가 많았지만, 개발자가 관리하던 키 구조에서 이를 처음부터 반영하지 못했습니다.
Google Sheets 도입과 협업 구조의 재설계
복잡한 커뮤니케이션 구조, 키 충돌, 문맥 불일치 문제들이 반복되면서, 번역 파일을 단순히 “개발자가 다루는 JSON”으로만 취급해서는 안된다고 판단했습니다.
번역은 디자이너, 번역가, PM 등 다양한 직군이 관여할 수 있는 작업이고, 이들이 개발자 없이도 능동적으로 참여할 수 있는 시스템이 필요했습니다.
이에 따라 Google Sheets를 단일 소통 창구로 삼고, 모든 다국어 데이터를 여기서 관리하도록 전환했습니다.
Google Sheets를 선택한 이유는 다음과 같습니다.
-
빠른 도입
별도의 툴 개발이나 복잡한 인프라 없이, 즉시 사용 할 수 있었습니다. -
실시간 협업
번역가와 디자이너가 동시에 접근해 작업할 수 있으며, 이력 관리와 변경 추적도 용이했습니다. -
JSON구조와의 유사성
키 기반 데이터 구조가 JSON과 호환되기때문에 자동화 스크립트를 통해 쉽게 양방향 동기화를 구축할 수 있었습니다.
목표 : 단일 협업 인터페이스
기존에는 번역가, 디자이너, 개발자가 서로 얽혀 복잡하게 소통해왔지만,
Google Sheets를 중심으로 번역가가 직접 텍스트를 수정하고, 디자이너가 문맥을 검토하며, 개발자는 그 데이터를 자동으로 가져와 배포하는 단방향 협업 흐름을 구축하여 모든 이해관계자가 하나의 창구만 바라보면 되는 구조를 목표로 삼았습니다.
Google Sheets <-> JSON 구조 설계와 동기화 방식
Google Sheets를 단순히 번역 문구를 나열하는 테이블로 쓰는 것이 아니라, 실제 JSON 구조에 맞게 계층적이고 가독성 있는 형태로 구성하는 것이 핵심 과제였습니다.
Key Depth 분리 방식
다국어 번역 키를 depth별로 나눠서 관리하는 방식을 선택했습니다.
예를 들어, 아래 JSON 구조를 보면:
{ "form": { "button": { "submit": "저장하기" } } }
form.button.submit
으로 표현됩니다.이를 Google Sheets에서는 다음과 같이 분리하여 저장합니다:
ko |
ja |
key |
key1 |
key2 |
key3 |
---|---|---|---|---|---|
저장하기 |
保存する |
form.button.submit |
form |
button |
submit |
이처럼 key depth를 열 단위로 분리한 이유는 전체 구조의 가독성을 높이고, 변환 과정을 단순화하기 위해서였습니다. 각 계층을 나눠 작성하면 JSON으로의 직렬화나 역직렬화가 훨씬 명확해지고, 텍스트의 문맥에 따라 키를 세분화해 관리하는 것도 용이해집니다.
자동화 스크립트
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 업데이트

node scripts/i18n.js common
-> “common” Sheet만 업데이트
번역 데이터를 코드로 반영하는 작업은 훨씬 단순하고 안정적으로 바뀌었습니다.
새로운 키가 생기거나 수정이 필요할 때도, 번역가가 Sheets에만 반영하면 곧바로 배포 준비가 완료됩니다.
추가 개선점
Google Sheets와 자동화 스크립트를 도입한 이후에도 협업 과정이나 효율적인 개발 과정을 위해 다음과 같은 개선 사항들을 적용했습니다.
런타임 호출을 위한 tF 함수 도입
번역 키를 상수처럼 다루고 싶을 때, 기존의 useTranslation 훅은 몇 가지 제약이 있었습니다. 특히 settingsList, menuItems 같은 컴포넌트 외부의 상수 배열 안에서 번역 문자열을 직접 사용해야 할 경우, 다음과 같은 문제가 생겼습니다:
- t()는 컴포넌트 내부에서만 사용할 수 있어서 상수에서 직접 호출 불가
-
상수 정의 시점에는 번역 데이터를 참조할 수 없어, 동적 호출이 불가능
이 문제를 해결하기 위해, tF()라는 헬퍼 함수를 도입했습니다.
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;
이 함수는 실제 번역 호출은 런타임 시점에 수행되도록 래핑해주기 때문에, 아래처럼 상수 정의에서도 번역 키를 유연하게 사용할 수 있게 해줍니다.
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;
컴포넌트 안에서는 name()으로 호출하면 실제 번역값이 반환됩니다:
<ul> {settingList.map((item, idx) => ( <li key={idx}>{item.name()}</li> // item.name() 호출 시 번역된 값을 반환 ))} </ul>
VSCode에서 tF(‘ 입력 시 자동완성과 타입 추론이 동작하는 모습
이처럼 tF()를 사용하면 기존의 t()처럼 컴포넌트 내부에서만 호출해야 했던 번역 키를, 외부 상수 정의에서도 안전하게 다룰 수 있습니다.
VSCode 확장 프로그램(i18n Ally) 도입
다국어 키가 많아지면서 점점 번역 키가 어떤 텍스트를 의미하는지 알기 어려워지는 상황이 생겼습니다.
t(‘key.value’) 형태의 호출만 보면 무슨 문장인지 감이 안 와서, 코드에 불필요한 주석을 달거나, JSON 파일을 열어 직접 확인하는 일이 잦아졌습니다.
이를 해결하기 위해 도입한 것이 VSCode 확장 프로그램인 i18n Ally였습니다.
이 툴은 에디터 내에서 다국어 키를 실시간으로 번역된 문장으로 보여주며, 코드를 읽는 것만으로도 실제 텍스트를 직관적으로 확인할 수 있도록 도와줍니다. 덕분에 키 주석을 따로 달지 않아도 되고, 번역 파일을 직접 찾아볼 필요도 줄어들었습니다.
또한 <Trans> 컴포넌트나 tF() 함수 내부에 들어 있는 번역 키도 정확히 인식할 수 있도록, VSCode의 settings.json에 별도 정규식을 추가하여 커스터마이징했습니다. 이를 통해 우리 팀만의 번역 키 사용 방식까지 확장 프로그램에서 인식할 수 있게 되었고, 작업 효율이 훨씬 개선되었습니다.
{ "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를 통해 개발자 각자가 사용하는 키 값을 실시간으로 확인할 수 있게 되었습니다.
이로 인해 중복 키 사용 문제가 대폭 줄어들었습니다.
실시간 확인이 가능해지면서 개발자 간의 키 충돌 문제가 자연스럽게 해결되었습니다.
의미와 페이지별 다른 키 값 사용
Confluence 가이드에 정의된 키 값 컨벤션을 참고하여, 같은 단어라도 페이지나 문맥에 따라 키를 분리해 관리할 수 있었습니다.
-
form.button.submit
: 회원가입 폼에서 사용되는 “저장하기”
-
payment.button.submit
: 결제 폼에서 사용되는 “저장하기” → “구매하기”
-
feedback.button.submit
: 피드백 폼에서 사용되는 “저장하기”
키 값을 정확한 컨벤션으로 분리하면서 번역가와 디자이너가 특정 문맥에 맞는 UX Writing을 조정하거나, 다른 페이지에서 사용하는 번역을 구분해 독립적으로 관리할 수 있었습니다.
마무리
Google Sheets, 자동화 스크립트, tF 함수, VSCode 확장 프로그램, 그리고 Confluence 가이드를 통해 저희 팀은 다국어화 작업의 협업 구조를 명확히 정리하고, 효율성을 크게 개선할 수 있었습니다. 이 경험은 일본 버전의 피처링코를 단 2개월 만에 안정적으로 런칭하는 데에도 큰 도움이 되었습니다.
팀의 환경에 맞춰 반복 작업을 줄이고, 각자의 역할에 집중할 수 있는 기반을 다졌다는 점에서 충분히 의미 있는 변화였습니다. 앞으로도 번역 데이터 변경 이력 관리, 키 자동 생성, 언어별 UI 테스트 자동화 등 지속적으로 개선해 나갈 계획입니다.
참고