docs: README를 한국어로 전면 재작성, 겪은 문제 4건 상세 기록 + compose 예시 추가

This commit is contained in:
syskiller
2026-08-26 09:19:09 +09:00
parent dc06b78c55
commit 55d6fd3bee
2 changed files with 112 additions and 40 deletions
+98 -40
View File
@@ -1,69 +1,127 @@
# TriliumNext Notes — Korean (한국어) Translation
# TriliumNext Notes 한국어 번역
Unofficial, community-made Korean translation for [TriliumNext Notes](https://github.com/TriliumNext/Notes).
As of this writing, TriliumNext ships with no Korean language pack, and the list of
selectable UI languages is hardcoded into the compiled server bundle rather than
derived from the `translations/` folder — so dropping these files in alone is not
enough to make "한국어" show up in the language picker. See below for the full setup.
[TriliumNext Notes](https://github.com/TriliumNext/Notes)의 비공식 커뮤니티 한국어 번역입니다.
이 글을 쓰는 시점 기준으로 TriliumNext는 한국어 언어팩이 없고, 심지어 UI 언어 선택기에
뜨는 지원 언어 목록 자체가 `translations/` 폴더를 스캔해서 만들어지는 게 아니라 **컴파일된
서버 번들(`main.cjs`) 안에 하드코딩된 배열**이라서, 번역 파일만 올바른 경로에 넣는다고
"한국어"가 언어 선택기에 나타나지 않습니다. 그래서 번역 파일 외에 서버 번들을 패치하는
스크립트도 함께 제공합니다.
- `translation.json` — client-side UI strings, 1,712 keys, 100% translated.
- `server.json` — server-side strings, 283 keys, 100% translated.
- `patch_main_cjs.js` — patches the compiled `main.cjs` server bundle to register
`ko` as a selectable language (see comments in the script for why this is needed).
## 포함된 파일
Tested against `triliumnext/notes:latest` (build date 2025-06-07, appVersion 0.95.0).
Variable names inside `main.cjs` are minified and **not stable across builds** — if
`patch_main_cjs.js` prints `[skip]` on a newer image, the markers it searches for
have changed and need to be re-derived from that build.
- `translation.json` — 클라이언트(브라우저) UI 문자열, 1,712개 키, 100% 번역 완료.
- `server.json` — 서버 문자열, 283개 키, 100% 번역 완료.
- `patch_main_cjs.js` — 컴파일된 `main.cjs` 서버 번들에 `ko`를 선택 가능한 언어로
등록하는 패치 스크립트 (왜 이게 필요한지는 스크립트 상단 주석 참고).
- `docker-compose.yml.example` — 아래 설치 방법을 그대로 반영한 예시 compose 파일.
## Setup
`triliumnext/notes:latest` (빌드일 2025-06-07, appVersion 0.95.0) 기준으로 테스트했습니다.
`main.cjs` 안의 변수명은 전부 난독화(minify)되어 있고 **빌드마다 이름이 바뀔 수 있습니다** —
`patch_main_cjs.js`를 새 이미지에 돌렸을 때 `[skip]`이 출력되면, 스크립트가 찾는 문자열
패턴이 그 빌드에서 바뀐 것이니 새 빌드에서 같은 패턴을 다시 찾아 스크립트를 갱신해야 합니다.
1. Copy the translation files into the running container (or better, bind-mount them
so they survive `docker compose down && up` — see "Important" below):
## 설치 방법
1. **번역 파일을 컨테이너에 반영** — 반드시 `docker-compose.yml`에 bind-mount로 등록할 것
(아래 "주의" 항목 참고, `docker cp`로만 넣으면 안 됨):
- `translation.json` → `/usr/src/app/public/translations/ko/translation.json`
- `server.json` → `/usr/src/app/assets/translations/ko/server.json`
2. Extract the container's unpatched `main.cjs` and run the patch script against it:
2. **컨테이너의 원본(패치 안 된) `main.cjs`를 추출**하고 패치 스크립트 실행:
```bash
docker create --name trilium-tmp triliumnext/notes:latest
docker cp trilium-tmp:/usr/src/app/main.cjs ./main.cjs
docker rm trilium-tmp
node patch_main_cjs.js ./main.cjs
node --check ./main.cjs # always verify syntax before deploying
node --check ./main.cjs # 반드시 배포 전에 문법 검사
```
3. Bind-mount the patched `main.cjs` over the original in `docker-compose.yml`:
3. **패치된 `main.cjs`를 `docker-compose.yml`에서 원본 위치에 bind-mount** —
`docker-compose.yml.example` 참고:
```yaml
services:
trilium:
image: triliumnext/notes:latest
restart: unless-stopped
ports:
- '127.0.0.1:18082:8080'
environment:
- TZ=Asia/Seoul
volumes:
- ./data:/home/node/trilium-data
- ./main.cjs:/usr/src/app/main.cjs:ro
- ./translation.json:/usr/src/app/public/translations/ko/translation.json:ro
- ./server.json:/usr/src/app/assets/translations/ko/server.json:ro
```
4. Start the container, then in the app go to Options → Localization and select "한국어".
4. 컨테이너 시작 후 앱 안에서 **옵션 → 언어 및 지역**에서 "한국어" 선택.
### Important: don't `docker cp` and stop there
## 겪었던 문제와 해결 (설치 전에 꼭 읽어보세요)
Files copied into a container with `docker cp` live only in its writable layer —
they disappear the moment the container is recreated (`docker compose down && up`,
not just `restart`). Always bind-mount the three files above from the host, as shown
in step 3, or the translation will silently vanish on the next redeploy while the
language list (which lives inside the bind-mounted `main.cjs`) keeps working —
producing a confusing partial-translation state that has nothing to do with locale
or browser and everything to do with a missing file on the server.
### 문제 1 — DB에서 `locale`을 직접 `'ko'`로 바꾸면 크래시
### Known gap: CKEditor's own toolbar
지원 언어 목록에 없는 값을 옵션 DB에 직접 넣으면 앱이 시작하다가 죽습니다. 이 방법으로는
안 되고, 반드시 아래처럼 서버 번들 자체를 패치해야 합니다.
CKEditor5's own UI strings (toolbar button labels like "Paragraph", "Heading 2",
"Bold") come from a separate translation system (`window.CKEDITOR_TRANSLATIONS`)
that isn't touched by this project. As of the tested build, **no** language —
not German, French, Spanish, Chinese, or any other already-"supported" language —
ships a real CKEditor dictionary either; it falls back to English UI for everyone.
This is a pre-existing gap in the upstream build, not something specific to Korean.
### 문제 2 — `main.cjs` 패치 위치를 잘못 잡으면 문법 자체가 깨짐
## License
지원 언어 배열(테스트한 빌드에서 변수명 `K5i`)에 새 항목을 삽입할 때, 배열의 여는 대괄호
`[` **바로 뒤**가 아니라 그 앞(변수 선언문 쪽)에 잘못 삽입하면 JS 문법 자체가 깨져서 앱이
아예 뜨지 못하고 크래시를 반복합니다. `patch_main_cjs.js`는 이 위치를 정확히 계산해서
삽입하지만, **패치 후에는 항상 `node --check`로 문법을 먼저 확인**하고 나서 컨테이너를
재시작하세요.
Same license as TriliumNext Notes itself. No warranty — this patches a compiled
binary by string-matching minified variable names, which is inherently fragile
across version upgrades.
### 문제 3 — 완전 재생성(`docker compose down && up`) 시에만 나타나는 크래시
지원 언어 배열만 고치면 `docker restart`로는 잘 동작하는 것처럼 보이지만, 컨테이너를 완전히
내렸다 올리면(`down && up`) 다시 크래시합니다. 원인은 Trilium 자체 번역과는 별개로,
**CKEditor 내장 다국어 리소스를 지연 로드하는 두 번째 하드코딩 맵**(테스트한 빌드에서
변수명 `IMi`)에도 `ko` 항목이 없어서 발생하는 `TypeError`입니다. 이 리소스는 애초에
한국어용 청크가 빌드에 포함되어 있지 않아 새로 만들 수 없으므로, `patch_main_cjs.js`는
이 항목을 영어 로더로 대체(alias)해서 우회합니다. **`restart`뿐 아니라 완전 재생성까지
반드시 테스트**하세요 — 재시작만으로는 이 문제가 드러나지 않습니다.
### 문제 4 — 번역이 배포 후 시간이 지나서 "모바일에서만" 다시 영어로 보임
가장 까다로웠던 문제입니다. 번역을 배포하고 정상 동작까지 확인했는데, 이후 **모바일
기기에서만** 옵션 페이지 등의 본문 텍스트가 다시 영어로 나오는 현상이 발생했습니다
(페이지 제목이나 "한국어"라는 언어 이름 자체는 정상). 데스크톱에서는 동일 페이지가
100% 정상이었습니다.
처음엔 모바일 브라우저 캐시 문제로 의심했지만, 캐시를 초기화하고 서로 다른 브라우저
엔진(Chrome, 삼성인터넷, 네이버) 세 개로 테스트해도 완전히 동일한 증상이 재현되어
캐시 이론은 기각했습니다.
**진짜 원인**: 번역 파일(`translation.json`, `server.json`)을 `docker cp`로 컨테이너
내부에 직접 복사만 하고 `docker-compose.yml`에는 bind-mount로 등록하지 않았던 것이
원인이었습니다. `docker cp`로 넣은 파일은 컨테이너의 쓰기 가능 레이어에만 존재하기 때문에,
이후 `main.cjs` 패치가 완전 재생성에도 살아남는지 테스트하려고 `docker compose down && up`을
실행했을 때 번역 파일 두 개가 컨테이너와 함께 통째로 사라졌습니다 (bind-mount 되어 있던
`main.cjs`만 살아남음). 서버가 `translations/ko/*.json` 요청에 404를 반환하기 시작했고,
i18next의 `fallbackLng:"en"` 설정 때문에 본문 텍스트가 조용히 영어로 대체됐습니다.
"한국어"라는 언어 이름만 정상이었던 이유는, 그 값이 이 JSON 파일이 아니라 별도의 DB 기반
API(`options/locales`)에서 오기 때문에 영향을 받지 않았던 것입니다.
데스크톱이 여전히 정상으로 보였던 이유는 단순히 파일이 사라지기 전에 브라우저가 이미
받아둔 번역 응답을 캐시하고 있었기 때문입니다 — 즉 데스크톱 쪽이 오히려 "낡은 캐시를
보여주던 쪽"이었고, 매번 새로 서버에 요청하는 모바일 쪽이 서버의 실제(망가진) 상태를
정확히 반영하고 있었던 것입니다. 세 개의 다른 모바일 브라우저에서 전부 동일한 증상이
나온 것도 이걸로 설명됩니다 — 캐시 종류와 무관하게 서버에 파일 자체가 없었으니까요.
**교훈**: `docker cp`로 컨테이너 안에 넣은 파일은 컨테이너가 사라지면 함께 사라집니다.
번역 파일도 `main.cjs`와 마찬가지로 **반드시 `docker-compose.yml`의 `volumes:`에
bind-mount로 선언**하세요 (위 설치 방법 3번, `docker-compose.yml.example` 참고). 그리고
증상이 "특정 기기에서만 재현됨", "캐시를 지워도 재현됨"처럼 보이더라도, 실제 원인은
클라이언트가 아니라 서버 쪽 데이터 유실일 수 있으므로 클라이언트 가설을 세우기 전에
서버가 실제로 무엇을 응답하는지(`curl`로 정확한 URL 직접 확인)부터 검증하는 게 우선입니다.
## 알려진 미해결 항목 — CKEditor 자체 툴바
CKEditor5 자체의 UI 문자열(툴바 버튼 이름 — "Paragraph", "Heading 2", "Bold" 등)은
이 프로젝트가 건드리는 번역 시스템과 완전히 별개인 `window.CKEDITOR_TRANSLATIONS`라는
자체 시스템에서 옵니다. 테스트한 빌드 기준으로, 독일어·프랑스어·스페인어·중국어를
포함해 이미 "지원"되는 언어 **어느 것도** 실제 CKEditor 사전을 갖고 있지 않아서 전부
영어 툴바로 나옵니다 — 한국어만의 문제가 아니라 업스트림 빌드 자체에 이미 있던 공백입니다.
## 라이선스
TriliumNext Notes 본체와 동일한 라이선스를 따릅니다. 무보증 — 이 프로젝트는 컴파일된
바이너리를 난독화된 변수명 문자열 매칭 방식으로 패치하는 것이라, 버전이 올라가면
깨지기 쉬운 구조입니다.