Compare commits

..

5 Commits

Author SHA1 Message Date
simosmik
1ed3358cbd Release 1.16.4 2026-02-11 15:26:18 +00:00
Haileyesus
c1e025b665 fix: claude code login issues (#375)
* fix: claude code login issues

1. Now, the browser opens in a new tab automatically
2. Clicking "C" to copy works
3. I have removed the "x-term" link selector since it didn't select the whole link

* fix: remove unnecessary terminal hyperlink for auth URL

* fix(shell): resolve clipboard handling for copy and paste events

* feat(shell): add authentication URL display and copy functionality - allows copy for mobile users

* revert: Update login command for unauthenticated users to use '/exit'

---------

Co-authored-by: Haileyesus <something@gmail.com>
2026-02-11 12:05:28 +01:00
Ayaan-buzzni
cf3d23ee31 feat(i18n): add Korean language support (#367)
* feat(i18n): add Korean language support

- Add Korean (ko) translation files for all namespaces:
  - common.json, auth.json, settings.json, sidebar.json, chat.json, codeEditor.json
- Register Korean locale in config.js and languages.js
- Follow translation guidelines:
  - Keep technical terms in English (UI, API, Shell, Git, etc.)
  - Use Korean phonetic for some terms (TaskMaster → 테스크마스터)
  - Maintain concise translations to match English length where possible

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(i18n): keep technical term "UI" in Korean translation

- Change "인터페이스" back to "UI" in sidebar subtitle
- Keep technical terms in English as per translation guidelines

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-09 09:47:10 +03:00
Haileyesus
e7d6c40452 Refactor WebSocket context + centralize platform flag (#363)
* fix: remove unnecessary websocket.js file and replace its usage directly in `WebSocketContext`

* fix: connect() doesn't need to be async

* fix: update WebSocket context import to use useWebSocket hook

* fix: use `useRef` for WebSocketContext

The main issue with using states was, previously the websocket never closed
properly on unmount, so multiple connections could be opened.

This was because the useEffect cleanup function was closing an old websocket
(that was initialized to null) instead of the current one.

We could have fixed this by adding `ws` to the useEffect dependency array, but
this was unnecessary since `ws` doesn't affect rendering so we shouldn't use a state.

* fix: replace `WebSocketContext` default value with null and add type definitions

* fix: add type definition for WebSocket URL and remove redundant protocol declaration

* fix: Prevent WebSocket reconnection attempts after unmount

Right now, when the WebSocketContext component unmounts,
there is still a pending reconnection attempt that tries
to reconnect the WebSocket after 3 seconds.

* refactor: Extract WebSocket URL construction into a separate function

* refactor: Centralize platform mode detection using IS_PLATFORM constant; use `token` from Auth context in WebSocket connection

* refactor: Use IS_PLATFORM constant for platform detection in authenticatedFetch function (backend)

* refactor: move IS_PLATFORM to config file for both frontend and backend

The reason we couldn't place it in shared/modelConstants.js is that the
frontend uses Vite which requires import.meta.env for environment variables,
while the backend uses process.env. Therefore, we created separate config files
for the frontend (src/constants/config.ts) and backend (server/constants/config.js).

* refactor: update import path for IS_PLATFORM constant to use config file

* refactor: replace `messages` with `latestMessage` in WebSocket context and related components

Why?
Because, messages was only being used to access the latest message in the components it's used in.

* refactor: optimize WebSocket connection handling with useCallback and useMemo

* refactor: comment out debug log for render count in AppContent component

* refactor(backend): update environment variable handling and replace VITE_IS_PLATFORM with IS_PLATFORM constant

* refactor: update WebSocket connection effect to depend on token changes for reconnection

---------
2026-02-03 10:05:15 +01:00
Haileyesus
216932e7f9 fix: correct spelling of "claude code" and update license to GPL-3.0 2026-02-02 20:51:44 +01:00
13 changed files with 1313 additions and 46 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@siteboon/claude-code-ui",
"version": "1.16.3",
"version": "1.16.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@siteboon/claude-code-ui",
"version": "1.16.3",
"version": "1.16.4",
"license": "MIT",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.29",

View File

@@ -1,6 +1,6 @@
{
"name": "@siteboon/claude-code-ui",
"version": "1.16.3",
"version": "1.16.4",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
@@ -33,14 +33,14 @@
"release": "./release.sh"
},
"keywords": [
"claude coode",
"claude code",
"ai",
"anthropic",
"ui",
"mobile"
],
"author": "Claude Code UI Contributors",
"license": "MIT",
"license": "GPL-3.0",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.1.29",
"@codemirror/lang-css": "^6.3.1",

View File

@@ -178,6 +178,69 @@ const server = http.createServer(app);
const ptySessionsMap = new Map();
const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g;
const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
function stripAnsiSequences(value = '') {
return value.replace(ANSI_ESCAPE_SEQUENCE_REGEX, '');
}
function normalizeDetectedUrl(url) {
if (!url || typeof url !== 'string') return null;
const cleaned = url.trim().replace(TRAILING_URL_PUNCTUATION_REGEX, '');
if (!cleaned) return null;
try {
const parsed = new URL(cleaned);
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return null;
}
return parsed.toString();
} catch {
return null;
}
}
function extractUrlsFromText(value = '') {
const directMatches = value.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/gi) || [];
// Handle wrapped terminal URLs split across lines by terminal width.
const wrappedMatches = [];
const continuationRegex = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+$/;
const lines = value.split(/\r?\n/);
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const startMatch = line.match(/https?:\/\/[^\s<>"'`\\\x1b\x07]+/i);
if (!startMatch) continue;
let combined = startMatch[0];
let j = i + 1;
while (j < lines.length) {
const continuation = lines[j].trim();
if (!continuation) break;
if (!continuationRegex.test(continuation)) break;
combined += continuation;
j++;
}
wrappedMatches.push(combined.replace(/\r?\n\s*/g, ''));
}
return Array.from(new Set([...directMatches, ...wrappedMatches]));
}
function shouldAutoOpenUrlFromOutput(value = '') {
const normalized = value.toLowerCase();
return (
normalized.includes('browser didn\'t open') ||
normalized.includes('open this url') ||
normalized.includes('continue in your browser') ||
normalized.includes('press enter to open') ||
normalized.includes('open_url:')
);
}
// Single WebSocket server that handles both paths
const wss = new WebSocketServer({
@@ -960,7 +1023,8 @@ function handleShellConnection(ws) {
console.log('🐚 Shell client connected');
let shellProcess = null;
let ptySessionKey = null;
let outputBuffer = [];
let urlDetectionBuffer = '';
const announcedAuthUrls = new Set();
ws.on('message', async (message) => {
try {
@@ -974,6 +1038,8 @@ function handleShellConnection(ws) {
const provider = data.provider || 'claude';
const initialCommand = data.initialCommand;
const isPlainShell = data.isPlainShell || (!!initialCommand && !hasSession) || provider === 'plain-shell';
urlDetectionBuffer = '';
announcedAuthUrls.clear();
// Login commands (Claude/Cursor auth) should never reuse cached sessions
const isLoginCommand = initialCommand && (
@@ -1113,9 +1179,7 @@ function handleShellConnection(ws) {
...process.env,
TERM: 'xterm-256color',
COLORTERM: 'truecolor',
FORCE_COLOR: '3',
// Override browser opening commands to echo URL for detection
BROWSER: os.platform() === 'win32' ? 'echo "OPEN_URL:"' : 'echo "OPEN_URL:"'
FORCE_COLOR: '3'
}
});
@@ -1145,38 +1209,47 @@ function handleShellConnection(ws) {
if (session.ws && session.ws.readyState === WebSocket.OPEN) {
let outputData = data;
// Check for various URL opening patterns
const patterns = [
// Direct browser opening commands
/(?:xdg-open|open|start)\s+(https?:\/\/[^\s\x1b\x07]+)/g,
// BROWSER environment variable override
const cleanChunk = stripAnsiSequences(data);
urlDetectionBuffer = `${urlDetectionBuffer}${cleanChunk}`.slice(-SHELL_URL_PARSE_BUFFER_LIMIT);
outputData = outputData.replace(
/OPEN_URL:\s*(https?:\/\/[^\s\x1b\x07]+)/g,
// Git and other tools opening URLs
/Opening\s+(https?:\/\/[^\s\x1b\x07]+)/gi,
// General URL patterns that might be opened
/Visit:\s*(https?:\/\/[^\s\x1b\x07]+)/gi,
/View at:\s*(https?:\/\/[^\s\x1b\x07]+)/gi,
/Browse to:\s*(https?:\/\/[^\s\x1b\x07]+)/gi
];
'[INFO] Opening in browser: $1'
);
patterns.forEach(pattern => {
let match;
while ((match = pattern.exec(data)) !== null) {
const url = match[1];
console.log('[DEBUG] Detected URL for opening:', url);
const emitAuthUrl = (detectedUrl, autoOpen = false) => {
const normalizedUrl = normalizeDetectedUrl(detectedUrl);
if (!normalizedUrl) return;
// Send URL opening message to client
const isNewUrl = !announcedAuthUrls.has(normalizedUrl);
if (isNewUrl) {
announcedAuthUrls.add(normalizedUrl);
session.ws.send(JSON.stringify({
type: 'url_open',
url: url
type: 'auth_url',
url: normalizedUrl,
autoOpen
}));
// Replace the OPEN_URL pattern with a user-friendly message
if (pattern.source.includes('OPEN_URL')) {
outputData = outputData.replace(match[0], `[INFO] Opening in browser: ${url}`);
}
}
});
};
const normalizedDetectedUrls = extractUrlsFromText(urlDetectionBuffer)
.map((url) => normalizeDetectedUrl(url))
.filter(Boolean);
// Prefer the most complete URL if shorter prefix variants are also present.
const dedupedDetectedUrls = Array.from(new Set(normalizedDetectedUrls)).filter((url, _, urls) =>
!urls.some((otherUrl) => otherUrl !== url && otherUrl.startsWith(url))
);
dedupedDetectedUrls.forEach((url) => emitAuthUrl(url, false));
if (shouldAutoOpenUrlFromOutput(cleanChunk) && dedupedDetectedUrls.length > 0) {
const bestUrl = dedupedDetectedUrls.reduce((longest, current) =>
current.length > longest.length ? current : longest
);
emitAuthUrl(bestUrl, true);
}
// Send regular output
session.ws.send(JSON.stringify({

View File

@@ -57,9 +57,7 @@ function LoginModal({
if (onComplete) {
onComplete(exitCode);
}
if (exitCode === 0) {
onClose();
}
// Keep modal open so users can read login output and close explicitly.
};
return (

View File

@@ -26,6 +26,31 @@ if (typeof document !== 'undefined') {
document.head.appendChild(styleSheet);
}
function fallbackCopyToClipboard(text) {
if (!text || typeof document === 'undefined') return false;
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
textarea.style.pointerEvents = 'none';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
let copied = false;
try {
copied = document.execCommand('copy');
} catch {
copied = false;
} finally {
document.body.removeChild(textarea);
}
return copied;
}
function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell = false, onProcessComplete, minimal = false, autoConnect = false }) {
const { t } = useTranslation('chat');
const terminalRef = useRef(null);
@@ -37,12 +62,15 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
const [isRestarting, setIsRestarting] = useState(false);
const [lastSessionId, setLastSessionId] = useState(null);
const [isConnecting, setIsConnecting] = useState(false);
const [authUrl, setAuthUrl] = useState('');
const [authUrlCopyStatus, setAuthUrlCopyStatus] = useState('idle');
const selectedProjectRef = useRef(selectedProject);
const selectedSessionRef = useRef(selectedSession);
const initialCommandRef = useRef(initialCommand);
const isPlainShellRef = useRef(isPlainShell);
const onProcessCompleteRef = useRef(onProcessComplete);
const authUrlRef = useRef('');
useEffect(() => {
selectedProjectRef.current = selectedProject;
@@ -52,6 +80,42 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
onProcessCompleteRef.current = onProcessComplete;
});
const openAuthUrlInBrowser = useCallback((url = authUrlRef.current) => {
if (!url) return false;
const popup = window.open(url, '_blank', 'noopener,noreferrer');
if (popup) {
try {
popup.opener = null;
} catch {
// Ignore cross-origin restrictions when trying to null opener
}
return true;
}
return false;
}, []);
const copyAuthUrlToClipboard = useCallback(async (url = authUrlRef.current) => {
if (!url) return false;
let copied = false;
try {
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(url);
copied = true;
}
} catch {
copied = false;
}
if (!copied) {
copied = fallbackCopyToClipboard(url);
}
return copied;
}, []);
const connectWebSocket = useCallback(async () => {
if (isConnecting || isConnected) return;
@@ -77,6 +141,9 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
ws.current.onopen = () => {
setIsConnected(true);
setIsConnecting(false);
authUrlRef.current = '';
setAuthUrl('');
setAuthUrlCopyStatus('idle');
setTimeout(() => {
if (fitAddon.current && terminal.current) {
@@ -119,8 +186,16 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
if (terminal.current) {
terminal.current.write(output);
}
} else if (data.type === 'auth_url' && data.url) {
authUrlRef.current = data.url;
setAuthUrl(data.url);
setAuthUrlCopyStatus('idle');
} else if (data.type === 'url_open') {
window.open(data.url, '_blank');
if (data.url) {
authUrlRef.current = data.url;
setAuthUrl(data.url);
setAuthUrlCopyStatus('idle');
}
}
} catch (error) {
console.error('[Shell] Error handling WebSocket message:', error, event.data);
@@ -130,6 +205,7 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
ws.current.onclose = (event) => {
setIsConnected(false);
setIsConnecting(false);
setAuthUrlCopyStatus('idle');
if (terminal.current) {
terminal.current.clear();
@@ -145,7 +221,7 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
setIsConnected(false);
setIsConnecting(false);
}
}, [isConnecting, isConnected]);
}, [isConnecting, isConnected, openAuthUrlInBrowser]);
const connectToShell = useCallback(() => {
if (!isInitialized || isConnected || isConnecting) return;
@@ -166,6 +242,9 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
setIsConnected(false);
setIsConnecting(false);
authUrlRef.current = '';
setAuthUrl('');
setAuthUrlCopyStatus('idle');
}, []);
const sessionDisplayName = useMemo(() => {
@@ -201,6 +280,9 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
setIsConnected(false);
setIsInitialized(false);
authUrlRef.current = '';
setAuthUrl('');
setAuthUrlCopyStatus('idle');
setTimeout(() => {
setIsRestarting(false);
@@ -272,7 +354,10 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
const webLinksAddon = new WebLinksAddon();
terminal.current.loadAddon(fitAddon.current);
terminal.current.loadAddon(webLinksAddon);
// Disable xterm link auto-detection in minimal (login) mode to avoid partial wrapped URL links.
if (!minimal) {
terminal.current.loadAddon(webLinksAddon);
}
// Note: ClipboardAddon removed - we handle clipboard operations manually in attachCustomKeyEventHandler
try {
@@ -284,12 +369,41 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
terminal.current.open(terminalRef.current);
terminal.current.attachCustomKeyEventHandler((event) => {
if ((event.ctrlKey || event.metaKey) && event.key === 'c' && terminal.current.hasSelection()) {
if (
event.type === 'keydown' &&
minimal &&
isPlainShellRef.current &&
authUrlRef.current &&
!event.ctrlKey &&
!event.metaKey &&
!event.altKey &&
event.key?.toLowerCase() === 'c'
) {
copyAuthUrlToClipboard(authUrlRef.current).catch(() => {});
}
if (
event.type === 'keydown' &&
(event.ctrlKey || event.metaKey) &&
event.key?.toLowerCase() === 'c' &&
terminal.current.hasSelection()
) {
event.preventDefault();
event.stopPropagation();
document.execCommand('copy');
return false;
}
if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
if (
event.type === 'keydown' &&
(event.ctrlKey || event.metaKey) &&
event.key?.toLowerCase() === 'v'
) {
// Block native browser/xterm paste so clipboard data is only sent after
// the explicit clipboard-read flow resolves (avoids duplicate pastes).
event.preventDefault();
event.stopPropagation();
navigator.clipboard.readText().then(text => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
ws.current.send(JSON.stringify({
@@ -359,7 +473,7 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
terminal.current = null;
}
};
}, [selectedProject?.path || selectedProject?.fullPath, isRestarting]);
}, [selectedProject?.path || selectedProject?.fullPath, isRestarting, minimal, copyAuthUrlToClipboard]);
useEffect(() => {
if (!autoConnect || !isInitialized || isConnecting || isConnected) return;
@@ -383,9 +497,47 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
}
if (minimal) {
const hasAuthUrl = Boolean(authUrl);
return (
<div className="h-full w-full bg-gray-900">
<div className="h-full w-full bg-gray-900 relative">
<div ref={terminalRef} className="h-full w-full focus:outline-none" style={{ outline: 'none' }} />
{hasAuthUrl && (
<div className="absolute inset-x-0 bottom-14 z-20 border-t border-gray-700/80 bg-gray-900/95 p-3 backdrop-blur-sm">
<div className="flex flex-col gap-2">
<p className="text-xs text-gray-300">Open or copy the login URL:</p>
<input
type="text"
value={authUrl}
readOnly
onClick={(event) => event.currentTarget.select()}
className="w-full rounded border border-gray-600 bg-gray-800 px-2 py-1 text-xs text-gray-100 focus:outline-none focus:ring-1 focus:ring-blue-500"
aria-label="Authentication URL"
/>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => {
openAuthUrlInBrowser(authUrl);
}}
className="flex-1 rounded bg-blue-600 px-3 py-2 text-xs font-medium text-white hover:bg-blue-700"
>
Open URL
</button>
<button
type="button"
onClick={async () => {
const copied = await copyAuthUrlToClipboard(authUrl);
setAuthUrlCopyStatus(copied ? 'copied' : 'failed');
}}
className="flex-1 rounded bg-gray-700 px-3 py-2 text-xs font-medium text-white hover:bg-gray-600"
>
{authUrlCopyStatus === 'copied' ? 'Copied' : 'Copy URL'}
</button>
</div>
</div>
</div>
)}
</div>
);
}
@@ -495,4 +647,4 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
);
}
export default Shell;
export default Shell;

View File

@@ -21,6 +21,13 @@ import enSidebar from './locales/en/sidebar.json';
import enChat from './locales/en/chat.json';
import enCodeEditor from './locales/en/codeEditor.json';
import koCommon from './locales/ko/common.json';
import koSettings from './locales/ko/settings.json';
import koAuth from './locales/ko/auth.json';
import koSidebar from './locales/ko/sidebar.json';
import koChat from './locales/ko/chat.json';
import koCodeEditor from './locales/ko/codeEditor.json';
import zhCommon from './locales/zh-CN/common.json';
import zhSettings from './locales/zh-CN/settings.json';
import zhAuth from './locales/zh-CN/auth.json';
@@ -60,6 +67,14 @@ i18n
chat: enChat,
codeEditor: enCodeEditor,
},
ko: {
common: koCommon,
settings: koSettings,
auth: koAuth,
sidebar: koSidebar,
chat: koChat,
codeEditor: koCodeEditor,
},
'zh-CN': {
common: zhCommon,
settings: zhSettings,

View File

@@ -14,6 +14,11 @@ export const languages = [
label: 'English',
nativeName: 'English',
},
{
value: 'ko',
label: 'Korean',
nativeName: '한국어',
},
{
value: 'zh-CN',
label: 'Simplified Chinese',

View File

@@ -0,0 +1,37 @@
{
"login": {
"title": "다시 오신 것을 환영합니다",
"description": "Claude Code UI 계정에 로그인하세요",
"username": "사용자명",
"password": "비밀번호",
"submit": "로그인",
"loading": "로그인 중...",
"errors": {
"invalidCredentials": "사용자명 또는 비밀번호가 잘못되었습니다",
"requiredFields": "모든 항목을 입력해주세요",
"networkError": "네트워크 오류. 다시 시도해주세요."
},
"placeholders": {
"username": "사용자명을 입력하세요",
"password": "비밀번호를 입력하세요"
}
},
"register": {
"title": "계정 생성",
"username": "사용자명",
"password": "비밀번호",
"confirmPassword": "비밀번호 확인",
"submit": "계정 생성",
"loading": "계정 생성 중...",
"errors": {
"passwordMismatch": "비밀번호가 일치하지 않습니다",
"usernameTaken": "이미 사용 중인 사용자명입니다",
"weakPassword": "비밀번호가 너무 약합니다"
}
},
"logout": {
"title": "로그아웃",
"confirm": "정말 로그아웃하시겠습니까?",
"button": "로그아웃"
}
}

View File

@@ -0,0 +1,205 @@
{
"codeBlock": {
"copy": "복사",
"copied": "복사됨",
"copyCode": "코드 복사"
},
"messageTypes": {
"user": "U",
"error": "오류",
"tool": "도구",
"claude": "Claude",
"cursor": "Cursor",
"codex": "Codex"
},
"tools": {
"settings": "도구 설정",
"error": "도구 오류",
"result": "도구 결과",
"viewParams": "입력 파라미터 보기",
"viewRawParams": "Raw 파라미터 보기",
"viewDiff": "편집 Diff 보기:",
"creatingFile": "새 파일 생성:",
"updatingTodo": "Todo 리스트 업데이트",
"read": "읽기",
"readFile": "파일 읽기",
"updateTodo": "Todo 리스트 업데이트",
"readTodo": "Todo 리스트 읽기",
"searchResults": "결과"
},
"search": {
"found": "{{count}}개의 {{type}} 발견",
"file": "파일",
"files": "파일",
"pattern": "패턴:",
"in": "위치:"
},
"fileOperations": {
"updated": "파일이 업데이트되었습니다",
"created": "파일이 생성되었습니다",
"written": "파일이 작성되었습니다",
"diff": "Diff",
"newFile": "새 파일",
"viewContent": "파일 내용 보기",
"viewFullOutput": "전체 출력 보기 ({{count}}자)",
"contentDisplayed": "파일 내용이 위의 Diff 보기에 표시됩니다"
},
"interactive": {
"title": "대화형 프롬프트",
"waiting": "CLI에서 응답을 기다리는 중",
"instruction": "Claude가 실행 중인 터미널에서 옵션을 선택해주세요.",
"selectedOption": "✓ Claude가 옵션 {{number}}을(를) 선택했습니다",
"instructionDetail": "CLI에서 화살표 키 또는 숫자를 입력하여 이 옵션을 대화형으로 선택합니다."
},
"thinking": {
"title": "생각 중...",
"emoji": "💭 생각 중..."
},
"json": {
"response": "JSON 응답"
},
"permissions": {
"grant": "{{tool}}에 대한 권한 부여",
"added": "권한이 추가되었습니다",
"addTo": "{{entry}}을(를) 허용된 도구에 추가합니다.",
"retry": "권한이 저장되었습니다. 도구를 사용하려면 요청을 재시도하세요.",
"error": "권한을 업데이트할 수 없습니다. 다시 시도해주세요.",
"openSettings": "설정 열기"
},
"todo": {
"updated": "Todo 리스트가 업데이트되었습니다",
"current": "현재 Todo 리스트"
},
"plan": {
"viewPlan": "📋 구현 계획 보기",
"title": "구현 계획"
},
"usageLimit": {
"resetAt": "Claude 사용량 한도에 도달했습니다. 한도는 **{{time}} {{timezone}}** - {{date}}에 초기화됩니다"
},
"codex": {
"permissionMode": "권한 모드",
"modes": {
"default": "기본 모드",
"acceptEdits": "편집 허용",
"bypassPermissions": "권한 우회",
"plan": "Plan 모드"
},
"descriptions": {
"default": "신뢰할 수 있는 명령어(ls, cat, grep, git status 등)만 자동 실행됩니다. 다른 명령어는 건너뜁니다. 워크스페이스에 쓰기 가능.",
"acceptEdits": "워크스페이스 내에서 모든 명령어가 자동 실행됩니다. 샌드박스 내 완전 자동 모드.",
"bypassPermissions": "제한 없는 전체 시스템 접근. 모든 명령어가 전체 디스크 및 네트워크 접근 권한으로 자동 실행됩니다. 주의해서 사용하세요.",
"plan": "계획 모드 - 명령어가 실행되지 않습니다"
},
"technicalDetails": "기술 상세"
},
"input": {
"placeholder": "/를 입력하여 명령어, @를 입력하여 파일, 또는 {{provider}}에게 무엇이든 물어보세요...",
"placeholderDefault": "메시지를 입력하세요...",
"disabled": "입력 비활성화",
"attachFiles": "파일 첨부",
"attachImages": "이미지 첨부",
"send": "전송",
"stop": "중지",
"hintText": {
"ctrlEnter": "Ctrl+Enter로 전송 • Shift+Enter로 줄바꿈 • Tab으로 모드 변경 • /로 슬래시 명령어",
"enter": "Enter로 전송 • Shift+Enter로 줄바꿈 • Tab으로 모드 변경 • /로 슬래시 명령어"
},
"clickToChangeMode": "클릭하여 권한 모드 변경 (또는 입력창에서 Tab)",
"showAllCommands": "모든 명령어 보기"
},
"thinkingMode": {
"selector": {
"title": "Thinking 모드",
"description": "확장된 thinking은 Claude에게 대안을 평가할 시간을 더 줍니다",
"active": "활성",
"tip": "높은 thinking 모드는 시간이 더 걸리지만 더 철저한 분석을 제공합니다"
},
"modes": {
"none": {
"name": "Standard",
"description": "일반 Claude 응답",
"prefix": ""
},
"think": {
"name": "Think",
"description": "기본 확장 thinking",
"prefix": "think"
},
"thinkHard": {
"name": "Think Hard",
"description": "더 철저한 평가",
"prefix": "think hard"
},
"thinkHarder": {
"name": "Think Harder",
"description": "대안을 포함한 심층 분석",
"prefix": "think harder"
},
"ultrathink": {
"name": "Ultrathink",
"description": "최대 thinking 예산",
"prefix": "ultrathink"
}
},
"buttonTitle": "Thinking 모드: {{mode}}"
},
"providerSelection": {
"title": "AI 어시스턴트 선택",
"description": "새 대화를 시작할 프로바이더를 선택하세요",
"selectModel": "모델 선택",
"providerInfo": {
"anthropic": "by Anthropic",
"openai": "by OpenAI",
"cursorEditor": "AI 코드 에디터"
},
"readyPrompt": {
"claude": "{{model}}로 Claude를 사용할 준비가 되었습니다. 아래에 메시지를 입력하세요.",
"cursor": "{{model}}로 Cursor를 사용할 준비가 되었습니다. 아래에 메시지를 입력하세요.",
"codex": "{{model}}로 Codex를 사용할 준비가 되었습니다. 아래에 메시지를 입력하세요.",
"default": "시작하려면 위에서 프로바이더를 선택하세요"
}
},
"session": {
"continue": {
"title": "대화 계속하기",
"description": "코드에 대해 질문하거나, 변경을 요청하거나, 개발 작업에 도움을 받으세요"
},
"loading": {
"olderMessages": "이전 메시지 로딩 중...",
"sessionMessages": "세션 메시지 로딩 중..."
},
"messages": {
"showingOf": "{{total}}개 중 {{shown}}개 표시",
"scrollToLoad": "위로 스크롤하여 더 로드",
"showingLast": "마지막 {{count}}개 메시지 표시 (총 {{total}}개)",
"loadEarlier": "이전 메시지 로드"
}
},
"shell": {
"selectProject": {
"title": "프로젝트 선택",
"description": "해당 디렉토리에서 대화형 Shell을 열 프로젝트를 선택하세요"
},
"status": {
"newSession": "새 세션",
"initializing": "초기화 중...",
"restarting": "재시작 중..."
},
"actions": {
"disconnect": "연결 끊기",
"disconnectTitle": "Shell 연결 끊기",
"restart": "재시작",
"restartTitle": "Shell 재시작 (먼저 연결 끊기)",
"connect": "Shell에서 계속",
"connectTitle": "Shell에 연결"
},
"loading": "터미널 로딩 중...",
"connecting": "Shell에 연결 중...",
"startSession": "새 Claude 세션 시작",
"resumeSession": "세션 재개: {{displayName}}...",
"runCommand": "{{projectName}}에서 {{command}} 실행",
"startCli": "{{projectName}}에서 Claude CLI 시작",
"defaultCommand": "명령어"
}
}

View File

@@ -0,0 +1,30 @@
{
"toolbar": {
"changes": "변경사항",
"previousChange": "이전 변경",
"nextChange": "다음 변경",
"hideDiff": "Diff 하이라이트 숨기기",
"showDiff": "Diff 하이라이트 표시",
"settings": "에디터 설정",
"collapse": "에디터 접기",
"expand": "에디터 전체 너비로 펼치기"
},
"loading": "{{fileName}} 로딩 중...",
"header": {
"showingChanges": "변경사항 표시"
},
"actions": {
"download": "파일 다운로드",
"save": "저장",
"saving": "저장 중...",
"saved": "저장됨!",
"exitFullscreen": "전체화면 종료",
"fullscreen": "전체화면",
"close": "닫기"
},
"footer": {
"lines": "줄:",
"characters": "문자:",
"shortcuts": "Ctrl+S로 저장 • Esc로 닫기"
}
}

View File

@@ -0,0 +1,222 @@
{
"buttons": {
"save": "저장",
"cancel": "취소",
"delete": "삭제",
"create": "생성",
"edit": "편집",
"close": "닫기",
"confirm": "확인",
"submit": "제출",
"retry": "재시도",
"refresh": "새로고침",
"search": "검색",
"clear": "지우기",
"copy": "복사",
"download": "다운로드",
"upload": "업로드",
"browse": "찾아보기"
},
"tabs": {
"chat": "채팅",
"shell": "Shell",
"files": "파일",
"git": "소스 관리",
"tasks": "작업"
},
"status": {
"loading": "로딩 중...",
"success": "성공",
"error": "오류",
"failed": "실패",
"pending": "대기 중",
"completed": "완료",
"inProgress": "진행 중"
},
"messages": {
"savedSuccessfully": "저장되었습니다",
"deletedSuccessfully": "삭제되었습니다",
"updatedSuccessfully": "업데이트되었습니다",
"operationFailed": "작업 실패",
"networkError": "네트워크 오류. 연결을 확인해주세요.",
"unauthorized": "인증되지 않았습니다. 로그인해주세요.",
"notFound": "찾을 수 없음",
"invalidInput": "잘못된 입력",
"requiredField": "필수 항목입니다",
"unknownError": "알 수 없는 오류가 발생했습니다"
},
"navigation": {
"settings": "설정",
"home": "홈",
"back": "뒤로",
"next": "다음",
"previous": "이전",
"logout": "로그아웃"
},
"common": {
"language": "언어",
"theme": "테마",
"darkMode": "다크 모드",
"lightMode": "라이트 모드",
"name": "이름",
"description": "설명",
"enabled": "활성화",
"disabled": "비활성화",
"optional": "선택사항",
"version": "버전",
"select": "선택",
"selectAll": "전체 선택",
"deselectAll": "전체 해제"
},
"time": {
"justNow": "방금 전",
"minutesAgo": "{{count}}분 전",
"hoursAgo": "{{count}}시간 전",
"daysAgo": "{{count}}일 전",
"yesterday": "어제"
},
"fileOperations": {
"newFile": "새 파일",
"newFolder": "새 폴더",
"rename": "이름 변경",
"move": "이동",
"copyPath": "경로 복사",
"openInEditor": "에디터에서 열기"
},
"mainContent": {
"loading": "Claude Code UI 로딩 중",
"settingUpWorkspace": "워크스페이스 설정 중...",
"chooseProject": "프로젝트 선택",
"selectProjectDescription": "사이드바에서 프로젝트를 선택하여 Claude와 코딩을 시작하세요. 각 프로젝트에는 채팅 세션과 파일 히스토리가 포함됩니다.",
"tip": "팁",
"createProjectMobile": "위의 메뉴 버튼을 눌러 프로젝트에 접근하세요",
"createProjectDesktop": "사이드바의 폴더 아이콘을 클릭하여 새 프로젝트를 생성하세요",
"newSession": "새 세션",
"untitledSession": "제목 없는 세션",
"projectFiles": "프로젝트 파일"
},
"fileTree": {
"loading": "파일 로딩 중...",
"files": "파일",
"simpleView": "간단히 보기",
"compactView": "컴팩트 보기",
"detailedView": "상세히 보기",
"searchPlaceholder": "파일 및 폴더 검색...",
"clearSearch": "검색 지우기",
"name": "이름",
"size": "크기",
"modified": "수정일",
"permissions": "권한",
"noFilesFound": "파일을 찾을 수 없음",
"checkProjectPath": "프로젝트 경로가 접근 가능한지 확인하세요",
"noMatchesFound": "일치하는 항목 없음",
"tryDifferentSearch": "다른 검색어를 시도하거나 검색을 지우세요",
"justNow": "방금 전",
"minAgo": "{{count}}분 전",
"hoursAgo": "{{count}}시간 전",
"daysAgo": "{{count}}일 전"
},
"projectWizard": {
"title": "새 프로젝트 생성",
"steps": {
"type": "유형",
"configure": "설정",
"confirm": "확인"
},
"step1": {
"question": "이미 워크스페이스가 있으신가요, 아니면 새로 생성하시겠습니까?",
"existing": {
"title": "기존 워크스페이스",
"description": "서버에 이미 워크스페이스가 있고 프로젝트 목록에 추가만 하면 됩니다"
},
"new": {
"title": "새 워크스페이스",
"description": "새 워크스페이스를 생성하고, 선택적으로 GitHub 저장소에서 clone합니다"
}
},
"step2": {
"existingPath": "워크스페이스 경로",
"newPath": "워크스페이스 경로",
"existingPlaceholder": "/path/to/existing/workspace",
"newPlaceholder": "/path/to/new/workspace",
"existingHelp": "기존 워크스페이스 디렉토리의 전체 경로",
"newHelp": "워크스페이스 디렉토리의 전체 경로",
"githubUrl": "GitHub URL (선택사항)",
"githubPlaceholder": "https://github.com/username/repository",
"githubHelp": "선택사항: 저장소를 clone하려면 GitHub URL을 입력하세요",
"githubAuth": "GitHub 인증 (선택사항)",
"githubAuthHelp": "비공개 저장소에만 필요합니다. 공개 저장소는 인증 없이 clone할 수 있습니다.",
"loadingTokens": "저장된 토큰 로딩 중...",
"storedToken": "저장된 토큰",
"newToken": "새 토큰",
"nonePublic": "없음 (공개)",
"selectToken": "토큰 선택",
"selectTokenPlaceholder": "-- 토큰 선택 --",
"tokenPlaceholder": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tokenHelp": "이 토큰은 이 작업에만 사용됩니다",
"publicRepoInfo": "공개 저장소는 인증이 필요하지 않습니다. 공개 저장소를 clone하는 경우 토큰을 생략할 수 있습니다.",
"noTokensHelp": "저장된 토큰이 없습니다. 설정 → API Keys에서 토큰을 추가하면 재사용이 편리합니다.",
"optionalTokenPublic": "GitHub 토큰 (공개 저장소는 선택사항)",
"tokenPublicPlaceholder": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (공개 저장소는 비워두세요)"
},
"step3": {
"reviewConfig": "설정 검토",
"workspaceType": "워크스페이스 유형:",
"existingWorkspace": "기존 워크스페이스",
"newWorkspace": "새 워크스페이스",
"path": "경로:",
"cloneFrom": "Clone 소스:",
"authentication": "인증:",
"usingStoredToken": "저장된 토큰 사용:",
"usingProvidedToken": "제공된 토큰 사용",
"noAuthentication": "인증 없음",
"sshKey": "SSH 키",
"existingInfo": "워크스페이스가 프로젝트 목록에 추가되며 Claude/Cursor 세션에서 사용할 수 있습니다.",
"newWithClone": "이 폴더에 저장소가 clone됩니다.",
"newEmpty": "워크스페이스가 프로젝트 목록에 추가되며 Claude/Cursor 세션에서 사용할 수 있습니다.",
"cloningRepository": "저장소 clone 중..."
},
"buttons": {
"cancel": "취소",
"back": "뒤로",
"next": "다음",
"createProject": "프로젝트 생성",
"creating": "생성 중...",
"cloning": "Clone 중..."
},
"errors": {
"selectType": "기존 워크스페이스를 사용할지 새로 생성할지 선택해주세요",
"providePath": "워크스페이스 경로를 입력해주세요",
"failedToCreate": "워크스페이스 생성 실패",
"failedToCreateFolder": "폴더 생성 실패"
}
},
"versionUpdate": {
"title": "업데이트 가능",
"newVersionReady": "새 버전이 준비되었습니다",
"currentVersion": "현재 버전",
"latestVersion": "최신 버전",
"whatsNew": "새로운 기능:",
"viewFullRelease": "전체 릴리스 보기",
"updateProgress": "업데이트 진행 상황:",
"manualUpgrade": "수동 업그레이드:",
"manualUpgradeHint": "또는 \"지금 업데이트\"를 클릭하여 자동으로 업데이트합니다.",
"updateCompleted": "업데이트가 완료되었습니다!",
"restartServer": "변경사항을 적용하려면 서버를 재시작하세요.",
"updateFailed": "업데이트 실패",
"buttons": {
"close": "닫기",
"later": "나중에",
"copyCommand": "명령어 복사",
"updateNow": "지금 업데이트",
"updating": "업데이트 중..."
},
"ariaLabels": {
"closeModal": "버전 업그레이드 모달 닫기",
"showSidebar": "사이드바 표시",
"settings": "설정",
"updateAvailable": "업데이트 가능",
"closeSidebar": "사이드바 닫기"
}
}
}

View File

@@ -0,0 +1,418 @@
{
"title": "설정",
"tabs": {
"account": "계정",
"permissions": "권한",
"mcpServers": "MCP 서버",
"appearance": "외관"
},
"account": {
"title": "계정",
"language": "언어",
"languageLabel": "표시 언어",
"languageDescription": "인터페이스에 사용할 언어를 선택하세요",
"username": "사용자명",
"email": "이메일",
"profile": "프로필",
"changePassword": "비밀번호 변경"
},
"mcp": {
"title": "MCP 서버",
"addServer": "서버 추가",
"editServer": "서버 편집",
"deleteServer": "서버 삭제",
"serverName": "서버 이름",
"serverType": "서버 유형",
"config": "설정",
"testConnection": "연결 테스트",
"status": "상태",
"connected": "연결됨",
"disconnected": "연결 끊김",
"scope": {
"label": "범위",
"user": "사용자",
"project": "프로젝트"
}
},
"appearance": {
"title": "외관",
"theme": "테마",
"codeEditor": "코드 에디터",
"editorTheme": "에디터 테마",
"wordWrap": "자동 줄바꿈",
"showMinimap": "미니맵 표시",
"lineNumbers": "줄 번호",
"fontSize": "글꼴 크기"
},
"actions": {
"saveChanges": "변경사항 저장",
"resetToDefaults": "기본값으로 초기화",
"cancelChanges": "변경 취소"
},
"quickSettings": {
"title": "빠른 설정",
"sections": {
"appearance": "외관",
"toolDisplay": "도구 표시",
"viewOptions": "보기 옵션",
"inputSettings": "입력 설정",
"whisperDictation": "Whisper 음성 인식"
},
"darkMode": "다크 모드",
"autoExpandTools": "도구 자동 펼치기",
"showRawParameters": "Raw 파라미터 표시",
"showThinking": "생각 과정 표시",
"autoScrollToBottom": "자동 스크롤",
"sendByCtrlEnter": "Ctrl+Enter로 전송",
"sendByCtrlEnterDescription": "활성화하면 Enter 대신 Ctrl+Enter로 메시지를 전송합니다. IME 사용자가 실수로 전송하는 것을 방지하는 데 유용합니다.",
"dragHandle": {
"dragging": "드래그 핸들",
"closePanel": "설정 패널 닫기",
"openPanel": "설정 패널 열기",
"draggingStatus": "드래그 중...",
"toggleAndMove": "클릭하여 토글, 드래그하여 이동"
},
"whisper": {
"modes": {
"default": "기본 모드",
"defaultDescription": "음성을 그대로 텍스트로 변환",
"prompt": "프롬프트 향상",
"promptDescription": "거친 아이디어를 명확하고 상세한 AI 프롬프트로 변환",
"vibe": "Vibe 모드",
"vibeDescription": "아이디어를 상세한 에이전트 지침 형식으로 변환"
}
}
},
"mainTabs": {
"agents": "에이전트",
"appearance": "외관",
"git": "Git",
"apiTokens": "API & 토큰",
"tasks": "작업"
},
"appearanceSettings": {
"darkMode": {
"label": "다크 모드",
"description": "라이트/다크 테마 전환"
},
"projectSorting": {
"label": "프로젝트 정렬",
"description": "사이드바에서 프로젝트 정렬 방식",
"alphabetical": "알파벳순",
"recentActivity": "최근 활동순"
},
"codeEditor": {
"title": "코드 에디터",
"theme": {
"label": "에디터 테마",
"description": "코드 에디터의 기본 테마"
},
"wordWrap": {
"label": "자동 줄바꿈",
"description": "에디터에서 기본적으로 자동 줄바꿈 활성화"
},
"showMinimap": {
"label": "미니맵 표시",
"description": "Diff 보기에서 쉬운 탐색을 위한 미니맵 표시"
},
"lineNumbers": {
"label": "줄 번호 표시",
"description": "에디터에 줄 번호 표시"
},
"fontSize": {
"label": "글꼴 크기",
"description": "에디터 글꼴 크기 (픽셀)"
}
}
},
"mcpForm": {
"title": {
"add": "MCP 서버 추가",
"edit": "MCP 서버 편집"
},
"importMode": {
"form": "폼 입력",
"json": "JSON 가져오기"
},
"scope": {
"label": "범위",
"userGlobal": "사용자 (전역)",
"projectLocal": "프로젝트 (로컬)",
"userDescription": "사용자 범위: 모든 프로젝트에서 사용 가능",
"projectDescription": "로컬 범위: 선택한 프로젝트에서만 사용 가능",
"cannotChange": "기존 서버를 편집할 때는 범위를 변경할 수 없습니다"
},
"fields": {
"serverName": "서버 이름",
"transportType": "전송 유형",
"command": "명령어",
"arguments": "인수 (한 줄에 하나씩)",
"jsonConfig": "JSON 설정",
"url": "URL",
"envVars": "환경 변수 (KEY=value, 한 줄에 하나씩)",
"headers": "헤더 (KEY=value, 한 줄에 하나씩)",
"selectProject": "프로젝트 선택..."
},
"placeholders": {
"serverName": "my-server"
},
"validation": {
"missingType": "필수 항목 누락: type",
"stdioRequiresCommand": "stdio 유형은 command 필드가 필요합니다",
"httpRequiresUrl": "{{type}} 유형은 url 필드가 필요합니다",
"invalidJson": "잘못된 JSON 형식",
"jsonHelp": "MCP 서버 설정을 JSON 형식으로 붙여넣으세요. 예시:",
"jsonExampleStdio": "• stdio: {\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"@upstash/context7-mcp\"]}",
"jsonExampleHttp": "• http/sse: {\"type\":\"http\",\"url\":\"https://api.example.com/mcp\"}"
},
"configDetails": "설정 상세 ({{configFile}}에서)",
"projectPath": "경로: {{path}}",
"actions": {
"cancel": "취소",
"saving": "저장 중...",
"addServer": "서버 추가",
"updateServer": "서버 업데이트"
}
},
"saveStatus": {
"success": "설정이 저장되었습니다!",
"error": "설정 저장 실패",
"saving": "저장 중..."
},
"footerActions": {
"save": "설정 저장",
"cancel": "취소"
},
"git": {
"title": "Git 설정",
"description": "커밋을 위한 Git 정보를 설정합니다. 이 설정은 git config --global로 전역 적용됩니다",
"name": {
"label": "Git 이름",
"help": "Git 커밋에 사용될 이름"
},
"email": {
"label": "Git 이메일",
"help": "Git 커밋에 사용될 이메일"
},
"actions": {
"save": "설정 저장",
"saving": "저장 중..."
},
"status": {
"success": "저장 완료"
}
},
"apiKeys": {
"title": "API 키",
"description": "다른 애플리케이션에서 외부 API에 접근하기 위한 API 키를 생성합니다.",
"newKey": {
"alertTitle": "⚠️ API 키를 저장하세요",
"alertMessage": "이 키는 지금만 볼 수 있습니다. 안전하게 보관하세요.",
"iveSavedIt": "저장했습니다"
},
"form": {
"placeholder": "API 키 이름 (예: Production Server)",
"createButton": "생성",
"cancelButton": "취소"
},
"newButton": "새 API 키",
"empty": "생성된 API 키가 없습니다.",
"list": {
"created": "생성일:",
"lastUsed": "마지막 사용:"
},
"confirmDelete": "이 API 키를 삭제하시겠습니까?",
"status": {
"active": "활성",
"inactive": "비활성"
},
"github": {
"title": "GitHub 토큰",
"description": "외부 API를 통해 비공개 저장소를 clone하기 위한 GitHub Personal Access Token을 추가합니다.",
"descriptionAlt": "비공개 저장소를 clone하기 위한 GitHub Personal Access Token을 추가합니다. 저장하지 않고 API 요청에 직접 토큰을 전달할 수도 있습니다.",
"addButton": "토큰 추가",
"form": {
"namePlaceholder": "토큰 이름 (예: Personal Repos)",
"tokenPlaceholder": "GitHub Personal Access Token (ghp_...)",
"descriptionPlaceholder": "설명 (선택사항)",
"addButton": "토큰 추가",
"cancelButton": "취소",
"howToCreate": "GitHub Personal Access Token 생성 방법 →"
},
"empty": "추가된 GitHub 토큰이 없습니다.",
"added": "추가일:",
"confirmDelete": "이 GitHub 토큰을 삭제하시겠습니까?"
},
"apiDocsLink": "API 문서",
"documentation": {
"title": "외부 API 문서",
"description": "외부 API를 사용하여 애플리케이션에서 Claude/Cursor 세션을 트리거하는 방법을 알아보세요.",
"viewLink": "API 문서 보기 →"
},
"loading": "로딩 중...",
"version": {
"updateAvailable": "업데이트 가능: v{{version}}"
}
},
"tasks": {
"checking": "TaskMaster 설치 확인 중...",
"notInstalled": {
"title": "TaskMaster AI CLI가 설치되지 않았습니다",
"description": "작업 관리 기능을 사용하려면 TaskMaster CLI가 필요합니다. 시작하려면 설치하세요:",
"installCommand": "npm install -g task-master-ai",
"viewOnGitHub": "GitHub에서 보기",
"afterInstallation": "설치 후:",
"steps": {
"restart": "이 애플리케이션을 재시작하세요",
"autoAvailable": "TaskMaster 기능이 자동으로 활성화됩니다",
"initCommand": "프로젝트 디렉토리에서 task-master init을 사용하세요"
}
},
"settings": {
"enableLabel": "TaskMaster 통합 활성화",
"enableDescription": "인터페이스 전체에 TaskMaster 작업, 배너 및 사이드바 표시"
}
},
"agents": {
"authStatus": {
"checking": "확인 중...",
"connected": "연결됨",
"notConnected": "연결되지 않음",
"disconnected": "연결 끊김",
"checkingAuth": "인증 상태 확인 중...",
"loggedInAs": "{{email}}(으)로 로그인됨",
"authenticatedUser": "인증된 사용자"
},
"account": {
"claude": {
"description": "Anthropic Claude AI 어시스턴트"
},
"cursor": {
"description": "Cursor AI 기반 코드 에디터"
},
"codex": {
"description": "OpenAI Codex AI 어시스턴트"
}
},
"connectionStatus": "연결 상태",
"login": {
"title": "로그인",
"reAuthenticate": "재인증",
"description": "AI 기능을 활성화하려면 {{agent}} 계정에 로그인하세요",
"reAuthDescription": "다른 계정으로 로그인하거나 자격 증명을 새로고침하세요",
"button": "로그인",
"reLoginButton": "재로그인"
},
"error": "오류: {{error}}"
},
"permissions": {
"title": "권한 설정",
"skipPermissions": {
"label": "권한 확인 건너뛰기 (주의해서 사용)",
"claudeDescription": "--dangerously-skip-permissions 플래그와 동일",
"cursorDescription": "Cursor CLI의 -f 플래그와 동일"
},
"allowedTools": {
"title": "허용된 도구",
"description": "권한 확인 없이 자동으로 허용되는 도구",
"placeholder": "예: \"Bash(git log:*)\" 또는 \"Write\"",
"quickAdd": "자주 쓰는 도구 빠른 추가:",
"empty": "설정된 허용 도구 없음"
},
"blockedTools": {
"title": "차단된 도구",
"description": "권한 확인 없이 자동으로 차단되는 도구",
"placeholder": "예: \"Bash(rm:*)\"",
"empty": "설정된 차단 도구 없음"
},
"allowedCommands": {
"title": "허용된 Shell 명령어",
"description": "권한 확인 없이 자동으로 허용되는 Shell 명령어",
"placeholder": "예: \"Shell(ls)\" 또는 \"Shell(git status)\"",
"quickAdd": "자주 쓰는 명령어 빠른 추가:",
"empty": "설정된 허용 명령어 없음"
},
"blockedCommands": {
"title": "차단된 Shell 명령어",
"description": "자동으로 차단되는 Shell 명령어",
"placeholder": "예: \"Shell(rm -rf)\" 또는 \"Shell(sudo)\"",
"empty": "설정된 차단 명령어 없음"
},
"toolExamples": {
"title": "도구 패턴 예시:",
"bashGitLog": "- 모든 git log 명령어 허용",
"bashGitDiff": "- 모든 git diff 명령어 허용",
"write": "- 모든 Write 도구 사용 허용",
"bashRm": "- 모든 rm 명령어 차단 (위험)"
},
"shellExamples": {
"title": "Shell 명령어 예시:",
"ls": "- ls 명령어 허용",
"gitStatus": "- git status 허용",
"npmInstall": "- npm install 허용",
"rmRf": "- 재귀 삭제 차단"
},
"codex": {
"permissionMode": "권한 모드",
"description": "Codex가 파일 수정 및 명령어 실행을 처리하는 방식을 제어합니다",
"modes": {
"default": {
"title": "기본",
"description": "신뢰할 수 있는 명령어(ls, cat, grep, git status 등)만 자동 실행됩니다. 다른 명령어는 건너뜁니다. 워크스페이스에 쓰기 가능."
},
"acceptEdits": {
"title": "편집 허용",
"description": "워크스페이스 내에서 모든 명령어가 자동 실행됩니다. 샌드박스 내 완전 자동 모드."
},
"bypassPermissions": {
"title": "권한 우회",
"description": "제한 없는 전체 시스템 접근. 모든 명령어가 전체 디스크 및 네트워크 접근 권한으로 자동 실행됩니다. 주의해서 사용하세요."
}
},
"technicalDetails": "기술 상세",
"technicalInfo": {
"default": "sandboxMode=workspace-write, approvalPolicy=untrusted. 신뢰할 수 있는 명령어: cat, cd, grep, head, ls, pwd, tail, git status/log/diff/show, find(-exec 제외) 등.",
"acceptEdits": "sandboxMode=workspace-write, approvalPolicy=never. 프로젝트 디렉토리 내에서 모든 명령어 자동 실행.",
"bypassPermissions": "sandboxMode=danger-full-access, approvalPolicy=never. 전체 시스템 접근, 신뢰할 수 있는 환경에서만 사용하세요.",
"overrideNote": "채팅 인터페이스의 모드 버튼을 사용하여 세션별로 재정의할 수 있습니다."
}
},
"actions": {
"add": "추가"
}
},
"mcpServers": {
"title": "MCP 서버",
"description": {
"claude": "Model Context Protocol 서버는 Claude에 추가 도구와 데이터 소스를 제공합니다",
"cursor": "Model Context Protocol 서버는 Cursor에 추가 도구와 데이터 소스를 제공합니다",
"codex": "Model Context Protocol 서버는 Codex에 추가 도구와 데이터 소스를 제공합니다"
},
"addButton": "MCP 서버 추가",
"empty": "설정된 MCP 서버 없음",
"serverType": "유형",
"scope": {
"local": "로컬",
"user": "사용자"
},
"config": {
"command": "명령어",
"url": "URL",
"args": "인수",
"environment": "환경"
},
"tools": {
"title": "도구",
"count": "({{count}}):",
"more": "+{{count}}개 더"
},
"actions": {
"edit": "서버 편집",
"delete": "서버 삭제"
},
"help": {
"title": "Codex MCP 정보",
"description": "Codex는 stdio 기반 MCP 서버를 지원합니다. 추가 도구와 리소스로 Codex의 기능을 확장하는 서버를 추가할 수 있습니다."
}
}
}

View File

@@ -0,0 +1,112 @@
{
"projects": {
"title": "프로젝트",
"newProject": "새 프로젝트",
"deleteProject": "프로젝트 삭제",
"renameProject": "프로젝트 이름 변경",
"noProjects": "프로젝트가 없습니다",
"loadingProjects": "프로젝트 로딩 중...",
"searchPlaceholder": "프로젝트 검색...",
"projectNamePlaceholder": "프로젝트 이름",
"starred": "즐겨찾기",
"all": "전체",
"untitledSession": "제목 없는 세션",
"newSession": "새 세션",
"codexSession": "Codex 세션",
"fetchingProjects": "Claude 프로젝트와 세션을 가져오는 중",
"projects": "프로젝트",
"noMatchingProjects": "일치하는 프로젝트 없음",
"tryDifferentSearch": "검색어를 변경해보세요",
"runClaudeCli": "프로젝트 디렉토리에서 Claude CLI를 실행하여 시작하세요"
},
"app": {
"title": "Claude Code UI",
"subtitle": "AI 코딩 어시스턴트 UI"
},
"sessions": {
"title": "세션",
"newSession": "새 세션",
"deleteSession": "세션 삭제",
"renameSession": "세션 이름 변경",
"noSessions": "세션이 없습니다",
"loadingSessions": "세션 로딩 중...",
"unnamed": "이름 없음",
"loading": "로딩 중...",
"showMore": "더 많은 세션 보기"
},
"tooltips": {
"viewEnvironments": "환경 보기",
"hideSidebar": "사이드바 숨기기",
"createProject": "새 프로젝트 생성",
"refresh": "프로젝트 및 세션 새로고침 (Ctrl+R)",
"renameProject": "프로젝트 이름 변경 (F2)",
"deleteProject": "빈 프로젝트 삭제 (Delete)",
"addToFavorites": "즐겨찾기에 추가",
"removeFromFavorites": "즐겨찾기에서 제거",
"editSessionName": "세션 이름 직접 편집",
"deleteSession": "이 세션 영구 삭제",
"save": "저장",
"cancel": "취소"
},
"navigation": {
"chat": "채팅",
"files": "파일",
"git": "Git",
"terminal": "터미널",
"tasks": "작업"
},
"actions": {
"refresh": "새로고침",
"settings": "설정",
"collapseAll": "모두 접기",
"expandAll": "모두 펼치기",
"cancel": "취소",
"save": "저장",
"delete": "삭제",
"rename": "이름 변경"
},
"status": {
"active": "활성",
"inactive": "비활성",
"thinking": "생각 중...",
"error": "오류",
"aborted": "중단됨",
"unknown": "알 수 없음"
},
"time": {
"justNow": "방금 전",
"oneMinuteAgo": "1분 전",
"minutesAgo": "{{count}}분 전",
"oneHourAgo": "1시간 전",
"hoursAgo": "{{count}}시간 전",
"oneDayAgo": "1일 전",
"daysAgo": "{{count}}일 전"
},
"messages": {
"deleteConfirm": "정말 삭제하시겠습니까?",
"renameSuccess": "이름이 변경되었습니다",
"deleteSuccess": "삭제되었습니다",
"errorOccurred": "오류가 발생했습니다",
"deleteSessionConfirm": "이 세션을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
"deleteProjectConfirm": "이 빈 프로젝트를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
"enterProjectPath": "프로젝트 경로를 입력해주세요",
"deleteSessionFailed": "세션 삭제 실패. 다시 시도해주세요.",
"deleteSessionError": "세션 삭제 오류. 다시 시도해주세요.",
"deleteProjectFailed": "프로젝트 삭제 실패. 다시 시도해주세요.",
"deleteProjectError": "프로젝트 삭제 오류. 다시 시도해주세요.",
"createProjectFailed": "프로젝트 생성 실패. 다시 시도해주세요.",
"createProjectError": "프로젝트 생성 오류. 다시 시도해주세요."
},
"version": {
"updateAvailable": "업데이트 가능"
},
"deleteConfirmation": {
"deleteProject": "프로젝트 삭제",
"deleteSession": "세션 삭제",
"confirmDelete": "정말 삭제하시겠습니까",
"sessionCount_one": "이 프로젝트에는 {{count}}개의 대화가 있습니다.",
"sessionCount_other": "이 프로젝트에는 {{count}}개의 대화가 있습니다.",
"allConversationsDeleted": "모든 대화가 영구적으로 삭제됩니다.",
"cannotUndo": "이 작업은 취소할 수 없습니다."
}
}