Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Use all OpenCode features from a familiar sidebar chat UI.
- Settings panel
- Keyboard navigation for inline popups (Tab / Arrow keys)
- Subtask display
- i18n support (English, Japanese)
- i18n support (English, Japanese, Simplified Chinese, Korean, Traditional Chinese, Spanish, Brazilian Portuguese, Russian)

### Requirements

Expand Down Expand Up @@ -204,7 +204,7 @@ OpenCode の全機能をサイドバーのチャット UI から操作できま
- 設定パネル
- インラインポップアップのキーボードナビゲーション(Tab / 矢印キー)
- サブタスク表示
- 多言語対応(英語、日本語)
- 多言語対応(英語、日本語、簡体字中国語、韓国語、繁体字中国語、スペイン語、ブラジルポルトガル語、ロシア語

### 必要条件

Expand Down
4 changes: 4 additions & 0 deletions package.nls.es.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"displayName": "OpenCodeGUI",
"description": "[Experimental / No oficial] Una interfaz de chat en la barra lateral de VS Code para OpenCode. Esta extensión se desarrolla principalmente mediante codificación asistida por IA y se proporciona tal cual, sin garantía. No está afiliada ni respaldada por el proyecto OpenCode."
}
4 changes: 4 additions & 0 deletions package.nls.ko.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"displayName": "OpenCodeGUI",
"description": "【실험적·비공식】OpenCode용 VS Code 사이드바 채팅 인터페이스. 이 확장 프로그램은 주로 AI 보조 코딩을 통해 개발되었으며 어떠한 보증 없이 있는 그대로 제공됩니다. OpenCode 프로젝트와 제휴 또는 보증 관계가 없습니다."
}
4 changes: 4 additions & 0 deletions package.nls.pt-br.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"displayName": "OpenCodeGUI",
"description": "[Experimental / Não oficial] Uma interface de chat na barra lateral do VS Code para o OpenCode. Esta extensão é desenvolvida principalmente por meio de codificação assistida por IA e fornecida como está, sem garantia. Não é afiliada nem endossada pelo projeto OpenCode."
}
4 changes: 4 additions & 0 deletions package.nls.ru.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"displayName": "OpenCodeGUI",
"description": "[Экспериментальное / Неофициальное] Интерфейс чата в боковой панели VS Code для OpenCode. Это расширение разработано преимущественно с помощью ИИ и предоставляется «как есть» без каких-либо гарантий. Не связано с проектом OpenCode и не одобрено им."
}
4 changes: 4 additions & 0 deletions package.nls.zh-cn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"displayName": "OpenCodeGUI",
"description": "【实验性·非官方】OpenCode 的 VS Code 侧边栏聊天界面。本扩展主要通过 AI 辅助编码开发,按原样提供,不含任何保证。与 OpenCode 项目无关联或背书关系。"
}
4 changes: 4 additions & 0 deletions package.nls.zh-tw.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"displayName": "OpenCodeGUI",
"description": "【實驗性·非官方】OpenCode 的 VS Code 側邊欄聊天介面。本擴充功能主要透過 AI 輔助編碼開發,按原樣提供,不含任何保證。與 OpenCode 專案無關聯或背書關係。"
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ describe("ToolConfigPanel", () => {
expect(container.querySelector(".muted.sm")).toBeInTheDocument();
});

// renders language options
// renders language options (auto + 8 locales = 9)
it("言語オプションをレンダリングすること", () => {
const { container } = render(<ToolConfigPanel {...defaultProps} />);
expect(container.querySelectorAll("input[name='locale']")).toHaveLength(3);
expect(container.querySelectorAll("input[name='locale']")).toHaveLength(9);
});
});

Expand Down
105 changes: 105 additions & 0 deletions webview/__tests__/hooks/useLocale.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,109 @@ describe("useLocale", () => {
expect(result.current.resolvedLocale).toBe("ja");
});
});

// resolveLocale for zh-cn
context("setVscodeLanguage で 'zh-cn' を設定した場合", () => {
// resolves to zh-cn
it("resolvedLocale が 'zh-cn' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.setVscodeLanguage("zh-cn"));
expect(result.current.resolvedLocale).toBe("zh-cn");
});
});

// resolveLocale for ko
context("setVscodeLanguage で 'ko' を設定した場合", () => {
// resolves to ko
it("resolvedLocale が 'ko' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.setVscodeLanguage("ko"));
expect(result.current.resolvedLocale).toBe("ko");
});
});

// resolveLocale for zh-tw
context("setVscodeLanguage で 'zh-tw' を設定した場合", () => {
// resolves to zh-tw
it("resolvedLocale が 'zh-tw' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.setVscodeLanguage("zh-tw"));
expect(result.current.resolvedLocale).toBe("zh-tw");
});
});

// resolveLocale for es
context("setVscodeLanguage で 'es' を設定した場合", () => {
// resolves to es
it("resolvedLocale が 'es' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.setVscodeLanguage("es"));
expect(result.current.resolvedLocale).toBe("es");
});
});

// resolveLocale for pt-br
context("setVscodeLanguage で 'pt-br' を設定した場合", () => {
// resolves to pt-br
it("resolvedLocale が 'pt-br' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.setVscodeLanguage("pt-br"));
expect(result.current.resolvedLocale).toBe("pt-br");
});
});

// resolveLocale for ru
context("setVscodeLanguage で 'ru' を設定した場合", () => {
// resolves to ru
it("resolvedLocale が 'ru' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.setVscodeLanguage("ru"));
expect(result.current.resolvedLocale).toBe("ru");
});
});

// handleLocaleSettingChange with new locales
context("handleLocaleSettingChange で直接ロケールを指定した場合", () => {
// sets zh-cn directly
it("resolvedLocale が 'zh-cn' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.handleLocaleSettingChange("zh-cn"));
expect(result.current.resolvedLocale).toBe("zh-cn");
});

// sets ko directly
it("resolvedLocale が 'ko' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.handleLocaleSettingChange("ko"));
expect(result.current.resolvedLocale).toBe("ko");
});

// sets zh-tw directly
it("resolvedLocale が 'zh-tw' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.handleLocaleSettingChange("zh-tw"));
expect(result.current.resolvedLocale).toBe("zh-tw");
});

// sets es directly
it("resolvedLocale が 'es' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.handleLocaleSettingChange("es"));
expect(result.current.resolvedLocale).toBe("es");
});

// sets pt-br directly
it("resolvedLocale が 'pt-br' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.handleLocaleSettingChange("pt-br"));
expect(result.current.resolvedLocale).toBe("pt-br");
});

// sets ru directly
it("resolvedLocale が 'ru' になること", () => {
const { result } = renderHook(() => useLocale());
act(() => result.current.handleLocaleSettingChange("ru"));
expect(result.current.resolvedLocale).toBe("ru");
});
});
});
17 changes: 13 additions & 4 deletions webview/components/organisms/ToolConfigPanel/ToolConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,18 @@ export function ToolConfigPanel({ paths, onOpenConfigFile, onClose, localeSettin
<div className={styles.section}>
<div className={styles.sectionTitle}>{t["config.language"]}</div>
<div>
{(["auto", "en", "ja"] as const).map((opt) => {
const label =
opt === "auto" ? t["config.langAuto"] : opt === "en" ? t["config.langEn"] : t["config.langJa"];
{(["auto", "en", "ja", "zh-cn", "ko", "zh-tw", "es", "pt-br", "ru"] as const).map((opt) => {
const labelMap = {
auto: t["config.langAuto"],
en: t["config.langEn"],
ja: t["config.langJa"],
"zh-cn": t["config.langZhCn"],
ko: t["config.langKo"],
"zh-tw": t["config.langZhTw"],
es: t["config.langEs"],
"pt-br": t["config.langPtBr"],
ru: t["config.langRu"],
} as const;
return (
<label key={opt} className={`${styles.toggle} ${styles.toolItem}`}>
<input
Expand All @@ -41,7 +50,7 @@ export function ToolConfigPanel({ paths, onOpenConfigFile, onClose, localeSettin
checked={localeSetting === opt}
onChange={() => onLocaleSettingChange(opt)}
/>
<span className={styles.toolName}>{label}</span>
<span className={styles.toolName}>{labelMap[opt]}</span>
</label>
);
})}
Expand Down
6 changes: 6 additions & 0 deletions webview/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export const en = {
"config.langAuto": "Auto (VS Code)",
"config.langEn": "English",
"config.langJa": "日本語",
"config.langZhCn": "简体中文",
"config.langKo": "한국어",
"config.langZhTw": "繁體中文",
"config.langEs": "Español",
"config.langPtBr": "Português (Brasil)",
"config.langRu": "Русский",

// FileChangesHeader
"fileChanges.title": "File Changes",
Expand Down
139 changes: 139 additions & 0 deletions webview/locales/es.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import type { en } from "./en";

export const es: typeof en = {
// ChatHeader
"header.sessions": "Sesiones",
"header.title.fallback": "OpenCode",
"header.newChat": "Nuevo chat",

// EmptyState
"empty.title": "OpenCode",
"empty.description": "Inicia una nueva conversación para comenzar.",
"empty.newChat": "Nuevo chat",

// InputArea
"input.addContext": "Añadir contexto",
"input.searchFiles": "Buscar archivos...",
"input.noFiles": "No se encontraron archivos",
"input.remove": "Eliminar",
"input.placeholder": "Pregunta a OpenCode... (escribe # para adjuntar archivos)",
"input.addFile": (name: string) => `Añadir ${name}`,
"input.openTerminal": "Abrir sesión en terminal",
"input.shellMode": "Modo Shell",
"input.placeholder.shell": "Ingresa un comando de shell...",
"input.settings": "Configuración",
"input.stop": "Detener",
"input.send": "Enviar",

// MessageItem
"message.fileFallback": "archivo",
"message.clickToEdit": "Haz clic para editar",
"message.cancel": "Cancelar",
"message.send": "Enviar",
"message.thought": "Pensamiento",
"message.thinking": "Pensando…",
"message.toggleThought": "Alternar detalles de pensamiento",

// MessagesArea
"checkpoint.revertTitle": "Revertir a este punto",
"checkpoint.retryFromHere": "Reintentar desde aquí",
"checkpoint.forkFromHere": "Bifurcar desde aquí",

// Undo/Redo
"header.undo": "Deshacer",
"header.redo": "Rehacer",

// PermissionView
"permission.allow": "Permitir",
"permission.once": "Solo una vez",
"permission.deny": "Denegar",

// SessionList
"session.noSessions": "Sin sesiones",
"session.untitled": "Sin título",
"session.delete": "Eliminar",
"session.select": "Seleccionar sesión",

// Time (relative)
"time.now": "ahora",
"time.minutes": (n: number) => `${n}min`,
"time.hours": (n: number) => `${n}h`,
"time.days": (n: number) => `${n}d`,

// ToolPartView - category labels
"tool.read": "Leer",
"tool.edit": "Editar",
"tool.create": "Crear",
"tool.run": "Ejecutar",
"tool.search": "Buscar",
"tool.tool": "Herramienta",
"tool.toggleDetails": "Alternar detalles",
"tool.completed": (done: number, total: number) => `${done}/${total} completados`,
"tool.moreLines": (n: number) => `… +${n} líneas más`,
"tool.addLines": (n: number) => `+${n} líneas`,
"tool.todos": (done: number, total: number) => `${done}/${total} tareas`,

// ModelSelector
"model.selectModel": "Seleccionar modelo",
"model.notConnected": "No conectado",
"model.connectedOnly": "Solo conectados",
"model.showAll": "Mostrar todos los proveedores",
"model.hideDisconnected": "Ocultar proveedores desconectados",

// TodoHeader
"todo.label": "Tareas",
"todo.toggleList": "Alternar lista de tareas",

// ContextIndicator
"context.title": (percent: number) => `Contexto: ${percent}% usado`,
"context.windowUsage": "Uso de ventana de contexto",
"context.inputTokens": "Tokens de entrada",
"context.contextLimit": "Límite de contexto",
"context.compressing": "Comprimiendo...",
"context.compress": "Comprimir conversación",

// ShellResultView
"shell.title": "Shell",

// ToolConfigPanel
"config.title": "Configuración",
"config.projectConfig": "Configuración del proyecto",
"config.globalConfig": "Configuración global",

"config.close": "Cerrar",

// Language setting
"config.language": "Idioma",
"config.langAuto": "Automático (VS Code)",
"config.langEn": "English",
"config.langJa": "日本語",
"config.langZhCn": "简体中文",
"config.langKo": "한국어",
"config.langZhTw": "繁體中文",
"config.langEs": "Español",
"config.langPtBr": "Português (Brasil)",
"config.langRu": "Русский",

// FileChangesHeader
"fileChanges.title": "Cambios de archivos",
"fileChanges.noChanges": "Sin cambios de archivos",
"fileChanges.openDiff": "Abrir en editor de diferencias",
"fileChanges.toggle": "Cambios de archivos",

// Share
"share.share": "Compartir sesión",
"share.unshare": "Dejar de compartir",
"share.copied": "URL de compartir copiada al portapapeles",

// ChildSession
"childSession.agent": "Agente",
"childSession.backToParent": "Volver a la sesión principal",

// Context menu sections
"input.section.files": "Archivos",
"input.section.agents": "Agentes",
"input.section.shell": "Modo Shell",

// AgentMention
"input.noAgents": "No hay agentes disponibles",
};
27 changes: 24 additions & 3 deletions webview/locales/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
import { createContext, useContext } from "react";
import { en } from "./en";
import { es } from "./es";
import { ja } from "./ja";
import { ko } from "./ko";
import { ptBr } from "./pt-br";
import { ru } from "./ru";
import { zhCn } from "./zh-cn";
import { zhTw } from "./zh-tw";

export type SupportedLocale = "en" | "ja";
export type SupportedLocale = "en" | "ja" | "zh-cn" | "ko" | "zh-tw" | "es" | "pt-br" | "ru";
export type LocaleSetting = "auto" | SupportedLocale;

const locales: Record<SupportedLocale, typeof en> = { en, ja };
const locales: Record<SupportedLocale, typeof en> = {
en,
ja,
"zh-cn": zhCn,
ko,
"zh-tw": zhTw,
es,
"pt-br": ptBr,
ru,
};

export function resolveLocale(setting: LocaleSetting, vscodeLanguage: string): SupportedLocale {
if (setting !== "auto") return setting;
// vscode.env.language は "ja", "en", "zh-cn" など
// vscode.env.language は "ja", "en", "zh-cn", "zh-tw", "ko", "es", "pt-br", "ru" など
if (vscodeLanguage.startsWith("ja")) return "ja";
if (vscodeLanguage === "zh-cn") return "zh-cn";
if (vscodeLanguage === "zh-tw") return "zh-tw";
if (vscodeLanguage.startsWith("ko")) return "ko";
if (vscodeLanguage.startsWith("es")) return "es";
if (vscodeLanguage === "pt-br") return "pt-br";
if (vscodeLanguage.startsWith("ru")) return "ru";
return "en";
}

Expand Down
Loading