한국어 포함 9개 언어 현지화, 글꼴 서브셋과 두부 글자와의 싸움
Godot 4 웹 게임 Salmonholic을 8개 언어로 번역한 뒤 한국어를 9번째로 넣었다. 5MB ICU 데이터를 피한 방법, 갈무리11 서브셋 글꼴, 빠진 글자를 잡는 검사까지 기록했다.

Salmonholic은 해외 웹 게임 포털에 올릴 게임이라서 영어가 기준 언어다. 그래도 만드는 나는 한국 사람이고, 한국어 화면은 꼭 넣고 싶었다. 현지화는 두 단계로 진행했다. 먼저 라틴·키릴 문자권 8개 언어를 넣었고, 다음 날 한국어를 9번째 언어로 넣었다. 두 단계의 난이도가 전혀 달랐다. 대부분의 문제가 번역문보다 글꼴에서 나왔다.
1단계: 191개 키와 8개 언어
9월 23일에 화면의 영어 문자열을 모두 키로 뽑았다. 원본은 CSV 한 장이고, 열은 keys,en,es,pt_BR,fr,de,it,tr,ru였다. 게임 화면용 170행과 HTML 로딩 셸용 21행을 합쳐 191개 키였다. 게임 쪽은 Godot의 tr()로 읽는다. 셸은 CSV에서 생성한 번역 객체를 HTML에 내장해서, 엔진이 뜨기 전 로딩 문구와 오류 문구도 번역되게 했다.
번역하지 않기로 정한 것도 있다. 플레이어 이름, LV/HP/XP, 무기 칩 약어, 브랜드명은 모든 언어에서 그대로 둔다. 플레이어 이름 라벨은 자동 번역도 껐다.
언어는 이 순서로 고른다.
| 순서 | 출처 |
|---|---|
| 1 | 설정에서 직접 고른 언어(저장됨) |
| 2 | 포털 SDK가 알려 준 언어 |
| 3 | 브라우저·OS 언어 |
| 4 | 영어 |
포털 SDK가 언어 정보를 늦게 보내는 경우도 있었다. 그래서 자동 모드일 때는 이미 그려진 화면을 다시 번역하게 했다.
가장 손이 많이 간 곳은 레벨업 카드였다. 카드 상자는 96×70 픽셀이고, 글꼴 크기 10에서 한 단어가 88픽셀을 넘으면 넘친다. 독일어와 러시아어는 단어가 길다. 모든 카드 단어의 폭을 스크립트로 재서 넘치는 번역을 짧게 고쳤다. 최종 결과는 초과 단어 0개였다.
러시아어 레벨업 카드. 러시아어는 단어가 길어서 카드 폭에 맞게 번역을 다듬었다.
번역이 게임 결과를 바꾸면 안 되니 결정론 테스트도 돌렸다. 같은 시드 11을 러시아어로 돌려 영어 기준 결과와 비교했고, 경기 결과 수치가 완전히 같았다. 용량도 쟀다. 번역 리소스는 74,792바이트였고 wasm은 바이트 단위까지 같았다.
한국어를 넣으려는데 4.8MB가 따라왔다
1단계에서 한국어를 뺀 이유는 글꼴이었다. Godot 4.7.2 기본 글꼴은 라틴 확장과 키릴은 되지만 한글이 없다. 여기에 웹 용량을 줄이려고 만든 slim 템플릿(Godot 웹 export에서 겪은 문제들 참고)은 대체 텍스트 서버를 쓰는데, 이 서버는 원래 복잡한 문자 조합을 위한 것이 아니다. 다행히 한글 음절은 조합 없이 글자 하나가 글리프 하나로 그려져서 대체 서버로도 표시할 수 있었다.
다음 날인 9월 24일에 한국어를 붙이다가 이상한 걸 발견했다. 프로젝트 번역 목록에 ko를 넣기만 하면, 텍스트 서버 데이터를 빼라고 설정해 두었는데도 export가 4,797,472바이트짜리 ICU 사전을 자동으로 넣었다. Godot 엔진의 export 코드가 언어 목록을 보고 판단하는 동작이었다. slim 템플릿은 이 사전을 쓰지도 않는다. 해결은 간단했다. 한국어만 프로젝트 번역 목록에서 빼고, 게임 시작 때 코드에서 TranslationServer.add_translation()으로 직접 등록했다. 한국어 포함 전후의 차이는 다음과 같다.
| 항목 | 한국어 제외 | 한국어 포함 | 차이 |
|---|---|---|---|
| PCK 원본 | 1,300,936B | 1,335,488B | +34,552B |
| PCK brotli | 756,563B | 781,963B | +25,400B |
ICU를 그대로 넣었다면 4.8MB가 늘었을 것이다. 실제로 늘어난 양은 약 25KB였다.
갈무리11 서브셋 글꼴
한글 글꼴로는 픽셀 글꼴 갈무리11을 골랐다. 게임의 도트 느낌과 맞고, SIL OFL 1.1 라이선스라 수정 배포가 가능하다. 수정본이라는 것을 구분하려고 글꼴 이름을 Salmon KO로 바꿨고 라이선스 전문도 함께 넣었다. 한글 11,172자를 다 넣으면 무거우니 CSV의 한국어 문자열에 실제로 쓰인 글자만 뽑는 서브셋 스크립트를 만들었다. 첫 버전은 417자, 71,196바이트였다. 이 글꼴을 기본 글꼴의 대체 글꼴로 연결했다. 그래서 라틴 글자는 원래 글꼴로, 한글만 갈무리로 그려진다. 작은 글씨의 획이 사라지지 않도록 안티앨리어싱은 회색조로 설정했다.
한국어 레벨업 카드. “마법 지팡이”, “부메랑 도끼”가 카드 폭 안에 두 줄로 들어간다.
서브셋 방식에는 알려진 한계가 있다. 번역문에 없는 한글은 그릴 수 없다. 그래서 플레이어가 아무 한글로나 지은 이름은 보장하지 않는다고 문서에 적어 두었다.
빠진 글자는 두부가 된다
서브셋 방식의 약점은 곧 드러났다. 같은 날 밤, 워밍업 튜토리얼의 한국어가 깨져 있었다. 새로 추가한 문구 “안내 건너뛰기”, “초밥을 먹으면 성장해요”, “고양이를 기다리며 몸을 풀어요”, “잠시 멈춤” 같은 문장에 서브셋에 없는 글자가 들어 있었다. 건, 너, 뛰, 먹, 밥, 몸, 풀, 며, 멈, 잠, 춤, 튼이 빠진 글자였다. 없는 글자는 네모 상자, 흔히 말하는 “두부”로 표시된다.
글꼴에 없는 문자가 두부로 찍힌 예. 이 캡처는 slim 템플릿 검증 때 무기 슬롯 라벨에서 나온 것이다.
그날 커밋 f637d1d(0.12.4)에서 글꼴을 446자로 다시 만들었다. 더 중요한 수정은 재발 방지였다. 번역표 검사 스크립트가 한국어 문자열의 한글 하나하나를 글꼴의 문자표와 대조하고, 빠진 글자가 있으면 실패하도록 바꿨다. 오류 메시지에는 빠진 글자와 “subset_ko.py 재실행”이라는 해결 방법을 같이 찍는다. 그 뒤로는 문구를 추가하고 글꼴 재생성을 잊어도 배포 전 검사에서 걸린다.
지금 상태와 남은 일
9월 27일 기준으로 번역표는 420개 키, 9개 언어다. 한글 글꼴은 음절 437자를 포함한 539개 글리프이고, 91,528바이트다. 캐릭터와 무기가 추가될 때마다 글꼴을 다시 만들었다. 그래서 글꼴 파일을 바꾼 커밋이 0.11.0부터 0.21.0까지 열여섯 번 있다.
솔직히 남은 문제도 있다. 영어 외 번역은 원어민 검수를 받지 못했다. 스페인어나 터키어 문장이 자연스러운지는 아직 확인하지 못했다. 플레이어 피드백이 오는 대로 고칠 생각이다.
한국어 화면은 Salmonholic 게임 페이지에서 브라우저 언어가 한국어면 자동으로 나온다. 영어 브라우저라도 설정에서 언어 버튼을 누르면 바꿀 수 있다. 포털 SDK 연동 이야기는 웹 게임 포털 대응기에 따로 적었다.


