Claude Code 화면의 영문(답변, 스피너, 입력창 힌트, 도구 묶음 줄, 안내 줄, 작업 알림, /config 메뉴, 명령 설명)을 한국어로 보여 준다

Claude Code mod 플러그인 두 개와, 이를 설치하는 마켓플레이스 local-mods를 담은 저장소입니다.
usage-line: 입력창 바로 위에 작업 상태와 사용량을 두 줄 띠로 보여 줍니다.korean-ui: 화면에 보이는 영문을 한국어로 바꿉니다. 아래 절 참고.~/.claude/statusline-usage.sh 상태 줄에 나오던 정보를 mod API로 다시 그립니다. 스크립트를 실행하지는 않으며, 스크립트에 있던 vim 모드 표시는 mod API로 읽을 수 없어 뺐습니다.
mod(function hooks) API는 early access 기능이라 Claude Code 릴리스에 따라 바뀔 수 있습니다.
proj on main [+1 ?3] │ Opus 5.5 ⚡high [status] [context] [diff] [compact]
ctx 42% 84k/200k · cache 87% │ $1.23 │ 5h 30% →14:00 · 7d 12% · Fable 5%
1줄: 어디서, 무엇으로
+), 수정(!), 미추적(?), ahead(⇡), behind(⇣) 개수를 0이 아닌 것만 표시합니다. detached 상태면 커밋 해시 앞 7자리를 보여 줍니다.claude-opus-5-5 → Opus 5.5)과 effort. effort는 마지막 모델 요청 값을 쓰고, 아직 요청이 없으면 settings의 modelSettings[모델 ID].effortLevel, 그것도 없으면 effortLevel을 씁니다.[status], [context] 버튼은 /status, /context를, [compact] 버튼은 이어서 작업할 수 있게 요약하라는 지시문을 붙여 /compact를 실행합니다. 응답 중에 누르면 응답이 끝난 뒤 실행됩니다.[diff](/diff), 5h나 7d 사용률이 70% 이상이면 [usage](/usage)가 [compact] 앞에 나타납니다. 미추적 파일만 있을 때는 [diff]를 보이지 않습니다.…로 잘립니다. 버튼을 [ label ]로 그리면 폴더·모델에 40칸이 남지 않을 만큼 좁을 때는 [compact]를 뺀 버튼을 테두리 없는 흐린 라벨로 그립니다. 마우스를 올리거나 포커스가 가면 진하게 보입니다.2줄: 얼마나
ctx: 컨텍스트 사용률과 토큰 수(사용/창 크기)cache: 프롬프트 캐시 적중률. 이 대화의 메인 스레드 요청에서 누적한 cache_read 토큰을 전체 입력 토큰으로 나눈 값입니다. /clear로 세션 id가 바뀌거나 Claude Code를 새로 시작하면(resume 포함) 0부터 다시 셉니다.5h(초기화 시각 포함), 7d 사용 한도와 모델별 주간 한도. 모델별 한도는 상태 줄 입력에 없어서 Claude Code가 ~/.claude.json에 캐시해 둔 값을 읽으며, 다른 계정의 캐시이거나 24시간 넘게 갱신되지 않았으면 표시하지 않습니다.라벨과 부가 정보는 흐리게, 값은 기본 글자색으로 그립니다. 사용률은 한도에 가까울 때만 색을 바꿉니다.
| 항목 | 노랑 | 굵은 빨강 |
|---|---|---|
ctx | 60% 이상 | 80% 이상 |
5h, 7d, 모델별 한도 | 70% 이상 | 90% 이상 |
띠에 두 줄을 그릴 자리가 없으면 한 줄로 합칩니다.
session.measure)git 정보는 git status --porcelain=v2 --branch 한 번으로 읽으므로 git이 PATH에 있어야 합니다. git 저장소가 아닌 폴더에서는 git 구간을 표시하지 않습니다.
플러그인 API가 바꿀 수 있게 열어 둔 화면 요소만 한국어로 바꿉니다.
| 위치 | 원래 | 바뀐 뒤 |
|---|---|---|
| Claude 답변·질문·작업 목록 | 영어로 답할 때가 있음 | 시스템 프롬프트에 지시를 붙여 한국어로 답하게 함 |
| 작업 중 표시 | Sauteing… | 궁리하는 중… |
| 턴 종료 줄 | ✻ Baked for 3s | ✻ 3초 동안 궁리함 |
| 입력창 아래 힌트 | ? for shortcuts, esc to interrupt | ? 단축키, esc 중단 |
| 모드 표시 | plan mode on | 계획 모드 켜짐 |
| 접힌 도구 묶음 | Read 3 files, ran 2 shell commands | 파일 3개 읽음, 명령 2개 실행 (ctrl+o 펼치기) |
/config 메뉴 항목 | Auto-compact, Show tips | 자동 압축, 팁 표시, 사전에 없는 항목은 모델 번역 |
/ 자동완성 명령 설명 | Start a new session … | 빈 컨텍스트로 새 세션 시작 … |
| 대화 중 엔진 안내 줄 | Conversation compacted, Session resumed without branch: main | 대화를 압축함, 그 밖의 문장은 모델 번역 |
| 백그라운드 작업 알림 줄 | Task … completed 같은 요약 | 모델 번역 |
| 스킬·플러그인 명령 설명, 사전에 없는 기본 명령 설명 | / 메뉴의 영문 설명 | 모델 번역 |
번역은 두 단계로 합니다.
/config 항목, 기본 명령 설명처럼 문구가 정해진 곳은 register.tsx·dict.ts의 사전으로 바로 바꿉니다. 사전 문구는 Claude Code 2.1.296 실행 파일에서 확인했습니다. 키 이름 뒤에 동작이 붙는 힌트(esc to close, / to search)는 키 + 동작 규칙으로 바꿉니다./config 항목 이름처럼 종류가 많거나 값이 섞여 드는 문장은 haiku 모델로 번역합니다. 결과는 $.store에 최근 500개까지 남겨 다시 쓰므로 같은 문장은 한 번만 번역합니다./config 항목 이름은 세션이 시작되고 3초 뒤 한 번에 번역합니다(10개씩 묶어 요청). 처음 설치한 세션이나 새 스킬·새 버전의 문구가 생겼을 때만 토큰이 듭니다.사전에도 없고 모델 번역 대상도 아닌 문장은 섞인 문장이 되지 않도록 원문 그대로 둡니다. 코드, 명령어, 경로, 식별자는 번역하지 않습니다.
바꾸지 못하는 것: 시작 로고, 권한 확인 창, 질문 창의 고정 버튼(Other, Submit), /status·/usage 창, /config의 탭 이름과 설정값, 도구 실행 줄(Bash(...))과 결과 요약(Read 120 lines). 이 화면들은 플러그인 API에 바꿀 수 있는 지점이 없습니다.
/config 항목 이름과 기본 명령 설명은 Claude Code 2.1.296에서 뽑은 영문을 키로 씁니다. 버전이 바뀌어 사전에 없는 문구가 생기면 모델 번역으로 채우고, 번역을 고정하고 싶을 때만 korean-ui/hooks/dict.ts에 추가합니다. 입력창 힌트·스피너·모드 표시는 자주 다시 그려지는 짧은 문구라 사전으로만 바꾸므로, 새 문구는 영문으로 남습니다.
스피너 아래 Tip: 줄은 플러그인 훅이 닿지 않아, Claude Code의 spinnerTipsOverride 설정으로 바꿉니다. korean-ui/tips.json에 2.1.291·2.1.296의 기본 팁을 옮긴 54개가 있습니다. 맥 전용, 기업·외부 클라우드 전용, 그때그때 값이 바뀌는 팁은 뺐습니다. ~/.claude/settings.json에 다음을 넣으면 기본 영어 팁 대신 이 목록이 팁: 머리말로 나옵니다.
"spinnerTipsOverride": {
"tipsFile": "~/.claude/mods/korean-ui/tips.json",
"excludeDefault": true,
"label": "팁"
}
팁은 파일에서 바로 읽으므로 저장소를 git pull하면 갱신됩니다. Claude Code에 새 팁이 생기면 이 파일에 직접 추가해야 합니다(최대 200개, 각 500자).
저장소를 내려받아 로컬 마켓플레이스로 추가한 뒤 플러그인을 설치합니다.
git clone https://github.com/jaeseongs95/claude-code-mods.git ~/.claude/mods
claude plugin marketplace add ~/.claude/mods
claude plugin install usage-line@local-mods
claude plugin install korean-ui@local-mods
필요한 플러그인만 설치해도 됩니다.
.claude-plugin/marketplace.json 마켓플레이스 local-mods 정의
usage-line/
.claude-plugin/plugin.json 플러그인 매니페스트
hooks/hooks.json 훅 모듈 목록
hooks/register.tsx 띠 그리기와 갱신 로직
hooks/usage-line.test.ts 테스트
types/index.d.ts state 타입 선언
korean-ui/
.claude-plugin/plugin.json 플러그인 매니페스트
hooks/hooks.json 훅 모듈 목록
hooks/register.tsx 화면 요소별 한글화 훅
hooks/dict.ts /config 항목 이름·명령 설명 사전
hooks/translate.ts 모델 번역 프롬프트와 응답 해석
hooks/register.test.ts 테스트
tips.json spinnerTipsOverride용 한국어 팁 목록
각 플러그인의 .claude-plugin/types/는 Claude Code가 mod를 불러올 때 생성하는 API 타입 선언이라 저장소에 포함하지 않습니다.
claude plugin validate usage-line
claude plugin test usage-line
claude plugin validate korean-ui
claude plugin test korean-uihooks/register.tsx 309 lines1import type { EngineInterface, Register, ToolGroupCall } from 'claude-code'
2
3import { COMMAND_DESCRIPTIONS, CONFIG_LABELS } from './dict'
4import { BATCH, needsTranslation, parseArray, SYSTEM, TRANSLATIONS_KEY, trim } from './translate'
5
6// 문자열 전체가 이 문구일 때만 바꾼다(문장 속 같은 단어는 건드리지 않는다).
7// 엔진 문구는 2.1.291 실행 파일에서 뽑고 2.1.296에서 다시 확인한 것이다. 끝의 말줄임표(…)는 떼고 찾는다.
8const WORDS: Record<string, string> = {
9 focus: '집중',
10 'memory paused': '메모리 일시 정지',
11 interrupted: '중단됨',
12 thinking: '생각 중',
13 waiting: '대기 중',
14 running: '실행 중',
15 retrying: '다시 시도하는 중',
16 summarizing: '요약하는 중',
17 'compacting conversation': '대화 압축 중',
18 'waiting for api response': 'API 응답 기다리는 중',
19 'esc again to clear': 'Esc를 한 번 더 누르면 지움',
20 'draft restored': '쓰던 글 복원됨',
21 'error compacting conversation': '대화 압축 오류',
22 'compaction blocked by precompact hook': 'PreCompact 훅이 압축을 막음',
23 'no background agents running': '실행 중인 백그라운드 에이전트 없음',
24 'ctrl+y to paste deleted text': 'Ctrl+Y로 지운 글 붙여넣기',
25 'plugins changed. run /reload-plugins to activate.': '플러그인이 바뀜. /reload-plugins로 적용하세요.',
26 'deeper reasoning requested for this turn': '이번 턴에 더 깊은 추론 요청됨',
27 'this prompt will launch an ultraplan cloud session': '이 프롬프트는 ultraplan 클라우드 세션을 시작함',
28 'clear the input to edit this queued shell command': '대기 중인 셸 명령을 고치려면 입력창을 비우세요',
29 'press up to edit queued messages': '↑ 키로 대기 중인 메시지 편집',
30 'press up to edit queued messages, enter to send them immediately': '↑ 키로 대기 중인 메시지 편집, Enter로 바로 보내기',
31 'press up to select a queued message to edit, or enter to send them now': '↑ 키로 고칠 메시지 선택, Enter로 지금 보내기',
32 'showing detailed transcript': '자세한 대화 기록 표시 중',
33 'conversation compacted': '대화를 압축함',
34 'session resumed': '세션 재개됨',
35 'session completed successfully': '세션이 끝남',
36}
37
38// 키 이름(ctrl+o, esc, enter, ↑ 등) 뒤에 오는 동작. 긴 것부터 맞춘다.
39const ACTIONS: Record<string, string> = {
40 'interrupt and send': '중단하고 보내기',
41 'run in background': '백그라운드 실행',
42 'show todos': '할 일 보기',
43 'hide todos': '할 일 숨기기',
44 'show tasks': '작업 보기',
45 'hide tasks': '작업 숨기기',
46 'go back': '뒤로',
47 'open in editor': '편집기에서 열기',
48 'switch mode': '모드 전환',
49 interrupt: '중단',
50 expand: '펼치기',
51 collapse: '접기',
52 cycle: '전환',
53 cancel: '취소',
54 edit: '편집',
55 confirm: '확인',
56 select: '선택',
57 close: '닫기',
58 continue: '계속',
59 save: '저장',
60 dismiss: '닫기',
61 view: '보기',
62 toggle: '전환',
63 stop: '중지',
64 delete: '삭제',
65 copy: '복사',
66 send: '보내기',
67 keep: '유지',
68 clear: '지우기',
69 back: '뒤로',
70 add: '추가',
71 skip: '건너뛰기',
72 open: '열기',
73 search: '검색',
74 retry: '다시 시도',
75 queue: '대기열에 넣기',
76 navigate: '이동',
77 submit: '제출',
78 undo: '되돌리기',
79 apply: '적용',
80 resume: '재개',
81 exit: '종료',
82}
83
84// 단어 중간(setup의 up 등)에서 시작하지 않도록 앞을 막는다.
85const KEY =
86 '(?<![\\w+-])((?:ctrl|shift|alt|option|cmd|meta)[+-]\\S+|(?:esc|escape|tab|enter|return|space|up|down|left|right|backspace)\\b|[↑↓←→/?!@#])'
87const key = (pattern: string) => new RegExp(pattern.replaceAll('KEY', KEY), 'gi')
88const actions = Object.keys(ACTIONS).sort((a, b) => b.length - a.length).join('|')
89
90// 엔진이 그리는 고정 문구. 앞에서부터 차례로 바꾸고, 사전에 없는 문장은 그대로 둔다.
91const PHRASES: ReadonlyArray<readonly [RegExp, string | ((...m: string[]) => string)]> = [
92 [/\? for shortcuts/gi, '? 단축키'],
93 [key('press KEY again to exit'), '종료하려면 $1 한 번 더'],
94 [key('press KEY again to clear'), '지우려면 $1 한 번 더'],
95 [key('KEY then KEY'), '$1 다음 $2'],
96 [key(`KEY to (${actions})\\b`), (_, k, action) => `${k} ${ACTIONS[action!.toLowerCase()]}`],
97 [/accept edits on/gi, '편집 자동 수락 켜짐'],
98 [/plan mode on/gi, '계획 모드 켜짐'],
99 [/bypass permissions on/gi, '권한 확인 생략 켜짐'],
100 [/auto mode on/gi, '자동 모드 켜짐'],
101 // /cost
102 [/total cost:/gi, '총 비용:'],
103 [/total duration \(API\):/gi, '총 소요 시간(API):'],
104 [/total duration \(wall\):/gi, '총 소요 시간(실제):'],
105 [/total code changes:/gi, '총 코드 변경:'],
106 [/(\d+) lines? added/gi, '$1줄 추가'],
107 [/(\d+) lines? removed/gi, '$1줄 삭제'],
108 [/usage by model:/gi, '모델별 사용량:'],
109 [/([\d.,]+[kmb]?) input\b/gi, '입력 $1'],
110 [/([\d.,]+[kmb]?) output\b/gi, '출력 $1'],
111 [/([\d.,]+[kmb]?) cache read\b/gi, '캐시 읽기 $1'],
112 [/([\d.,]+[kmb]?) cache write\b/gi, '캐시 쓰기 $1'],
113]
114
115export const ko = (text: string): string => {
116 const bare = text.trim().replace(/(…|\.\.\.)$/, '')
117 const word = WORDS[bare.toLowerCase()]
118 if (word) return word + (bare.length < text.trim().length ? '…' : '')
119 return PHRASES.reduce((s, [from, to]) => (typeof to === 'string' ? s.replace(from, to) : s.replace(from, to)), text)
120}
121
122// 사전에 없는 문장(안내 줄, 작업 알림, 스킬 설명)은 모델로 번역해 $.store에 남겨 다시 쓴다.
123const loadCache = async ($: EngineInterface) => ((await $.store.get(TRANSLATIONS_KEY)) ?? {}) as Record<string, string>
124
125const translateMany = async ($: EngineInterface, texts: readonly string[]): Promise<string[]> => {
126 const cache = await loadCache($)
127 const missing = [...new Set(texts.filter(t => needsTranslation(t) && !(t in cache)))]
128 let isChanged = false
129 for (let i = 0; i < missing.length; i += BATCH) {
130 const batch = missing.slice(i, i + BATCH)
131 const reply = await $.model
132 .complete({ model: 'haiku', system: SYSTEM, prompt: JSON.stringify(batch), maxTokens: 8000, effort: 'low', timeoutMs: 30_000 })
133 .catch(() => undefined)
134 const out = reply?.isAnswered ? parseArray(reply.text, batch.length) : undefined
135 if (!out) break // 실패하면 원문을 그대로 보여 주고 다음 기회에 다시 시도한다.
136 batch.forEach((t, j) => (cache[t] = out[j]!))
137 isChanged = true
138 }
139 if (isChanged) await $.store.set(TRANSLATIONS_KEY, trim(cache))
140 return texts.map(t => cache[t] ?? t)
141}
142
143// 같은 문장을 동시에 여러 번 요청하지 않도록 진행 중인 요청을 공유한다.
144const pending = new Map<string, Promise<string>>()
145
146// 사전으로 먼저 바꾸고, 못 바꾼 문장만 모델 번역으로 넘긴다.
147const toKorean = ($: EngineInterface, text: string): Promise<string> => {
148 const fixed = ko(text)
149 if (fixed !== text || !needsTranslation(text)) return Promise.resolve(fixed)
150 const running = pending.get(text)
151 if (running) return running
152 const job = translateMany($, [text])
153 .then(([out]) => out ?? text)
154 .finally(() => pending.delete(text))
155 pending.set(text, job)
156 return job
157}
158
159const SPINNER = ['생각하는 중', '궁리하는 중', '고민하는 중', '정리하는 중', '분석하는 중', '다듬는 중', '짜 맞추는 중', '빚는 중']
160const DONE = ['작업함', '생각함', '궁리함', '정리함', '다듬음', '빚어냄']
161
162// 같은 행은 다시 그려도 같은 단어가 나오도록 id로 고른다.
163const pick = (list: readonly string[], seed: string) => {
164 let n = 0
165 for (const c of seed) n = (n * 31 + c.charCodeAt(0)) >>> 0
166 return list[n % list.length]!
167}
168
169export const duration = (ms: number) => {
170 const s = Math.round(ms / 1000)
171 if (s < 60) return `${s}초`
172 if (s < 3600) return `${Math.floor(s / 60)}분 ${s % 60}초`
173 return `${Math.floor(s / 3600)}시간 ${Math.floor((s % 3600) / 60)}분`
174}
175
176const KINDS: Record<string, string> = {
177 Read: '파일 {n}개 읽음',
178 NotebookRead: '노트북 {n}개 읽음',
179 Grep: '{n}회 검색',
180 Glob: '{n}회 파일 찾기',
181 WebSearch: '웹 {n}회 검색',
182 WebFetch: '웹 페이지 {n}개 가져옴',
183 Bash: '명령 {n}개 실행',
184 PowerShell: '명령 {n}개 실행',
185 LS: '폴더 {n}개 확인',
186}
187
188export const groupLine = (calls: ReadonlyArray<Pick<ToolGroupCall, 'tool'>>) => {
189 const counts = new Map<string, number>()
190 for (const { tool } of calls) counts.set(tool, (counts.get(tool) ?? 0) + 1)
191 return [...counts]
192 .map(([tool, n]) => (KINDS[tool] ?? `${tool} {n}회`).replace('{n}', String(n)))
193 .join(', ')
194}
195
196
197const LANGUAGE = {
198 id: 'korean-ui:language',
199 scope: 'session',
200 text: [
201 '# 응답 언어',
202 '사용자에게 보이는 모든 글(답변, 진행 설명, 질문, 작업 목록, 요약)은 한국어로 쓴다.',
203 '코드, 명령어, 파일 경로, 식별자, URL, 오류 메시지 원문 인용은 번역하지 않는다.',
204 ].join('\n'),
205} as const
206
207export const register: Register = on => {
208 // 답변 본문: 화면에서 번역하지 않고 처음부터 한국어로 쓰게 한다.
209 on('prompt.compose', async ($, e, next) => {
210 const { sections } = await next(e)
211 return { sections: [...sections.filter(s => s.id !== LANGUAGE.id), LANGUAGE] }
212 })
213
214 on('ui.render', { component: 'Spinner' }, ($, e, next) =>
215 next({
216 ...e,
217 props: {
218 ...e.props,
219 word: pick(SPINNER, e.requestId + e.props.word),
220 message: e.props.message === null ? null : ko(e.props.message),
221 },
222 }),
223 )
224
225 on('ui.render', { component: 'TurnDuration' }, ($, e) => {
226 const { Text } = $.ui.resolve(e)
227 return <Text dimColor>✻ {duration(e.props.durationMs)} 동안 {pick(DONE, e.requestId)}</Text>
228 })
229
230 on('ui.render', { component: 'PromptHint' }, ($, e, next) =>
231 e.props.hint ? next({ ...e, props: { ...e.props, hint: ko(e.props.hint) } }) : next(e),
232 )
233
234 on('ui.render', { component: 'ToolProgress' }, ($, e, next) =>
235 next({ ...e, props: { ...e.props, hint: ko(e.props.hint) } }),
236 )
237
238 on('ui.render', { component: 'SessionMode' }, ($, e, next) =>
239 next({ ...e, props: { ...e.props, modes: e.props.modes.map(ko) } }),
240 )
241
242 on('ui.render', { component: 'InfoNotice' }, ($, e, next) =>
243 next({ ...e, props: { ...e.props, text: ko(e.props.text) } }),
244 )
245
246 on('ui.render', { component: 'CommandOutput' }, ($, e, next) =>
247 next({ ...e, props: { ...e.props, text: ko(e.props.text) } }),
248 )
249
250 // 대화 중 엔진 안내 줄(notice). 모델이 읽지 않는 행이라 저장되는 글자를 바꿔도 된다.
251 on('session.append', { door: 'notice' }, async ($, e, next) => {
252 const { message } = e
253 if (message.type !== 'system' || message.name === 'local_command') return next(e)
254 const content = await Promise.all(
255 message.content.map(async block =>
256 block.type === 'text' && typeof block.text === 'string' ? { ...block, text: await toKorean($, block.text) } : block,
257 ),
258 )
259 return next({ ...e, message: { ...message, content } })
260 }).catch(($, e, next) => next(e))
261
262 // 백그라운드 작업 알림 줄. 화면에 그리는 글자만 바꾸고 모델이 읽는 원문은 그대로 둔다.
263 on('ui.render', { component: 'UserMessage' }, async ($, e, next) =>
264 e.props.origin.kind === 'task-notification'
265 ? next({ ...e, props: { ...e.props, text: await toKorean($, e.props.text) } })
266 : next(e),
267 )
268
269 // /config 메뉴는 엔진 화면 그대로 두고 항목 이름만 바꾼다. 사전에 없는 항목은 모델 번역을 쓴다.
270 on('config.describe', async ($, e, next) => {
271 const row = await next(e)
272 return { ...row, label: CONFIG_LABELS[row.label] ?? (await loadCache($))[row.label] ?? row.label }
273 })
274
275 // 사전에 없는 명령 설명(스킬·플러그인, 새 버전의 기본 명령)과 /config 항목 이름은 시작 직후 한 번에
276 // 번역해 두고, 끝나면 메뉴가 다시 묻게 한다. 이미 한국어인 것은 needsTranslation이 거른다.
277 on('session.start', async ($, e, next) => {
278 const work = async () => {
279 const [commands, rows] = await Promise.all([$.command.list(), $.config.list()])
280 await translateMany($, [
281 ...commands.filter(c => !COMMAND_DESCRIPTIONS[c.name]).map(c => c.description),
282 ...rows.filter(r => !CONFIG_LABELS[r.label]).map(r => r.label),
283 ])
284 $.ui.invalidate('command.describe')
285 $.ui.invalidate('config.describe')
286 }
287 $.clock.after(3000, () => void work().catch(() => {}))
288 return next(e)
289 })
290
291 on('command.describe', async ($, e, next) => {
292 const row = await next(e)
293 const description = COMMAND_DESCRIPTIONS[e.command] ?? (await loadCache($))[row.description] ?? row.description
294 return { ...row, description }
295 })
296
297 // 접힌 도구 묶음(`Read 3 files, ran 2 shell commands`)만 한국어 한 줄로 다시 그린다.
298 on('ui.render', { component: 'ToolGroup' }, ($, e, next) => {
299 if (e.props.isExpanded) return next(e)
300 const { Text } = $.ui.resolve(e)
301 return (
302 <Text dimColor>
303 {groupLine(e.props.calls)}
304 {e.props.isActive ? '…' : ''} (ctrl+o 펼치기)
305 </Text>
306 )
307 })
308}
309hooks/dict.ts 167 lines1// /config 메뉴 항목 이름. 키는 엔진이 그리는 영문 label 그대로다(2.1.296 실행 파일에서 뽑음).
2export const CONFIG_LABELS: Record<string, string> = {
3 'Push when Claude decides': 'Claude 판단 시 푸시 알림',
4 'Agents view': '에이전트 보기',
5 Artifacts: '아티팩트',
6 'Question auto-continue timeout': '질문 자동 진행 대기 시간',
7 'Auto-compact': '자동 압축',
8 'Auto-connect to IDE (external terminal)': 'IDE 자동 연결(외부 터미널)',
9 'Continue automatically at usage limit': '사용 한도 도달 시 자동 계속',
10 'Auto-install IDE extension': 'IDE 확장 자동 설치',
11 'Auto-update channel': '자동 업데이트 채널',
12 'Rewind code (checkpoints)': '코드 되돌리기(체크포인트)',
13 'Skip the /copy picker': '/copy 선택 창 건너뛰기',
14 'Copy on select': '선택하면 복사',
15 'Messages from your other sessions': '다른 세션에서 온 메시지',
16 'Open agents view by default': '에이전트 보기로 시작',
17 'Start in agent view': '에이전트 보기로 시작',
18 'Default view': '기본 보기',
19 'Dialog expiry': '대화상자 만료',
20 'Diff tool': '비교 도구',
21 'Editor mode': '편집기 모드',
22 'Claude-drafted feedback': 'Claude가 쓴 피드백 초안',
23 'Respect .gitignore in file picker': '파일 선택에서 .gitignore 따르기',
24 'Push when actions required': '조치가 필요할 때 푸시 알림',
25 Language: '언어',
26 Model: '모델',
27 'Claude-proposed goals': 'Claude가 제안하는 목표',
28 'Local notifications': '로컬 알림',
29 Notifications: '알림',
30 'Synced project memory (this directory; applies next session)': '동기화된 프로젝트 메모리(이 폴더, 다음 세션부터 적용)',
31 'Output style': '출력 스타일',
32 'Default permission mode': '기본 권한 모드',
33 'Precompute compaction': '압축 미리 계산',
34 'Terminal progress bar': '터미널 진행률 표시',
35 'Prompt suggestions': '프롬프트 제안',
36 'Session recap': '세션 요약',
37 'Reduce motion': '움직임 줄이기',
38 'Enable Remote Control for all sessions': '모든 세션에서 원격 제어 켜기',
39 "Use this machine's settings in cloud sessions": '클라우드 세션에서 이 컴퓨터 설정 사용',
40 'Show status in terminal tab': '터미널 탭에 상태 표시',
41 Theme: '테마',
42 'Thinking mode': '생각 모드',
43 'Time format': '시간 형식',
44 'Show message timestamps': '메시지 시각 표시',
45 'Show tips': '팁 표시',
46 'Show turn duration': '턴 소요 시간 표시',
47 'Unattended commands from cloud sessions on this computer': '클라우드 세션의 무인 명령을 이 컴퓨터에서 실행',
48 'Use auto mode during plan': '계획 중에도 자동 모드 사용',
49 'Wait for limit to reset': '한도 초기화까지 대기',
50 'Ultracode keyword trigger': 'Ultracode 키워드로 실행',
51 'Dynamic workflow size': '동적 워크플로 규모',
52 'Dynamic workflows': '동적 워크플로',
53 'Worktree base ref': '워크트리 기준 ref',
54 'Verbose output': '자세한 출력',
55 'Auto-scroll': '자동 스크롤',
56 'Show last response in external editor': '마지막 답변을 외부 편집기로 보기',
57 'Show PR status footer': 'PR 상태 줄 표시',
58 'Claude in Chrome enabled by default': 'Claude in Chrome 기본으로 켜기',
59 'External CLAUDE.md includes': '외부 CLAUDE.md 가져오기 허용',
60}
61
62// 슬래시 명령 자동완성·/help 설명. 키는 명령 이름(슬래시 없이).
63export const COMMAND_DESCRIPTIONS: Record<string, string> = {
64 'add-dir': '작업 폴더 추가',
65 advisor: '중요한 순간에 더 강한 모델에게 자문',
66 agents: '서브에이전트 만들기·관리',
67 alias: '명령 별칭 만들기·보기',
68 artifacts: '게시하거나 공유받은 아티팩트 보기',
69 'auto-mode-setup': '자동 모드에 작업 환경 알려 주기',
70 autocompact: '자동 요약을 시작할 컨텍스트 사용량 설정',
71 'autofix-pr': '현재 PR의 문제를 지켜보고 자동 수정',
72 background: '이 세션을 백그라운드로 보내고 터미널 비우기',
73 branch: '지금 시점에서 대화를 분기',
74 brief: '간단 응답 모드 전환',
75 btw: '대화를 끊지 않고 짧은 질문하기',
76 bug: '버그 신고 또는 대화 공유',
77 cd: '세션을 다른 작업 폴더로 옮기기',
78 chrome: 'Claude in Chrome 설정 열기',
79 clear: '빈 컨텍스트로 새 세션 시작(이전 세션은 /resume으로 다시 열 수 있음)',
80 'cloud-plugins': '클라우드 세션에서 이 컴퓨터의 플러그인 사용 여부 선택',
81 color: '이 세션의 입력창 색 설정',
82 compact: '지금까지의 대화를 요약해 컨텍스트 확보',
83 config: '설정 열기',
84 context: '컨텍스트 사용량을 색 격자로 보기',
85 copy: 'Claude의 마지막 답변을 클립보드에 복사(/copy N은 N번째 최근 답변)',
86 cost: '세션 비용 보기',
87 daemon: '백그라운드 서비스와 루틴 관리',
88 'design-consent': 'Claude 에이전트에게 Design 프로젝트 접근 허용',
89 'design-revoke': 'Claude 에이전트의 Design 프로젝트 접근 해제',
90 desktop: '이 세션을 Claude Desktop에서 이어 하기',
91 doctor: 'Claude Code 설치 상태 진단',
92 effort: '모델 추론 강도 설정',
93 exit: 'Claude Code 종료',
94 export: '대화를 파일이나 클립보드로 내보내기',
95 'extra-usage': '/usage-credits로 이름이 바뀜',
96 feedback: 'Anthropic에 의견 보내기 또는 버그 신고',
97 focus: '집중 보기 전환: 프롬프트·요약·답변만 표시',
98 fork: '이 대화를 새 백그라운드 세션으로 복사하고 여기서 계속',
99 goal: 'Claude가 멈추기 전에 확인할 목표 설정',
100 heapdump: 'JS 힙을 바탕 화면에 덤프(Linux에서 바탕 화면 폴더가 없으면 홈 폴더)',
101 help: '도움말과 사용할 수 있는 명령 보기',
102 hooks: '도구 이벤트 훅 설정 보기',
103 ide: 'IDE 연동 관리와 상태 보기',
104 import: '다른 AI 코딩 에이전트의 설정 가져오기',
105 init: '코드베이스 설명을 담은 CLAUDE.md 만들기',
106 insights: 'Claude Code 세션 분석 보고서 만들기',
107 install: 'Claude Code 네이티브 빌드 설치',
108 'install-github-app': '저장소에 Claude GitHub Actions 설정',
109 'install-slack-app': 'Claude Slack 앱 설치',
110 keybindings: '단축키 설정 파일 열기',
111 'list-agents': '메시지를 보낼 수 있는 에이전트·세션 목록',
112 login: 'Anthropic 계정으로 로그인',
113 logout: 'Anthropic 계정에서 로그아웃',
114 loops: '반복 작업 목록·만들기·삭제',
115 mcp: 'MCP 서버 관리',
116 memory: 'CLAUDE.md 파일과 메모리 설정 편집',
117 mobile: 'Claude 모바일 앱 다운로드 QR 코드 보기',
118 model: '이 세션의 모델 설정',
119 'output-style': '출력 스타일 목록 보기 또는 바꾸기',
120 permissions: '도구 허용·거부 규칙 관리',
121 plan: '계획 모드 켜기 또는 현재 계획 보기',
122 plugin: 'Claude Code 플러그인 관리',
123 powerup: '짧은 대화형 강좌로 Claude Code 기능 익히기',
124 'privacy-settings': '개인정보 설정 보기·변경',
125 radio: 'Claude FM 로파이 라디오 듣기',
126 'rate-limit-options': '사용 한도와 업그레이드 옵션 관리',
127 recap: '지금 세션 한 줄 요약 만들기',
128 'release-notes': '릴리스 노트 보기',
129 'reload-plugins': '바뀐 플러그인을 현재 세션에 적용',
130 'reload-skills': '디스크에서 추가·변경된 스킬 다시 읽기',
131 'remote-control': '휴대폰이나 claude.ai/code에서 이 세션 조작',
132 'remote-env': '클라우드 에이전트의 기본 환경 선택',
133 rename: '현재 대화 이름 바꾸기',
134 resume: '이전 대화 이어 하기',
135 rewind: '대화와 코드를 이전 시점으로 되돌리기',
136 schedule: '예약된 원격 Claude Code 에이전트 만들기·관리',
137 'scroll-speed': '마우스 휠 스크롤 속도 조절',
138 'security-review': '현재 브랜치의 변경 사항 보안 검토',
139 session: '클라우드 세션 URL과 QR 코드 보기',
140 'setup-bedrock': 'Amazon Bedrock 인증·리전·고정 모델 다시 설정',
141 'setup-vertex': 'Google Vertex AI 인증·프로젝트·리전·고정 모델 다시 설정',
142 skills: '사용할 수 있는 스킬 목록',
143 'skill-doctor': '쓰이지 않고 컨텍스트만 차지하는 스킬 보기',
144 statusline: '상태 줄 설정',
145 status: '버전·모델·계정·API 연결·도구 상태 보기',
146 stickers: 'Claude Code 스티커 주문',
147 stop: '이 백그라운드 세션 멈추기(대화 기록과 워크트리는 남김)',
148 subtask: '전체 컨텍스트를 가진 서브에이전트에 일을 맡기고 결과 받기',
149 tasks: '백그라운드에서 실행 중인 작업 보기·관리',
150 'team-onboarding': '내 사용 기록으로 팀원용 Claude Code 안내서 만들기',
151 teleport: '이 세션을 클라우드로 보내거나 claude.ai의 세션 이어 하기',
152 'terminal-setup': '터미널 줄바꿈 키(Shift+Enter) 설정',
153 theme: '테마 바꾸기',
154 tui: '터미널 화면 방식 설정(default | fullscreen)',
155 ultraplan: '클라우드 세션이 계획 초안을 쓰고 직접 고쳐 승인',
156 ultrareview: '클라우드 세션으로 브랜치의 버그 찾기·검증',
157 upgrade: 'Max로 업그레이드해 사용 한도 늘리기',
158 usage: '세션 비용·요금제 사용량·활동 통계 보기',
159 'usage-credits': '사용 크레딧 설정 또는 관리자에게 요청',
160 version: '이 세션의 버전 보기',
161 vim: 'Vim 편집 모드 전환',
162 voice: '음성 모드 전환',
163 'web-setup': 'GitHub 계정으로 클라우드 세션 설정',
164 wellbeing: '휴식 알림과 방해 금지 시간 설정',
165 workflows: '실행 중이거나 끝난 워크플로 보기',
166}
167hooks/translate.ts 30 lines1// 모델 번역에 쓰는 순수 함수와 상수. $를 쓰는 부분은 엔진 규칙상 register.tsx 안에 있다.
2
3export const TRANSLATIONS_KEY = 'translations'
4export const MAX_CACHED = 500
5export const BATCH = 10
6
7export const SYSTEM = [
8 'You translate Claude Code CLI interface text into natural Korean.',
9 'Input: a JSON array of English strings. Output: only a JSON array of Korean strings, same length and order.',
10 'Keep unchanged: code, commands, slash commands, file paths, URLs, identifiers, key names (ctrl+o, Esc), numbers, and product names (Claude Code, GitHub, MCP).',
11 'Use short, plain UI wording. Do not add explanations.',
12].join('\n')
13
14const HANGUL = /[가-힣]/
15const WORD = /[A-Za-z]{2,}/
16
17export const needsTranslation = (text: string) => !HANGUL.test(text) && WORD.test(text)
18
19export const parseArray = (text: string, length: number): string[] | undefined => {
20 try {
21 const value: unknown = JSON.parse(text.match(/\[[\s\S]*\]/)?.[0] ?? '')
22 return Array.isArray(value) && value.length === length && value.every(v => typeof v === 'string') ? value : undefined
23 } catch {
24 return undefined
25 }
26}
27
28// ponytail: 최근 MAX_CACHED개만 남긴다. 넘치면 오래 전에 넣은 것부터 버린다.
29export const trim = (cache: Record<string, string>) => Object.fromEntries(Object.entries(cache).slice(-MAX_CACHED))
30