diff --git a/docs/users/features/language.md b/docs/users/features/language.md index e5067a319..22143d03a 100644 --- a/docs/users/features/language.md +++ b/docs/users/features/language.md @@ -25,6 +25,7 @@ Use the `/language ui` command: /language ui en-US # English /language ui ru-RU # Russian /language ui de-DE # German +/language ui ja-JP # Japanese ``` Aliases are also supported: @@ -34,6 +35,7 @@ Aliases are also supported: /language ui en # English /language ui ru # Russian /language ui de # German +/language ui ja # Japanese ``` ### Auto-detection @@ -63,6 +65,7 @@ On first startup, if no `output-language.md` file exists, Qwen Code automaticall - System locale `en` creates a rule for English responses - System locale `ru` creates a rule for Russian responses - System locale `de` creates a rule for German responses +- System locale `ja` creates a rule for Japanese responses ### Manual Setting diff --git a/integration-tests/sdk-typescript/abort-and-lifecycle.test.ts b/integration-tests/sdk-typescript/abort-and-lifecycle.test.ts index e28e9046d..d4566fcf3 100644 --- a/integration-tests/sdk-typescript/abort-and-lifecycle.test.ts +++ b/integration-tests/sdk-typescript/abort-and-lifecycle.test.ts @@ -16,7 +16,11 @@ import { type ContentBlock, type SDKUserMessage, } from '@qwen-code/sdk'; -import { SDKTestHelper, createSharedTestOptions } from './test-helper.js'; +import { + SDKTestHelper, + createSharedTestOptions, + createResultWaiter, +} from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); @@ -254,6 +258,12 @@ describe('AbortController and Process Lifecycle (E2E)', () => { describe('Closed stdin behavior (asyncGenerator prompt)', () => { it('should reject control requests after stdin closes', async () => { + const resultWaiter = createResultWaiter(1); + let promptDoneResolve: () => void = () => {}; + const promptDonePromise = new Promise((resolve) => { + promptDoneResolve = resolve; + }); + async function* createPrompt(): AsyncIterable { yield { type: 'user', @@ -264,6 +274,9 @@ describe('AbortController and Process Lifecycle (E2E)', () => { }, parent_tool_use_id: null, }; + + await resultWaiter.waitForResult(0); + promptDoneResolve(); } const q = query({ @@ -281,13 +294,14 @@ describe('AbortController and Process Lifecycle (E2E)', () => { for await (const message of q) { if (isSDKResultMessage(message)) { firstResultReceived = true; + resultWaiter.notifyResult(); break; } } expect(firstResultReceived).toBe(true); - - await new Promise((resolve) => setTimeout(resolve, 50)); + await promptDonePromise; + q.endInput(); await expect(q.setPermissionMode('default')).rejects.toThrow( 'Input stream closed', diff --git a/integration-tests/sdk-typescript/mcp-server.test.ts b/integration-tests/sdk-typescript/mcp-server.test.ts index 9b3f21938..cf1de26d4 100644 --- a/integration-tests/sdk-typescript/mcp-server.test.ts +++ b/integration-tests/sdk-typescript/mcp-server.test.ts @@ -19,6 +19,7 @@ import { type SDKMessage, type ToolUseBlock, type SDKSystemMessage, + type SDKUserMessage, } from '@qwen-code/sdk'; import { SDKTestHelper, @@ -26,6 +27,7 @@ import { extractText, findToolUseBlocks, createSharedTestOptions, + createResultWaiter, } from './test-helper.js'; const SHARED_TEST_OPTIONS = { @@ -296,6 +298,176 @@ describe('MCP Server Integration (E2E)', () => { await q.close(); } }); + + it('should support multi-turn asyncGenerator prompt with MCP tools', async () => { + const resultWaiter = createResultWaiter(2); + + async function* createMultiTurnPrompt(): AsyncIterable { + const sessionId = crypto.randomUUID(); + + yield { + type: 'user', + session_id: sessionId, + message: { + role: 'user', + content: 'Use the add tool to calculate 2 + 3. Give me the result.', + }, + parent_tool_use_id: null, + }; + + await resultWaiter.waitForResult(0); + + yield { + type: 'user', + session_id: sessionId, + message: { + role: 'user', + content: + 'Now use the multiply tool to calculate 5 * 4. Give me the result.', + }, + parent_tool_use_id: null, + }; + + await resultWaiter.waitForResult(1); + } + + const q = query({ + prompt: createMultiTurnPrompt(), + options: { + ...SHARED_TEST_OPTIONS, + cwd: testDir, + debug: false, + mcpServers: { + 'test-math-server': { + command: 'node', + args: [serverScriptPath], + }, + }, + }, + }); + + const messages: SDKMessage[] = []; + let assistantText = ''; + const toolCalls: string[] = []; + + try { + for await (const message of q) { + messages.push(message); + + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } + if (isSDKAssistantMessage(message)) { + const toolUseBlocks = findToolUseBlocks(message); + toolUseBlocks.forEach((block) => { + toolCalls.push(block.name); + }); + assistantText += extractText(message.message.content); + } + } + + expect(toolCalls).toContain('add'); + expect(toolCalls).toContain('multiply'); + expect(assistantText).toMatch(/5/); + expect(assistantText).toMatch(/20/); + + const lastMessage = messages[messages.length - 1]; + expect(isSDKResultMessage(lastMessage)).toBe(true); + } finally { + await q.close(); + } + }); + + it('should support multi-turn MCP tools with canUseTool', async () => { + const canUseToolCalls: Array<{ toolName: string }> = []; + const resultWaiter = createResultWaiter(2); + + async function* createMultiTurnPrompt(): AsyncIterable { + const sessionId = crypto.randomUUID(); + + yield { + type: 'user', + session_id: sessionId, + message: { + role: 'user', + content: 'Use the add tool to calculate 9 + 1. Give me the result.', + }, + parent_tool_use_id: null, + }; + + await resultWaiter.waitForResult(0); + + yield { + type: 'user', + session_id: sessionId, + message: { + role: 'user', + content: + 'Now use the multiply tool to calculate 4 * 3. Give me the result.', + }, + parent_tool_use_id: null, + }; + + await resultWaiter.waitForResult(1); + } + + const q = query({ + prompt: createMultiTurnPrompt(), + options: { + ...SHARED_TEST_OPTIONS, + cwd: testDir, + permissionMode: 'default', + canUseTool: async (toolName) => { + canUseToolCalls.push({ toolName }); + return { + behavior: 'allow', + updatedInput: {}, + }; + }, + debug: false, + mcpServers: { + 'test-math-server': { + command: 'node', + args: [serverScriptPath], + }, + }, + }, + }); + + const messages: SDKMessage[] = []; + let assistantText = ''; + const toolCalls: string[] = []; + + try { + for await (const message of q) { + messages.push(message); + + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } + if (isSDKAssistantMessage(message)) { + const toolUseBlocks = findToolUseBlocks(message); + toolUseBlocks.forEach((block) => { + toolCalls.push(block.name); + }); + assistantText += extractText(message.message.content); + } + } + + expect(toolCalls).toContain('add'); + expect(toolCalls).toContain('multiply'); + expect(canUseToolCalls.map((call) => call.toolName)).toEqual( + expect.arrayContaining(['add', 'multiply']), + ); + expect(assistantText).toMatch(/10/); + expect(assistantText).toMatch(/12/); + + const lastMessage = messages[messages.length - 1]; + expect(isSDKResultMessage(lastMessage)).toBe(true); + } finally { + await q.close(); + } + }); }); describe('MCP Tool Message Flow', () => { diff --git a/integration-tests/sdk-typescript/multi-turn.test.ts b/integration-tests/sdk-typescript/multi-turn.test.ts index c1b96cc7c..4cf845fc5 100644 --- a/integration-tests/sdk-typescript/multi-turn.test.ts +++ b/integration-tests/sdk-typescript/multi-turn.test.ts @@ -22,7 +22,11 @@ import { type ControlMessage, type ToolUseBlock, } from '@qwen-code/sdk'; -import { SDKTestHelper, createSharedTestOptions } from './test-helper.js'; +import { + SDKTestHelper, + createSharedTestOptions, + createResultWaiter, +} from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); @@ -76,6 +80,8 @@ describe('Multi-Turn Conversations (E2E)', () => { describe('AsyncIterable Prompt Support', () => { it('should handle multi-turn conversation using AsyncIterable prompt', async () => { + const resultWaiter = createResultWaiter(3); + // Create multi-turn conversation generator async function* createMultiTurnConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -90,7 +96,7 @@ describe('Multi-Turn Conversations (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 100)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -102,7 +108,7 @@ describe('Multi-Turn Conversations (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 100)); + await resultWaiter.waitForResult(1); yield { type: 'user', @@ -113,6 +119,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(2); } // Create multi-turn query using AsyncIterable prompt @@ -133,6 +141,9 @@ describe('Multi-Turn Conversations (E2E)', () => { for await (const message of q) { messages.push(message); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { assistantMessages.push(message); const text = extractText(message.message.content); @@ -153,6 +164,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }); it('should maintain session context across turns', async () => { + const resultWaiter = createResultWaiter(2); + async function* createContextualConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -162,12 +175,12 @@ describe('Multi-Turn Conversations (E2E)', () => { message: { role: 'user', content: - 'Suppose we have 3 rabbits and 4 carrots. How many animals are there?', + 'Suppose we have 3 rabbits and 4 carrots. Identify: How many **animals** are there?', }, parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 200)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -178,6 +191,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -193,6 +208,9 @@ describe('Multi-Turn Conversations (E2E)', () => { try { for await (const message of q) { + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { assistantMessages.push(message); } @@ -213,6 +231,8 @@ describe('Multi-Turn Conversations (E2E)', () => { describe('Tool Usage in Multi-Turn', () => { it('should handle tool usage across multiple turns', async () => { + const resultWaiter = createResultWaiter(2); + async function* createToolConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -226,7 +246,7 @@ describe('Multi-Turn Conversations (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 200)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -237,6 +257,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -257,6 +279,9 @@ describe('Multi-Turn Conversations (E2E)', () => { for await (const message of q) { messages.push(message); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { assistantMessages.push(message); const hasToolUseBlock = message.message.content.some( @@ -286,6 +311,8 @@ describe('Multi-Turn Conversations (E2E)', () => { describe('Message Flow and Sequencing', () => { it('should process messages in correct sequence', async () => { + const resultWaiter = createResultWaiter(2); + async function* createSequentialConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -299,7 +326,7 @@ describe('Multi-Turn Conversations (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 100)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -310,6 +337,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -329,6 +358,9 @@ describe('Multi-Turn Conversations (E2E)', () => { const messageType = getMessageType(message); messageSequence.push(messageType); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { const text = extractText(message.message.content); assistantResponses.push(text); @@ -351,6 +383,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }); it('should handle conversation completion correctly', async () => { + const resultWaiter = createResultWaiter(2); + async function* createSimpleConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -364,7 +398,7 @@ describe('Multi-Turn Conversations (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 100)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -375,6 +409,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -394,6 +430,7 @@ describe('Multi-Turn Conversations (E2E)', () => { messageCount++; if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); completedNaturally = true; expect(message.subtype).toBe('success'); } @@ -441,6 +478,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }); it('should handle conversation with delays', async () => { + const resultWaiter = createResultWaiter(2); + async function* createDelayedConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -455,7 +494,7 @@ describe('Multi-Turn Conversations (E2E)', () => { } as SDKUserMessage; // Longer delay to test patience - await new Promise((resolve) => setTimeout(resolve, 500)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -466,6 +505,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -481,6 +522,9 @@ describe('Multi-Turn Conversations (E2E)', () => { try { for await (const message of q) { + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { assistantMessages.push(message); } @@ -495,6 +539,8 @@ describe('Multi-Turn Conversations (E2E)', () => { describe('Partial Messages in Multi-Turn', () => { it('should receive partial messages when includePartialMessages is enabled', async () => { + const resultWaiter = createResultWaiter(2); + async function* createMultiTurnConversation(): AsyncIterable { const sessionId = crypto.randomUUID(); @@ -508,7 +554,7 @@ describe('Multi-Turn Conversations (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 100)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -519,6 +565,8 @@ describe('Multi-Turn Conversations (E2E)', () => { }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -539,6 +587,9 @@ describe('Multi-Turn Conversations (E2E)', () => { for await (const message of q) { messages.push(message); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKPartialAssistantMessage(message)) { partialMessageCount++; } diff --git a/integration-tests/sdk-typescript/permission-control.test.ts b/integration-tests/sdk-typescript/permission-control.test.ts index eee344755..4c253dc28 100644 --- a/integration-tests/sdk-typescript/permission-control.test.ts +++ b/integration-tests/sdk-typescript/permission-control.test.ts @@ -31,6 +31,7 @@ import { hasErrorToolResults, findSystemMessage, findToolCalls, + createResultWaiter, } from './test-helper.js'; const TEST_TIMEOUT = 30000; @@ -44,6 +45,7 @@ const SHARED_TEST_OPTIONS = createSharedTestOptions(); function createStreamingInputWithControlPoint( firstMessage: string, secondMessage: string, + resultWaiter: { waitForResult: (index: number) => Promise }, ): { generator: AsyncIterable; resume: () => void; @@ -66,7 +68,7 @@ function createStreamingInputWithControlPoint( parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 200)); + await resultWaiter.waitForResult(0); await resumePromise; @@ -81,6 +83,8 @@ function createStreamingInputWithControlPoint( }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); })(); const resume = () => { @@ -320,9 +324,11 @@ describe('Permission Control (E2E)', () => { describe('setPermissionMode API', () => { it('should change permission mode from default to yolo', async () => { + const resultWaiter = createResultWaiter(2); const { generator, resume } = createStreamingInputWithControlPoint( 'What is 1 + 1?', 'What is 2 + 2?', + resultWaiter, ); const q = query({ @@ -361,6 +367,9 @@ describe('Permission Control (E2E)', () => { resolvers.second?.(); } } + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } } })(); @@ -397,9 +406,11 @@ describe('Permission Control (E2E)', () => { }); it('should change permission mode from yolo to plan', async () => { + const resultWaiter = createResultWaiter(2); const { generator, resume } = createStreamingInputWithControlPoint( 'What is 3 + 3?', 'What is 4 + 4?', + resultWaiter, ); const q = query({ @@ -437,6 +448,9 @@ describe('Permission Control (E2E)', () => { resolvers.second?.(); } } + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } } })(); @@ -473,9 +487,11 @@ describe('Permission Control (E2E)', () => { }); it('should change permission mode to auto-edit', async () => { + const resultWaiter = createResultWaiter(2); const { generator, resume } = createStreamingInputWithControlPoint( 'What is 5 + 5?', 'What is 6 + 6?', + resultWaiter, ); const q = query({ @@ -513,6 +529,9 @@ describe('Permission Control (E2E)', () => { resolvers.second?.(); } } + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } } })(); @@ -584,9 +603,11 @@ describe('Permission Control (E2E)', () => { input: Record; }> = []; + const resultWaiter = createResultWaiter(2); const { generator, resume } = createStreamingInputWithControlPoint( 'Create a file named first.txt', 'Create a file named second.txt', + resultWaiter, ); const q = query({ @@ -630,6 +651,7 @@ describe('Permission Control (E2E)', () => { secondResponseReceived = true; resolvers.second?.(); } + resultWaiter.notifyResult(); } } })(); diff --git a/integration-tests/sdk-typescript/system-control.test.ts b/integration-tests/sdk-typescript/system-control.test.ts index a977e6471..0ae28c4c5 100644 --- a/integration-tests/sdk-typescript/system-control.test.ts +++ b/integration-tests/sdk-typescript/system-control.test.ts @@ -8,9 +8,14 @@ import { query, isSDKAssistantMessage, isSDKSystemMessage, + isSDKResultMessage, type SDKUserMessage, } from '@qwen-code/sdk'; -import { SDKTestHelper, createSharedTestOptions } from './test-helper.js'; +import { + SDKTestHelper, + createSharedTestOptions, + createResultWaiter, +} from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); @@ -26,6 +31,7 @@ const SHARED_TEST_OPTIONS = createSharedTestOptions(); function createStreamingInputWithControlPoint( firstMessage: string, secondMessage: string, + resultWaiter: { waitForResult: (index: number) => Promise }, ): { generator: AsyncIterable; resume: () => void; @@ -48,7 +54,7 @@ function createStreamingInputWithControlPoint( parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 200)); + await resultWaiter.waitForResult(0); await resumePromise; @@ -63,6 +69,8 @@ function createStreamingInputWithControlPoint( }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(1); })(); const resume = () => { @@ -89,9 +97,11 @@ describe('System Control (E2E)', () => { describe('setModel API', () => { it('should change model dynamically during streaming input', async () => { + const resultWaiter = createResultWaiter(2); const { generator, resume } = createStreamingInputWithControlPoint( 'Tell me the model name.', 'Tell me the model name now again.', + resultWaiter, ); const q = query({ @@ -126,6 +136,9 @@ describe('System Control (E2E)', () => { if (isSDKSystemMessage(message)) { systemMessages.push({ model: message.model }); } + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { if (!firstResponseReceived) { firstResponseReceived = true; @@ -181,6 +194,7 @@ describe('System Control (E2E)', () => { it('should handle multiple model changes in sequence', async () => { const sessionId = crypto.randomUUID(); + const resultWaiter = createResultWaiter(3); let resumeResolve1: (() => void) | null = null; let resumeResolve2: (() => void) | null = null; const resumePromise1 = new Promise((resolve) => { @@ -198,7 +212,7 @@ describe('System Control (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 200)); + await resultWaiter.waitForResult(0); await resumePromise1; await new Promise((resolve) => setTimeout(resolve, 200)); @@ -209,7 +223,7 @@ describe('System Control (E2E)', () => { parent_tool_use_id: null, } as SDKUserMessage; - await new Promise((resolve) => setTimeout(resolve, 200)); + await resultWaiter.waitForResult(1); await resumePromise2; await new Promise((resolve) => setTimeout(resolve, 200)); @@ -219,6 +233,8 @@ describe('System Control (E2E)', () => { message: { role: 'user', content: 'Third message' }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(2); })(); const q = query({ @@ -246,6 +262,9 @@ describe('System Control (E2E)', () => { if (isSDKSystemMessage(message)) { systemMessages.push({ model: message.model }); } + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } if (isSDKAssistantMessage(message)) { if (responseCount < resolvers.length) { resolvers[responseCount]?.(); @@ -318,6 +337,7 @@ describe('System Control (E2E)', () => { describe('supportedCommands API', () => { it('should return list of supported slash commands', async () => { const sessionId = crypto.randomUUID(); + const resultWaiter = createResultWaiter(1); const generator = (async function* () { yield { type: 'user', @@ -325,6 +345,8 @@ describe('System Control (E2E)', () => { message: { role: 'user', content: 'Hello' }, parent_tool_use_id: null, } as SDKUserMessage; + + await resultWaiter.waitForResult(0); })(); const q = query({ @@ -343,6 +365,9 @@ describe('System Control (E2E)', () => { const messageConsumer = (async () => { try { for await (const _message of q) { + if (isSDKResultMessage(_message)) { + resultWaiter.notifyResult(); + } // Just consume messages } } catch (error) { diff --git a/integration-tests/sdk-typescript/test-helper.ts b/integration-tests/sdk-typescript/test-helper.ts index d7efc026c..07f44f890 100644 --- a/integration-tests/sdk-typescript/test-helper.ts +++ b/integration-tests/sdk-typescript/test-helper.ts @@ -655,6 +655,29 @@ export function hasErrorToolResults(messages: SDKMessage[]): boolean { // Streaming Input Utilities // ============================================================================ +export function createResultWaiter(expectedResults: number): { + waitForResult: (index: number) => Promise; + notifyResult: () => void; +} { + const resolvers: Array<() => void> = []; + const promises = Array.from({ length: expectedResults }, () => { + return new Promise((resolve) => { + resolvers.push(resolve); + }); + }); + let resolvedCount = 0; + + return { + waitForResult: (index: number) => promises[index], + notifyResult: () => { + if (resolvedCount < resolvers.length) { + resolvers[resolvedCount]?.(); + resolvedCount += 1; + } + }, + }; +} + /** * Create a simple streaming input from an array of message contents */ diff --git a/integration-tests/sdk-typescript/tool-control.test.ts b/integration-tests/sdk-typescript/tool-control.test.ts index 90819aad1..aecb98ae6 100644 --- a/integration-tests/sdk-typescript/tool-control.test.ts +++ b/integration-tests/sdk-typescript/tool-control.test.ts @@ -15,6 +15,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { query, isSDKAssistantMessage, + isSDKResultMessage, type SDKMessage, type SDKUserMessage, } from '@qwen-code/sdk'; @@ -25,6 +26,7 @@ import { findToolResults, assertSuccessfulCompletion, createSharedTestOptions, + createResultWaiter, } from './test-helper.js'; const SHARED_TEST_OPTIONS = createSharedTestOptions(); @@ -751,6 +753,7 @@ describe('Tool Control Parameters (E2E)', () => { async () => { await helper.createFile('test.txt', 'original content'); + const resultWaiter = createResultWaiter(1); const canUseToolCalls: Array<{ toolName: string; input: Record; @@ -768,7 +771,7 @@ describe('Tool Control Parameters (E2E)', () => { parent_tool_use_id: null, }; - await new Promise((resolve) => setTimeout(resolve, 3000)); + await resultWaiter.waitForResult(0); } const q = query({ @@ -795,6 +798,9 @@ describe('Tool Control Parameters (E2E)', () => { try { for await (const message of q) { messages.push(message); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } } const toolCalls = findToolCalls(messages); @@ -827,6 +833,7 @@ describe('Tool Control Parameters (E2E)', () => { async () => { await helper.createFile('test.txt', 'original content'); + const resultWaiter = createResultWaiter(1); // Create an async generator that yields a single message async function* createPrompt(): AsyncIterable { yield { @@ -838,7 +845,7 @@ describe('Tool Control Parameters (E2E)', () => { }, parent_tool_use_id: null, }; - await new Promise((resolve) => setTimeout(resolve, 3000)); + await resultWaiter.waitForResult(0); } const q = query({ @@ -866,6 +873,9 @@ describe('Tool Control Parameters (E2E)', () => { try { for await (const message of q) { messages.push(message); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } } // write_file should have been attempted but stream was closed @@ -892,6 +902,7 @@ describe('Tool Control Parameters (E2E)', () => { async () => { await helper.createFile('data.txt', 'initial data'); + const resultWaiter = createResultWaiter(2); const canUseToolCalls: string[] = []; // Create an async generator that yields multiple messages @@ -908,8 +919,7 @@ describe('Tool Control Parameters (E2E)', () => { parent_tool_use_id: null, }; - // Small delay to simulate multi-turn conversation - await new Promise((resolve) => setTimeout(resolve, 100)); + await resultWaiter.waitForResult(0); yield { type: 'user', @@ -920,6 +930,8 @@ describe('Tool Control Parameters (E2E)', () => { }, parent_tool_use_id: null, }; + + await resultWaiter.waitForResult(1); } const q = query({ @@ -942,6 +954,9 @@ describe('Tool Control Parameters (E2E)', () => { try { for await (const message of q) { messages.push(message); + if (isSDKResultMessage(message)) { + resultWaiter.notifyResult(); + } } const toolCalls = findToolCalls(messages); @@ -951,17 +966,14 @@ describe('Tool Control Parameters (E2E)', () => { expect(toolNames).toContain('read_file'); expect(toolNames).toContain('write_file'); - // canUseTool should not be called once stream is closed - expect(canUseToolCalls).toHaveLength(0); + expect(canUseToolCalls).toContain('write_file'); const writeFileResults = findToolResults(messages, 'write_file'); expect(writeFileResults.length).toBeGreaterThan(0); - for (const result of writeFileResults) { - expect(result.content).toContain('Error: Input closed'); - } const content = await helper.readFile('data.txt'); - expect(content).toBe('initial data'); + expect(content).toContain('initial data'); + expect(content).toContain(' - updated'); } finally { await q.close(); } diff --git a/package-lock.json b/package-lock.json index 17963ad94..590630a59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@qwen-code/qwen-code", - "version": "0.8.0", + "version": "0.8.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@qwen-code/qwen-code", - "version": "0.8.0", + "version": "0.8.2", "workspaces": [ "packages/*" ], @@ -17289,7 +17289,7 @@ }, "packages/cli": { "name": "@qwen-code/qwen-code", - "version": "0.8.0", + "version": "0.8.2", "dependencies": { "@google/genai": "1.30.0", "@iarna/toml": "^2.2.5", @@ -17926,7 +17926,7 @@ }, "packages/core": { "name": "@qwen-code/qwen-code-core", - "version": "0.8.0", + "version": "0.8.2", "hasInstallScript": true, "dependencies": { "@anthropic-ai/sdk": "^0.36.1", @@ -18573,7 +18573,7 @@ }, "packages/sdk-typescript": { "name": "@qwen-code/sdk", - "version": "0.1.3", + "version": "0.1.4", "license": "Apache-2.0", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", @@ -21395,7 +21395,7 @@ }, "packages/test-utils": { "name": "@qwen-code/qwen-code-test-utils", - "version": "0.8.0", + "version": "0.8.2", "dev": true, "license": "Apache-2.0", "devDependencies": { @@ -21407,7 +21407,7 @@ }, "packages/vscode-ide-companion": { "name": "qwen-code-vscode-ide-companion", - "version": "0.8.0", + "version": "0.8.2", "license": "LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", diff --git a/package.json b/package.json index 9e714499a..076ab33e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code", - "version": "0.8.0", + "version": "0.8.2", "engines": { "node": ">=20.0.0" }, @@ -13,7 +13,7 @@ "url": "git+https://github.com/QwenLM/qwen-code.git" }, "config": { - "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.0" + "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.2" }, "scripts": { "start": "cross-env node scripts/start.js", diff --git a/packages/cli/package.json b/packages/cli/package.json index 8c198fc61..20c0d54e8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code", - "version": "0.8.0", + "version": "0.8.2", "description": "Qwen Code", "repository": { "type": "git", @@ -33,7 +33,7 @@ "dist" ], "config": { - "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.0" + "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.8.2" }, "dependencies": { "@google/genai": "1.30.0", diff --git a/packages/cli/src/commands/extensions/utils.ts b/packages/cli/src/commands/extensions/utils.ts index d7d2d0a59..52cd1cd4c 100644 --- a/packages/cli/src/commands/extensions/utils.ts +++ b/packages/cli/src/commands/extensions/utils.ts @@ -14,6 +14,7 @@ import { import { isWorkspaceTrusted } from '../../config/trustedFolders.js'; import * as os from 'node:os'; import chalk from 'chalk'; +import { t } from '../../i18n/index.js'; export async function getExtensionManager(): Promise { const workspaceDir = process.cwd(); @@ -48,32 +49,44 @@ export function extensionToOutputString( const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗'); let output = `${inline ? '' : status} ${extension.config.name} (${extension.config.version})`; - output += `\n Path: ${extension.path}`; + output += `\n ${t('Path:')} ${extension.path}`; if (extension.installMetadata) { - output += `\n Source: ${extension.installMetadata.source} (Type: ${extension.installMetadata.type})`; + output += `\n ${t('Source:')} ${extension.installMetadata.source} (${t('Type:')} ${extension.installMetadata.type})`; if (extension.installMetadata.ref) { - output += `\n Ref: ${extension.installMetadata.ref}`; + output += `\n ${t('Ref:')} ${extension.installMetadata.ref}`; } if (extension.installMetadata.releaseTag) { - output += `\n Release tag: ${extension.installMetadata.releaseTag}`; + output += `\n ${t('Release tag:')} ${extension.installMetadata.releaseTag}`; } } - output += `\n Enabled (User): ${userEnabled}`; - output += `\n Enabled (Workspace): ${workspaceEnabled}`; + output += `\n ${t('Enabled (User):')} ${userEnabled}`; + output += `\n ${t('Enabled (Workspace):')} ${workspaceEnabled}`; if (extension.contextFiles.length > 0) { - output += `\n Context files:`; + output += `\n ${t('Context files:')}`; extension.contextFiles.forEach((contextFile) => { output += `\n ${contextFile}`; }); } if (extension.commands && extension.commands.length > 0) { - output += `\n Commands:`; + output += `\n ${t('Commands:')}`; extension.commands.forEach((command) => { output += `\n /${command}`; }); } + if (extension.skills && extension.skills.length > 0) { + output += `\n ${t('Skills:')}`; + extension.skills.forEach((skill) => { + output += `\n ${skill.name}`; + }); + } + if (extension.agents && extension.agents.length > 0) { + output += `\n ${t('Agents:')}`; + extension.agents.forEach((agent) => { + output += `\n ${agent.name}`; + }); + } if (extension.config.mcpServers) { - output += `\n MCP servers:`; + output += `\n ${t('MCP servers:')}`; Object.keys(extension.config.mcpServers).forEach((key) => { output += `\n ${key}`; }); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 2e9d1cf94..cdde1c8e3 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -18,6 +18,7 @@ import { DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD, } from '@qwen-code/qwen-code-core'; import type { CustomTheme } from '../ui/themes/theme.js'; +import { getLanguageSettingsOptions } from '../i18n/languages.js'; export type SettingsType = | 'boolean' @@ -211,13 +212,7 @@ const SETTINGS_SCHEMA = { 'You can also use custom language codes (e.g., "es", "fr") by placing JS language files ' + 'in ~/.qwen/locales/ (e.g., ~/.qwen/locales/es.js).', showInDialog: true, - options: [ - { value: 'auto', label: 'Auto (detect from system)' }, - { value: 'en', label: 'English' }, - { value: 'zh', label: '中文 (Chinese)' }, - { value: 'ru', label: 'Русский (Russian)' }, - { value: 'de', label: 'Deutsch (German)' }, - ], + options: [] as readonly SettingEnumOption[], }, outputLanguage: { type: 'string', @@ -227,7 +222,7 @@ const SETTINGS_SCHEMA = { default: 'auto', description: 'The language for LLM output. Use "auto" to detect from system settings, ' + - 'or set a specific language (e.g., "English", "中文", "日本語").', + 'or set a specific language.', showInDialog: true, }, terminalBell: { @@ -1189,6 +1184,15 @@ const SETTINGS_SCHEMA = { export type SettingsSchemaType = typeof SETTINGS_SCHEMA; export function getSettingsSchema(): SettingsSchemaType { + // Inject dynamic language options + const schema = SETTINGS_SCHEMA as unknown as SettingsSchema; + if (schema['general']?.properties?.['language']) { + ( + schema['general'].properties['language'] as { + options?: SettingEnumOption[]; + } + ).options = getLanguageSettingsOptions(); + } return SETTINGS_SCHEMA; } diff --git a/packages/cli/src/core/initializer.ts b/packages/cli/src/core/initializer.ts index c21d637e3..fe81816d9 100644 --- a/packages/cli/src/core/initializer.ts +++ b/packages/cli/src/core/initializer.ts @@ -14,7 +14,7 @@ import { import { type LoadedSettings, SettingScope } from '../config/settings.js'; import { performInitialAuth } from './auth.js'; import { validateTheme } from './theme.js'; -import { initializeI18n } from '../i18n/index.js'; +import { initializeI18n, type SupportedLanguage } from '../i18n/index.js'; import { initializeLlmOutputLanguage } from '../utils/languageUtils.js'; export interface InitializationResult { @@ -38,9 +38,9 @@ export async function initializeApp( // Initialize i18n system const languageSetting = process.env['QWEN_CODE_LANG'] || - settings.merged.general?.language || + (settings.merged.general?.language as string) || 'auto'; - await initializeI18n(languageSetting); + await initializeI18n(languageSetting as SupportedLanguage | 'auto'); // Auto-detect and set LLM output language on first use initializeLlmOutputLanguage(settings.merged.general?.outputLanguage); diff --git a/packages/cli/src/i18n/index.ts b/packages/cli/src/i18n/index.ts index 1338fb571..64384029d 100644 --- a/packages/cli/src/i18n/index.ts +++ b/packages/cli/src/i18n/index.ts @@ -10,6 +10,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'; import { homedir } from 'node:os'; import { type SupportedLanguage, + SUPPORTED_LANGUAGES, getLanguageNameFromLocale, } from './languages.js'; @@ -55,16 +56,17 @@ const getLocalePath = ( // Language detection export function detectSystemLanguage(): SupportedLanguage { const envLang = process.env['QWEN_CODE_LANG'] || process.env['LANG']; - if (envLang?.startsWith('zh')) return 'zh'; - if (envLang?.startsWith('en')) return 'en'; - if (envLang?.startsWith('ru')) return 'ru'; - if (envLang?.startsWith('de')) return 'de'; + if (envLang) { + for (const lang of SUPPORTED_LANGUAGES) { + if (envLang.startsWith(lang.code)) return lang.code; + } + } try { const locale = Intl.DateTimeFormat().resolvedOptions().locale; - if (locale.startsWith('zh')) return 'zh'; - if (locale.startsWith('ru')) return 'ru'; - if (locale.startsWith('de')) return 'de'; + for (const lang of SUPPORTED_LANGUAGES) { + if (locale.startsWith(lang.code)) return lang.code; + } } catch { // Fallback to default } diff --git a/packages/cli/src/i18n/languages.ts b/packages/cli/src/i18n/languages.ts index c0e57eefa..733f11863 100644 --- a/packages/cli/src/i18n/languages.ts +++ b/packages/cli/src/i18n/languages.ts @@ -4,7 +4,14 @@ * SPDX-License-Identifier: Apache-2.0 */ -export type SupportedLanguage = 'en' | 'zh' | 'ru' | 'de' | string; +export type SupportedLanguage = + | 'en' + | 'zh' + | 'ru' + | 'de' + | 'ja' + | 'pt' + | string; export interface LanguageDefinition { /** The internal locale code used by the i18n system (e.g., 'en', 'zh'). */ @@ -13,6 +20,8 @@ export interface LanguageDefinition { id: string; /** The full English name of the language (e.g., 'English', 'Chinese'). */ fullName: string; + /** The native name of the language (e.g., 'English', '中文'). */ + nativeName?: string; } export const SUPPORTED_LANGUAGES: readonly LanguageDefinition[] = [ @@ -20,21 +29,37 @@ export const SUPPORTED_LANGUAGES: readonly LanguageDefinition[] = [ code: 'en', id: 'en-US', fullName: 'English', + nativeName: 'English', }, { code: 'zh', id: 'zh-CN', fullName: 'Chinese', + nativeName: '中文', }, { code: 'ru', id: 'ru-RU', fullName: 'Russian', + nativeName: 'Русский', }, { code: 'de', id: 'de-DE', fullName: 'German', + nativeName: 'Deutsch', + }, + { + code: 'ja', + id: 'ja-JP', + fullName: 'Japanese', + nativeName: '日本語', + }, + { + code: 'pt', + id: 'pt-BR', + fullName: 'Portuguese', + nativeName: 'Português', }, ]; @@ -46,3 +71,28 @@ export function getLanguageNameFromLocale(locale: SupportedLanguage): string { const lang = SUPPORTED_LANGUAGES.find((l) => l.code === locale); return lang?.fullName || 'English'; } + +/** + * Gets the language options for the settings schema. + */ +export function getLanguageSettingsOptions(): Array<{ + value: string; + label: string; +}> { + return [ + { value: 'auto', label: 'Auto (detect from system)' }, + ...SUPPORTED_LANGUAGES.map((l) => ({ + value: l.code, + label: l.nativeName + ? `${l.nativeName} (${l.fullName})` + : `${l.fullName} (${l.id})`, + })), + ]; +} + +/** + * Gets a string containing all supported language IDs (e.g., "en-US|zh-CN"). + */ +export function getSupportedLanguageIds(separator = '|'): string { + return SUPPORTED_LANGUAGES.map((l) => l.id).join(separator); +} diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js index fdda3a352..44d982378 100644 --- a/packages/cli/src/i18n/locales/de.js +++ b/packages/cli/src/i18n/locales/de.js @@ -480,6 +480,17 @@ export default { 'Either an extension name or --all must be provided': 'Entweder ein Erweiterungsname oder --all muss angegeben werden', 'Lists installed extensions.': 'Listet installierte Erweiterungen auf.', + 'Path:': 'Pfad:', + 'Source:': 'Quelle:', + 'Type:': 'Typ:', + 'Ref:': 'Ref:', + 'Release tag:': 'Release-Tag:', + 'Enabled (User):': 'Aktiviert (Benutzer):', + 'Enabled (Workspace):': 'Aktiviert (Arbeitsbereich):', + 'Context files:': 'Kontextdateien:', + 'Skills:': 'Skills:', + 'Agents:': 'Agents:', + 'MCP servers:': 'MCP-Server:', 'Link extension failed to install.': 'Verknüpfte Erweiterung konnte nicht installiert werden.', 'Extension "{{name}}" linked successfully and enabled.': @@ -569,8 +580,8 @@ export default { // ============================================================================ // Commands - Language // ============================================================================ - 'Invalid language. Available: en-US, zh-CN': - 'Ungültige Sprache. Verfügbar: en-US, zh-CN', + 'Invalid language. Available: {{options}}': + 'Ungültige Sprache. Verfügbar: {{options}}', 'Language subcommands do not accept additional arguments.': 'Sprach-Unterbefehle akzeptieren keine zusätzlichen Argumente.', 'Current UI language: {{lang}}': 'Aktuelle UI-Sprache: {{lang}}', @@ -579,12 +590,14 @@ export default { 'LLM output language not set': 'LLM-Ausgabesprache nicht festgelegt', 'Set UI language': 'UI-Sprache festlegen', 'Set LLM output language': 'LLM-Ausgabesprache festlegen', - 'Usage: /language ui [zh-CN|en-US]': 'Verwendung: /language ui [zh-CN|en-US]', + 'Usage: /language ui [{{options}}]': 'Verwendung: /language ui [{{options}}]', 'Usage: /language output ': 'Verwendung: /language output ', 'Example: /language output 中文': 'Beispiel: /language output Deutsch', - 'Example: /language output English': 'Beispiel: /language output English', + 'Example: /language output English': 'Beispiel: /language output Englisch', 'Example: /language output 日本語': 'Beispiel: /language output Japanisch', + 'Example: /language output Português': + 'Beispiel: /language output Portugiesisch', 'UI language changed to {{lang}}': 'UI-Sprache geändert zu {{lang}}', 'LLM output language set to {{lang}}': 'LLM-Ausgabesprache auf {{lang}} gesetzt', @@ -600,12 +613,7 @@ export default { 'To request additional UI language packs, please open an issue on GitHub.': 'Um zusätzliche UI-Sprachpakete anzufordern, öffnen Sie bitte ein Issue auf GitHub.', 'Available options:': 'Verfügbare Optionen:', - ' - zh-CN: Simplified Chinese': ' - zh-CN: Vereinfachtes Chinesisch', - ' - en-US: English': ' - en-US: Englisch', - 'Set UI language to Simplified Chinese (zh-CN)': - 'UI-Sprache auf Vereinfachtes Chinesisch (zh-CN) setzen', - 'Set UI language to English (en-US)': - 'UI-Sprache auf Englisch (en-US) setzen', + 'Set UI language to {{name}}': 'UI-Sprache auf {{name}} setzen', // ============================================================================ // Commands - Approval Mode diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index 94e330c28..95d908b11 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -492,6 +492,17 @@ export default { 'Either an extension name or --all must be provided': 'Either an extension name or --all must be provided', 'Lists installed extensions.': 'Lists installed extensions.', + 'Path:': 'Path:', + 'Source:': 'Source:', + 'Type:': 'Type:', + 'Ref:': 'Ref:', + 'Release tag:': 'Release tag:', + 'Enabled (User):': 'Enabled (User):', + 'Enabled (Workspace):': 'Enabled (Workspace):', + 'Context files:': 'Context files:', + 'Skills:': 'Skills:', + 'Agents:': 'Agents:', + 'MCP servers:': 'MCP servers:', 'Link extension failed to install.': 'Link extension failed to install.', 'Extension "{{name}}" linked successfully and enabled.': 'Extension "{{name}}" linked successfully and enabled.', @@ -576,8 +587,8 @@ export default { // ============================================================================ // Commands - Language // ============================================================================ - 'Invalid language. Available: en-US, zh-CN': - 'Invalid language. Available: en-US, zh-CN', + 'Invalid language. Available: {{options}}': + 'Invalid language. Available: {{options}}', 'Language subcommands do not accept additional arguments.': 'Language subcommands do not accept additional arguments.', 'Current UI language: {{lang}}': 'Current UI language: {{lang}}', @@ -586,11 +597,12 @@ export default { 'LLM output language not set': 'LLM output language not set', 'Set UI language': 'Set UI language', 'Set LLM output language': 'Set LLM output language', - 'Usage: /language ui [zh-CN|en-US]': 'Usage: /language ui [zh-CN|en-US]', + 'Usage: /language ui [{{options}}]': 'Usage: /language ui [{{options}}]', 'Usage: /language output ': 'Usage: /language output ', 'Example: /language output 中文': 'Example: /language output 中文', 'Example: /language output English': 'Example: /language output English', 'Example: /language output 日本語': 'Example: /language output 日本語', + 'Example: /language output Português': 'Example: /language output Português', 'UI language changed to {{lang}}': 'UI language changed to {{lang}}', 'LLM output language set to {{lang}}': 'LLM output language set to {{lang}}', 'LLM output language rule file generated at {{path}}': @@ -605,11 +617,7 @@ export default { 'To request additional UI language packs, please open an issue on GitHub.': 'To request additional UI language packs, please open an issue on GitHub.', 'Available options:': 'Available options:', - ' - zh-CN: Simplified Chinese': ' - zh-CN: Simplified Chinese', - ' - en-US: English': ' - en-US: English', - 'Set UI language to Simplified Chinese (zh-CN)': - 'Set UI language to Simplified Chinese (zh-CN)', - 'Set UI language to English (en-US)': 'Set UI language to English (en-US)', + 'Set UI language to {{name}}': 'Set UI language to {{name}}', // ============================================================================ // Commands - Approval Mode diff --git a/packages/cli/src/i18n/locales/ja.js b/packages/cli/src/i18n/locales/ja.js new file mode 100644 index 000000000..201d1ee3d --- /dev/null +++ b/packages/cli/src/i18n/locales/ja.js @@ -0,0 +1,886 @@ +/** + * @license + * Copyright 2025 Qwen + * SPDX-License-Identifier: Apache-2.0 + */ + +// Japanese translations for Qwen Code CLI + +export default { + // ============================================================================ + // Help / UI Components + // ============================================================================ + 'Basics:': '基本操作:', + 'Add context': 'コンテキストを追加', + 'Use {{symbol}} to specify files for context (e.g., {{example}}) to target specific files or folders.': + '{{symbol}} を使用してコンテキスト用のファイルを指定します(例: {{example}}) また、特定のファイルやフォルダを対象にできます', + '@': '@', + '@src/myFile.ts': '@src/myFile.ts', + 'Shell mode': 'シェルモード', + 'YOLO mode': 'YOLOモード', + 'plan mode': 'プランモード', + 'auto-accept edits': '編集を自動承認', + 'Accepting edits': '編集を承認中', + '(shift + tab to cycle)': '(Shift + Tab で切り替え)', + 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).': + '{{symbol}} でシェルコマンドを実行(例: {{example1}})、または自然言語で入力(例: {{example2}})', + '!': '!', + '!npm run start': '!npm run start', + 'start server': 'サーバーを起動', + 'Commands:': 'コマンド:', + 'shell command': 'シェルコマンド', + 'Model Context Protocol command (from external servers)': + 'Model Context Protocol コマンド(外部サーバーから)', + 'Keyboard Shortcuts:': 'キーボードショートカット:', + 'Jump through words in the input': '入力欄の単語間を移動', + 'Close dialogs, cancel requests, or quit application': + 'ダイアログを閉じる、リクエストをキャンセル、またはアプリを終了', + 'New line': '改行', + 'New line (Alt+Enter works for certain linux distros)': + '改行(一部のLinuxディストリビューションではAlt+Enterが有効)', + 'Clear the screen': '画面をクリア', + 'Open input in external editor': '外部エディタで入力を開く', + 'Send message': 'メッセージを送信', + 'Initializing...': '初期化中...', + 'Connecting to MCP servers... ({{connected}}/{{total}})': + 'MCPサーバーに接続中... ({{connected}}/{{total}})', + 'Type your message or @path/to/file': + 'メッセージを入力、@パス/ファイルでファイルを添付(D&D対応)', + "Press 'i' for INSERT mode and 'Esc' for NORMAL mode.": + "'i' でINSERTモード、'Esc' でNORMALモード", + 'Cancel operation / Clear input (double press)': + '操作をキャンセル / 入力をクリア(2回押し)', + 'Cycle approval modes': '承認モードを切り替え', + 'Cycle through your prompt history': 'プロンプト履歴を順に表示', + 'For a full list of shortcuts, see {{docPath}}': + 'ショートカットの完全なリストは {{docPath}} を参照', + 'docs/keyboard-shortcuts.md': 'docs/keyboard-shortcuts.md', + 'for help on Qwen Code': 'Qwen Code のヘルプ', + 'show version info': 'バージョン情報を表示', + 'submit a bug report': 'バグレポートを送信', + 'About Qwen Code': 'Qwen Code について', + + // ============================================================================ + // System Information Fields + // ============================================================================ + 'CLI Version': 'CLIバージョン', + 'Git Commit': 'Gitコミット', + Model: 'モデル', + Sandbox: 'サンドボックス', + 'OS Platform': 'OSプラットフォーム', + 'OS Arch': 'OSアーキテクチャ', + 'OS Release': 'OSリリース', + 'Node.js Version': 'Node.js バージョン', + 'NPM Version': 'NPM バージョン', + 'Session ID': 'セッションID', + 'Auth Method': '認証方式', + 'Base URL': 'ベースURL', + 'Memory Usage': 'メモリ使用量', + 'IDE Client': 'IDEクライアント', + + // ============================================================================ + // Commands - General + // ============================================================================ + 'Analyzes the project and creates a tailored QWEN.md file.': + 'プロジェクトを分析し、カスタマイズされた QWEN.md ファイルを作成', + 'list available Qwen Code tools. Usage: /tools [desc]': + '利用可能な Qwen Code ツールを一覧表示。使い方: /tools [desc]', + 'Available Qwen Code CLI tools:': '利用可能な Qwen Code CLI ツール:', + 'No tools available': '利用可能なツールはありません', + 'View or change the approval mode for tool usage': + 'ツール使用の承認モードを表示または変更', + 'View or change the language setting': '言語設定を表示または変更', + 'change the theme': 'テーマを変更', + 'Select Theme': 'テーマを選択', + Preview: 'プレビュー', + '(Use Enter to select, Tab to configure scope)': + '(Enter で選択、Tab でスコープを設定)', + '(Use Enter to apply scope, Tab to select theme)': + '(Enter でスコープを適用、Tab でテーマを選択)', + 'Theme configuration unavailable due to NO_COLOR env variable.': + 'NO_COLOR 環境変数のためテーマ設定は利用できません', + 'Theme "{{themeName}}" not found.': 'テーマ "{{themeName}}" が見つかりません', + 'Theme "{{themeName}}" not found in selected scope.': + '選択したスコープにテーマ "{{themeName}}" が見つかりません', + 'Clear conversation history and free up context': + '会話履歴をクリアしてコンテキストを解放', + 'Compresses the context by replacing it with a summary.': + 'コンテキストを要約に置き換えて圧縮', + 'open full Qwen Code documentation in your browser': + 'ブラウザで Qwen Code のドキュメントを開く', + 'Configuration not available.': '設定が利用できません', + 'change the auth method': '認証方式を変更', + 'Copy the last result or code snippet to clipboard': + '最後の結果またはコードスニペットをクリップボードにコピー', + + // ============================================================================ + // Commands - Agents + // ============================================================================ + 'Manage subagents for specialized task delegation.': + '専門タスクを委任するサブエージェントを管理', + 'Manage existing subagents (view, edit, delete).': + '既存のサブエージェントを管理(表示、編集、削除)', + 'Create a new subagent with guided setup.': + 'ガイド付きセットアップで新しいサブエージェントを作成', + + // ============================================================================ + // Agents - Management Dialog + // ============================================================================ + Agents: 'エージェント', + 'Choose Action': 'アクションを選択', + 'Edit {{name}}': '{{name}} を編集', + 'Edit Tools: {{name}}': 'ツールを編集: {{name}}', + 'Edit Color: {{name}}': '色を編集: {{name}}', + 'Delete {{name}}': '{{name}} を削除', + 'Unknown Step': '不明なステップ', + 'Esc to close': 'Esc で閉じる', + 'Enter to select, ↑↓ to navigate, Esc to close': + 'Enter で選択、↑↓ で移動、Esc で閉じる', + 'Esc to go back': 'Esc で戻る', + 'Enter to confirm, Esc to cancel': 'Enter で確定、Esc でキャンセル', + 'Enter to select, ↑↓ to navigate, Esc to go back': + 'Enter で選択、↑↓ で移動、Esc で戻る', + 'Invalid step: {{step}}': '無効なステップ: {{step}}', + 'No subagents found.': 'サブエージェントが見つかりません', + "Use '/agents create' to create your first subagent.": + "'/agents create' で最初のサブエージェントを作成してください", + '(built-in)': '(組み込み)', + '(overridden by project level agent)': + '(プロジェクトレベルのエージェントで上書き)', + 'Project Level ({{path}})': 'プロジェクトレベル ({{path}})', + 'User Level ({{path}})': 'ユーザーレベル ({{path}})', + 'Built-in Agents': '組み込みエージェント', + 'Using: {{count}} agents': '使用中: {{count}} エージェント', + 'View Agent': 'エージェントを表示', + 'Edit Agent': 'エージェントを編集', + 'Delete Agent': 'エージェントを削除', + Back: '戻る', + 'No agent selected': 'エージェントが選択されていません', + 'File Path: ': 'ファイルパス: ', + 'Tools: ': 'ツール: ', + 'Color: ': '色: ', + 'Description:': '説明:', + 'System Prompt:': 'システムプロンプト:', + 'Open in editor': 'エディタで開く', + 'Edit tools': 'ツールを編集', + 'Edit color': '色を編集', + '❌ Error:': '❌ エラー:', + 'Are you sure you want to delete agent "{{name}}"?': + 'エージェント "{{name}}" を削除してもよろしいですか?', + 'Project Level (.qwen/agents/)': 'プロジェクトレベル (.qwen/agents/)', + 'User Level (~/.qwen/agents/)': 'ユーザーレベル (~/.qwen/agents/)', + '✅ Subagent Created Successfully!': + '✅ サブエージェントの作成に成功しました!', + 'Subagent "{{name}}" has been saved to {{level}} level.': + 'サブエージェント "{{name}}" を {{level}} に保存しました', + 'Name: ': '名前: ', + 'Location: ': '場所: ', + '❌ Error saving subagent:': '❌ サブエージェント保存エラー:', + 'Warnings:': '警告:', + 'Step {{n}}: Choose Location': 'ステップ {{n}}: 場所を選択', + 'Step {{n}}: Choose Generation Method': 'ステップ {{n}}: 作成方法を選択', + 'Generate with Qwen Code (Recommended)': 'Qwen Code で生成(推奨)', + 'Manual Creation': '手動作成', + 'Generating subagent configuration...': 'サブエージェント設定を生成中...', + 'Failed to generate subagent: {{error}}': + 'サブエージェントの生成に失敗: {{error}}', + 'Step {{n}}: Describe Your Subagent': + 'ステップ {{n}}: サブエージェントを説明', + 'Step {{n}}: Enter Subagent Name': 'ステップ {{n}}: サブエージェント名を入力', + 'Step {{n}}: Enter System Prompt': 'ステップ {{n}}: システムプロンプトを入力', + 'Step {{n}}: Enter Description': 'ステップ {{n}}: 説明を入力', + 'Step {{n}}: Select Tools': 'ステップ {{n}}: ツールを選択', + 'All Tools (Default)': '全ツール(デフォルト)', + 'All Tools': '全ツール', + 'Read-only Tools': '読み取り専用ツール', + 'Read & Edit Tools': '読み取り&編集ツール', + 'Read & Edit & Execution Tools': '読み取り&編集&実行ツール', + 'Selected tools:': '選択されたツール:', + 'Step {{n}}: Choose Background Color': 'ステップ {{n}}: 背景色を選択', + 'Step {{n}}: Confirm and Save': 'ステップ {{n}}: 確認して保存', + 'Esc to cancel': 'Esc でキャンセル', + cancel: 'キャンセル', + 'go back': '戻る', + '↑↓ to navigate, ': '↑↓ で移動、', + 'Name cannot be empty.': '名前は空にできません', + 'System prompt cannot be empty.': 'システムプロンプトは空にできません', + 'Description cannot be empty.': '説明は空にできません', + 'Failed to launch editor: {{error}}': 'エディタの起動に失敗: {{error}}', + 'Failed to save and edit subagent: {{error}}': + 'サブエージェントの保存と編集に失敗: {{error}}', + 'Name "{{name}}" already exists at {{level}} level - will overwrite existing subagent': + '"{{name}}" は {{level}} に既に存在します - 既存のサブエージェントを上書きします', + 'Name "{{name}}" exists at user level - project level will take precedence': + '"{{name}}" はユーザーレベルに存在します - プロジェクトレベルが優先されます', + 'Name "{{name}}" exists at project level - existing subagent will take precedence': + '"{{name}}" はプロジェクトレベルに存在します - 既存のサブエージェントが優先されます', + 'Description is over {{length}} characters': + '説明が {{length}} 文字を超えています', + 'System prompt is over {{length}} characters': + 'システムプロンプトが {{length}} 文字を超えています', + 'Describe what this subagent should do and when it should be used. (Be comprehensive for best results)': + 'このサブエージェントの役割と使用タイミングを説明してください(詳細に記述するほど良い結果が得られます)', + 'e.g., Expert code reviewer that reviews code based on best practices...': + '例: ベストプラクティスに基づいてコードをレビューするエキスパートレビュアー...', + 'All tools selected, including MCP tools': + 'MCPツールを含むすべてのツールを選択', + 'Read-only tools:': '読み取り専用ツール:', + 'Edit tools:': '編集ツール:', + 'Execution tools:': '実行ツール:', + 'Press Enter to save, e to save and edit, Esc to go back': + 'Enter で保存、e で保存して編集、Esc で戻る', + 'Press Enter to continue, {{navigation}}Esc to {{action}}': + 'Enter で続行、{{navigation}}Esc で{{action}}', + 'Enter a clear, unique name for this subagent.': + 'このサブエージェントの明確で一意な名前を入力してください', + 'e.g., Code Reviewer': '例: コードレビュアー', + "Write the system prompt that defines this subagent's behavior. Be comprehensive for best results.": + 'このサブエージェントの動作を定義するシステムプロンプトを記述してください (詳細に書くほど良い結果が得られます)', + 'e.g., You are an expert code reviewer...': + '例: あなたはエキスパートコードレビュアーです...', + 'Describe when and how this subagent should be used.': + 'このサブエージェントをいつどのように使用するかを説明してください', + 'e.g., Reviews code for best practices and potential bugs.': + '例: ベストプラクティスと潜在的なバグについてコードをレビューします。', + // Commands - General (continued) + '(Use Enter to select{{tabText}})': '(Enter で選択{{tabText}})', + ', Tab to change focus': '、Tab でフォーカス変更', + 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': + '変更を確認するには Qwen Code を再起動する必要があります。 r を押して終了し、変更を適用してください', + 'The command "/{{command}}" is not supported in non-interactive mode.': + 'コマンド "/{{command}}" は非対話モードではサポートされていません', + 'View and edit Qwen Code settings': 'Qwen Code の設定を表示・編集', + Settings: '設定', + 'Vim Mode': 'Vim モード', + 'Disable Auto Update': '自動更新を無効化', + Language: '言語', + 'Output Format': '出力形式', + 'Hide Tips': 'ヒントを非表示', + 'Hide Banner': 'バナーを非表示', + 'Show Memory Usage': 'メモリ使用量を表示', + 'Show Line Numbers': '行番号を表示', + Text: 'テキスト', + JSON: 'JSON', + Plan: 'プラン', + Default: 'デフォルト', + 'Auto Edit': '自動編集', + YOLO: 'YOLO', + 'toggle vim mode on/off': 'Vim モードのオン/オフを切り替え', + 'exit the cli': 'CLIを終了', + Timeout: 'タイムアウト', + 'Max Retries': '最大リトライ回数', + 'Auto Accept': '自動承認', + 'Folder Trust': 'フォルダの信頼', + 'Enable Prompt Completion': 'プロンプト補完を有効化', + 'Debug Keystroke Logging': 'キーストロークのデバッグログ', + 'Hide Window Title': 'ウィンドウタイトルを非表示', + 'Show Status in Title': 'タイトルにステータスを表示', + 'Hide Context Summary': 'コンテキスト要約を非表示', + 'Hide CWD': '作業ディレクトリを非表示', + 'Hide Sandbox Status': 'サンドボックス状態を非表示', + 'Hide Model Info': 'モデル情報を非表示', + 'Hide Footer': 'フッターを非表示', + 'Show Citations': '引用を表示', + 'Custom Witty Phrases': 'カスタムウィットフレーズ', + 'Enable Welcome Back': 'ウェルカムバック機能を有効化', + 'Disable Loading Phrases': 'ローディングフレーズを無効化', + 'Screen Reader Mode': 'スクリーンリーダーモード', + 'IDE Mode': 'IDEモード', + 'Max Session Turns': '最大セッションターン数', + 'Skip Next Speaker Check': '次の発言者チェックをスキップ', + 'Skip Loop Detection': 'ループ検出をスキップ', + 'Skip Startup Context': '起動時コンテキストをスキップ', + 'Enable OpenAI Logging': 'OpenAI ログを有効化', + 'OpenAI Logging Directory': 'OpenAI ログディレクトリ', + 'Disable Cache Control': 'キャッシュ制御を無効化', + 'Memory Discovery Max Dirs': 'メモリ検出の最大ディレクトリ数', + 'Load Memory From Include Directories': + 'インクルードディレクトリからメモリを読み込み', + 'Respect .gitignore': '.gitignore を優先', + 'Respect .qwenignore': '.qwenignore を優先', + 'Enable Recursive File Search': '再帰的ファイル検索を有効化', + 'Disable Fuzzy Search': 'ファジー検索を無効化', + 'Enable Interactive Shell': '対話型シェルを有効化', + 'Show Color': '色を表示', + 'Use Ripgrep': 'Ripgrep を使用', + 'Use Builtin Ripgrep': '組み込み Ripgrep を使用', + 'Enable Tool Output Truncation': 'ツール出力の切り詰めを有効化', + 'Tool Output Truncation Threshold': 'ツール出力切り詰めのしきい値', + 'Tool Output Truncation Lines': 'ツール出力の切り詰め行数', + 'Vision Model Preview': 'ビジョンモデルプレビュー', + 'Tool Schema Compliance': 'ツールスキーマ準拠', + 'Auto (detect from system)': '自動(システムから検出)', + 'check session stats. Usage: /stats [model|tools]': + 'セッション統計を確認。使い方: /stats [model|tools]', + 'Show model-specific usage statistics.': 'モデル別の使用統計を表示', + 'Show tool-specific usage statistics.': 'ツール別の使用統計を表示', + 'list configured MCP servers and tools, or authenticate with OAuth-enabled servers': + '設定済みのMCPサーバーとツールを一覧表示、またはOAuth対応サーバーで認証', + 'Manage workspace directories': 'ワークスペースディレクトリを管理', + 'Add directories to the workspace. Use comma to separate multiple paths': + 'ワークスペースにディレクトリを追加。複数パスはカンマで区切ってください', + 'Show all directories in the workspace': + 'ワークスペース内のすべてのディレクトリを表示', + 'set external editor preference': '外部エディタの設定', + 'Manage extensions': '拡張機能を管理', + 'List active extensions': '有効な拡張機能を一覧表示', + 'Update extensions. Usage: update |--all': + '拡張機能を更新。使い方: update <拡張機能名>|--all', + 'manage IDE integration': 'IDE連携を管理', + 'check status of IDE integration': 'IDE連携の状態を確認', + 'install required IDE companion for {{ideName}}': + '{{ideName}} 用の必要なIDEコンパニオンをインストール', + 'enable IDE integration': 'IDE連携を有効化', + 'disable IDE integration': 'IDE連携を無効化', + 'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.': + '現在の環境ではIDE連携はサポートされていません。この機能を使用するには、VS Code または VS Code 派生エディタで Qwen Code を実行してください', + 'Set up GitHub Actions': 'GitHub Actions を設定', + 'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)': + '複数行入力用のターミナルキーバインドを設定(VS Code、Cursor、Windsurf、Trae)', + 'Please restart your terminal for the changes to take effect.': + '変更を有効にするにはターミナルを再起動してください', + 'Failed to configure terminal: {{error}}': + 'ターミナルの設定に失敗: {{error}}', + 'Could not determine {{terminalName}} config path on Windows: APPDATA environment variable is not set.': + 'Windows で {{terminalName}} の設定パスを特定できません: APPDATA 環境変数が設定されていません', + '{{terminalName}} keybindings.json exists but is not a valid JSON array. Please fix the file manually or delete it to allow automatic configuration.': + '{{terminalName}} の keybindings.json は存在しますが、有効なJSON配列ではありません。ファイルを手動で修正するか、削除して自動設定を許可してください', + 'File: {{file}}': 'ファイル: {{file}}', + 'Failed to parse {{terminalName}} keybindings.json. The file contains invalid JSON. Please fix the file manually or delete it to allow automatic configuration.': + '{{terminalName}} の keybindings.json の解析に失敗しました。ファイルに無効なJSONが含まれています。手動で修正するか、削除して自動設定を許可してください', + 'Error: {{error}}': 'エラー: {{error}}', + 'Shift+Enter binding already exists': 'Shift+Enter バインドは既に存在します', + 'Ctrl+Enter binding already exists': 'Ctrl+Enter バインドは既に存在します', + 'Existing keybindings detected. Will not modify to avoid conflicts.': + '既存のキーバインドが検出されました。競合を避けるため変更をしません', + 'Please check and modify manually if needed: {{file}}': + '必要に応じて手動で確認・変更してください: {{file}}', + 'Added Shift+Enter and Ctrl+Enter keybindings to {{terminalName}}.': + '{{terminalName}} に Shift+Enter と Ctrl+Enter のキーバインドを追加しました', + 'Modified: {{file}}': '変更済み: {{file}}', + '{{terminalName}} keybindings already configured.': + '{{terminalName}} のキーバインドは既に設定されています', + 'Failed to configure {{terminalName}}.': + '{{terminalName}} の設定に失敗しました', + 'Your terminal is already configured for an optimal experience with multiline input (Shift+Enter and Ctrl+Enter).': + 'ターミナルは複数行入力(Shift+Enter と Ctrl+Enter)に最適化されています', + 'Could not detect terminal type. Supported terminals: VS Code, Cursor, Windsurf, and Trae.': + 'ターミナルの種類を検出できませんでした。サポートされているターミナル: VS Code、Cursor、Windsurf、Trae', + 'Terminal "{{terminal}}" is not supported yet.': + 'ターミナル "{{terminal}}" はまだサポートされていません', + // Commands - Language + 'Invalid language. Available: {{options}}': + '無効な言語です。使用可能: {{options}}', + 'Language subcommands do not accept additional arguments.': + '言語サブコマンドは追加の引数を受け付けません', + 'Current UI language: {{lang}}': '現在のUI言語: {{lang}}', + 'Current LLM output language: {{lang}}': '現在のLLM出力言語: {{lang}}', + 'LLM output language not set': 'LLM出力言語が設定されていません', + 'Set UI language': 'UI言語を設定', + 'Set LLM output language': 'LLM出力言語を設定', + 'Usage: /language ui [{{options}}]': '使い方: /language ui [{{options}}]', + 'Usage: /language output ': '使い方: /language output <言語>', + 'Example: /language output 中文': '例: /language output 中文', + 'Example: /language output English': '例: /language output English', + 'Example: /language output 日本語': '例: /language output 日本語', + 'Example: /language output Português': '例: /language output Português', + 'UI language changed to {{lang}}': 'UI言語を {{lang}} に変更しました', + 'LLM output language rule file generated at {{path}}': + 'LLM出力言語ルールファイルを {{path}} に生成しました', + 'Please restart the application for the changes to take effect.': + '変更を有効にするにはアプリケーションを再起動してください', + 'Failed to generate LLM output language rule file: {{error}}': + 'LLM出力言語ルールファイルの生成に失敗: {{error}}', + 'Invalid command. Available subcommands:': + '無効なコマンドです。使用可能なサブコマンド:', + 'Available subcommands:': '使用可能なサブコマンド:', + 'To request additional UI language packs, please open an issue on GitHub.': + '追加のUI言語パックをリクエストするには、GitHub で Issue を作成してください', + 'Available options:': '使用可能なオプション:', + 'Set UI language to {{name}}': 'UI言語を {{name}} に設定', + // Approval Mode + 'Approval Mode': '承認モード', + 'Current approval mode: {{mode}}': '現在の承認モード: {{mode}}', + 'Available approval modes:': '利用可能な承認モード:', + 'Approval mode changed to: {{mode}}': '承認モードを変更しました: {{mode}}', + 'Approval mode changed to: {{mode}} (saved to {{scope}} settings{{location}})': + '承認モードを {{mode}} に変更しました({{scope}} 設定{{location}}に保存)', + 'Usage: /approval-mode [--session|--user|--project]': + '使い方: /approval-mode <モード> [--session|--user|--project]', + 'Scope subcommands do not accept additional arguments.': + 'スコープサブコマンドは追加の引数を受け付けません', + 'Plan mode - Analyze only, do not modify files or execute commands': + 'プランモード - 分析のみ、ファイルの変更やコマンドの実行はしません', + 'Default mode - Require approval for file edits or shell commands': + 'デフォルトモード - ファイル編集やシェルコマンドには承認が必要', + 'Auto-edit mode - Automatically approve file edits': + '自動編集モード - ファイル編集を自動承認', + 'YOLO mode - Automatically approve all tools': + 'YOLOモード - すべてのツールを自動承認', + '{{mode}} mode': '{{mode}}モード', + 'Settings service is not available; unable to persist the approval mode.': + '設定サービスが利用できません。承認モードを保存できません', + 'Failed to save approval mode: {{error}}': + '承認モードの保存に失敗: {{error}}', + 'Failed to change approval mode: {{error}}': + '承認モードの変更に失敗: {{error}}', + 'Apply to current session only (temporary)': + '現在のセッションのみに適用(一時的)', + 'Persist for this project/workspace': 'このプロジェクト/ワークスペースに保存', + 'Persist for this user on this machine': 'このマシンのこのユーザーに保存', + 'Analyze only, do not modify files or execute commands': + '分析のみ、ファイルの変更やコマンドの実行はしません', + 'Require approval for file edits or shell commands': + 'ファイル編集やシェルコマンドには承認が必要', + 'Automatically approve file edits': 'ファイル編集を自動承認', + 'Automatically approve all tools': 'すべてのツールを自動承認', + 'Workspace approval mode exists and takes priority. User-level change will have no effect.': + 'ワークスペースの承認モードが存在し、優先されます。ユーザーレベルの変更は効果がありません', + '(Use Enter to select, Tab to change focus)': + '(Enter で選択、Tab でフォーカス変更)', + 'Apply To': '適用先', + 'User Settings': 'ユーザー設定', + 'Workspace Settings': 'ワークスペース設定', + // Memory + 'Commands for interacting with memory.': 'メモリ操作のコマンド', + 'Show the current memory contents.': '現在のメモリ内容を表示', + 'Show project-level memory contents.': 'プロジェクトレベルのメモリ内容を表示', + 'Show global memory contents.': 'グローバルメモリ内容を表示', + 'Add content to project-level memory.': + 'プロジェクトレベルのメモリにコンテンツを追加', + 'Add content to global memory.': 'グローバルメモリにコンテンツを追加', + 'Refresh the memory from the source.': 'ソースからメモリを更新', + 'Usage: /memory add --project ': + '使い方: /memory add --project <記憶するテキスト>', + 'Usage: /memory add --global ': + '使い方: /memory add --global <記憶するテキスト>', + 'Attempting to save to project memory: "{{text}}"': + 'プロジェクトメモリへの保存を試行中: "{{text}}"', + 'Attempting to save to global memory: "{{text}}"': + 'グローバルメモリへの保存を試行中: "{{text}}"', + 'Current memory content from {{count}} file(s):': + '{{count}} 個のファイルからの現在のメモリ内容:', + 'Memory is currently empty.': 'メモリは現在空です', + 'Project memory file not found or is currently empty.': + 'プロジェクトメモリファイルが見つからないか、現在空です', + 'Global memory file not found or is currently empty.': + 'グローバルメモリファイルが見つからないか、現在空です', + 'Global memory is currently empty.': 'グローバルメモリは現在空です', + 'Global memory content:\n\n---\n{{content}}\n---': + 'グローバルメモリ内容:\n\n---\n{{content}}\n---', + 'Project memory content from {{path}}:\n\n---\n{{content}}\n---': + '{{path}} からのプロジェクトメモリ内容:\n\n---\n{{content}}\n---', + 'Project memory is currently empty.': 'プロジェクトメモリは現在空です', + 'Refreshing memory from source files...': + 'ソースファイルからメモリを更新中...', + 'Add content to the memory. Use --global for global memory or --project for project memory.': + 'メモリにコンテンツを追加。グローバルメモリには --global、プロジェクトメモリには --project を使用', + 'Usage: /memory add [--global|--project] ': + '使い方: /memory add [--global|--project] <記憶するテキスト>', + 'Attempting to save to memory {{scope}}: "{{fact}}"': + 'メモリ {{scope}} への保存を試行中: "{{fact}}"', + // MCP + 'Authenticate with an OAuth-enabled MCP server': + 'OAuth対応のMCPサーバーで認証', + 'List configured MCP servers and tools': + '設定済みのMCPサーバーとツールを一覧表示', + 'No MCP servers configured.': 'MCPサーバーが設定されていません', + 'Restarts MCP servers.': 'MCPサーバーを再起動します', + 'Config not loaded.': '設定が読み込まれていません', + 'Could not retrieve tool registry.': 'ツールレジストリを取得できませんでした', + 'No MCP servers configured with OAuth authentication.': + 'OAuth認証が設定されたMCPサーバーはありません', + 'MCP servers with OAuth authentication:': 'OAuth認証のMCPサーバー:', + 'Use /mcp auth to authenticate.': + '認証するには /mcp auth <サーバー名> を使用', + "MCP server '{{name}}' not found.": "MCPサーバー '{{name}}' が見つかりません", + "Successfully authenticated and refreshed tools for '{{name}}'.": + "'{{name}}' の認証とツール更新に成功しました", + "Failed to authenticate with MCP server '{{name}}': {{error}}": + "MCPサーバー '{{name}}' での認証に失敗: {{error}}", + "Re-discovering tools from '{{name}}'...": + "'{{name}}' からツールを再検出中...", + 'Configured MCP servers:': '設定済みMCPサーバー:', + Ready: '準備完了', + Disconnected: '切断', + '{{count}} tool': '{{count}} ツール', + '{{count}} tools': '{{count}} ツール', + 'Restarting MCP servers...': 'MCPサーバーを再起動中...', + // Chat + 'Manage conversation history.': '会話履歴を管理します', + 'List saved conversation checkpoints': + '保存された会話チェックポイントを一覧表示', + 'No saved conversation checkpoints found.': + '保存された会話チェックポイントが見つかりません', + 'List of saved conversations:': '保存された会話の一覧:', + 'Note: Newest last, oldest first': + '注: 最新のものが下にあり、過去のものが上にあります', + 'Save the current conversation as a checkpoint. Usage: /chat save ': + '現在の会話をチェックポイントとして保存。使い方: /chat save <タグ>', + 'Missing tag. Usage: /chat save ': + 'タグが不足しています。使い方: /chat save <タグ>', + 'Delete a conversation checkpoint. Usage: /chat delete ': + '会話チェックポイントを削除。使い方: /chat delete <タグ>', + 'Missing tag. Usage: /chat delete ': + 'タグが不足しています。使い方: /chat delete <タグ>', + "Conversation checkpoint '{{tag}}' has been deleted.": + "会話チェックポイント '{{tag}}' を削除しました", + "Error: No checkpoint found with tag '{{tag}}'.": + "エラー: タグ '{{tag}}' のチェックポイントが見つかりません", + 'Resume a conversation from a checkpoint. Usage: /chat resume ': + 'チェックポイントから会話を再開。使い方: /chat resume <タグ>', + 'Missing tag. Usage: /chat resume ': + 'タグが不足しています。使い方: /chat resume <タグ>', + 'No saved checkpoint found with tag: {{tag}}.': + 'タグ {{tag}} のチェックポイントが見つかりません', + 'A checkpoint with the tag {{tag}} already exists. Do you want to overwrite it?': + 'タグ {{tag}} のチェックポイントは既に存在します。上書きしますか?', + 'No chat client available to save conversation.': + '会話を保存するためのチャットクライアントがありません', + 'Conversation checkpoint saved with tag: {{tag}}.': + 'タグ {{tag}} で会話チェックポイントを保存しました', + 'No conversation found to save.': '保存する会話が見つかりません', + 'No chat client available to share conversation.': + '会話を共有するためのチャットクライアントがありません', + 'Invalid file format. Only .md and .json are supported.': + '無効なファイル形式です。.md と .json のみサポートされています', + 'Error sharing conversation: {{error}}': '会話の共有中にエラー: {{error}}', + 'Conversation shared to {{filePath}}': '会話を {{filePath}} に共有しました', + 'No conversation found to share.': '共有する会話が見つかりません', + 'Share the current conversation to a markdown or json file. Usage: /chat share ': + '現在の会話をmarkdownまたはjsonファイルに共有。使い方: /chat share <ファイル>', + // Summary + 'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md': + 'プロジェクトサマリーを生成し、.qwen/PROJECT_SUMMARY.md に保存', + 'No chat client available to generate summary.': + 'サマリーを生成するためのチャットクライアントがありません', + 'Already generating summary, wait for previous request to complete': + 'サマリー生成中です。前のリクエストの完了をお待ちください', + 'No conversation found to summarize.': '要約する会話が見つかりません', + 'Failed to generate project context summary: {{error}}': + 'プロジェクトコンテキストサマリーの生成に失敗: {{error}}', + 'Saved project summary to {{filePathForDisplay}}.': + 'プロジェクトサマリーを {{filePathForDisplay}} に保存しました', + 'Saving project summary...': 'プロジェクトサマリーを保存中...', + 'Generating project summary...': 'プロジェクトサマリーを生成中...', + 'Failed to generate summary - no text content received from LLM response': + 'サマリーの生成に失敗 - LLMレスポンスからテキストコンテンツを受信できませんでした', + // Model + 'Switch the model for this session': 'このセッションのモデルを切り替え', + 'Content generator configuration not available.': + 'コンテンツジェネレーター設定が利用できません', + 'Authentication type not available.': '認証タイプが利用できません', + 'No models available for the current authentication type ({{authType}}).': + '現在の認証タイプ({{authType}})で利用可能なモデルはありません', + // Clear + 'Starting a new session, resetting chat, and clearing terminal.': + '新しいセッションを開始し、チャットをリセットし、ターミナルをクリアしています', + 'Starting a new session and clearing.': + '新しいセッションを開始してクリアしています', + // Compress + 'Already compressing, wait for previous request to complete': + '圧縮中です。前のリクエストの完了をお待ちください', + 'Failed to compress chat history.': 'チャット履歴の圧縮に失敗しました', + 'Failed to compress chat history: {{error}}': + 'チャット履歴の圧縮に失敗: {{error}}', + 'Compressing chat history': 'チャット履歴を圧縮中', + 'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.': + 'チャット履歴を {{originalTokens}} トークンから {{newTokens}} トークンに圧縮しました', + 'Compression was not beneficial for this history size.': + 'この履歴サイズには圧縮の効果がありませんでした', + 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.': + 'チャット履歴の圧縮でサイズが減少しませんでした。圧縮プロンプトに問題がある可能性があります', + 'Could not compress chat history due to a token counting error.': + 'トークンカウントエラーのため、チャット履歴を圧縮できませんでした', + 'Chat history is already compressed.': 'チャット履歴は既に圧縮されています', + // Directory + 'Configuration is not available.': '設定が利用できません', + 'Please provide at least one path to add.': + '追加するパスを少なくとも1つ指定してください', + 'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.': + '制限的なサンドボックスプロファイルでは /directory add コマンドはサポートされていません。代わりにセッション開始時に --include-directories を使用してください', + "Error adding '{{path}}': {{error}}": + "'{{path}}' の追加中にエラー: {{error}}", + 'Successfully added QWEN.md files from the following directories if there are:\n- {{directories}}': + '以下のディレクトリから QWEN.md ファイルを追加しました(存在する場合):\n- {{directories}}', + 'Error refreshing memory: {{error}}': 'メモリの更新中にエラー: {{error}}', + 'Successfully added directories:\n- {{directories}}': + 'ディレクトリを正常に追加しました:\n- {{directories}}', + 'Current workspace directories:\n{{directories}}': + '現在のワークスペースディレクトリ:\n{{directories}}', + // Docs + 'Please open the following URL in your browser to view the documentation:\n{{url}}': + 'ドキュメントを表示するには、ブラウザで以下のURLを開いてください:\n{{url}}', + 'Opening documentation in your browser: {{url}}': + ' ブラウザでドキュメントを開きました: {{url}}', + // Dialogs - Tool Confirmation + 'Do you want to proceed?': '続行しますか?', + 'Yes, allow once': 'はい(今回のみ許可)', + 'Allow always': '常に許可する', + No: 'いいえ', + 'No (esc)': 'いいえ (Esc)', + 'Yes, allow always for this session': 'はい、このセッションで常に許可', + 'Modify in progress:': '変更中:', + 'Save and close external editor to continue': + '続行するには外部エディタを保存して閉じてください', + 'Apply this change?': 'この変更を適用しますか?', + 'Yes, allow always': 'はい、常に許可', + 'Modify with external editor': '外部エディタで編集', + 'No, suggest changes (esc)': 'いいえ、変更を提案 (Esc)', + "Allow execution of: '{{command}}'?": "'{{command}}' の実行を許可しますか?", + 'Yes, allow always ...': 'はい、常に許可...', + 'Yes, and auto-accept edits': 'はい、編集を自動承認', + 'Yes, and manually approve edits': 'はい、編集を手動承認', + 'No, keep planning (esc)': 'いいえ、計画を続ける (Esc)', + 'URLs to fetch:': '取得するURL:', + 'MCP Server: {{server}}': 'MCPサーバー: {{server}}', + 'Tool: {{tool}}': 'ツール: {{tool}}', + 'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?': + 'サーバー "{{server}}" からの MCPツール "{{tool}}" の実行を許可しますか?', + 'Yes, always allow tool "{{tool}}" from server "{{server}}"': + 'はい、サーバー "{{server}}" からのツール "{{tool}}" を常に許可', + 'Yes, always allow all tools from server "{{server}}"': + 'はい、サーバー "{{server}}" からのすべてのツールを常に許可', + // Dialogs - Shell Confirmation + 'Shell Command Execution': 'シェルコマンド実行', + 'A custom command wants to run the following shell commands:': + 'カスタムコマンドが以下のシェルコマンドを実行しようとしています:', + // Dialogs - Pro Quota + 'Pro quota limit reached for {{model}}.': + '{{model}} のProクォータ上限に達しました', + 'Change auth (executes the /auth command)': + '認証を変更(/auth コマンドを実行)', + 'Continue with {{model}}': '{{model}} で続行', + // Dialogs - Welcome Back + 'Current Plan:': '現在のプラン:', + 'Progress: {{done}}/{{total}} tasks completed': + '進捗: {{done}}/{{total}} タスク完了', + ', {{inProgress}} in progress': '、{{inProgress}} 進行中', + 'Pending Tasks:': '保留中のタスク:', + 'What would you like to do?': '何をしますか?', + 'Choose how to proceed with your session:': + 'セッションの続行方法を選択してください:', + 'Start new chat session': '新しいチャットセッションを開始', + 'Continue previous conversation': '前回の会話を続行', + '👋 Welcome back! (Last updated: {{timeAgo}})': + '👋 おかえりなさい!(最終更新: {{timeAgo}})', + '🎯 Overall Goal:': '🎯 全体目標:', + // Dialogs - Auth + 'Get started': '始める', + 'How would you like to authenticate for this project?': + 'このプロジェクトの認証方法を選択してください:', + 'OpenAI API key is required to use OpenAI authentication.': + 'OpenAI認証を使用するには OpenAI APIキーが必要です', + 'You must select an auth method to proceed. Press Ctrl+C again to exit.': + '続行するには認証方法を選択してください。Ctrl+C をもう一度押すと終了します', + '(Use Enter to Set Auth)': '(Enter で認証を設定)', + 'Terms of Services and Privacy Notice for Qwen Code': + 'Qwen Code の利用規約とプライバシー通知', + 'Qwen OAuth': 'Qwen OAuth', + OpenAI: 'OpenAI', + 'Failed to login. Message: {{message}}': + 'ログインに失敗しました。メッセージ: {{message}}', + 'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.': + '認証は {{enforcedType}} に強制されていますが、現在 {{currentType}} を使用しています', + 'Qwen OAuth authentication timed out. Please try again.': + 'Qwen OAuth認証がタイムアウトしました。再度お試しください', + 'Qwen OAuth authentication cancelled.': + 'Qwen OAuth認証がキャンセルされました', + 'Qwen OAuth Authentication': 'Qwen OAuth認証', + 'Please visit this URL to authorize:': + '認証するには以下のURLにアクセスしてください:', + 'Or scan the QR code below:': 'または以下のQRコードをスキャン:', + 'Waiting for authorization': '認証を待っています', + 'Time remaining:': '残り時間:', + '(Press ESC or CTRL+C to cancel)': '(ESC または CTRL+C でキャンセル)', + 'Qwen OAuth Authentication Timeout': 'Qwen OAuth認証タイムアウト', + 'OAuth token expired (over {{seconds}} seconds). Please select authentication method again.': + 'OAuthトークンが期限切れです({{seconds}}秒以上)。認証方法を再度選択してください', + 'Press any key to return to authentication type selection.': + '認証タイプ選択に戻るには任意のキーを押してください', + 'Waiting for Qwen OAuth authentication...': 'Qwen OAuth認証を待っています...', + 'Note: Your existing API key in settings.json will not be cleared when using Qwen OAuth. You can switch back to OpenAI authentication later if needed.': + '注: Qwen OAuthを使用しても、settings.json内の既存のAPIキーはクリアされません。必要に応じて後でOpenAI認証に切り替えることができます', + 'Authentication timed out. Please try again.': + '認証がタイムアウトしました。再度お試しください', + 'Waiting for auth... (Press ESC or CTRL+C to cancel)': + '認証を待っています... (ESC または CTRL+C でキャンセル)', + 'Failed to authenticate. Message: {{message}}': + '認証に失敗しました。メッセージ: {{message}}', + 'Authenticated successfully with {{authType}} credentials.': + '{{authType}} 認証情報で正常に認証されました', + 'Invalid QWEN_DEFAULT_AUTH_TYPE value: "{{value}}". Valid values are: {{validValues}}': + '無効な QWEN_DEFAULT_AUTH_TYPE 値: "{{value}}"。有効な値: {{validValues}}', + 'OpenAI Configuration Required': 'OpenAI設定が必要です', + 'Please enter your OpenAI configuration. You can get an API key from': + 'OpenAI設定を入力してください。APIキーは以下から取得できます', + 'API Key:': 'APIキー:', + 'Invalid credentials: {{errorMessage}}': '無効な認証情報: {{errorMessage}}', + 'Failed to validate credentials': '認証情報の検証に失敗しました', + 'Press Enter to continue, Tab/↑↓ to navigate, Esc to cancel': + 'Enter で続行、Tab/↑↓ で移動、Esc でキャンセル', + // Dialogs - Model + 'Select Model': 'モデルを選択', + '(Press Esc to close)': '(Esc で閉じる)', + 'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)': + 'Alibaba Cloud ModelStudioの最新Qwen Coderモデル(バージョン: qwen3-coder-plus-2025-09-23)', + 'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)': + 'Alibaba Cloud ModelStudioの最新Qwen Visionモデル(バージョン: qwen3-vl-plus-2025-09-23)', + // Dialogs - Permissions + 'Manage folder trust settings': 'フォルダ信頼設定を管理', + // Status Bar + 'Using:': '使用中:', + '{{count}} open file': '{{count}} 個のファイルを開いています', + '{{count}} open files': '{{count}} 個のファイルを開いています', + '(ctrl+g to view)': '(Ctrl+G で表示)', + '{{count}} {{name}} file': '{{count}} {{name}} ファイル', + '{{count}} {{name}} files': '{{count}} {{name}} ファイル', + '{{count}} MCP server': '{{count}} MCPサーバー', + '{{count}} MCP servers': '{{count}} MCPサーバー', + '{{count}} Blocked': '{{count}} ブロック', + '(ctrl+t to view)': '(Ctrl+T で表示)', + '(ctrl+t to toggle)': '(Ctrl+T で切り替え)', + 'Press Ctrl+C again to exit.': 'Ctrl+C をもう一度押すと終了します', + 'Press Ctrl+D again to exit.': 'Ctrl+D をもう一度押すと終了します', + 'Press Esc again to clear.': 'Esc をもう一度押すとクリアします', + // MCP Status + 'Please view MCP documentation in your browser:': + 'ブラウザでMCPドキュメントを確認してください:', + 'or use the cli /docs command': 'または CLI の /docs コマンドを使用', + '⏳ MCP servers are starting up ({{count}} initializing)...': + '⏳ MCPサーバーを起動中({{count}} 初期化中)...', + 'Note: First startup may take longer. Tool availability will update automatically.': + '注: 初回起動には時間がかかる場合があります。ツールの利用可能状況は自動的に更新されます', + 'Starting... (first startup may take longer)': + '起動中...(初回起動には時間がかかる場合があります)', + '{{count}} prompt': '{{count}} プロンプト', + '{{count}} prompts': '{{count}} プロンプト', + '(from {{extensionName}})': '({{extensionName}} から)', + OAuth: 'OAuth', + 'OAuth expired': 'OAuth 期限切れ', + 'OAuth not authenticated': 'OAuth 未認証', + 'tools and prompts will appear when ready': + 'ツールとプロンプトは準備完了後に表示されます', + '{{count}} tools cached': '{{count}} ツール(キャッシュ済み)', + 'Tools:': 'ツール:', + 'Parameters:': 'パラメータ:', + 'Prompts:': 'プロンプト:', + Blocked: 'ブロック', + '💡 Tips:': '💡 ヒント:', + Use: '使用', + 'to show server and tool descriptions': 'サーバーとツールの説明を表示', + 'to show tool parameter schemas': 'ツールパラメータスキーマを表示', + 'to hide descriptions': '説明を非表示', + 'to authenticate with OAuth-enabled servers': 'OAuth対応サーバーで認証', + Press: '押す', + 'to toggle tool descriptions on/off': 'ツール説明の表示/非表示を切り替え', + "Starting OAuth authentication for MCP server '{{name}}'...": + "MCPサーバー '{{name}}' のOAuth認証を開始中...", + // Startup Tips + 'Tips for getting started:': '始めるためのヒント:', + '1. Ask questions, edit files, or run commands.': + '1. 質問したり、ファイルを編集したり、コマンドを実行したりできます', + '2. Be specific for the best results.': + '2. 具体的に指示すると最良の結果が得られます', + 'files to customize your interactions with Qwen Code.': + 'Qwen Code との対話をカスタマイズするためのファイル', + 'for more information.': '詳細情報を確認できます', + // Exit Screen / Stats + 'Agent powering down. Goodbye!': 'エージェントを終了します。さようなら!', + 'To continue this session, run': 'このセッションを続行するには、次を実行:', + 'Interaction Summary': 'インタラクション概要', + 'Session ID:': 'セッションID:', + 'Tool Calls:': 'ツール呼び出し:', + 'Success Rate:': '成功率:', + 'User Agreement:': 'ユーザー同意:', + reviewed: 'レビュー済み', + 'Code Changes:': 'コード変更:', + Performance: 'パフォーマンス', + 'Wall Time:': '経過時間:', + 'Agent Active:': 'エージェント稼働時間:', + 'API Time:': 'API時間:', + 'Tool Time:': 'ツール時間:', + 'Session Stats': 'セッション統計', + 'Model Usage': 'モデル使用量', + Reqs: 'リクエスト', + 'Input Tokens': '入力トークン', + 'Output Tokens': '出力トークン', + 'Savings Highlight:': '節約ハイライト:', + 'of input tokens were served from the cache, reducing costs.': + '入力トークンがキャッシュから提供され、コストを削減しました', + 'Tip: For a full token breakdown, run `/stats model`.': + 'ヒント: トークンの詳細な内訳は `/stats model` を実行してください', + 'Model Stats For Nerds': 'マニア向けモデル統計', + 'Tool Stats For Nerds': 'マニア向けツール統計', + Metric: 'メトリック', + API: 'API', + Requests: 'リクエスト', + Errors: 'エラー', + 'Avg Latency': '平均レイテンシ', + Tokens: 'トークン', + Total: '合計', + Prompt: 'プロンプト', + Cached: 'キャッシュ', + Thoughts: '思考', + Tool: 'ツール', + Output: '出力', + 'No API calls have been made in this session.': + 'このセッションではAPI呼び出しが行われていません', + 'Tool Name': 'ツール名', + Calls: '呼び出し', + 'Success Rate': '成功率', + 'Avg Duration': '平均時間', + 'User Decision Summary': 'ユーザー決定サマリー', + 'Total Reviewed Suggestions:': '総レビュー提案数:', + ' » Accepted:': ' » 承認:', + ' » Rejected:': ' » 却下:', + ' » Modified:': ' » 変更:', + ' Overall Agreement Rate:': ' 全体承認率:', + 'No tool calls have been made in this session.': + 'このセッションではツール呼び出しが行われていません', + 'Session start time is unavailable, cannot calculate stats.': + 'セッション開始時刻が利用できないため、統計を計算できません', + // Loading + 'Waiting for user confirmation...': 'ユーザーの確認を待っています...', + '(esc to cancel, {{time}})': '(Esc でキャンセル、{{time}})', + // Witty Loading Phrases + WITTY_LOADING_PHRASES: [ + '運任せで検索中...', + '中の人がタイピング中...', + 'ロジックを最適化中...', + '電子の数を確認中...', + '宇宙のバグをチェック中...', + '大量の0と1をコンパイル中...', + 'HDDと思い出をデフラグ中...', + 'ビットをこっそり入れ替え中...', + 'ニューロンの接続を再構築中...', + 'どこかに行ったセミコロンを捜索中...', + 'フラックスキャパシタを調整中...', + 'フォースと交感中...', + 'アルゴリズムをチューニング中...', + '白いウサギを追跡中...', + 'カセットフーフー中...', + 'ローディングメッセージを考え中...', + 'ほぼ完了...多分...', + '最新のミームについて調査中...', + 'この表示を改善するアイデアを思索中...', + 'この問題を考え中...', + 'それはバグでなく誰も知らない新機能だよ', + 'ダイヤルアップ接続音が終わるのを待機中...', + 'コードに油を追加中...', + + // かなり意訳が入ってるもの + 'イヤホンをほどき中...', + 'カフェインをコードに変換中...', + '天動説を地動説に書き換え中...', + 'プールで時計の完成を待機中...', + '笑撃的な回答を用意中...', + '適切なミームを記述中...', + 'Aボタンを押して次へ...', + 'コードにリックロールを仕込み中...', + 'プログラマーが貧乏なのはキャッシュを使いすぎるから...', + 'プログラマーがダークモードなのはバグを見たくないから...', + 'コードが壊れた?叩けば治るさ', + 'USBの差し込みに挑戦中...', + ], +}; diff --git a/packages/cli/src/i18n/locales/pt.js b/packages/cli/src/i18n/locales/pt.js new file mode 100644 index 000000000..40410ce61 --- /dev/null +++ b/packages/cli/src/i18n/locales/pt.js @@ -0,0 +1,1390 @@ +/** + * @license + * Copyright 2025 Qwen + * SPDX-License-Identifier: Apache-2.0 + */ + +// Portuguese translations for Qwen Code CLI (pt-BR) + +export default { + // ============================================================================ + // Help / UI Components + // ============================================================================ + 'Basics:': 'Noções básicas:', + 'Add context': 'Adicionar contexto', + 'Use {{symbol}} to specify files for context (e.g., {{example}}) to target specific files or folders.': + 'Use {{symbol}} para especificar arquivos para o contexto (ex: {{example}}) para atingir arquivos ou pastas específicos.', + '@': '@', + '@src/myFile.ts': '@src/myFile.ts', + 'Shell mode': 'Modo shell', + 'YOLO mode': 'Modo YOLO', + 'plan mode': 'modo planejamento', + 'auto-accept edits': 'aceitar edições automaticamente', + 'Accepting edits': 'Aceitando edições', + '(shift + tab to cycle)': '(shift + tab para alternar)', + 'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).': + 'Execute comandos shell via {{symbol}} (ex: {{example1}}) ou use linguagem natural (ex: {{example2}}).', + '!': '!', + '!npm run start': '!npm run start', + 'start server': 'iniciar servidor', + 'Commands:': 'Comandos:', + 'shell command': 'comando shell', + 'Model Context Protocol command (from external servers)': + 'Comando Model Context Protocol (de servidores externos)', + 'Keyboard Shortcuts:': 'Atalhos de teclado:', + 'Toggle this help display': 'Alternar exibição desta ajuda', + 'Toggle shell mode': 'Alternar modo shell', + 'Open command menu': 'Abrir menu de comandos', + 'Add file context': 'Adicionar contexto de arquivo', + 'Accept suggestion / Autocomplete': 'Aceitar sugestão / Autocompletar', + 'Reverse search history': 'Pesquisa reversa no histórico', + 'Press ? again to close': 'Pressione ? novamente para fechar', + // Keyboard shortcuts panel descriptions + 'for shell mode': 'para modo shell', + 'for commands': 'para comandos', + 'for file paths': 'para caminhos de arquivo', + 'to clear input': 'para limpar entrada', + 'to cycle approvals': 'para alternar aprovações', + 'to quit': 'para sair', + 'for newline': 'para nova linha', + 'to clear screen': 'para limpar a tela', + 'to search history': 'para pesquisar no histórico', + 'to paste images': 'para colar imagens', + 'for external editor': 'para editor externo', + 'Jump through words in the input': 'Pular palavras na entrada', + 'Close dialogs, cancel requests, or quit application': + 'Fechar diálogos, cancelar solicitações ou sair do aplicativo', + 'New line': 'Nova linha', + 'New line (Alt+Enter works for certain linux distros)': + 'Nova linha (Alt+Enter funciona em certas distros linux)', + 'Clear the screen': 'Limpar a tela', + 'Open input in external editor': 'Abrir entrada no editor externo', + 'Send message': 'Enviar mensagem', + 'Initializing...': 'Inicializando...', + 'Connecting to MCP servers... ({{connected}}/{{total}})': + 'Conectando aos servidores MCP... ({{connected}}/{{total}})', + 'Type your message or @path/to/file': + 'Digite sua mensagem ou @caminho/do/arquivo', + '? for shortcuts': '? para atalhos', + "Press 'i' for INSERT mode and 'Esc' for NORMAL mode.": + "Pressione 'i' para modo INSERÇÃO e 'Esc' para modo NORMAL.", + 'Cancel operation / Clear input (double press)': + 'Cancelar operação / Limpar entrada (pressionar duas vezes)', + 'Cycle approval modes': 'Alternar modos de aprovação', + 'Cycle through your prompt history': 'Alternar histórico de prompts', + 'For a full list of shortcuts, see {{docPath}}': + 'Para uma lista completa de atalhos, consulte {{docPath}}', + 'docs/keyboard-shortcuts.md': 'docs/keyboard-shortcuts.md', + 'for help on Qwen Code': 'para ajuda sobre o Qwen Code', + 'show version info': 'mostrar informações de versão', + 'submit a bug report': 'enviar um relatório de erro', + 'About Qwen Code': 'Sobre o Qwen Code', + Status: 'Status', + + // ============================================================================ + // System Information Fields + // ============================================================================ + 'Qwen Code': 'Qwen Code', + Runtime: 'Runtime', + OS: 'SO', + Auth: 'Autenticação', + 'CLI Version': 'Versão da CLI', + 'Git Commit': 'Commit do Git', + Model: 'Modelo', + Sandbox: 'Sandbox', + 'OS Platform': 'Plataforma do SO', + 'OS Arch': 'Arquitetura do SO', + 'OS Release': 'Versão do SO', + 'Node.js Version': 'Versão do Node.js', + 'NPM Version': 'Versão do NPM', + 'Session ID': 'ID da Sessão', + 'Auth Method': 'Método de Autenticação', + 'Base URL': 'URL Base', + Proxy: 'Proxy', + 'Memory Usage': 'Uso de Memória', + 'IDE Client': 'Cliente IDE', + + // ============================================================================ + // Commands - General + // ============================================================================ + 'Analyzes the project and creates a tailored QWEN.md file.': + 'Analisa o projeto e cria um arquivo QWEN.md personalizado.', + 'list available Qwen Code tools. Usage: /tools [desc]': + 'listar ferramentas Qwen Code disponíveis. Uso: /tools [desc]', + 'Available Qwen Code CLI tools:': 'Ferramentas CLI do Qwen Code disponíveis:', + 'No tools available': 'Nenhuma ferramenta disponível', + 'View or change the approval mode for tool usage': + 'Ver ou alterar o modo de aprovação para uso de ferramentas', + 'Invalid approval mode "{{arg}}". Valid modes: {{modes}}': + 'Modo de aprovação inválido "{{arg}}". Modos válidos: {{modes}}', + 'Approval mode set to "{{mode}}"': + 'Modo de aprovação definido como "{{mode}}"', + 'View or change the language setting': + 'Ver ou alterar a configuração de idioma', + 'change the theme': 'alterar o tema', + 'Select Theme': 'Selecionar Tema', + Preview: 'Visualizar', + '(Use Enter to select, Tab to configure scope)': + '(Use Enter para selecionar, Tab para configurar o escopo)', + '(Use Enter to apply scope, Tab to go back)': + '(Use Enter para aplicar o escopo, Tab para voltar)', + 'Theme configuration unavailable due to NO_COLOR env variable.': + 'Configuração de tema indisponível devido à variável de ambiente NO_COLOR.', + 'Theme "{{themeName}}" not found.': 'Tema "{{themeName}}" não encontrado.', + 'Theme "{{themeName}}" not found in selected scope.': + 'Tema "{{themeName}}" não encontrado no escopo selecionado.', + 'Clear conversation history and free up context': + 'Limpar histórico de conversa e liberar contexto', + 'Compresses the context by replacing it with a summary.': + 'Comprime o contexto substituindo-o por um resumo.', + 'open full Qwen Code documentation in your browser': + 'abrir documentação completa do Qwen Code no seu navegador', + 'Configuration not available.': 'Configuração não disponível.', + 'change the auth method': 'alterar o método de autenticação', + 'Copy the last result or code snippet to clipboard': + 'Copiar o último resultado ou trecho de código para a área de transferência', + + // ============================================================================ + // Commands - Agents + // ============================================================================ + 'Manage subagents for specialized task delegation.': + 'Gerenciar subagentes para delegação de tarefas especializadas.', + 'Manage existing subagents (view, edit, delete).': + 'Gerenciar subagentes existentes (ver, editar, excluir).', + 'Create a new subagent with guided setup.': + 'Criar um novo subagente com configuração guiada.', + + // ============================================================================ + // Agents - Management Dialog + // ============================================================================ + Agents: 'Agentes', + 'Choose Action': 'Escolher Ação', + 'Edit {{name}}': 'Editar {{name}}', + 'Edit Tools: {{name}}': 'Editar Ferramentas: {{name}}', + 'Edit Color: {{name}}': 'Editar Cor: {{name}}', + 'Delete {{name}}': 'Excluir {{name}}', + 'Unknown Step': 'Etapa Desconhecida', + 'Esc to close': 'Esc para fechar', + 'Enter to select, ↑↓ to navigate, Esc to close': + 'Enter para selecionar, ↑↓ para navegar, Esc para fechar', + 'Esc to go back': 'Esc para voltar', + 'Enter to confirm, Esc to cancel': 'Enter para confirmar, Esc para cancelar', + 'Enter to select, ↑↓ to navigate, Esc to go back': + 'Enter para selecionar, ↑↓ para navegar, Esc para voltar', + 'Invalid step: {{step}}': 'Etapa inválida: {{step}}', + 'No subagents found.': 'Nenhum subagente encontrado.', + "Use '/agents create' to create your first subagent.": + "Use '/agents create' para criar seu primeiro subagente.", + '(built-in)': '(integrado)', + '(overridden by project level agent)': + '(substituído por agente de nível de projeto)', + 'Project Level ({{path}})': 'Nível de Projeto ({{path}})', + 'User Level ({{path}})': 'Nível de Usuário ({{path}})', + 'Built-in Agents': 'Agentes Integrados', + 'Extension Agents': 'Agentes de Extensão', + 'Using: {{count}} agents': 'Usando: {{count}} agentes', + 'View Agent': 'Ver Agente', + 'Edit Agent': 'Editar Agente', + 'Delete Agent': 'Excluir Agente', + Back: 'Voltar', + 'No agent selected': 'Nenhum agente selecionado', + 'File Path: ': 'Caminho do Arquivo: ', + 'Tools: ': 'Ferramentas: ', + 'Color: ': 'Cor: ', + 'Description:': 'Descrição:', + 'System Prompt:': 'Prompt do Sistema:', + 'Open in editor': 'Abrir no editor', + 'Edit tools': 'Editar ferramentas', + 'Edit color': 'Editar cor', + '❌ Error:': '❌ Erro:', + 'Are you sure you want to delete agent "{{name}}"?': + 'Tem certeza que deseja excluir o agente "{{name}}"?', + + // ============================================================================ + // Agents - Creation Wizard + // ============================================================================ + 'Project Level (.qwen/agents/)': 'Nível de Projeto (.qwen/agents/)', + 'User Level (~/.qwen/agents/)': 'Nível de Usuário (~/.qwen/agents/)', + '✅ Subagent Created Successfully!': '✅ Subagente criado com sucesso!', + 'Subagent "{{name}}" has been saved to {{level}} level.': + 'O subagente "{{name}}" foi salvo no nível {{level}}.', + 'Name: ': 'Nome: ', + 'Location: ': 'Localização: ', + '❌ Error saving subagent:': '❌ Erro ao salvar subagente:', + 'Warnings:': 'Avisos:', + 'Name "{{name}}" already exists at {{level}} level - will overwrite existing subagent': + 'O nome "{{name}}" já existe no nível {{level}} - o subagente existente será substituído', + 'Name "{{name}}" exists at user level - project level will take precedence': + 'O nome "{{name}}" existe no nível de usuário - o nível de projeto terá precedência', + 'Name "{{name}}" exists at project level - existing subagent will take precedence': + 'O nome "{{name}}" existe no nível de projeto - o subagente existente terá precedência', + 'Description is over {{length}} characters': + 'A descrição tem mais de {{length}} caracteres', + 'System prompt is over {{length}} characters': + 'O prompt do sistema tem mais de {{length}} caracteres', + + // ============================================================================ + // Agents - Creation Wizard Steps + // ============================================================================ + 'Step {{n}}: Choose Location': 'Etapa {{n}}: Escolher Localização', + 'Step {{n}}: Choose Generation Method': + 'Etapa {{n}}: Escolher Método de Geração', + 'Generate with Qwen Code (Recommended)': 'Gerar com Qwen Code (Recomendado)', + 'Manual Creation': 'Criação Manual', + 'Describe what this subagent should do and when it should be used. (Be comprehensive for best results)': + 'Descreva o que este subagente deve fazer e quando deve ser usado. (Seja abrangente para melhores resultados)', + 'e.g., Expert code reviewer that reviews code based on best practices...': + 'ex: Revisor de código especialista que revisa código com base em melhores práticas...', + 'Generating subagent configuration...': + 'Gerando configuração do subagente...', + 'Failed to generate subagent: {{error}}': + 'Falha ao gerar subagente: {{error}}', + 'Step {{n}}: Describe Your Subagent': 'Etapa {{n}}: Descreva Seu Subagente', + 'Step {{n}}: Enter Subagent Name': 'Etapa {{n}}: Digite o Nome do Subagente', + 'Step {{n}}: Enter System Prompt': 'Etapa {{n}}: Digite o Prompt do Sistema', + 'Step {{n}}: Enter Description': 'Etapa {{n}}: Digite a Descrição', + + // ============================================================================ + // Agents - Tool Selection + // ============================================================================ + 'Step {{n}}: Select Tools': 'Etapa {{n}}: Selecionar Ferramentas', + 'All Tools (Default)': 'Todas as Ferramentas (Padrão)', + 'All Tools': 'Todas as Ferramentas', + 'Read-only Tools': 'Ferramentas de Somente Leitura', + 'Read & Edit Tools': 'Ferramentas de Leitura e Edição', + 'Read & Edit & Execution Tools': 'Ferramentas de Leitura, Edição e Execução', + 'All tools selected, including MCP tools': + 'Todas as ferramentas selecionadas, incluindo ferramentas MCP', + 'Selected tools:': 'Ferramentas selecionadas:', + 'Read-only tools:': 'Ferramentas de somente leitura:', + 'Edit tools:': 'Ferramentas de edição:', + 'Execution tools:': 'Ferramentas de execução:', + 'Step {{n}}: Choose Background Color': 'Etapa {{n}}: Escolher Cor de Fundo', + 'Step {{n}}: Confirm and Save': 'Etapa {{n}}: Confirmar e Salvar', + + // ============================================================================ + // Agents - Navigation & Instructions + // ============================================================================ + 'Esc to cancel': 'Esc para cancelar', + 'Press Enter to save, e to save and edit, Esc to go back': + 'Pressione Enter para salvar, e para salvar e editar, Esc para voltar', + 'Press Enter to continue, {{navigation}}Esc to {{action}}': + 'Pressione Enter para continuar, {{navigation}}Esc para {{action}}', + cancel: 'cancelar', + 'go back': 'voltar', + '↑↓ to navigate, ': '↑↓ para navegar, ', + 'Enter a clear, unique name for this subagent.': + 'Digite um nome claro e único para este subagente.', + 'e.g., Code Reviewer': 'ex: Revisor de Código', + 'Name cannot be empty.': 'O nome não pode estar vazio.', + "Write the system prompt that defines this subagent's behavior. Be comprehensive for best results.": + 'Escreva o prompt do sistema que define o comportamento deste subagente. Seja abrangente para melhores resultados.', + 'e.g., You are an expert code reviewer...': + 'ex: Você é um revisor de código especialista...', + 'System prompt cannot be empty.': 'O prompt do sistema não pode estar vazio.', + 'Describe when and how this subagent should be used.': + 'Descreva quando e como este subagente deve ser usado.', + 'e.g., Reviews code for best practices and potential bugs.': + 'ex: Revisa o código em busca de melhores práticas e erros potenciais.', + 'Description cannot be empty.': 'A descrição não pode estar vazia.', + 'Failed to launch editor: {{error}}': 'Falha ao iniciar editor: {{error}}', + 'Failed to save and edit subagent: {{error}}': + 'Falha ao salvar e editar subagente: {{error}}', + + // ============================================================================ + // Commands - General (continued) + // ============================================================================ + 'View and edit Qwen Code settings': 'Ver e editar configurações do Qwen Code', + Settings: 'Configurações', + 'To see changes, Qwen Code must be restarted. Press r to exit and apply changes now.': + 'Para ver as alterações, o Qwen Code deve ser reiniciado. Pressione r para sair e aplicar as alterações agora.', + 'The command "/{{command}}" is not supported in non-interactive mode.': + 'O comando "/{{command}}" não é suportado no modo não interativo.', + + // ============================================================================ + // Settings Labels + // ============================================================================ + 'Vim Mode': 'Modo Vim', + 'Disable Auto Update': 'Desativar Atualização Automática', + 'Attribution: commit': 'Atribuição: commit', + 'Terminal Bell Notification': 'Notificação Sonora do Terminal', + 'Enable Usage Statistics': 'Ativar Estatísticas de Uso', + Theme: 'Tema', + 'Preferred Editor': 'Editor Preferido', + 'Auto-connect to IDE': 'Conexão Automática com IDE', + 'Enable Prompt Completion': 'Ativar Autocompletar de Prompts', + 'Debug Keystroke Logging': 'Log de Depuração de Teclas', + 'Language: UI': 'Idioma: Interface', + 'Language: Model': 'Idioma: Modelo', + 'Output Format': 'Formato de Saída', + 'Hide Window Title': 'Ocultar Título da Janela', + 'Show Status in Title': 'Mostrar Status no Título', + 'Hide Tips': 'Ocultar Dicas', + 'Show Line Numbers in Code': 'Mostrar Números de Linhas no Código', + 'Show Citations': 'Mostrar Citações', + 'Custom Witty Phrases': 'Frases de Efeito Personalizadas', + 'Show Welcome Back Dialog': 'Mostrar Diálogo de Bem-vindo de Volta', + 'Enable User Feedback': 'Ativar Feedback do Usuário', + 'How is Qwen doing this session? (optional)': + 'Como o Qwen está se saindo nesta sessão? (opcional)', + Bad: 'Ruim', + Fine: 'Bom', + Good: 'Ótimo', + Dismiss: 'Ignorar', + 'Not Sure Yet': 'Não tenho certeza ainda', + 'Any other key': 'Qualquer outra tecla', + 'Disable Loading Phrases': 'Desativar Frases de Carregamento', + 'Screen Reader Mode': 'Modo de Leitor de Tela', + 'IDE Mode': 'Modo IDE', + 'Max Session Turns': 'Máximo de Turnos da Sessão', + 'Skip Next Speaker Check': 'Pular Verificação do Próximo Falante', + 'Skip Loop Detection': 'Pular Detecção de Loop', + 'Skip Startup Context': 'Pular Contexto de Inicialização', + 'Enable OpenAI Logging': 'Ativar Log do OpenAI', + 'OpenAI Logging Directory': 'Diretório de Log do OpenAI', + Timeout: 'Tempo Limite', + 'Max Retries': 'Máximo de Tentativas', + 'Disable Cache Control': 'Desativar Controle de Cache', + 'Memory Discovery Max Dirs': 'Descoberta de Memória Máx. Diretorios', + 'Load Memory From Include Directories': + 'Carregar Memória de Diretórios Incluídos', + 'Respect .gitignore': 'Respeitar .gitignore', + 'Respect .qwenignore': 'Respeitar .qwenignore', + 'Enable Recursive File Search': 'Ativar Pesquisa Recursiva de Arquivos', + 'Disable Fuzzy Search': 'Desativar Pesquisa Difusa', + 'Interactive Shell (PTY)': 'Shell Interativo (PTY)', + 'Show Color': 'Mostrar Cores', + 'Auto Accept': 'Aceitar Automaticamente', + 'Use Ripgrep': 'Usar Ripgrep', + 'Use Builtin Ripgrep': 'Usar Ripgrep Integrado', + 'Enable Tool Output Truncation': 'Ativar Truncamento de Saída de Ferramenta', + 'Tool Output Truncation Threshold': + 'Limite de Truncamento de Saída de Ferramenta', + 'Tool Output Truncation Lines': + 'Linhas de Truncamento de Saída de Ferramenta', + 'Folder Trust': 'Confiança de Pasta', + 'Vision Model Preview': 'Visualização de Modelo de Visão', + 'Tool Schema Compliance': 'Conformidade de Esquema de Ferramenta', + 'Experimental: Skills': 'Experimental: Habilidades', + + // Settings enum options + 'Auto (detect from system)': 'Automático (detectar do sistema)', + Text: 'Texto', + JSON: 'JSON', + Plan: 'Planejamento', + Default: 'Padrão', + 'Auto Edit': 'Edição Automática', + YOLO: 'YOLO', + 'toggle vim mode on/off': 'alternar modo vim ligado/desligado', + 'check session stats. Usage: /stats [model|tools]': + 'verificar estatísticas da sessão. Uso: /stats [model|tools]', + 'Show model-specific usage statistics.': + 'Mostrar estatísticas de uso específicas do modelo.', + 'Show tool-specific usage statistics.': + 'Mostrar estatísticas de uso específicas da ferramenta.', + 'exit the cli': 'sair da cli', + 'list configured MCP servers and tools, or authenticate with OAuth-enabled servers': + 'listar servidores e ferramentas MCP configurados, ou autenticar com servidores habilitados para OAuth', + 'Manage workspace directories': 'Gerenciar diretórios do workspace', + 'Add directories to the workspace. Use comma to separate multiple paths': + 'Adicionar diretórios ao workspace. Use vírgula para separar vários caminhos', + 'Show all directories in the workspace': + 'Mostrar todos os diretórios no workspace', + 'set external editor preference': 'definir preferência de editor externo', + 'Select Editor': 'Selecionar Editor', + 'Editor Preference': 'Preferência de Editor', + 'These editors are currently supported. Please note that some editors cannot be used in sandbox mode.': + 'Estes editores são suportados atualmente. Note que alguns editores não podem ser usados no modo sandbox.', + 'Your preferred editor is:': 'Seu editor preferido é:', + 'Manage extensions': 'Gerenciar extensões', + 'List active extensions': 'Listar extensões ativas', + 'Update extensions. Usage: update |--all': + 'Atualizar extensões. Uso: update |--all', + 'Disable an extension': 'Desativar uma extensão', + 'Enable an extension': 'Ativar uma extensão', + 'Install an extension from a git repo or local path': + 'Instalar uma extensão de um repositório git ou caminho local', + 'Uninstall an extension': 'Desinstalar uma extensão', + 'No extensions installed.': 'Nenhuma extensão instalada.', + 'Usage: /extensions update |--all': + 'Uso: /extensions update |--all', + 'Extension "{{name}}" not found.': 'Extensão "{{name}}" não encontrada.', + 'No extensions to update.': 'Nenhuma extensão para atualizar.', + 'Usage: /extensions install ': 'Uso: /extensions install ', + 'Installing extension from "{{source}}"...': + 'Instalando extensão de "{{source}}"...', + 'Extension "{{name}}" installed successfully.': + 'Extensão "{{name}}" instalada com sucesso.', + 'Failed to install extension from "{{source}}": {{error}}': + 'Falha ao instalar extensão de "{{source}}": {{error}}', + 'Usage: /extensions uninstall ': + 'Uso: /extensions uninstall ', + 'Uninstalling extension "{{name}}"...': + 'Desinstalando extensão "{{name}}"...', + 'Extension "{{name}}" uninstalled successfully.': + 'Extensão "{{name}}" desinstalada com sucesso.', + 'Failed to uninstall extension "{{name}}": {{error}}': + 'Falha ao desinstalar extensão "{{name}}": {{error}}', + 'Usage: /extensions {{command}} [--scope=]': + 'Uso: /extensions {{command}} [--scope=]', + 'Unsupported scope "{{scope}}", deve ser um de "user" ou "workspace"': + 'Escopo não suportado "{{scope}}", deve ser um de "user" ou "workspace"', + 'Extension "{{name}}" disabled for scope "{{scope}}"': + 'Extensão "{{name}}" desativada para o escopo "{{scope}}"', + 'Extension "{{name}}" enabled for scope "{{scope}}"': + 'Extensão "{{name}}" ativada para o escopo "{{scope}}"', + 'Do you want to continue? [Y/n]: ': 'Você deseja continuar? [Y/n]: ', + 'Do you want to continue?': 'Você deseja continuar?', + 'Installing extension "{{name}}".': 'Instalando extensão "{{name}}".', + '**Extensions may introduce unexpected behavior. Ensure you have investigated the extension source and trust the author.**': + '**As extensões podem introduzir comportamentos inesperados. Certifique-se de ter investigado a fonte da extensão e confie no autor.**', + 'This extension will run the following MCP servers:': + 'Esta extensão executará os seguintes servidores MCP:', + local: 'local', + remote: 'remoto', + 'This extension will add the following commands: {{commands}}.': + 'Esta extensão adicionará os seguintes comandos: {{commands}}.', + 'This extension will append info to your QWEN.md context using {{fileName}}': + 'Esta extensão anexará informações ao seu contexto QWEN.md usando {{fileName}}', + 'This extension will exclude the following core tools: {{tools}}': + 'Esta extensão excluirá as seguintes ferramentas principais: {{tools}}', + 'This extension will install the following skills:': + 'Esta extensão instalará as seguintes habilidades:', + 'This extension will install the following subagents:': + 'Esta extensão instalará os seguintes subagentes:', + 'Installation cancelled for "{{name}}".': + 'Instalação cancelada para "{{name}}".', + '--ref and --auto-update are not applicable for marketplace extensions.': + '--ref e --auto-update não são aplicáveis para extensões de marketplace.', + 'Extension "{{name}}" installed successfully and enabled.': + 'Extensão "{{name}}" instalada com sucesso e ativada.', + 'Installs an extension from a git repository URL, local path, or claude marketplace (marketplace-url:plugin-name).': + 'Instala uma extensão de uma URL de repositório git, caminho local ou marketplace do claude (marketplace-url:plugin-name).', + 'The github URL, local path, or marketplace source (marketplace-url:plugin-name) of the extension to install.': + 'A URL do github, caminho local ou fonte do marketplace (marketplace-url:plugin-name) da extensão para instalar.', + 'The git ref to install from.': 'A referência git para instalar.', + 'Enable auto-update for this extension.': + 'Ativar atualização automática para esta extensão.', + 'Enable pre-release versions for this extension.': + 'Ativar versões de pré-lançamento para esta extensão.', + 'Acknowledge the security risks of installing an extension and skip the confirmation prompt.': + 'Reconhecer os riscos de segurança de instalar uma extensão e pular o prompt de confirmação.', + 'The source argument must be provided.': + 'O argumento fonte deve ser fornecido.', + 'Extension "{{name}}" successfully uninstalled.': + 'Extensão "{{name}}" desinstalada com sucesso.', + 'Uninstalls an extension.': 'Desinstala uma extensão.', + 'The name or source path of the extension to uninstall.': + 'O nome ou caminho da fonte da extensão para desinstalar.', + 'Please include the name of the extension to uninstall as a positional argument.': + 'Inclua o nome da extensão para desinstalar como um argumento posicional.', + 'Enables an extension.': 'Ativa uma extensão.', + 'The name of the extension to enable.': 'O nome da extensão para ativar.', + 'The scope to enable the extenison in. If not set, will be enabled in all scopes.': + 'O escopo para ativar a extensão. Se não definido, será ativada em todos os escopos.', + 'Extension "{{name}}" successfully enabled for scope "{{scope}}".': + 'Extensão "{{name}}" ativada com sucesso para o escopo "{{scope}}".', + 'Extension "{{name}}" successfully enabled in all scopes.': + 'Extensão "{{name}}" ativada com sucesso em todos os escopos.', + 'Invalid scope: {{scope}}. Please use one of {{scopes}}.': + 'Escopo inválido: {{scope}}. Use um de {{scopes}}.', + 'Disables an extension.': 'Desativa uma extensão.', + 'The name of the extension to disable.': 'O nome da extensão para desativar.', + 'The scope to disable the extenison in.': + 'O escopo para desativar a extensão.', + 'Extension "{{name}}" successfully disabled for scope "{{scope}}".': + 'Extensão "{{name}}" desativada com sucesso para o escopo "{{scope}}".', + 'Extension "{{name}}" successfully updated: {{oldVersion}} → {{newVersion}}.': + 'Extensão "{{name}}" atualizada com sucesso: {{oldVersion}} → {{newVersion}}.', + 'Unable to install extension "{{name}}" due to missing install metadata': + 'Não foi possível instalar a extensão "{{name}}" devido à falta de metadados de instalação', + 'Extension "{{name}}" is already up to date.': + 'A extensão "{{name}}" já está atualizada.', + 'Updates all extensions or a named extension to the latest version.': + 'Atualiza todas as extensões ou uma extensão nomeada para a última versão.', + 'Update all extensions.': 'Atualizar todas as extensões.', + 'Either an extension name or --all must be provided': + 'Um nome de extensão ou --all deve ser fornecido', + 'Lists installed extensions.': 'Lista as extensões instaladas.', + 'Link extension failed to install.': 'Falha ao instalar link da extensão.', + 'Extension "{{name}}" linked successfully and enabled.': + 'Extensão "{{name}}" vinculada com sucesso e ativada.', + 'Links an extension from a local path. Updates made to the local path will always be reflected.': + 'Vincula uma extensão de um caminho local. Atualizações feitas no caminho local sempre serão refletidas.', + 'The name of the extension to link.': 'O nome da extensão para vincular.', + 'Set a specific setting for an extension.': + 'Define uma configuração específica para uma extensão.', + 'Name of the extension to configure.': 'Nome da extensão para configurar.', + 'The setting to configure (name or env var).': + 'A configuração para configurar (nome ou var env).', + 'The scope to set the setting in.': 'O escopo para definir a configuração.', + 'List all settings for an extension.': + 'Listar todas as configurações de uma extensão.', + 'Name of the extension.': 'Nome da extensão.', + 'Extension "{{name}}" has no settings to configure.': + 'A extensão "{{name}}" não tem configurações para configurar.', + 'Settings for "{{name}}":': 'Configurações para "{{name}}":', + '(workspace)': '(workspace)', + '(user)': '(usuário)', + '[not set]': '[não definido]', + '[value stored in keychain]': '[valor armazenado no chaveiro]', + 'Value:': 'Valor:', + 'Manage extension settings.': 'Gerenciar configurações de extensão.', + 'You need to specify a command (set or list).': + 'Você precisa especificar um comando (set ou list).', + + // ============================================================================ + // Plugin Choice / Marketplace + // ============================================================================ + 'No plugins available in this marketplace.': + 'Nenhum plugin disponível neste marketplace.', + 'Select a plugin to install from marketplace "{{name}}":': + 'Selecione um plugin para instalar do marketplace "{{name}}":', + 'Plugin selection cancelled.': 'Seleção de plugin cancelada.', + 'Select a plugin from "{{name}}"': 'Selecione um plugin de "{{name}}"', + 'Use ↑↓ or j/k to navigate, Enter to select, Escape to cancel': + 'Use ↑↓ ou j/k para navegar, Enter para selecionar, Escape para cancelar', + '{{count}} more above': '{{count}} mais acima', + '{{count}} more below': '{{count}} mais abaixo', + 'manage IDE integration': 'gerenciar integração com IDE', + 'check status of IDE integration': 'verificar status da integração com IDE', + 'install required IDE companion for {{ideName}}': + 'instalar companion IDE necessário para {{ideName}}', + 'enable IDE integration': 'ativar integração com IDE', + 'disable IDE integration': 'desativar integração com IDE', + 'IDE integration is not supported in your current environment. To use this feature, run Qwen Code in one of these supported IDEs: VS Code or VS Code forks.': + 'A integração com IDE não é suportada no seu ambiente atual. Para usar este recurso, execute o Qwen Code em um destes IDEs suportados: VS Code ou forks do VS Code.', + 'Set up GitHub Actions': 'Configurar GitHub Actions', + 'Configure terminal keybindings for multiline input (VS Code, Cursor, Windsurf, Trae)': + 'Configurar atalhos de terminal para entrada multilinhas (VS Code, Cursor, Windsurf, Trae)', + 'Please restart your terminal for the changes to take effect.': + 'Reinicie seu terminal para que as alterações tenham efeito.', + 'Failed to configure terminal: {{error}}': + 'Falha ao configurar terminal: {{error}}', + 'Could not determine {{terminalName}} config path on Windows: APPDATA environment variable is not set.': + 'Não foi possível determinar o caminho de configuração de {{terminalName}} no Windows: variável de ambiente APPDATA não está definida.', + '{{terminalName}} keybindings.json exists but is not a valid JSON array. Please fix the file manually or delete it to allow automatic configuration.': + '{{terminalName}} keybindings.json existe mas não é um array JSON válido. Corrija o arquivo manualmente ou exclua-o para permitir a configuração automática.', + 'File: {{file}}': 'Arquivo: {{file}}', + 'Failed to parse {{terminalName}} keybindings.json. The file contains invalid JSON. Please fix the file manually or delete it to allow automatic configuration.': + 'Falha ao analisar {{terminalName}} keybindings.json. O arquivo contém JSON inválido. Corrija o arquivo manualmente ou exclua-o para permitir a configuração automática.', + 'Error: {{error}}': 'Erro: {{error}}', + 'Shift+Enter binding already exists': 'Atalho Shift+Enter já existe', + 'Ctrl+Enter binding already exists': 'Atalho Ctrl+Enter já existe', + 'Existing keybindings detected. Will not modify to avoid conflicts.': + 'Atalhos existentes detectados. Não serão modificados para evitar conflitos.', + 'Please check and modify manually if needed: {{file}}': + 'Verifique e modifique manualmente se necessário: {{file}}', + 'Added Shift+Enter and Ctrl+Enter keybindings to {{terminalName}}.': + 'Adicionados atalhos Shift+Enter e Ctrl+Enter para {{terminalName}}.', + 'Modified: {{file}}': 'Modificado: {{file}}', + '{{terminalName}} keybindings already configured.': + 'Atalhos de {{terminalName}} já configurados.', + 'Failed to configure {{terminalName}}.': + 'Falha ao configurar {{terminalName}}.', + 'Your terminal is already configured for an optimal experience with multiline input (Shift+Enter and Ctrl+Enter).': + 'Seu terminal já está configurado para uma experiência ideal com entrada multilinhas (Shift+Enter e Ctrl+Enter).', + 'Could not detect terminal type. Supported terminals: VS Code, Cursor, Windsurf, and Trae.': + 'Não foi possível detectar o tipo de terminal. Terminais suportados: VS Code, Cursor, Windsurf e Trae.', + 'Terminal "{{terminal}}" is not supported yet.': + 'O terminal "{{terminal}}" ainda não é suportado.', + + // ============================================================================ + // Commands - Language + // ============================================================================ + 'Invalid language. Available: {{options}}': + 'Idioma inválido. Disponíveis: {{options}}', + 'Language subcommands do not accept additional arguments.': + 'Subcomandos de idioma não aceitam argumentos adicionais.', + 'Current UI language: {{lang}}': 'Idioma atual da interface: {{lang}}', + 'Current LLM output language: {{lang}}': + 'Idioma atual da saída do LLM: {{lang}}', + 'LLM output language not set': 'Idioma de saída do LLM não definido', + 'Set UI language': 'Definir idioma da interface', + 'Set LLM output language': 'Definir idioma de saída do LLM', + 'Usage: /language ui [{{options}}]': 'Uso: /language ui [{{options}}]', + 'Usage: /language output ': 'Uso: /language output ', + 'Example: /language output 中文': 'Exemplo: /language output Português', + 'Example: /language output English': 'Exemplo: /language output Inglês', + 'Example: /language output 日本語': 'Exemplo: /language output Japonês', + 'Example: /language output Português': 'Exemplo: /language output Português', + 'UI language changed to {{lang}}': + 'Idioma da interface alterado para {{lang}}', + 'LLM output language set to {{lang}}': + 'Idioma de saída do LLM definido para {{lang}}', + 'LLM output language rule file generated at {{path}}': + 'Arquivo de regra de idioma de saída do LLM gerado em {{path}}', + 'Please restart the application for the changes to take effect.': + 'Reinicie o aplicativo para que as alterações tenham efeito.', + 'Failed to generate LLM output language rule file: {{error}}': + 'Falha ao gerar arquivo de regra de idioma de saída do LLM: {{error}}', + 'Invalid command. Available subcommands:': + 'Comando inválido. Subcomandos disponíveis:', + 'Available subcommands:': 'Subcomandos disponíveis:', + 'To request additional UI language packs, please open an issue on GitHub.': + 'Para solicitar pacotes de idiomas de interface adicionais, abra um problema no GitHub.', + 'Available options:': 'Opções disponíveis:', + 'Set UI language to {{name}}': 'Definir idioma da interface para {{name}}', + + // ============================================================================ + // Commands - Approval Mode + // ============================================================================ + 'Tool Approval Mode': 'Modo de Aprovação de Ferramenta', + 'Current approval mode: {{mode}}': 'Modo de aprovação atual: {{mode}}', + 'Available approval modes:': 'Modos de aprovação disponíveis:', + 'Approval mode changed to: {{mode}}': + 'Modo de aprovação alterado para: {{mode}}', + 'Approval mode changed to: {{mode}} (saved to {{scope}} settings{{location}})': + 'Modo de aprovação alterado para: {{mode}} (salvo nas configurações de {{scope}}{{location}})', + 'Usage: /approval-mode [--session|--user|--project]': + 'Uso: /approval-mode [--session|--user|--project]', + + 'Scope subcommands do not accept additional arguments.': + 'Subcomandos de escopo não aceitam argumentos adicionais.', + 'Plan mode - Analyze only, do not modify files or execute commands': + 'Modo planejamento - Apenas analisa, não modifica arquivos nem executa comandos', + 'Default mode - Require approval for file edits or shell commands': + 'Modo padrão - Exige aprovação para edições de arquivos ou comandos shell', + 'Auto-edit mode - Automatically approve file edits': + 'Modo auto-edição - Aprova automaticamente edições de arquivos', + 'YOLO mode - Automatically approve all tools': + 'Modo YOLO - Aprova automaticamente todas as ferramentas', + '{{mode}} mode': 'Modo {{mode}}', + 'Settings service is not available; unable to persist the approval mode.': + 'Serviço de configurações não disponível; não foi possível persistir o modo de aprovação.', + 'Failed to save approval mode: {{error}}': + 'Falha ao salvar modo de aprovação: {{error}}', + 'Failed to change approval mode: {{error}}': + 'Falha ao alterar modo de aprovação: {{error}}', + 'Apply to current session only (temporary)': + 'Aplicar apenas à sessão atual (temporário)', + 'Persist for this project/workspace': 'Persistir para este projeto/workspace', + 'Persist for this user on this machine': + 'Persistir para este usuário nesta máquina', + 'Analyze only, do not modify files or execute commands': + 'Apenas analisar, não modificar arquivos nem executar comandos', + 'Require approval for file edits or shell commands': + 'Exigir aprovação para edições de arquivos ou comandos shell', + 'Automatically approve file edits': + 'Aprovar automaticamente edições de arquivos', + 'Automatically approve all tools': + 'Aprovar automaticamente todas as ferramentas', + 'Workspace approval mode exists and takes priority. User-level change will have no effect.': + 'O modo de aprovação do workspace existe e tem prioridade. A alteração no nível do usuário não terá efeito.', + 'Apply To': 'Aplicar A', + 'User Settings': 'Configurações do Usuário', + 'Workspace Settings': 'Configurações do Workspace', + + // ============================================================================ + // Commands - Memory + // ============================================================================ + 'Commands for interacting with memory.': + 'Comandos para interagir com a memória.', + 'Show the current memory contents.': + 'Mostrar os conteúdos atuais da memória.', + 'Show project-level memory contents.': + 'Mostrar conteúdos da memória de nível de projeto.', + 'Show global memory contents.': 'Mostrar conteúdos da memória global.', + 'Add content to project-level memory.': + 'Adicionar conteúdo à memória de nível de projeto.', + 'Add content to global memory.': 'Adicionar conteúdo à memória global.', + 'Refresh the memory from the source.': 'Atualizar a memória da fonte.', + 'Usage: /memory add --project ': + 'Uso: /memory add --project ', + 'Usage: /memory add --global ': + 'Uso: /memory add --global ', + 'Attempting to save to project memory: "{{text}}"': + 'Tentando salvar na memória do projeto: "{{text}}"', + 'Attempting to save to global memory: "{{text}}"': + 'Tentando salvar na memória global: "{{text}}"', + 'Current memory content from {{count}} file(s):': + 'Conteúdo da memória atual de {{count}} arquivo(s):', + 'Memory is currently empty.': 'A memória está vazia no momento.', + 'Project memory file not found or is currently empty.': + 'Arquivo de memória do projeto não encontrado ou está vazio.', + 'Global memory file not found or is currently empty.': + 'Arquivo de memória global não encontrado ou está vazio.', + 'Global memory is currently empty.': + 'A memória global está vazia no momento.', + 'Global memory content:\n\n---\n{{content}}\n---': + 'Conteúdo da memória global:\n\n---\n{{content}}\n---', + 'Project memory content from {{path}}:\n\n---\n{{content}}\n---': + 'Conteúdo da memória do projeto de {{path}}:\n\n---\n{{content}}\n---', + 'Project memory is currently empty.': + 'A memória do projeto está vazia no momento.', + 'Refreshing memory from source files...': + 'Atualizando memória dos arquivos fonte...', + 'Add content to the memory. Use --global for global memory or --project for project memory.': + 'Adicionar conteúdo à memória. Use --global para memória global ou --project para memória do projeto.', + 'Usage: /memory add [--global|--project] ': + 'Uso: /memory add [--global|--project] ', + 'Attempting to save to memory {{scope}}: "{{fact}}"': + 'Tentando salvar na memória {{scope}}: "{{fact}}"', + + // ============================================================================ + // Commands - MCP + // ============================================================================ + 'Authenticate with an OAuth-enabled MCP server': + 'Autenticar com um servidor MCP habilitado para OAuth', + 'List configured MCP servers and tools': + 'Listar servidores e ferramentas MCP configurados', + 'Restarts MCP servers.': 'Reinicia os servidores MCP.', + 'Config not loaded.': 'Configuração não carregada.', + 'Could not retrieve tool registry.': + 'Não foi possível recuperar o registro de ferramentas.', + 'No MCP servers configured with OAuth authentication.': + 'Nenhum servidor MCP configurado com autenticação OAuth.', + 'MCP servers with OAuth authentication:': + 'Servidores MCP com autenticação OAuth:', + 'Use /mcp auth to authenticate.': + 'Use /mcp auth para autenticar.', + "MCP server '{{name}}' not found.": "Servidor MCP '{{name}}' não encontrado.", + "Successfully authenticated and refreshed tools for '{{name}}'.": + "Autenticado com sucesso e ferramentas atualizadas para '{{name}}'.", + "Failed to authenticate with MCP server '{{name}}': {{error}}": + "Falha ao autenticar com o servidor MCP '{{name}}': {{error}}", + "Re-discovering tools from '{{name}}'...": + "Redescobrindo ferramentas de '{{name}}'...", + + // ============================================================================ + // Commands - Chat + // ============================================================================ + 'Manage conversation history.': 'Gerenciar histórico de conversas.', + 'List saved conversation checkpoints': + 'Listar checkpoints de conversa salvos', + 'No saved conversation checkpoints found.': + 'Nenhum checkpoint de conversa salvo encontrado.', + 'List of saved conversations:': 'Lista de conversas salvas:', + 'Note: Newest last, oldest first': + 'Nota: Mais novos por último, mais antigos primeiro', + 'Save the current conversation as a checkpoint. Usage: /chat save ': + 'Salvar a conversa atual como um checkpoint. Uso: /chat save ', + 'Missing tag. Usage: /chat save ': 'Tag ausente. Uso: /chat save ', + 'Delete a conversation checkpoint. Usage: /chat delete ': + 'Excluir um checkpoint de conversa. Uso: /chat delete ', + 'Missing tag. Usage: /chat delete ': + 'Tag ausente. Uso: /chat delete ', + "Conversation checkpoint '{{tag}}' has been deleted.": + "O checkpoint de conversa '{{tag}}' foi excluído.", + "Error: No checkpoint found with tag '{{tag}}'.": + "Erro: Nenhum checkpoint encontrado com a tag '{{tag}}'.", + 'Resume a conversation from a checkpoint. Usage: /chat resume ': + 'Retomar uma conversa de um checkpoint. Uso: /chat resume ', + 'Missing tag. Usage: /chat resume ': + 'Tag ausente. Uso: /chat resume ', + 'No saved checkpoint found with tag: {{tag}}.': + 'Nenhum checkpoint salvo encontrado com a tag: {{tag}}.', + 'A checkpoint with the tag {{tag}} already exists. Do you want to overwrite it?': + 'Um checkpoint com a tag {{tag}} já existe. Você deseja substituí-lo?', + 'No chat client available to save conversation.': + 'Nenhum cliente de chat disponível para salvar a conversa.', + 'Conversation checkpoint saved with tag: {{tag}}.': + 'Checkpoint de conversa salvo com a tag: {{tag}}.', + 'No conversation found to save.': 'Nenhuma conversa encontrada para salvar.', + 'No chat client available to share conversation.': + 'Nenhum cliente de chat disponível para compartilhar a conversa.', + 'Invalid file format. Only .md and .json are supported.': + 'Formato de arquivo inválido. Apenas .md e .json são suportados.', + 'Error sharing conversation: {{error}}': + 'Erro ao compartilhar conversa: {{error}}', + 'Conversation shared to {{filePath}}': + 'Conversa compartilhada em {{filePath}}', + 'No conversation found to share.': + 'Nenhuma conversa encontrada para compartilhar.', + 'Share the current conversation to a markdown or json file. Usage: /chat share ': + 'Compartilhar a conversa atual para um arquivo markdown ou json. Uso: /chat share ', + + // ============================================================================ + // Commands - Summary + // ============================================================================ + 'Generate a project summary and save it to .qwen/PROJECT_SUMMARY.md': + 'Gerar um resumo do projeto e salvá-lo em .qwen/PROJECT_SUMMARY.md', + 'No chat client available to generate summary.': + 'Nenhum cliente de chat disponível para gerar o resumo.', + 'Already generating summary, wait for previous request to complete': + 'Já gerando resumo, aguarde a conclusão da solicitação anterior', + 'No conversation found to summarize.': + 'Nenhuma conversa encontrada para resumir.', + 'Failed to generate project context summary: {{error}}': + 'Falha ao gerar resumo do contexto do projeto: {{error}}', + 'Saved project summary to {{filePathForDisplay}}.': + 'Resumo do projeto salvo em {{filePathForDisplay}}.', + 'Saving project summary...': 'Salvando resumo do projeto...', + 'Generating project summary...': 'Gerando resumo do projeto...', + 'Failed to generate summary - no text content received from LLM response': + 'Falha ao gerar resumo - nenhum conteúdo de texto recebido da resposta do LLM', + + // ============================================================================ + // Commands - Model + // ============================================================================ + 'Switch the model for this session': 'Trocar o modelo para esta sessão', + 'Content generator configuration not available.': + 'Configuração do gerador de conteúdo não disponível.', + 'Authentication type not available.': 'Tipo de autenticação não disponível.', + 'No models available for the current authentication type ({{authType}}).': + 'Nenhum modelo disponível para o tipo de autenticação atual ({{authType}}).', + + // ============================================================================ + // Commands - Clear + // ============================================================================ + 'Starting a new session, resetting chat, and clearing terminal.': + 'Iniciando uma nova sessão, resetando o chat e limpando o terminal.', + 'Starting a new session and clearing.': + 'Iniciando uma nova sessão e limpando.', + + // ============================================================================ + // Commands - Compress + // ============================================================================ + 'Already compressing, wait for previous request to complete': + 'Já comprimindo, aguarde a conclusão da solicitação anterior', + 'Failed to compress chat history.': 'Falha ao comprimir histórico do chat.', + 'Failed to compress chat history: {{error}}': + 'Falha ao comprimir histórico do chat: {{error}}', + 'Compressing chat history': 'Comprimindo histórico do chat', + 'Chat history compressed from {{originalTokens}} to {{newTokens}} tokens.': + 'Histórico do chat comprimido de {{originalTokens}} para {{newTokens}} tokens.', + 'Compression was not beneficial for this history size.': + 'A compressão não foi benéfica para este tamanho de histórico.', + 'Chat history compression did not reduce size. This may indicate issues with the compression prompt.': + 'A compressão do histórico do chat não reduziu o tamanho. Isso pode indicar problemas com o prompt de compressão.', + 'Could not compress chat history due to a token counting error.': + 'Não foi possível comprimir o histórico do chat devido a um erro de contagem de tokens.', + 'Chat history is already compressed.': + 'O histórico do chat já está comprimido.', + + // ============================================================================ + // Commands - Directory + // ============================================================================ + 'Configuration is not available.': 'A configuração não está disponível.', + 'Please provide at least one path to add.': + 'Forneça pelo menos um caminho para adicionar.', + 'The /directory add command is not supported in restrictive sandbox profiles. Please use --include-directories when starting the session instead.': + 'O comando /directory add não é suportado em perfis de sandbox restritivos. Use --include-directories ao iniciar a sessão.', + "Error adding '{{path}}': {{error}}": + "Erro ao adicionar '{{path}}': {{error}}", + 'Successfully added QWEN.md files from the following directories if there are:\n- {{directories}}': + 'Arquivos QWEN.md adicionados com sucesso dos seguintes diretórios, se houverem:\n- {{directories}}', + 'Error refreshing memory: {{error}}': 'Erro ao atualizar memória: {{error}}', + 'Successfully added directories:\n- {{directories}}': + 'Diretórios adicionados com sucesso:\n- {{directories}}', + 'Current workspace directories:\n{{directories}}': + 'Diretórios atuais do workspace:\n{{directories}}', + + // ============================================================================ + // Commands - Docs + // ============================================================================ + 'Please open the following URL in your browser to view the documentation:\n{{url}}': + 'Abra a seguinte URL no seu navegador para ver a documentação:\n{{url}}', + 'Opening documentation in your browser: {{url}}': + 'Abrindo documentação no seu navegador: {{url}}', + + // ============================================================================ + // Dialogs - Tool Confirmation + // ============================================================================ + 'Do you want to proceed?': 'Você deseja prosseguir?', + 'Yes, allow once': 'Sim, permitir uma vez', + 'Allow always': 'Permitir sempre', + No: 'Não', + 'No (esc)': 'Não (esc)', + 'Yes, allow always for this session': 'Sim, permitir sempre para esta sessão', + 'Modify in progress:': 'Modificação em progresso:', + 'Save and close external editor to continue': + 'Salve e feche o editor externo para continuar', + 'Apply this change?': 'Aplicar esta alteração?', + 'Yes, allow always': 'Sim, permitir sempre', + 'Modify with external editor': 'Modificar com editor externo', + 'No, suggest changes (esc)': 'Não, sugerir alterações (esc)', + "Allow execution of: '{{command}}'?": + "Permitir a execução de: '{{command}}'?", + 'Yes, allow always ...': 'Sim, permitir sempre ...', + 'Yes, and auto-accept edits': 'Sim, e aceitar edições automaticamente', + 'Yes, and manually approve edits': 'Sim, e aprovar edições manualmente', + 'No, keep planning (esc)': 'Não, continuar planejando (esc)', + 'URLs to fetch:': 'URLs para buscar:', + 'MCP Server: {{server}}': 'Servidor MCP: {{server}}', + 'Tool: {{tool}}': 'Ferramenta: {{tool}}', + 'Allow execution of MCP tool "{{tool}}" from server "{{server}}"?': + 'Permitir a execução da ferramenta MCP "{{tool}}" do servidor "{{server}}"?', + 'Yes, always allow tool "{{tool}}" from server "{{server}}"': + 'Sim, sempre permitir a ferramenta "{{tool}}" do servidor "{{server}}"', + 'Yes, always allow all tools from server "{{server}}"': + 'Sim, sempre permitir todas as ferramentas do servidor "{{server}}"', + + // ============================================================================ + // Dialogs - Shell Confirmation + // ============================================================================ + 'Shell Command Execution': 'Execução de Comando Shell', + 'A custom command wants to run the following shell commands:': + 'Um comando personalizado deseja executar os seguintes comandos shell:', + + // ============================================================================ + // Dialogs - Pro Quota + // ============================================================================ + 'Pro quota limit reached for {{model}}.': + 'Limite de cota Pro atingido para {{model}}.', + 'Change auth (executes the /auth command)': + 'Alterar autenticação (executa o comando /auth)', + 'Continue with {{model}}': 'Continuar com {{model}}', + + // ============================================================================ + // Dialogs - Welcome Back + // ============================================================================ + 'Current Plan:': 'Plano Atual:', + 'Progress: {{done}}/{{total}} tasks completed': + 'Progresso: {{done}}/{{total}} tarefas concluídas', + ', {{inProgress}} in progress': ', {{inProgress}} em progresso', + 'Pending Tasks:': 'Tarefas Pendentes:', + 'What would you like to do?': 'O que você gostaria de fazer?', + 'Choose how to proceed with your session:': + 'Escolha como proceder com sua sessão:', + 'Start new chat session': 'Iniciar nova sessão de chat', + 'Continue previous conversation': 'Continuar conversa anterior', + '👋 Welcome back! (Last updated: {{timeAgo}})': + '👋 Bem-vindo de volta! (Última atualização: {{timeAgo}})', + '🎯 Overall Goal:': '🎯 Objetivo Geral:', + + // ============================================================================ + // Dialogs - Auth + // ============================================================================ + 'Get started': 'Começar', + 'How would you like to authenticate for this project?': + 'Como você gostaria de se autenticar para este projeto?', + 'OpenAI API key is required to use OpenAI authentication.': + 'A chave da API do OpenAI é necessária para usar a autenticação do OpenAI.', + 'You must select an auth method to proceed. Press Ctrl+C again to exit.': + 'Você deve selecionar um método de autenticação para prosseguir. Pressione Ctrl+C novamente para sair.', + '(Use Enter to Set Auth)': '(Use Enter para Definir Autenticação)', + 'Terms of Services and Privacy Notice for Qwen Code': + 'Termos de Serviço e Aviso de Privacidade do Qwen Code', + 'Qwen OAuth': 'Qwen OAuth', + OpenAI: 'OpenAI', + 'Failed to login. Message: {{message}}': + 'Falha ao fazer login. Mensagem: {{message}}', + 'Authentication is enforced to be {{enforcedType}}, but you are currently using {{currentType}}.': + 'A autenticação é forçada para {{enforcedType}}, mas você está usando {{currentType}} no momento.', + 'Qwen OAuth authentication timed out. Please try again.': + 'A autenticação Qwen OAuth expirou. Tente novamente.', + 'Qwen OAuth authentication cancelled.': 'Autenticação Qwen OAuth cancelada.', + 'Qwen OAuth Authentication': 'Autenticação Qwen OAuth', + 'Please visit this URL to authorize:': 'Visite esta URL para autorizar:', + 'Or scan the QR code below:': 'Ou escaneie o código QR abaixo:', + 'Waiting for authorization': 'Aguardando autorização', + 'Time remaining:': 'Tempo restante:', + '(Press ESC or CTRL+C to cancel)': '(Pressione ESC ou CTRL+C para cancelar)', + 'Qwen OAuth Authentication Timeout': + 'Tempo Limite de Autenticação Qwen OAuth', + 'OAuth token expired (over {{seconds}} seconds). Please select authentication method again.': + 'Token OAuth expirado (mais de {{seconds}} segundos). Selecione o método de autenticação novamente.', + 'Press any key to return to authentication type selection.': + 'Pressione qualquer tecla para retornar à seleção do tipo de autenticação.', + 'Waiting for Qwen OAuth authentication...': + 'Aguardando autenticação Qwen OAuth...', + 'Note: Your existing API key in settings.json will not be cleared when using Qwen OAuth. You can switch back to OpenAI authentication later if needed.': + 'Nota: Sua chave de API existente no settings.json não será limpa ao usar o Qwen OAuth. Você pode voltar para a autenticação do OpenAI mais tarde, se necessário.', + 'Authentication timed out. Please try again.': + 'A autenticação expirou. Tente novamente.', + 'Waiting for auth... (Press ESC or CTRL+C to cancel)': + 'Aguardando autenticação... (Pressione ESC ou CTRL+C para cancelar)', + 'Missing API key for OpenAI-compatible auth. Set settings.security.auth.apiKey, or set the {{envKeyHint}} environment variable.': + 'Chave de API ausente para autenticação compatível com OpenAI. Defina settings.security.auth.apiKey ou a variável de ambiente {{envKeyHint}}.', + '{{envKeyHint}} environment variable not found.': + 'Variável de ambiente {{envKeyHint}} não encontrada.', + '{{envKeyHint}} environment variable not found. Please set it in your .env file or environment variables.': + 'Variável de ambiente {{envKeyHint}} não encontrada. Defina-a no seu arquivo .env ou variáveis de ambiente.', + '{{envKeyHint}} environment variable not found (or set settings.security.auth.apiKey). Please set it in your .env file or environment variables.': + 'Variável de ambiente {{envKeyHint}} não encontrada (ou defina settings.security.auth.apiKey). Defina-a no seu arquivo .env ou variáveis de ambiente.', + 'Missing API key for OpenAI-compatible auth. Set the {{envKeyHint}} environment variable.': + 'Chave de API ausente para autenticação compatível com OpenAI. Defina a variável de ambiente {{envKeyHint}}.', + 'Anthropic provider missing required baseUrl in modelProviders[].baseUrl.': + 'Provedor Anthropic sem a baseUrl necessária em modelProviders[].baseUrl.', + 'ANTHROPIC_BASE_URL environment variable not found.': + 'Variável de ambiente ANTHROPIC_BASE_URL não encontrada.', + 'Invalid auth method selected.': + 'Método de autenticação inválido selecionado.', + 'Failed to authenticate. Message: {{message}}': + 'Falha ao autenticar. Mensagem: {{message}}', + 'Authenticated successfully with {{authType}} credentials.': + 'Autenticado com sucesso com credenciais {{authType}}.', + 'Invalid QWEN_DEFAULT_AUTH_TYPE value: "{{value}}". Valid values are: {{validValues}}': + 'Valor QWEN_DEFAULT_AUTH_TYPE inválido: "{{value}}". Valores válidos são: {{validValues}}', + 'OpenAI Configuration Required': 'Configuração do OpenAI Necessária', + 'Please enter your OpenAI configuration. You can get an API key from': + 'Insira sua configuração do OpenAI. Você pode obter uma chave de API de', + 'API Key:': 'Chave da API:', + 'Invalid credentials: {{errorMessage}}': + 'Credenciais inválidas: {{errorMessage}}', + 'Failed to validate credentials': 'Falha ao validar credenciais', + 'Press Enter to continue, Tab/↑↓ to navigate, Esc to cancel': + 'Pressione Enter para continuar, Tab/↑↓ para navegar, Esc para cancelar', + + // ============================================================================ + // Dialogs - Model + // ============================================================================ + 'Select Model': 'Selecionar Modelo', + '(Press Esc to close)': '(Pressione Esc para fechar)', + 'Current (effective) configuration': 'Configuração atual (efetiva)', + AuthType: 'AuthType', + 'API Key': 'Chave da API', + unset: 'não definido', + '(default)': '(padrão)', + '(set)': '(definido)', + '(not set)': '(não definido)', + "Failed to switch model to '{{modelId}}'.\n\n{{error}}": + "Falha ao trocar o modelo para '{{modelId}}'.\n\n{{error}}", + 'The latest Qwen Coder model from Alibaba Cloud ModelStudio (version: qwen3-coder-plus-2025-09-23)': + 'O modelo Qwen Coder mais recente do Alibaba Cloud ModelStudio (versão: qwen3-coder-plus-2025-09-23)', + 'The latest Qwen Vision model from Alibaba Cloud ModelStudio (version: qwen3-vl-plus-2025-09-23)': + 'O modelo Qwen Vision mais recente do Alibaba Cloud ModelStudio (versão: qwen3-vl-plus-2025-09-23)', + + // ============================================================================ + // Dialogs - Permissions + // ============================================================================ + 'Manage folder trust settings': + 'Gerenciar configurações de confiança de pasta', + + // ============================================================================ + // Status Bar + // ============================================================================ + 'Using:': 'Usando:', + '{{count}} open file': '{{count}} arquivo aberto', + '{{count}} open files': '{{count}} arquivos abertos', + '(ctrl+g to view)': '(ctrl+g para ver)', + '{{count}} {{name}} file': '{{count}} arquivo {{name}}', + '{{count}} {{name}} files': '{{count}} arquivos {{name}}', + '{{count}} MCP server': '{{count}} servidor MCP', + '{{count}} MCP servers': '{{count}} servidores MCP', + '{{count}} Blocked': '{{count}} Bloqueados', + '(ctrl+t to view)': '(ctrl+t para ver)', + '(ctrl+t to toggle)': '(ctrl+t para alternar)', + 'Press Ctrl+C again to exit.': 'Pressione Ctrl+C novamente para sair.', + 'Press Ctrl+D again to exit.': 'Pressione Ctrl+D novamente para sair.', + 'Press Esc again to clear.': 'Pressione Esc novamente para limpar.', + + // ============================================================================ + // MCP Status + // ============================================================================ + 'No MCP servers configured.': 'Nenhum servidor MCP configurado.', + 'Please view MCP documentation in your browser:': + 'Veja a documentação do MCP no seu navegador:', + 'or use the cli /docs command': 'ou use o comando cli /docs', + '⏳ MCP servers are starting up ({{count}} initializing)...': + '⏳ Servidores MCP estão iniciando ({{count}} inicializando)...', + 'Note: First startup may take longer. Tool availability will update automatically.': + 'Nota: A primeira inicialização pode demorar mais. A disponibilidade da ferramenta será atualizada automaticamente.', + 'Configured MCP servers:': 'Servidores MCP configurados:', + Ready: 'Pronto', + 'Starting... (first startup may take longer)': + 'Iniciando... (a primeira inicialização pode demorar mais)', + Disconnected: 'Desconectado', + '{{count}} tool': '{{count}} ferramenta', + '{{count}} tools': '{{count}} ferramentas', + '{{count}} prompt': '{{count}} prompt', + '{{count}} prompts': '{{count}} prompts', + '(from {{extensionName}})': '(de {{extensionName}})', + OAuth: 'OAuth', + 'OAuth expired': 'OAuth expirado', + 'OAuth not authenticated': 'OAuth não autenticado', + 'tools and prompts will appear when ready': + 'ferramentas e prompts aparecerão quando estiverem prontos', + '{{count}} tools cached': '{{count}} ferramentas em cache', + 'Tools:': 'Ferramentas:', + 'Parameters:': 'Parâmetros:', + 'Prompts:': 'Prompts:', + Blocked: 'Bloqueado', + '💡 Tips:': '💡 Dicas:', + Use: 'Use', + 'to show server and tool descriptions': + 'para mostrar descrições de servidores e ferramentas', + 'to show tool parameter schemas': + 'para mostrar esquemas de parâmetros de ferramentas', + 'to hide descriptions': 'para ocultar descrições', + 'to authenticate with OAuth-enabled servers': + 'para autenticar com servidores habilitados para OAuth', + Press: 'Pressione', + 'to toggle tool descriptions on/off': + 'para alternar descrições de ferramentas ligadas/desligadas', + "Starting OAuth authentication for MCP server '{{name}}'...": + "Iniciando autenticação OAuth para servidor MCP '{{name}}'...", + 'Restarting MCP servers...': 'Reiniciando servidores MCP...', + + // ============================================================================ + // Startup Tips + // ============================================================================ + 'Tips:': 'Dicas:', + 'Use /compress when the conversation gets long to summarize history and free up context.': + 'Use /compress quando a conversa ficar longa para resumir o histórico e liberar contexto.', + 'Start a fresh idea with /clear or /new; the previous session stays available in history.': + 'Comece uma nova ideia com /clear ou /new; a sessão anterior permanece disponível no histórico.', + 'Use /bug to submit issues to the maintainers when something goes off.': + 'Use /bug para enviar problemas aos mantenedores quando algo der errado.', + 'Switch auth type quickly with /auth.': + 'Troque o tipo de autenticação rapidamente com /auth.', + 'You can run any shell commands from Qwen Code using ! (e.g. !ls).': + 'Você pode executar quaisquer comandos shell do Qwen Code usando ! (ex: !ls).', + 'Type / to open the command popup; Tab autocompletes slash commands and saved prompts.': + 'Digite / para abrir o popup de comandos; Tab autocompleta comandos de barra e prompts salvos.', + 'You can resume a previous conversation by running qwen --continue or qwen --resume.': + 'Você pode retomar uma conversa anterior executando qwen --continue ou qwen --resume.', + 'You can switch permission mode quickly with Shift+Tab or /approval-mode.': + 'Você pode alternar o modo de permissão rapidamente com Shift+Tab ou /approval-mode.', + + // ============================================================================ + // Exit Screen / Stats + // ============================================================================ + 'Agent powering down. Goodbye!': 'Agente desligando. Adeus!', + 'To continue this session, run': 'Para continuar esta sessão, execute', + 'Interaction Summary': 'Resumo da Interação', + 'Session ID:': 'ID da Sessão:', + 'Tool Calls:': 'Chamadas de Ferramenta:', + 'Success Rate:': 'Taxa de Sucesso:', + 'User Agreement:': 'Acordo do Usuário:', + reviewed: 'revisado', + 'Code Changes:': 'Alterações de Código:', + Performance: 'Desempenho', + 'Wall Time:': 'Tempo Total:', + 'Agent Active:': 'Agente Ativo:', + 'API Time:': 'Tempo de API:', + 'Tool Time:': 'Tempo de Ferramenta:', + 'Session Stats': 'Estatísticas da Sessão', + 'Model Usage': 'Uso do Modelo', + Reqs: 'Reqs', + 'Input Tokens': 'Tokens de Entrada', + 'Output Tokens': 'Tokens de Saída', + 'Savings Highlight:': 'Destaque de Economia:', + 'of input tokens were served from the cache, reducing costs.': + 'de tokens de entrada foram servidos do cache, reduzindo custos.', + 'Tip: For a full token breakdown, run `/stats model`.': + 'Dica: Para um detalhamento completo de tokens, execute `/stats model`.', + 'Model Stats For Nerds': 'Estatísticas de Modelo Para Nerds', + 'Tool Stats For Nerds': 'Estatísticas de Ferramenta Para Nerds', + Metric: 'Métrica', + API: 'API', + Requests: 'Solicitações', + Errors: 'Erros', + 'Avg Latency': 'Latência Média', + Tokens: 'Tokens', + Total: 'Total', + Prompt: 'Prompt', + Cached: 'Cacheado', + Thoughts: 'Pensamentos', + Tool: 'Ferramenta', + Output: 'Saída', + 'No API calls have been made in this session.': + 'Nenhuma chamada de API foi feita nesta sessão.', + 'Tool Name': 'Nome da Ferramenta', + Calls: 'Chamadas', + 'Success Rate': 'Taxa de Sucesso', + 'Avg Duration': 'Duração Média', + 'User Decision Summary': 'Resumo de Decisão do Usuário', + 'Total Reviewed Suggestions:': 'Total de Sugestões Revisadas:', + ' » Accepted:': ' » Aceitas:', + ' » Rejected:': ' » Rejeitadas:', + ' » Modified:': ' » Modificadas:', + ' Overall Agreement Rate:': ' Taxa Geral de Acordo:', + 'No tool calls have been made in this session.': + 'Nenhuma chamada de ferramenta foi feita nesta sessão.', + 'Session start time is unavailable, cannot calculate stats.': + 'Hora de início da sessão indisponível, não é possível calcular estatísticas.', + + // ============================================================================ + // Command Format Migration + // ============================================================================ + 'Command Format Migration': 'Migração de Formato de Comando', + 'Found {{count}} TOML command file:': + 'Encontrado {{count}} arquivo de comando TOML:', + 'Found {{count}} TOML command files:': + 'Encontrados {{count}} arquivos de comando TOML:', + '... and {{count}} more': '... e mais {{count}}', + 'The TOML format is deprecated. Would you like to migrate them to Markdown format?': + 'O formato TOML está obsoleto. Você gostaria de migrá-los para o formato Markdown?', + '(Backups will be created and original files will be preserved)': + '(Backups serão criados e arquivos originais serão preservados)', + + // ============================================================================ + // Loading Phrases + // ============================================================================ + 'Waiting for user confirmation...': 'Aguardando confirmação do usuário...', + '(esc to cancel, {{time}})': '(esc para cancelar, {{time}})', + + WITTY_LOADING_PHRASES: [ + 'Estou com sorte', + 'Enviando maravilhas...', + 'Pintando os serifos de volta...', + 'Navegando pelo mofo limoso...', + 'Consultando os espíritos digitais...', + 'Reticulando splines...', + 'Aquecendo os hamsters da IA...', + 'Perguntando à concha mágica...', + 'Gerando réplica espirituosa...', + 'Polindo os algoritmos...', + 'Não apresse a perfeição (ou meu código)...', + 'Preparando bytes frescos...', + 'Contando elétrons...', + 'Engajando processadores cognitivos...', + 'Verificando erros de sintaxe no universo...', + 'Um momento, otimizando o humor...', + 'Embaralhando piadas...', + 'Desembaraçando redes neurais...', + 'Compilando brilhantismo...', + 'Carregando humor.exe...', + 'Invocando a nuvem da sabedoria...', + 'Preparando uma resposta espirituosa...', + 'Só um segundo, estou depurando a realidade...', + 'Confundindo as opções...', + 'Sintonizando as frequências cósmicas...', + 'Criando uma resposta digna da sua paciência...', + 'Compilando os 1s e 0s...', + 'Resolvendo dependências... e crises existenciais...', + 'Desfragmentando memórias... tanto RAM quanto pessoais...', + 'Reiniciando o módulo de humor...', + 'Fazendo cache do essencial (principalmente memes de gatos)...', + 'Otimizando para velocidade absurda', + 'Trocando bits... não conte para os bytes...', + 'Coletando lixo... volto já...', + 'Montando a internet...', + 'Convertendo café em código...', + 'Atualizando a sintaxe da realidade...', + 'Reconectando as sinapses...', + 'Procurando um ponto e vírgula perdido...', + 'Lubrificando as engrenagens da máquina...', + 'Pré-aquecendo os servidores...', + 'Calibrando o capacitor de fluxo...', + 'Engajando o motor de improbabilidade...', + 'Canalizando a Força...', + 'Alinhando as estrelas para uma resposta ideal...', + 'Assim dizemos todos...', + 'Carregando a próxima grande ideia...', + 'Só um momento, estou na zona...', + 'Preparando para deslumbrá-lo com brilhantismo...', + 'Só um tique, estou polindo minha inteligência...', + 'Segure firme, estou criando uma obra-prima...', + 'Só um instante, estou depurando o universo...', + 'Só um momento, estou alinhando os pixels...', + 'Só um segundo, estou otimizando o humor...', + 'Só um momento, estou ajustando os algoritmos...', + 'Velocidade de dobra engajada...', + 'Minerando mais cristais de Dilithium...', + 'Não entre em pânico...', + 'Seguindo o coelho branco...', + 'A verdade está lá fora... em algum lugar...', + 'Soprando o cartucho...', + 'Carregando... Faça um barrel roll!', + 'Aguardando o respawn...', + 'Terminando a Kessel Run em menos de 12 parsecs...', + 'O bolo não é uma mentira, só ainda está carregando...', + 'Mexendo na tela de criação de personagem...', + 'Só um momento, estou encontrando o meme certo...', + "Pressionando 'A' para continuar...", + 'Pastoreando gatos digitais...', + 'Polindo os pixels...', + 'Encontrando um trocadilho adequado para a tela de carregamento...', + 'Distraindo você com esta frase espirituosa...', + 'Quase lá... provavelmente...', + 'Nossos hamsters estão trabalhando o mais rápido que podem...', + 'Dando um tapinha na cabeça do Cloudy...', + 'Acariciando o gato...', + 'Dando um Rickroll no meu chefe...', + 'Never gonna give you up, never gonna let you down...', + 'Tocando o baixo...', + 'Provando as amoras...', + 'Estou indo longe, estou indo pela velocidade...', + 'Isso é vida real? Ou é apenas fantasia?...', + 'Tenho um bom pressentimento sobre isso...', + 'Cutucando o urso...', + 'Fazendo pesquisa sobre os últimos memes...', + 'Descobrindo como tornar isso mais espirituoso...', + 'Hmmm... deixe-me pensar...', + 'O que você chama de um peixe sem olhos? Um pxe...', + 'Por que o computador foi à terapia? Porque tinha muitos bytes...', + 'Por que programadores não gostam da natureza? Porque tem muitos bugs...', + 'Por que programadores preferem o modo escuro? Porque a luz atrai bugs...', + 'Por que o desenvolvedor faliu? Porque usou todo o seu cache...', + 'O que você pode fazer com um lápis quebrado? Nada, ele não tem ponta...', + 'Aplicando manutenção percussiva...', + 'Procurando a orientação correta do USB...', + 'Garantindo que a fumaça mágica permaneça dentro dos fios...', + 'Tentando sair do Vim...', + 'Girando a roda do hamster...', + 'Isso não é um bug, é um recurso não documentado...', + 'Engajar.', + 'Eu voltarei... com uma resposta.', + 'Meu outro processo é uma TARDIS...', + 'Comungando com o espírito da máquina...', + 'Deixando os pensamentos marinarem...', + 'Lembrei agora onde coloquei minhas chaves...', + 'Ponderando a orbe...', + 'Eu vi coisas que vocês não acreditariam... como um usuário que lê mensagens de carregamento.', + 'Iniciando olhar pensativo...', + 'Qual é o lanche favorito de um computador? Microchips.', + 'Por que desenvolvedores Java usam óculos? Porque eles não C#.', + 'Carregando o laser... pew pew!', + 'Dividindo por zero... só brincando!', + 'Procurando por um supervisor adulto... digo, processando.', + 'Fazendo bip boop.', + 'Buffering... porque até as IAs precisam de um momento.', + 'Entrelaçando partículas quânticas para uma resposta mais rápida...', + 'Polindo o cromo... nos algoritmos.', + 'Você não está entretido? (Trabalhando nisso!)', + 'Invocando os gremlins do código... para ajudar, é claro.', + 'Só esperando o som da conexão discada terminar...', + 'Recalibrando o humorômetro.', + 'Minha outra tela de carregamento é ainda mais engraçada.', + 'Tenho quase certeza que tem um gato andando no teclado em algum lugar...', + 'Aumentando... Aumentando... Ainda carregando.', + 'Não é um bug, é um recurso... desta tela de carregamento.', + 'Você já tentou desligar e ligar de novo? (A tela de carregamento, não eu.)', + 'Construindo pilares adicionais...', + ], + + // ============================================================================ + // Extension Settings Input + // ============================================================================ + 'Enter value...': 'Digite o valor...', + 'Enter sensitive value...': 'Digite o valor sensível...', + 'Press Enter to submit, Escape to cancel': + 'Pressione Enter para enviar, Escape para cancelar', + + // ============================================================================ + // Command Migration Tool + // ============================================================================ + 'Markdown file already exists: {{filename}}': + 'Arquivo Markdown já existe: {{filename}}', + 'TOML Command Format Deprecation Notice': + 'Aviso de Obsolescência do Formato de Comando TOML', + 'Found {{count}} command file(s) in TOML format:': + 'Encontrado(s) {{count}} arquivo(s) de comando no formato TOML:', + 'The TOML format for commands is being deprecated in favor of Markdown format.': + 'O formato TOML para comandos está sendo descontinuado em favor do formato Markdown.', + 'Markdown format is more readable and easier to edit.': + 'O formato Markdown é mais legível e fácil de editar.', + 'You can migrate these files automatically using:': + 'Você pode migrar esses arquivos automaticamente usando:', + 'Or manually convert each file:': 'Ou converter manualmente cada arquivo:', + 'TOML: prompt = "..." / description = "..."': + 'TOML: prompt = "..." / description = "..."', + 'Markdown: YAML frontmatter + content': + 'Markdown: YAML frontmatter + conteúdo', + 'The migration tool will:': 'A ferramenta de migração irá:', + 'Convert TOML files to Markdown': 'Converter arquivos TOML para Markdown', + 'Create backups of original files': 'Criar backups dos arquivos originais', + 'Preserve all command functionality': + 'Preservar toda a funcionalidade do comando', + 'TOML format will continue to work for now, but migration is recommended.': + 'O formato TOML continuará a funcionar por enquanto, mas a migração é recomendada.', + + // ============================================================================ + // Extensions - Explore Command + // ============================================================================ + 'Open extensions page in your browser': + 'Abrir página de extensões no seu navegador', + 'Unknown extensions source: {{source}}.': + 'Fonte de extensões desconhecida: {{source}}.', + 'Would open extensions page in your browser: {{url}} (skipped in test environment)': + 'Abriria a página de extensões no seu navegador: {{url}} (pulado no ambiente de teste)', + 'View available extensions at {{url}}': + 'Ver extensões disponíveis em {{url}}', + 'Opening extensions page in your browser: {{url}}': + 'Abrindo página de extensões no seu navegador: {{url}}', + 'Failed to open browser. Check out the extensions gallery at {{url}}': + 'Falha ao abrir o navegador. Confira a galeria de extensões em {{url}}', +}; diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index e659115f7..8bdee0b5c 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -496,6 +496,17 @@ export default { 'Either an extension name or --all must be provided': 'Необходимо указать имя расширения или --all', 'Lists installed extensions.': 'Показывает установленные расширения.', + 'Path:': 'Путь:', + 'Source:': 'Источник:', + 'Type:': 'Тип:', + 'Ref:': 'Ссылка:', + 'Release tag:': 'Тег релиза:', + 'Enabled (User):': 'Включено (Пользователь):', + 'Enabled (Workspace):': 'Включено (Рабочее пространство):', + 'Context files:': 'Контекстные файлы:', + 'Skills:': 'Навыки:', + 'Agents:': 'Агенты:', + 'MCP servers:': 'MCP-серверы:', 'Link extension failed to install.': 'Не удалось установить связанное расширение.', 'Extension "{{name}}" linked successfully and enabled.': @@ -580,8 +591,8 @@ export default { // ============================================================================ // Команды - Язык // ============================================================================ - 'Invalid language. Available: en-US, zh-CN': - 'Неверный язык. Доступны: en-US, zh-CN, ru-RU', + 'Invalid language. Available: {{options}}': + 'Недопустимый язык. Доступны: {{options}}', 'Language subcommands do not accept additional arguments.': 'Подкоманды языка не принимают дополнительных аргументов.', 'Current UI language: {{lang}}': 'Текущий язык интерфейса: {{lang}}', @@ -589,13 +600,14 @@ export default { 'LLM output language not set': 'Язык вывода LLM не установлен', 'Set UI language': 'Установка языка интерфейса', 'Set LLM output language': 'Установка языка вывода LLM', - 'Usage: /language ui [zh-CN|en-US]': - 'Использование: /language ui [zh-CN|en-US|ru-RU]', + 'Usage: /language ui [{{options}}]': + 'Использование: /language ui [{{options}}]', 'Usage: /language output ': 'Использование: /language output ', 'Example: /language output 中文': 'Пример: /language output 中文', 'Example: /language output English': 'Пример: /language output English', 'Example: /language output 日本語': 'Пример: /language output 日本語', + 'Example: /language output Português': 'Пример: /language output Português', 'UI language changed to {{lang}}': 'Язык интерфейса изменен на {{lang}}', 'LLM output language set to {{lang}}': 'Язык вывода LLM установлен на {{lang}}', @@ -611,12 +623,7 @@ export default { 'To request additional UI language packs, please open an issue on GitHub.': 'Для запроса дополнительных языковых пакетов интерфейса, пожалуйста, создайте обращение на GitHub.', 'Available options:': 'Доступные варианты:', - ' - zh-CN: Simplified Chinese': ' - zh-CN: Упрощенный китайский', - ' - en-US: English': ' - en-US: Английский', - 'Set UI language to Simplified Chinese (zh-CN)': - 'Установить язык интерфейса на упрощенный китайский (zh-CN)', - 'Set UI language to English (en-US)': - 'Установить язык интерфейса на английский (en-US)', + 'Set UI language to {{name}}': 'Установить язык интерфейса на {{name}}', // ============================================================================ // Команды - Режим подтверждения diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index df1c15f84..4f0523d7d 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -469,6 +469,17 @@ export default { 'Either an extension name or --all must be provided': '必须提供扩展名称或 --all', 'Lists installed extensions.': '列出已安装的扩展。', + 'Path:': '路径:', + 'Source:': '来源:', + 'Type:': '类型:', + 'Ref:': '引用:', + 'Release tag:': '发布标签:', + 'Enabled (User):': '已启用(用户):', + 'Enabled (Workspace):': '已启用(工作区):', + 'Context files:': '上下文文件:', + 'Skills:': '技能:', + 'Agents:': '代理:', + 'MCP servers:': 'MCP 服务器:', 'Link extension failed to install.': '链接扩展安装失败。', 'Extension "{{name}}" linked successfully and enabled.': '扩展 "{{name}}" 链接成功并已启用。', @@ -548,8 +559,8 @@ export default { // ============================================================================ // Commands - Language // ============================================================================ - 'Invalid language. Available: en-US, zh-CN': - '无效的语言。可用选项:en-US, zh-CN', + 'Invalid language. Available: {{options}}': + '无效的语言。可用选项:{{options}}', 'Language subcommands do not accept additional arguments.': '语言子命令不接受额外参数', 'Current UI language: {{lang}}': '当前 UI 语言:{{lang}}', @@ -557,11 +568,12 @@ export default { 'LLM output language not set': '未设置 LLM 输出语言', 'Set UI language': '设置 UI 语言', 'Set LLM output language': '设置 LLM 输出语言', - 'Usage: /language ui [zh-CN|en-US]': '用法:/language ui [zh-CN|en-US]', + 'Usage: /language ui [{{options}}]': '用法:/language ui [{{options}}]', 'Usage: /language output ': '用法:/language output <语言>', 'Example: /language output 中文': '示例:/language output 中文', 'Example: /language output English': '示例:/language output English', 'Example: /language output 日本語': '示例:/language output 日本語', + 'Example: /language output Português': '示例:/language output Português', 'UI language changed to {{lang}}': 'UI 语言已更改为 {{lang}}', 'LLM output language set to {{lang}}': 'LLM 输出语言已设置为 {{lang}}', 'LLM output language rule file generated at {{path}}': @@ -575,11 +587,7 @@ export default { 'To request additional UI language packs, please open an issue on GitHub.': '如需请求其他 UI 语言包,请在 GitHub 上提交 issue', 'Available options:': '可用选项:', - ' - zh-CN: Simplified Chinese': ' - zh-CN: 简体中文', - ' - en-US: English': ' - en-US: English', - 'Set UI language to Simplified Chinese (zh-CN)': - '将 UI 语言设置为简体中文 (zh-CN)', - 'Set UI language to English (en-US)': '将 UI 语言设置为英语 (en-US)', + 'Set UI language to {{name}}': '将 UI 语言设置为 {{name}}', // ============================================================================ // Commands - Approval Mode diff --git a/packages/cli/src/ui/commands/languageCommand.test.ts b/packages/cli/src/ui/commands/languageCommand.test.ts index 9234773eb..e8ebaac01 100644 --- a/packages/cli/src/ui/commands/languageCommand.test.ts +++ b/packages/cli/src/ui/commands/languageCommand.test.ts @@ -21,6 +21,8 @@ vi.mock('../../i18n/index.js', () => ({ en: 'English', ru: 'Russian', de: 'German', + ja: 'Japanese', + pt: 'Portuguese', }; return map[locale] || 'English'; }), @@ -72,6 +74,7 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => { // Import modules after mocking import * as i18n from '../../i18n/index.js'; +import { SUPPORTED_LANGUAGES } from '../../i18n/languages.js'; import { languageCommand } from './languageCommand.js'; import { initializeLlmOutputLanguage } from '../../utils/languageUtils.js'; @@ -565,10 +568,9 @@ describe('languageCommand', () => { it('should have nested language subcommands', () => { const nestedNames = uiSubcommand?.subCommands?.map((c) => c.name); - expect(nestedNames).toContain('zh-CN'); - expect(nestedNames).toContain('en-US'); - expect(nestedNames).toContain('ru-RU'); - expect(nestedNames).toContain('de-DE'); + for (const lang of SUPPORTED_LANGUAGES) { + expect(nestedNames).toContain(lang.id); + } }); it('should have action that sets language', async () => { @@ -678,6 +680,24 @@ describe('languageCommand', () => { }); }); + const jaJPSubcommand = uiSubcommand?.subCommands?.find( + (c) => c.name === 'ja-JP', + ); + it('ja-JP action should set Japanese', async () => { + if (!jaJPSubcommand?.action) { + throw new Error('ja-JP subcommand must have an action.'); + } + + const result = await jaJPSubcommand.action(mockContext, ''); + + expect(i18n.setLanguageAsync).toHaveBeenCalledWith('ja'); + expect(result).toEqual({ + type: 'message', + messageType: 'info', + content: expect.stringContaining('UI language changed'), + }); + }); + it('should reject extra arguments', async () => { if (!zhCNSubcommand?.action) { throw new Error('zh-CN subcommand must have an action.'); @@ -798,5 +818,31 @@ describe('languageCommand', () => { 'utf-8', ); }); + + it('should detect Japanese locale and create Japanese rule file', () => { + vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(i18n.detectSystemLanguage).mockReturnValue('ja'); + + initializeLlmOutputLanguage(); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining('output-language.md'), + expect.stringContaining('Japanese'), + 'utf-8', + ); + }); + + it('should detect Portuguese locale and create Portuguese rule file', () => { + vi.mocked(fs.existsSync).mockReturnValue(false); + vi.mocked(i18n.detectSystemLanguage).mockReturnValue('pt'); + + initializeLlmOutputLanguage(); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining('output-language.md'), + expect.stringContaining('Portuguese'), + 'utf-8', + ); + }); }); }); diff --git a/packages/cli/src/ui/commands/languageCommand.ts b/packages/cli/src/ui/commands/languageCommand.ts index e4158ce5c..56b08e1bb 100644 --- a/packages/cli/src/ui/commands/languageCommand.ts +++ b/packages/cli/src/ui/commands/languageCommand.ts @@ -18,7 +18,10 @@ import { type SupportedLanguage, t, } from '../../i18n/index.js'; -import { SUPPORTED_LANGUAGES } from '../../i18n/languages.js'; +import { + SUPPORTED_LANGUAGES, + getSupportedLanguageIds, +} from '../../i18n/languages.js'; import { OUTPUT_LANGUAGE_AUTO, isAutoLanguage, @@ -62,11 +65,14 @@ function parseUiLanguageArg(input: string): SupportedLanguage | null { } /** - * Formats a UI language code for display (e.g., "zh" -> "Chinese(zh-CN)"). + * Formats a UI language code for display (e.g., "zh" -> "中文 (Chinese) [zh-CN]"). */ function formatUiLanguageDisplay(lang: SupportedLanguage): string { const option = SUPPORTED_LANGUAGES.find((o) => o.code === lang); - return option ? `${option.fullName}(${option.id})` : lang; + if (!option) return lang; + return option.nativeName && option.nativeName !== option.fullName + ? `${option.nativeName} (${option.fullName}) [${option.id}]` + : `${option.fullName} [${option.id}]`; } /** @@ -219,7 +225,7 @@ export const languageCommand: SlashCommand = { messageType: 'error', content: [ t('Invalid command. Available subcommands:'), - ` - /language ui [${SUPPORTED_LANGUAGES.map((o) => o.id).join('|')}] - ${t('Set UI language')}`, + ` - /language ui [${getSupportedLanguageIds()}] - ${t('Set UI language')}`, ` - /language output - ${t('Set LLM output language')}`, ].join('\n'), }; @@ -245,7 +251,7 @@ export const languageCommand: SlashCommand = { t('Current LLM output language: {{lang}}', { lang: outputLangDisplay }), '', t('Available subcommands:'), - ` /language ui [${SUPPORTED_LANGUAGES.map((o) => o.id).join('|')}] - ${t('Set UI language')}`, + ` /language ui [${getSupportedLanguageIds()}] - ${t('Set UI language')}`, ` /language output - ${t('Set LLM output language')}`, ].join('\n'), }; @@ -274,12 +280,12 @@ export const languageCommand: SlashCommand = { t('Set UI language'), '', t('Usage: /language ui [{{options}}]', { - options: SUPPORTED_LANGUAGES.map((o) => o.id).join('|'), + options: getSupportedLanguageIds(), }), '', t('Available options:'), ...SUPPORTED_LANGUAGES.map( - (o) => ` - ${o.id}: ${t(o.fullName)}`, + (o) => ` - ${o.id}: ${o.nativeName || o.fullName}`, ), '', t( @@ -295,7 +301,7 @@ export const languageCommand: SlashCommand = { type: 'message', messageType: 'error', content: t('Invalid language. Available: {{options}}', { - options: SUPPORTED_LANGUAGES.map((o) => o.id).join(','), + options: getSupportedLanguageIds(','), }), }; } @@ -308,7 +314,9 @@ export const languageCommand: SlashCommand = { (lang): SlashCommand => ({ name: lang.id, get description() { - return t('Set UI language to {{name}}', { name: lang.fullName }); + return t('Set UI language to {{name}}', { + name: lang.nativeName || lang.fullName, + }); }, kind: CommandKind.BUILT_IN, action: async (context, args) => { diff --git a/packages/core/package.json b/packages/core/package.json index a7edfe600..802fce48f 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code-core", - "version": "0.8.0", + "version": "0.8.2", "description": "Qwen Code Core", "repository": { "type": "git", diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts index d05f216c3..3f0e17197 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.test.ts @@ -96,6 +96,7 @@ describe('AnthropicContentGenerator', () => { mockConfig = { getCliVersion: vi.fn().mockReturnValue('1.2.3'), + getProxy: vi.fn().mockReturnValue(undefined), } as unknown as Config; }); diff --git a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts index efaf7fb7a..d66787635 100644 --- a/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts +++ b/packages/core/src/core/anthropicContentGenerator/anthropicContentGenerator.ts @@ -29,6 +29,7 @@ import { RequestTokenEstimator } from '../../utils/request-tokenizer/index.js'; import { safeJsonParse } from '../../utils/safeJsonParse.js'; import { AnthropicContentConverter } from './converter.js'; import { buildRuntimeFetchOptions } from '../../utils/runtimeFetchOptions.js'; +import { DEFAULT_TIMEOUT } from '../openaiContentGenerator/constants.js'; type StreamingBlockState = { type: string; @@ -57,12 +58,15 @@ export class AnthropicContentGenerator implements ContentGenerator { const baseURL = contentGeneratorConfig.baseUrl; // Configure runtime options to ensure user-configured timeout works as expected // bodyTimeout is always disabled (0) to let Anthropic SDK timeout control the request - const runtimeOptions = buildRuntimeFetchOptions('anthropic'); + const runtimeOptions = buildRuntimeFetchOptions( + 'anthropic', + this.cliConfig.getProxy(), + ); this.client = new Anthropic({ apiKey: contentGeneratorConfig.apiKey, baseURL, - timeout: contentGeneratorConfig.timeout, + timeout: contentGeneratorConfig.timeout || DEFAULT_TIMEOUT, maxRetries: contentGeneratorConfig.maxRetries, defaultHeaders, ...runtimeOptions, diff --git a/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts b/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts index b38ebed63..09f4c83ca 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/dashscope.test.ts @@ -47,7 +47,7 @@ describe('DashScopeOpenAICompatibleProvider', () => { vi.clearAllMocks(); const mockedBuildRuntimeFetchOptions = buildRuntimeFetchOptions as unknown as MockedFunction< - (sdkType: 'openai') => OpenAIRuntimeFetchOptions + (sdkType: 'openai', proxyUrl?: string) => OpenAIRuntimeFetchOptions >; mockedBuildRuntimeFetchOptions.mockReturnValue(undefined); @@ -68,6 +68,7 @@ describe('DashScopeOpenAICompatibleProvider', () => { getContentGeneratorConfig: vi.fn().mockReturnValue({ disableCacheControl: false, }), + getProxy: vi.fn().mockReturnValue(undefined), } as unknown as Config; provider = new DashScopeOpenAICompatibleProvider( diff --git a/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts b/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts index c5c575d99..0a8458e0a 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/dashscope.ts @@ -71,7 +71,10 @@ export class DashScopeOpenAICompatibleProvider const defaultHeaders = this.buildHeaders(); // Configure fetch options to ensure user-configured timeout works as expected // bodyTimeout is always disabled (0) to let OpenAI SDK timeout control the request - const fetchOptions = buildRuntimeFetchOptions('openai'); + const fetchOptions = buildRuntimeFetchOptions( + 'openai', + this.cliConfig.getProxy(), + ); return new OpenAI({ apiKey, baseURL: baseUrl, diff --git a/packages/core/src/core/openaiContentGenerator/provider/default.test.ts b/packages/core/src/core/openaiContentGenerator/provider/default.test.ts index 6d2585bc7..fc921c7c0 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/default.test.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/default.test.ts @@ -45,7 +45,7 @@ describe('DefaultOpenAICompatibleProvider', () => { vi.clearAllMocks(); const mockedBuildRuntimeFetchOptions = buildRuntimeFetchOptions as unknown as MockedFunction< - (sdkType: 'openai') => OpenAIRuntimeFetchOptions + (sdkType: 'openai', proxyUrl?: string) => OpenAIRuntimeFetchOptions >; mockedBuildRuntimeFetchOptions.mockReturnValue(undefined); @@ -61,6 +61,7 @@ describe('DefaultOpenAICompatibleProvider', () => { // Mock Config mockCliConfig = { getCliVersion: vi.fn().mockReturnValue('1.0.0'), + getProxy: vi.fn().mockReturnValue(undefined), } as unknown as Config; provider = new DefaultOpenAICompatibleProvider( diff --git a/packages/core/src/core/openaiContentGenerator/provider/default.ts b/packages/core/src/core/openaiContentGenerator/provider/default.ts index 7e333f916..b7d8644c9 100644 --- a/packages/core/src/core/openaiContentGenerator/provider/default.ts +++ b/packages/core/src/core/openaiContentGenerator/provider/default.ts @@ -46,7 +46,10 @@ export class DefaultOpenAICompatibleProvider const defaultHeaders = this.buildHeaders(); // Configure fetch options to ensure user-configured timeout works as expected // bodyTimeout is always disabled (0) to let OpenAI SDK timeout control the request - const fetchOptions = buildRuntimeFetchOptions('openai'); + const fetchOptions = buildRuntimeFetchOptions( + 'openai', + this.cliConfig.getProxy(), + ); return new OpenAI({ apiKey, baseURL: baseUrl, diff --git a/packages/core/src/utils/runtimeFetchOptions.test.ts b/packages/core/src/utils/runtimeFetchOptions.test.ts new file mode 100644 index 000000000..3cb6efbd1 --- /dev/null +++ b/packages/core/src/utils/runtimeFetchOptions.test.ts @@ -0,0 +1,76 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { describe, it, expect, vi } from 'vitest'; +import { buildRuntimeFetchOptions } from './runtimeFetchOptions.js'; + +type UndiciOptions = Record; + +vi.mock('undici', () => { + class MockAgent { + options: UndiciOptions; + constructor(options: UndiciOptions) { + this.options = options; + } + } + + class MockProxyAgent { + options: UndiciOptions; + constructor(options: UndiciOptions) { + this.options = options; + } + } + + return { + Agent: MockAgent, + ProxyAgent: MockProxyAgent, + }; +}); + +describe('buildRuntimeFetchOptions (node runtime)', () => { + it('disables undici timeouts for Agent in OpenAI options', () => { + const result = buildRuntimeFetchOptions('openai'); + + expect(result).toBeDefined(); + expect(result && 'dispatcher' in result).toBe(true); + + const dispatcher = (result as { dispatcher?: { options?: UndiciOptions } }) + .dispatcher; + expect(dispatcher?.options).toMatchObject({ + headersTimeout: 0, + bodyTimeout: 0, + }); + }); + + it('uses ProxyAgent with disabled timeouts when proxy is set', () => { + const result = buildRuntimeFetchOptions('openai', 'http://proxy.local'); + + expect(result).toBeDefined(); + expect(result && 'dispatcher' in result).toBe(true); + + const dispatcher = (result as { dispatcher?: { options?: UndiciOptions } }) + .dispatcher; + expect(dispatcher?.options).toMatchObject({ + uri: 'http://proxy.local', + headersTimeout: 0, + bodyTimeout: 0, + }); + }); + + it('returns httpAgent with disabled timeouts for Anthropic options', () => { + const result = buildRuntimeFetchOptions('anthropic'); + + expect(result).toBeDefined(); + expect(result && 'httpAgent' in result).toBe(true); + + const httpAgent = (result as { httpAgent?: { options?: UndiciOptions } }) + .httpAgent; + expect(httpAgent?.options).toMatchObject({ + headersTimeout: 0, + bodyTimeout: 0, + }); + }); +}); diff --git a/packages/core/src/utils/runtimeFetchOptions.ts b/packages/core/src/utils/runtimeFetchOptions.ts index 1baa459e9..8eab8929f 100644 --- a/packages/core/src/utils/runtimeFetchOptions.ts +++ b/packages/core/src/utils/runtimeFetchOptions.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EnvHttpProxyAgent } from 'undici'; +import { Agent, ProxyAgent, type Dispatcher } from 'undici'; /** * JavaScript runtime type @@ -29,7 +29,7 @@ export function detectRuntime(): Runtime { */ export type OpenAIRuntimeFetchOptions = | { - dispatcher?: EnvHttpProxyAgent; + dispatcher?: Dispatcher; timeout?: false; } | undefined; @@ -54,12 +54,14 @@ export type SDKType = 'openai' | 'anthropic'; */ export function buildRuntimeFetchOptions( sdkType: 'openai', + proxyUrl?: string, ): OpenAIRuntimeFetchOptions; /** * Build runtime-specific fetch options for Anthropic SDK */ export function buildRuntimeFetchOptions( sdkType: 'anthropic', + proxyUrl?: string, ): AnthropicRuntimeFetchOptions; /** * Build runtime-specific fetch options based on the detected runtime and SDK type @@ -71,13 +73,14 @@ export function buildRuntimeFetchOptions( */ export function buildRuntimeFetchOptions( sdkType: SDKType, + proxyUrl?: string, ): OpenAIRuntimeFetchOptions | AnthropicRuntimeFetchOptions { const runtime = detectRuntime(); - // Always disable bodyTimeout (set to 0) to let SDK's timeout parameter - // control the total request time. bodyTimeout only monitors intervals between - // data chunks, not the total request time, so we disable it to ensure user-configured - // timeout works as expected for both streaming and non-streaming requests. + // Always disable undici timeouts (set to 0) to let SDK's timeout parameter + // control the total request time. bodyTimeout monitors intervals between data + // chunks, headersTimeout waits for response headers, so we disable both to + // ensure user-configured timeouts work as expected for long-running requests. switch (runtime) { case 'bun': { @@ -110,22 +113,17 @@ export function buildRuntimeFetchOptions( } case 'node': { - // Node.js: Use EnvHttpProxyAgent to configure proxy and disable bodyTimeout - // EnvHttpProxyAgent automatically reads proxy settings from environment variables - // (HTTP_PROXY, HTTPS_PROXY, NO_PROXY, etc.) to preserve proxy functionality - // bodyTimeout is always 0 (disabled) to let SDK timeout control the request + // Node.js: Use ProxyAgent when proxy is configured, otherwise Agent. + // undici timeouts are disabled to let SDK timeout control the request. try { - const agent = new EnvHttpProxyAgent({ - bodyTimeout: 0, // Disable to let SDK timeout control total request time - }); - + const dispatcher = createDispatcher(proxyUrl); if (sdkType === 'openai') { return { - dispatcher: agent, + dispatcher, }; } else { return { - httpAgent: agent, + httpAgent: dispatcher, }; } } catch { @@ -139,20 +137,16 @@ export function buildRuntimeFetchOptions( } default: { - // Unknown runtime: Try to use EnvHttpProxyAgent if available - // EnvHttpProxyAgent automatically reads proxy settings from environment variables + // Unknown runtime: Use ProxyAgent when proxy is configured, otherwise Agent. try { - const agent = new EnvHttpProxyAgent({ - bodyTimeout: 0, // Disable to let SDK timeout control total request time - }); - + const dispatcher = createDispatcher(proxyUrl); if (sdkType === 'openai') { return { - dispatcher: agent, + dispatcher, }; } else { return { - httpAgent: agent, + httpAgent: dispatcher, }; } } catch { @@ -165,3 +159,17 @@ export function buildRuntimeFetchOptions( } } } + +function createDispatcher(proxyUrl?: string): Dispatcher { + if (proxyUrl) { + return new ProxyAgent({ + uri: proxyUrl, + headersTimeout: 0, + bodyTimeout: 0, + }); + } + return new Agent({ + headersTimeout: 0, + bodyTimeout: 0, + }); +} diff --git a/packages/sdk-typescript/package.json b/packages/sdk-typescript/package.json index 7c4486d9d..6215f6a09 100644 --- a/packages/sdk-typescript/package.json +++ b/packages/sdk-typescript/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/sdk", - "version": "0.1.3", + "version": "0.1.4", "description": "TypeScript SDK for programmatic access to qwen-code CLI", "main": "./dist/index.cjs", "module": "./dist/index.mjs", diff --git a/packages/sdk-typescript/src/transport/ProcessTransport.ts b/packages/sdk-typescript/src/transport/ProcessTransport.ts index 6d71c69e0..ff4518833 100644 --- a/packages/sdk-typescript/src/transport/ProcessTransport.ts +++ b/packages/sdk-typescript/src/transport/ProcessTransport.ts @@ -282,9 +282,9 @@ export class ProcessTransport implements Transport { if (this.childStdin.writableEnded || this.childStdin.destroyed) { this.inputClosed = true; logger.warn( - `Cannot write to ${this.childStdin.writableEnded ? 'ended' : 'destroyed'} stdin stream, ignoring write`, + `Cannot write to ${this.childStdin.writableEnded ? 'ended' : 'destroyed'} stdin stream`, ); - return; + throw new Error('Input stream closed'); } if (this.childProcess?.killed || this.childProcess?.exitCode !== null) { @@ -319,10 +319,9 @@ export class ProcessTransport implements Transport { errorMsg.includes('write after end'); if (isStreamClosedError) { - // Soft-fail: log and return without throwing or changing ready state this.inputClosed = true; logger.warn(`Stream closed, cannot write: ${errorMsg}`); - return; + throw new Error('Input stream closed'); } // For other errors, maintain original behavior diff --git a/packages/sdk-typescript/test/unit/ProcessTransport.test.ts b/packages/sdk-typescript/test/unit/ProcessTransport.test.ts index 577bbaf7f..87bf6bc2a 100644 --- a/packages/sdk-typescript/test/unit/ProcessTransport.test.ts +++ b/packages/sdk-typescript/test/unit/ProcessTransport.test.ts @@ -647,7 +647,7 @@ describe('ProcessTransport', () => { ); }); - it('should not throw when writing to ended stream (soft-fail)', () => { + it('should throw when writing to ended stream', () => { mockPrepareSpawnInfo.mockReturnValue({ command: 'qwen', args: [], @@ -664,8 +664,7 @@ describe('ProcessTransport', () => { mockStdin.end(); - // Should not throw - soft-fail behavior - expect(() => transport.write('test')).not.toThrow(); + expect(() => transport.write('test')).toThrow('Input stream closed'); }); it('should throw if writing to terminated process', () => { diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 914761139..ea8a06096 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@qwen-code/qwen-code-test-utils", - "version": "0.8.0", + "version": "0.8.2", "private": true, "main": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index 42444ccb9..d13f52e0f 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -2,7 +2,7 @@ "name": "qwen-code-vscode-ide-companion", "displayName": "Qwen Code Companion", "description": "Enable Qwen Code with direct access to your VS Code workspace.", - "version": "0.8.0", + "version": "0.8.2", "publisher": "qwenlm", "icon": "assets/icon.png", "repository": {