Initial public release: TriliumNext Korean translation (1,995 strings) + language-list patch script

This commit is contained in:
syskiller
2026-08-26 08:28:52 +09:00
commit dc06b78c55
4 changed files with 2379 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
# TriliumNext Notes — Korean (한국어) Translation
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.
- `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.
## Setup
1. Copy the translation files into the running container (or better, bind-mount them
so they survive `docker compose down && up` — see "Important" below):
- `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:
```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
```
3. Bind-mount the patched `main.cjs` over the original in `docker-compose.yml`:
```yaml
services:
trilium:
image: triliumnext/notes:latest
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 "한국어".
### 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.
### Known gap: CKEditor's own toolbar
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.
## License
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.
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env node
/**
* Patches TriliumNext's bundled main.cjs to register Korean ("ko") as a
* selectable UI language.
*
* Why this is needed: the list of supported UI languages is not derived
* from the translations/ folders on disk — it's a hardcoded array literal
* baked into the minified server bundle (variable name may change between
* builds; as of triliumnext/notes:latest on 2026-08-25 it was `K5i`).
* A second hardcoded map (`IMi` as of the same build) lazy-loads a
* per-language resource (CKEditor's own locale bundle) and has no entry
* for "ko" either — since that resource is genuinely not bundled for any
* language we didn't already support, we alias it to the English loader
* instead of trying to fabricate a missing chunk.
*
* Usage: node patch_main_cjs.js /path/to/main.cjs
*
* Run this against a FRESH copy of main.cjs extracted from the image
* (`docker cp trilium:/usr/src/app/main.cjs .`) any time the image is
* updated, since the minified variable names (K5i, IMi, xvr) and byte
* offsets are not stable across TriliumNext releases. After patching,
* always run `node --check main.cjs` before deploying it.
*/
const fs = require('fs');
const path = process.argv[2];
if (!path) {
console.error('Usage: node patch_main_cjs.js /path/to/main.cjs');
process.exit(1);
}
let content = fs.readFileSync(path, 'utf8');
let patchedAnything = false;
// --- Patch 1: register "ko" in the main supported-locales array ---
// Original starts with: K5i=[{id:"en",name:"English",electronLocale:"en"},{id:"de"
// We insert a new first element right after the opening `[`.
{
const marker = 'K5i=[{id:"en",name:"English",electronLocale:"en"},{id:"de"';
const idx = content.indexOf(marker);
if (idx === -1) {
console.log('[skip] locale-list marker not found (variable name may have changed, or already patched)');
} else {
const insertPoint = idx + 'K5i=['.length;
const insertion = '{id:"ko",name:"한국어",electronLocale:"ko"},';
content = content.slice(0, insertPoint) + insertion + content.slice(insertPoint);
console.log('[ok] inserted "ko" into the supported-locales array');
patchedAnything = true;
}
}
// --- Patch 2: alias "ko" to the English loader in the per-language resource map ---
// Original: IMi={ar:...,cn:...,de:...,en:()=>Promise.resolve().then(()=>Xe(xvr())),es:...
{
const imiIdx = content.indexOf('IMi={ar:');
const marker = 'en:()=>Promise.resolve().then(()=>Xe(xvr())),es:';
const idx = content.indexOf(marker);
if (imiIdx === -1 || idx === -1 || idx < imiIdx || idx > imiIdx + 700) {
console.log('[skip] per-language resource map marker not found or not where expected (already patched, or minified names changed)');
} else {
const insertion = 'en:()=>Promise.resolve().then(()=>Xe(xvr())),ko:()=>Promise.resolve().then(()=>Xe(xvr())),es:';
content = content.slice(0, idx) + insertion + content.slice(idx + marker.length);
console.log('[ok] aliased "ko" to the English loader in the resource map');
patchedAnything = true;
}
}
if (!patchedAnything) {
console.log('Nothing patched (file may already be patched, or this is a different TriliumNext version — re-derive the markers manually).');
process.exit(0);
}
fs.writeFileSync(path, content, 'utf8');
console.log('Wrote', path, '— now run: node --check', path);
+307
View File
@@ -0,0 +1,307 @@
{
"keyboard_actions": {
"back-in-note-history": "기록에서 이전 노트로 이동",
"forward-in-note-history": "기록에서 다음 노트로 이동",
"open-jump-to-note-dialog": "\"노트로 이동\" 대화상자 열기",
"scroll-to-active-note": "노트 트리를 활성 노트로 스크롤",
"quick-search": "빠른 검색 바 활성화",
"search-in-subtree": "활성 노트의 하위 트리에서 노트 검색",
"expand-subtree": "현재 노트의 하위 트리 펼치기",
"collapse-tree": "전체 노트 트리 접기",
"collapse-subtree": "현재 노트의 하위 트리 접기",
"sort-child-notes": "하위 노트 정렬",
"creating-and-moving-notes": "노트 만들기 및 이동",
"create-note-after": "활성 노트 다음에 노트 만들기",
"create-note-into": "활성 노트의 하위로 노트 만들기",
"create-note-into-inbox": "받은편지함(정의된 경우) 또는 일일 노트에 노트 만들기",
"delete-note": "노트 삭제",
"move-note-up": "노트 위로 이동",
"move-note-down": "노트 아래로 이동",
"move-note-up-in-hierarchy": "계층 구조에서 노트 위로 이동",
"move-note-down-in-hierarchy": "계층 구조에서 노트 아래로 이동",
"edit-note-title": "트리에서 노트 상세로 이동해서 제목 편집",
"edit-branch-prefix": "\"브랜치 접두사 편집\" 대화상자 표시",
"cloneNotesTo": "선택한 노트 복제",
"moveNotesTo": "선택한 노트 이동",
"note-clipboard": "노트 클립보드",
"copy-notes-to-clipboard": "선택한 노트를 클립보드에 복사",
"paste-notes-from-clipboard": "클립보드의 노트를 활성 노트에 붙여넣기",
"cut-notes-to-clipboard": "선택한 노트를 클립보드로 잘라내기",
"select-all-notes-in-parent": "현재 노트 레벨의 모든 노트 선택",
"add-note-above-to-the-selection": "선택 영역에 위 노트 추가",
"add-note-below-to-selection": "선택 영역에 아래 노트 추가",
"duplicate-subtree": "하위 트리 복제",
"tabs-and-windows": "탭 및 창",
"open-new-tab": "새 탭 열기",
"close-active-tab": "활성 탭 닫기",
"reopen-last-tab": "마지막으로 닫은 탭 다시 열기",
"activate-next-tab": "오른쪽 탭 활성화",
"activate-previous-tab": "왼쪽 탭 활성화",
"open-new-window": "새 빈 창 열기",
"toggle-tray": "시스템 트레이에서 애플리케이션 표시/숨김",
"first-tab": "목록의 첫 번째 탭 활성화",
"second-tab": "목록의 두 번째 탭 활성화",
"third-tab": "목록의 세 번째 탭 활성화",
"fourth-tab": "목록의 네 번째 탭 활성화",
"fifth-tab": "목록의 다섯 번째 탭 활성화",
"sixth-tab": "목록의 여섯 번째 탭 활성화",
"seventh-tab": "목록의 일곱 번째 탭 활성화",
"eight-tab": "목록의 여덟 번째 탭 활성화",
"ninth-tab": "목록의 아홉 번째 탭 활성화",
"last-tab": "목록의 마지막 탭 활성화",
"dialogs": "대화상자",
"show-note-source": "\"노트 소스\" 대화상자 표시",
"show-options": "\"옵션\" 페이지 열기",
"show-revisions": "\"노트 리비전\" 대화상자 표시",
"show-recent-changes": "\"최근 변경 내역\" 대화상자 표시",
"show-sql-console": "\"SQL 콘솔\" 페이지 열기",
"show-backend-log": "\"백엔드 로그\" 페이지 열기",
"show-help": "내장 사용자 가이드 열기",
"show-cheatsheet": "일반적인 키보드 작업 모달 표시",
"text-note-operations": "텍스트 노트 작업",
"add-link-to-text": "텍스트에 링크를 추가하는 대화상자 열기",
"follow-link-under-cursor": "캐럿이 위치한 링크 따라가기",
"insert-date-and-time-to-text": "텍스트에 현재 날짜 및 시간 삽입",
"paste-markdown-into-text": "클립보드의 Markdown을 텍스트 노트에 붙여넣기",
"cut-into-note": "현재 노트에서 선택 영역을 잘라내서 선택한 텍스트로 하위 노트 만들기",
"add-include-note-to-text": "노트를 포함하는 대화상자 열기",
"edit-readonly-note": "읽기 전용 노트 편집",
"attributes-labels-and-relations": "속성 (라벨 및 관계)",
"add-new-label": "새 라벨 만들기",
"create-new-relation": "새 관계 만들기",
"ribbon-tabs": "리본 탭",
"toggle-basic-properties": "기본 속성 전환",
"toggle-file-properties": "파일 속성 전환",
"toggle-image-properties": "이미지 속성 전환",
"toggle-owned-attributes": "소유한 속성 전환",
"toggle-inherited-attributes": "상속된 속성 전환",
"toggle-promoted-attributes": "승격된 속성 전환",
"toggle-link-map": "링크 지도 전환",
"toggle-note-info": "노트 정보 전환",
"toggle-note-paths": "노트 경로 전환",
"toggle-similar-notes": "유사한 노트 전환",
"other": "기타",
"toggle-right-pane": "목차와 하이라이트가 포함된 오른쪽 패널 표시 전환",
"print-active-note": "활성 노트 인쇄",
"open-note-externally": "기본 애플리케이션으로 노트를 파일로 열기",
"render-active-note": "활성 노트 렌더링(다시 렌더링)",
"run-active-note": "활성 JavaScript(프론트엔드/백엔드) 코드 노트 실행",
"toggle-note-hoisting": "활성 노트의 노트 호이스팅 전환",
"unhoist": "어디서든 호이스팅 해제",
"reload-frontend-app": "프론트엔드 다시 로드",
"open-dev-tools": "개발자 도구 열기",
"find-in-text": "검색 패널 전환",
"toggle-left-note-tree-panel": "왼쪽(노트 트리) 패널 전환",
"toggle-full-screen": "전체 화면 전환",
"zoom-out": "축소",
"zoom-in": "확대",
"note-navigation": "노트 탐색",
"reset-zoom-level": "확대/축소 수준 초기화",
"copy-without-formatting": "서식 없이 선택한 텍스트 복사",
"force-save-revision": "활성 노트의 새 리비전을 강제로 생성/저장",
"toggle-book-properties": "북 속성 전환",
"toggle-classic-editor-toolbar": "고정 도구모음이 있는 편집기의 서식 탭 전환",
"export-as-pdf": "현재 노트를 PDF로 내보내기",
"toggle-zen-mode": "젠 모드 활성화/비활성화 (더 집중된 편집을 위한 최소 UI)"
},
"login": {
"title": "로그인",
"heading": "Trilium 로그인",
"incorrect-totp": "TOTP가 올바르지 않습니다. 다시 시도하세요.",
"incorrect-password": "비밀번호가 올바르지 않습니다. 다시 시도하세요.",
"password": "비밀번호",
"remember-me": "로그인 상태 유지",
"button": "로그인",
"sign_in_with_sso": "{{ ssoIssuerName }}(으)로 로그인"
},
"set_password": {
"title": "비밀번호 설정",
"heading": "비밀번호 설정",
"description": "웹에서 Trilium을 사용하려면 먼저 비밀번호를 설정해야 합니다. 이후 이 비밀번호로 로그인합니다.",
"password": "비밀번호",
"password-confirmation": "비밀번호 확인",
"button": "비밀번호 설정"
},
"javascript-required": "Trilium을 사용하려면 JavaScript가 활성화되어 있어야 합니다.",
"setup": {
"heading": "TriliumNext Notes 설정",
"new-document": "신규 사용자이며, 노트를 위한 새 Trilium 문서를 만들고 싶습니다",
"sync-from-desktop": "이미 데스크톱 인스턴스가 있으며, 동기화를 설정하고 싶습니다",
"sync-from-server": "이미 서버 인스턴스가 있으며, 동기화를 설정하고 싶습니다",
"next": "다음",
"init-in-progress": "문서 초기화 진행 중",
"redirecting": "곧 애플리케이션으로 이동합니다.",
"title": "설정"
},
"setup_sync-from-desktop": {
"heading": "데스크톱에서 동기화",
"description": "이 설정은 데스크톱 인스턴스에서 시작해야 합니다:",
"step1": "TriliumNext Notes 데스크톱 인스턴스를 여세요.",
"step2": "Trilium 메뉴에서 옵션을 클릭하세요.",
"step3": "동기화 카테고리를 클릭하세요.",
"step4": "서버 인스턴스 주소를 {{- host}}로 변경하고 저장을 클릭하세요.",
"step5": "\"동기화 테스트\" 버튼을 클릭해서 연결이 성공하는지 확인하세요.",
"step6": "이 단계를 완료했으면 {{- link}}를 클릭하세요.",
"step6-here": "여기"
},
"setup_sync-from-server": {
"heading": "서버에서 동기화",
"instructions": "아래에 Trilium 서버 주소와 자격 증명을 입력하세요. 서버에서 전체 Trilium 문서를 다운로드하고 이에 대한 동기화를 설정합니다. 문서 크기와 연결 속도에 따라 시간이 걸릴 수 있습니다.",
"server-host": "Trilium 서버 주소",
"server-host-placeholder": "https://<hostname>:<port>",
"proxy-server": "프록시 서버 (선택 사항)",
"proxy-server-placeholder": "https://<hostname>:<port>",
"note": "참고:",
"proxy-instruction": "프록시 설정을 비워두면 시스템 프록시가 사용됩니다(데스크톱 애플리케이션에만 적용)",
"password": "비밀번호",
"password-placeholder": "비밀번호",
"back": "뒤로",
"finish-setup": "설정 완료"
},
"setup_sync-in-progress": {
"heading": "동기화 진행 중",
"successful": "동기화가 올바르게 설정되었습니다. 초기 동기화가 완료되기까지 시간이 걸립니다. 완료되면 로그인 페이지로 이동합니다.",
"outstanding-items": "미완료 동기화 항목:",
"outstanding-items-default": "해당 없음"
},
"share_404": {
"title": "찾을 수 없음",
"heading": "찾을 수 없음"
},
"share_page": {
"parent": "상위:",
"clipped-from": "이 노트는 원래 {{- url}}에서 클립되었습니다",
"child-notes": "하위 노트:",
"no-content": "이 노트에는 내용이 없습니다."
},
"weekdays": {
"monday": "월요일",
"tuesday": "화요일",
"wednesday": "수요일",
"thursday": "목요일",
"friday": "금요일",
"saturday": "토요일",
"sunday": "일요일"
},
"weekdayNumber": "{weekNumber}주차",
"months": {
"january": "1월",
"february": "2월",
"march": "3월",
"april": "4월",
"may": "5월",
"june": "6월",
"july": "7월",
"august": "8월",
"september": "9월",
"october": "10월",
"november": "11월",
"december": "12월"
},
"quarterNumber": "{quarterNumber}분기",
"special_notes": {
"search_prefix": "검색:"
},
"test_sync": {
"not-configured": "동기화 서버 호스트가 구성되지 않았습니다. 먼저 동기화를 구성하세요.",
"successful": "동기화 서버 핸드셰이크가 성공했으며, 동기화가 시작되었습니다."
},
"hidden-subtree": {
"root-title": "숨겨진 노트",
"search-history-title": "검색 기록",
"note-map-title": "노트 지도",
"sql-console-history-title": "SQL 콘솔 기록",
"shared-notes-title": "공유된 노트",
"bulk-action-title": "일괄 작업",
"backend-log-title": "백엔드 로그",
"user-hidden-title": "사용자 숨김",
"launch-bar-templates-title": "런치바 템플릿",
"base-abstract-launcher-title": "기본 추상 런처",
"command-launcher-title": "명령 런처",
"note-launcher-title": "노트 런처",
"script-launcher-title": "스크립트 런처",
"built-in-widget-title": "내장 위젯",
"spacer-title": "여백",
"custom-widget-title": "사용자 지정 위젯",
"launch-bar-title": "런치바",
"available-launchers-title": "사용 가능한 런처",
"go-to-previous-note-title": "이전 노트로 이동",
"go-to-next-note-title": "다음 노트로 이동",
"new-note-title": "새 노트",
"search-notes-title": "노트 검색",
"jump-to-note-title": "노트로 이동",
"calendar-title": "캘린더",
"recent-changes-title": "최근 변경 내역",
"bookmarks-title": "북마크",
"open-today-journal-note-title": "오늘의 일지 노트 열기",
"quick-search-title": "빠른 검색",
"protected-session-title": "보호된 세션",
"sync-status-title": "동기화 상태",
"settings-title": "설정",
"llm-chat-title": "노트와 채팅",
"options-title": "옵션",
"appearance-title": "외관",
"shortcuts-title": "단축키",
"text-notes": "텍스트 노트",
"code-notes-title": "코드 노트",
"images-title": "이미지",
"spellcheck-title": "맞춤법 검사",
"password-title": "비밀번호",
"multi-factor-authentication-title": "MFA",
"etapi-title": "ETAPI",
"backup-title": "백업",
"sync-title": "동기화",
"ai-llm-title": "AI/LLM",
"other": "기타",
"advanced-title": "고급",
"visible-launchers-title": "표시된 런처",
"user-guide": "사용자 가이드",
"localization": "언어 및 지역",
"inbox-title": "받은편지함"
},
"notes": {
"new-note": "새 노트",
"duplicate-note-suffix": "(사본)",
"duplicate-note-title": "{{- noteTitle }} {{ duplicateNoteSuffix }}"
},
"backend_log": {
"log-does-not-exist": "백엔드 로그 파일 '{{ fileName }}'이(가) (아직) 존재하지 않습니다.",
"reading-log-failed": "백엔드 로그 파일 '{{ fileName }}'을(를) 읽지 못했습니다."
},
"content_renderer": {
"note-cannot-be-displayed": "이 노트 유형은 표시할 수 없습니다."
},
"pdf": {
"export_filter": "PDF 문서 (*.pdf)",
"unable-to-export-message": "현재 노트를 PDF로 내보낼 수 없습니다.",
"unable-to-export-title": "PDF로 내보낼 수 없음",
"unable-to-save-message": "선택한 파일에 쓸 수 없습니다. 다시 시도하거나 다른 위치를 선택하세요."
},
"tray": {
"tooltip": "TriliumNext Notes",
"close": "Trilium 종료",
"recents": "최근 노트",
"bookmarks": "북마크",
"today": "오늘의 일지 노트 열기",
"new-note": "새 노트",
"show-windows": "창 표시",
"open_new_window": "새 창 열기"
},
"migration": {
"old_version": "현재 버전에서 직접 마이그레이션은 지원되지 않습니다. 먼저 최신 v0.60.4로 업그레이드한 후 이 버전으로 업그레이드하세요.",
"error_message": "버전 {{version}}(으)로 마이그레이션하는 중 오류: {{stack}}",
"wrong_db_version": "데이터베이스 버전({{version}})이 애플리케이션이 예상하는 버전({{targetVersion}})보다 최신입니다. 즉 더 새롭고 호환되지 않는 버전의 Trilium에서 생성되었다는 뜻입니다. 이 문제를 해결하려면 최신 버전의 Trilium으로 업그레이드하세요."
},
"modals": {
"error_title": "오류"
},
"share_theme": {
"site-theme": "사이트 테마",
"search_placeholder": "검색...",
"image_alt": "글 이미지",
"last-updated": "{{- date}}에 마지막으로 업데이트됨",
"subpages": "하위 페이지:",
"on-this-page": "이 페이지에서",
"expand": "펼치기"
}
}
+1930
View File
File diff suppressed because it is too large Load Diff