2026. 7. 12. 22:15ㆍ개발
최근 holistics/dbx-multi-lang 저장소에 한국어 locale을 추가하는 PR을 보냈다.
처음에는 단순히 “한국어 번역 파일을 추가하면 되겠지” 정도로 생각했다. 한국어 문장을 채워 넣고, 파일을 추가하고, PR을 올리면 끝나는 작업에 가까울 거라고 봤다. 그런데 막상 작업해보니 생각보다 단순한 번역 작업은 아니었다. 기존 locale 구조를 맞춰야 했고, 패키지에서 실제로 export되도록 연결해야 했고, placeholder나 특수 문자가 런타임에서 깨지지 않는지도 확인해야 했다.
결과적으로 PR은 merge되었고, dbdiagram.io와 dbdocs.io에서 사용할 수 있는 다국어 패키지에 한국어 locale이 추가되었다. 큰 기능을 만든 것은 아니지만, 한국어 사용자가 제품을 더 자연스럽게 이해할 수 있게 만드는 변화라는 점에서 의미가 있었다.
이번 작업을 하면서 가장 크게 느낀 건 로컬라이징도 결국 코드에 가깝다는 점이었다. 사람이 읽는 자연어처럼 보이지만, 실제로는 애플리케이션이 읽고 해석하는 리소스다. key 구조가 맞아야 하고, interpolation이 유지되어야 하고, i18n 라이브러리의 문법도 지켜야 한다. 단어 하나를 바꾸는 일처럼 보여도 실제로는 화면의 의미, 사용자 행동, 런타임 안정성과 연결되어 있었다.
그래서 이번 글에서는 한국어 locale을 추가하면서 어떤 구조를 봤고, 어떤 방식으로 검증했고, 리뷰 과정에서 무엇을 배웠는지 정리해보려고 한다. 거창한 오픈소스 기여기는 아니지만, 작은 PR을 보내는 과정에서도 생각보다 배울 게 많았다. 특히 나처럼 프론트엔드 개발을 하면서 제품 문구나 UI 흐름을 자주 마주치는 사람에게는 꽤 현실적인 경험이었다.

프로젝트 구조 파악하기
dbx-multi-lang은 dbdiagram.io와 dbdocs.io의 다국어 지원을 관리하는 저장소다. 각 언어별 번역 문구가 YAML 파일로 관리되고, 빌드 과정을 거쳐 dist/{locale}.json 형태로 만들어지는 구조였다. 처음 저장소를 봤을 때는 언어별 폴더가 있어서 비교적 단순한 구조처럼 보였다. 하지만 실제로 한국어를 추가하려면 단순히 locales/ko 폴더만 만들면 끝나는 게 아니었다
.
먼저 기존 언어들이 어떤 구조로 되어 있는지 봐야 했다. 각 locale은 같은 key 구조를 가져야 하고, 영어 원문에 있는 key가 한국어에는 빠지면 안 된다. UI에서 특정 key를 찾는데 한국어 파일에만 빠져 있으면 결국 런타임에서 문제가 생길 수 있기 때문이다. 그래서 기존 locale 파일을 기준으로 어떤 파일이 있고, 각 파일 안에 어떤 nested key가 있는지 확인하는 과정이 필요했다.
그리고 locale 자체도 패키지에 등록해야 했다. locales/metadata.yaml에 ko를 추가하고, src/index.js에서 dist/ko.json을 import하도록 연결하고, package.json의 exports에도 ./dist/ko.json을 추가해야 했다. 그래야 패키지를 사용하는 쪽에서 한국어 리소스를 실제로 가져다 쓸 수 있다.
이 과정에서 로컬라이징은 단순히 텍스트를 채우는 일이 아니라, 애플리케이션이 그 언어를 하나의 정식 리소스로 인식하게 만드는 작업이라는 생각이 들었다. 번역 파일을 추가하는 것과 제품에서 사용할 수 있는 locale을 추가하는 것은 조금 다른 문제였다. 전자는 문장을 만드는 일이고, 후자는 그 문장이 빌드와 배포 구조 안에 들어가도록 연결하는 일에 가까웠다.

한국어 번역 작업: 문맥과 일관성 맞추기
이번 PR에서는 locales/ko 아래에 12개의 YAML 파일을 추가했다. 계정 설정, 인증, 결제, 공통 UI, 데이터베이스 관련 문구, 다이어그램, 모달, 포털, 워크스페이스처럼 서비스의 여러 영역에 걸쳐 있는 문구들이었다. 파일 수만 보면 많다고 느껴질 수 있지만, 실제로 더 어려웠던 부분은 양보다 문맥이었다.
처음에는 영어 문장을 한국어로 옮기는 일처럼 보였지만, 실제로는 각 문구가 어디에서 쓰이는지 계속 상상해야 했다. 버튼인지, 토스트 메시지인지, 에러 문구인지, 설정 화면의 설명인지에 따라 한국어 표현이 달라져야 했다. 예를 들어 버튼 문구는 길면 어색하다. 반대로 에러 메시지는 너무 짧으면 사용자가 다음에 뭘 해야 하는지 알기 어렵다. 계정 삭제나 워크스페이스 삭제처럼 위험한 동작은 조금 딱딱하더라도 명확해야 한다.
또 하나 신경 쓴 건 용어의 일관성이었다. workspace를 어떤 곳에서는 “작업 공간”이라고 하고, 다른 곳에서는 “워크스페이스”라고 하면 같은 제품 안에서 어색해진다. diagram, collaborator, permission, publish 같은 단어들도 마찬가지였다. 한국어로 자연스럽게 풀어 쓰는 게 좋은 경우도 있고, 제품에서 이미 익숙하게 쓰는 외래어를 유지하는 편이 나은 경우도 있었다. 그래서 전체 문맥에서 자연스럽게 읽히는 표현을 고르되, 한 번 정한 표현은 최대한 유지하려고 했다.
그리고 번역 중간중간 {count}, {email}, {provider} 같은 placeholder가 있었다. 이런 값은 번역하면서 실수로 지우거나 순서를 바꾸면 안 된다. HTML 태그가 포함된 문구도 있었다. 문장 자체는 한국어지만 그 안에는 런타임에서 치환될 데이터와 UI에서 해석될 태그가 섞여 있었다. 결국 사람이 읽는 문장이면서 동시에 i18n 런타임이 해석하는 데이터이기도 했다.
이 지점에서 번역 파일도 그냥 문서가 아니라 코드에 더 가깝게 느껴졌다. 자연스러운 한국어를 만드는 것도 중요하지만, 원래 문구가 가진 구조를 망가뜨리지 않는 것이 그만큼 중요했다.

빌드와 테스트로 검증하기
번역을 다 했다고 바로 PR을 올리기에는 불안했다. 특히 locale 파일은 눈으로 봤을 때 자연스러워 보여도, key가 빠져 있거나 placeholder가 맞지 않으면 나중에 문제가 생길 수 있다. 파일이 많고 nested 구조도 깊다 보니 눈으로 하나씩 비교하는 것만으로는 충분하지 않다고 느꼈다.
그래서 npm run build를 돌려서 dist/ko.json이 정상적으로 생성되는지 확인했다. 12개의 YAML 파일이 하나의 한국어 JSON 리소스로 잘 병합되는지 보는 과정이었다. 빌드가 성공한다는 것은 최소한 YAML 문법이 깨지지 않았고, 빌드 파이프라인 안에서 한국어 locale이 정상적인 입력으로 처리된다는 뜻이었다.
그다음에는 npm test로 locale completeness 테스트를 확인했다. 기존 영어 locale과 비교했을 때 빠진 key가 없는지, placeholder나 tag가 어긋나지 않는지 확인하는 테스트였다. 로컬라이징 작업에서는 이런 검증이 특히 중요한 것 같다. 문구 하나하나는 작아 보이지만, 빠진 key 하나가 특정 화면에서는 바로 깨진 UI가 될 수 있다. 또 placeholder가 맞지 않으면 문장이 이상해지는 정도가 아니라 런타임 에러로 이어질 수도 있다.
이번 작업을 하면서 “번역 PR에도 테스트 플랜이 필요하다”는 걸 확실히 느꼈다. PR 본문에도 build와 test를 어떻게 확인했는지 적었다. npm run build로 dist/ko.json이 생성되는지 확인했고, npm test로 locale completeness 테스트가 통과하는지 확인했고, placeholder와 tag mismatch가 없는지도 봤다고 적었다.
리뷰어 입장에서도 단순히 파일이 많이 추가된 PR보다, 어떤 방식으로 검증했는지 적혀 있는 PR이 훨씬 보기 편할 것 같았다. 특히 번역 PR은 변경된 줄 수가 많아지기 쉽다. 그런 상황에서 “이 사람이 어떤 기준으로 확인했는지”가 PR 본문에 남아 있으면 리뷰 부담을 조금 줄일 수 있다.
리뷰에서 배운 것: vue-i18n의 @ escape 문제
PR을 올리고 나서 자동 리뷰에서 흥미로운 지적이 있었다. 몇몇 문구에 dbdiagram@holistics.io 같은 이메일 주소가 들어가 있었는데, vue-i18n v9 이상에서는 @ 문자가 linked message 문법에 사용된다고 했다. 그래서 이메일 주소 안의 @를 그대로 두면 런타임에서 메시지를 컴파일할 때 에러가 날 수 있다는 내용이었다.
해결 방법은 @를 그대로 쓰지 않고 다음처럼 escape하는 것이었다.
dbdiagram{'@'}holistics.io
처음 봤을 때는 조금 의외였다. 이메일 주소는 그냥 문자열이라고 생각하기 쉽다. 사용자가 보는 화면에서도 이메일 주소는 특별한 기능을 하는 코드가 아니라 단순 텍스트에 가깝다. 그런데 i18n 리소스 입장에서는 이 문자열도 런타임 파서가 읽는 입력이다. 파서가 특별한 의미로 해석하는 문자가 있다면, 사람이 보기에는 평범한 문장이어도 실행 시점에는 문제가 될 수 있다.
이 부분이 이번 PR에서 제일 기억에 남았다. 로컬라이징은 단순히 언어를 옮기는 일이 아니라, 사용하는 i18n 라이브러리의 문법과 제약까지 같이 이해해야 하는 작업이었다. 특히 @, {}, HTML 태그, interpolation 같은 요소들은 번역 과정에서도 코드처럼 조심해서 다뤄야 한다. 한국어 문장으로 자연스럽게 바꾸는 과정에서 이런 기호를 건드리면, 문장은 좋아졌는데 애플리케이션은 깨지는 상황이 생길 수 있다.
자동 리뷰가 이런 부분을 잡아준 것도 좋았다. 사람이 보기에는 지나치기 쉬운 문제인데, 실제 서비스에서는 꽤 치명적인 런타임 에러가 될 수 있기 때문이다. 나도 처음부터 vue-i18n의 linked message 문법까지 의식하고 작업한 것은 아니었기 때문에, 리뷰를 통해 새로 배운 부분이었다.
이 경험 덕분에 앞으로 i18n 파일을 볼 때는 문장 자체뿐 아니라 그 문장이 어떤 파서와 런타임을 거치는지도 같이 봐야겠다는 생각을 하게 됐다. 번역 문자열은 그냥 텍스트가 아니라 실행 환경을 가진 데이터였다.

작은 PR이지만 실제 제품 경험을 바꾸는 기여
이후 maintainer가 changelog를 업데이트했고, PR은 승인된 뒤 merge되었다. 큰 기능을 만든 것도 아니고, 복잡한 아키텍처를 바꾼 것도 아니다. 하지만 한국어 사용자가 서비스를 볼 때 처음부터 한국어로 이해할 수 있게 만드는 변화라는 점에서는 충분히 의미 있는 기여라고 느꼈다.
오픈소스 기여라고 하면 뭔가 거대한 기능을 추가하거나 어려운 버그를 고쳐야 할 것 같은 부담이 있다. 나도 예전에는 기여라고 하면 꽤 큰 단위의 작업을 떠올렸던 것 같다. 그런데 이번 경험을 통해 꼭 그렇지는 않다는 생각이 들었다. 번역, 문서, 누락된 export, 테스트 보강처럼 작아 보이는 작업도 실제 사용자 경험과 연결된다.
그리고 그런 작업 안에도 제품 구조를 이해하고, 테스트하고, 리뷰를 반영하는 개발자의 과정이 들어 있다. 이번 PR도 단순히 한국어 문장을 추가한 것만은 아니었다. 기존 locale 구조를 보고, 빌드 결과를 확인하고, 패키지 export를 연결하고, 리뷰에서 나온 런타임 이슈를 반영했다. 크기는 작지만 하나의 기능이 제품에 들어가기 위해 거치는 흐름을 다시 경험한 셈이다.
특히 로컬라이징은 사용자 경험과 가까운 작업이라 더 직접적으로 느껴졌다. 어떤 사람에게는 영어 UI가 큰 장벽이 아닐 수 있지만, 어떤 사람에게는 처음 제품을 이해하는 데 꽤 큰 비용이 된다. 한국어 locale이 추가되면 적어도 한국어 사용자에게는 그 비용이 조금 줄어든다. 그런 점에서 작은 PR이지만 실제 제품 경험을 바꾸는 기여라고 생각했다.
이번 PR은 그런 점에서 좋은 시작점이었다. 앞으로도 꼭 큰 기능이 아니더라도, 내가 발견한 작은 불편이나 빠진 부분을 고쳐서 upstream에 보내는 일을 더 해봐도 좋겠다는 생각이 들었다. 특히 내가 실제로 쓰거나 관심 있는 제품이라면 더 그렇다. 사용자의 입장에서 어색한 부분을 발견하고, 개발자의 입장에서 구조를 확인하고, 다시 기여자의 입장에서 PR로 정리하는 과정이 자연스럽게 이어질 수 있기 때문이다.
기여가 merge되는 경험도 생각보다 동기부여가 됐다. 내가 만든 코드나 문구가 내 저장소 안에만 남는 게 아니라, 다른 사람이 사용하는 프로젝트의 일부가 된다는 감각이 있었다. 그게 아주 작은 변화라도 오픈소스 기여의 재미는 거기에 있는 것 같다.
마치며
이번에 한국어 locale을 추가하면서 로컬라이징을 조금 다르게 보게 됐다. 예전에는 번역 파일을 제품의 부속물처럼 생각했던 것 같다. 기능이 먼저 있고, 그 기능에 붙는 텍스트를 여러 언어로 바꿔놓은 정도로 봤다. 하지만 실제로 작업해보니 locale 파일은 UI와 런타임 사이에 있는 중요한 인터페이스였다.
key 구조가 맞아야 하고, placeholder가 유지되어야 하고, i18n 라이브러리의 문법도 지켜야 한다. 자연스러운 표현을 고르는 일도 중요하지만, 그 표현이 애플리케이션 안에서 안전하게 해석되는지도 봐야 한다. 문장이 아무리 좋아도 key가 빠지거나 특수 문자가 잘못 처리되면 결국 사용자에게는 깨진 화면이나 에러로 보일 수 있다.
결국 i18n 번역 파일도 코드다. 사람이 읽는 문장이지만, 동시에 애플리케이션이 실행 중에 해석하는 리소스다. 그래서 자연스러운 한국어 표현만큼이나 구조, 검증, 런타임 제약을 함께 봐야 한다. 이번 작업에서 @ escape 문제를 겪은 것도 이 생각을 더 강하게 만들었다.
작은 PR이었지만, 오픈소스에 기여하는 흐름을 다시 경험할 수 있었고, 로컬라이징 작업을 더 개발자다운 관점으로 볼 수 있게 된 계기였다. 코드를 많이 바꾸지 않아도, 제품 구조를 이해하고 검증을 거치고 리뷰를 반영하는 과정은 똑같이 중요했다.
다음에 비슷한 작업을 한다면 번역 glossary를 먼저 정리하거나, placeholder와 특수 문자 검증을 더 자동화해보고 싶다. 예를 들어 주요 용어를 먼저 정해두면 번역 중간에 표현이 흔들리는 일을 줄일 수 있고, @나 {} 같은 특수 문자는 스크립트로 한 번 더 확인할 수도 있을 것 같다.
작은 개선이라도 실제 제품 경험을 조금 더 나아지게 만들 수 있다면, 그런 기여를 계속 해봐도 좋겠다는 생각이 들었다. 이번 글도 그런 의미에서 단순한 작업 기록이라기보다는, 작은 PR을 통해 로컬라이징과 오픈소스 기여를 다시 생각해본 기록에 가깝다. 다음에는 더 자연스러운 한국어 문구뿐 아니라, 그 문구를 안전하게 유지할 수 있는 검증 방식까지 같이 고민해보고 싶다.
'개발' 카테고리의 다른 글
| 쉽고 간편한 SSL/TLS 인증서 등록, 발급, 관리를 위한 Certificate Manager (0) | 2026.05.26 |
|---|---|
| 프론트엔드 개발자의 시선으로 바라본 AWS의 전체 구조 (0) | 2026.04.30 |
| Electron 웹뷰 앱을 Tauri Native UI로 마이그레이션하기 1: 왜 지금 갈아타는가 (0) | 2026.04.14 |
| [Nextjs] Next.js로 구현하는 로그인 (1) | 2025.03.12 |
| [Next.js] 서버 컴포넌트 vs 클라이언트 컴포넌트 (2) | 2025.03.04 |