Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 7 additions & 4 deletions frontend/app/aipanel/aipanel-contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import { isDev } from "@/app/store/global";
import { globalStore } from "@/app/store/jotaiStore";
import { RpcApi } from "@/app/store/wshclientapi";
import { TabRpcClient } from "@/app/store/wshrpcutil";
import i18n from "@/app/i18n/index";
import { WaveAIModel } from "./waveai-model";

const t = i18n.t.bind(i18n);

export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boolean): Promise<void> {
e.preventDefault();
e.stopPropagation();
Expand All @@ -27,7 +30,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo
}

menu.push({
label: "New Chat",
label: t("app.newChat"),
click: () => {
model.clearChat();
},
Expand Down Expand Up @@ -121,14 +124,14 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo
}

menu.push({
label: "Max Output Tokens",
label: t("app.maxOutputTokens"),
submenu: maxTokensSubmenu,
});

menu.push({ type: "separator" });

menu.push({
label: "Configure Modes",
label: t("app.configureModes"),
click: () => {
RpcApi.RecordTEventCommand(
TabRpcClient,
Expand All @@ -148,7 +151,7 @@ export async function handleWaveAIContextMenu(e: React.MouseEvent, showCopy: boo
menu.push({ type: "separator" });

menu.push({
label: "Hide Wave AI",
label: t("app.hideWaveAI"),
click: () => {
model.closeWaveAIPanel();
},
Expand Down
12 changes: 7 additions & 5 deletions frontend/app/aipanel/aipanelheader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu";
import { useAtomValue } from "jotai";
import { memo } from "react";
import { useTranslation } from "react-i18next";
import { WaveAIModel } from "./waveai-model";

export const AIPanelHeader = memo(() => {
const { t } = useTranslation();
const model = WaveAIModel.getInstance();
const widgetAccess = useAtomValue(model.widgetAccessAtom);
const inBuilder = model.inBuilder;
Expand All @@ -32,8 +34,8 @@ export const AIPanelHeader = memo(() => {
<div className="flex items-center flex-shrink-0 whitespace-nowrap">
{!inBuilder && (
<div className="flex items-center text-sm whitespace-nowrap">
<span className="text-gray-300 @xs:hidden mr-1 text-[12px]">Context</span>
<span className="text-gray-300 hidden @xs:inline mr-2 text-[12px]">Widget Context</span>
<span className="text-gray-300 @xs:hidden mr-1 text-[12px]">{t("app.context")}</span>
<span className="text-gray-300 hidden @xs:inline mr-2 text-[12px]">{t("app.widgetContext")}</span>
<button
onClick={() => {
model.setWidgetAccess(!widgetAccess);
Expand All @@ -44,7 +46,7 @@ export const AIPanelHeader = memo(() => {
className={`relative inline-flex h-6 w-14 items-center rounded-full transition-colors cursor-pointer ${
widgetAccess ? "bg-accent-600" : "bg-zinc-600"
}`}
title={`Widget Access ${widgetAccess ? "ON" : "OFF"}`}
title={t("app.widgetAccess", { state: widgetAccess ? t("app.on") : t("app.off") })}
>
<span
className={`absolute inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
Expand All @@ -56,7 +58,7 @@ export const AIPanelHeader = memo(() => {
widgetAccess ? "ml-2.5 mr-6 text-left" : "ml-6 mr-1 text-right"
}`}
>
{widgetAccess ? "ON" : "OFF"}
{widgetAccess ? t("app.on") : t("app.off")}
</span>
</button>
</div>
Expand All @@ -65,7 +67,7 @@ export const AIPanelHeader = memo(() => {
<button
onClick={handleKebabClick}
className="text-gray-400 hover:text-white cursor-pointer transition-colors p-1 rounded flex-shrink-0 ml-2 focus:outline-none"
title="More options"
title={t("app.moreOptions")}
>
<i className="fa fa-ellipsis-vertical"></i>
</button>
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2026, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0

import "./i18n/index";

import {
clearBadgesForBlockOnFocus,
clearBadgesForTabOnFocus,
Expand Down
22 changes: 13 additions & 9 deletions frontend/app/block/blockframe-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import * as util from "@/util/util";
import { cn, makeIconClass } from "@/util/util";
import * as jotai from "jotai";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { BlockEnv } from "./blockenv";
import { BlockFrameProps } from "./blocktypes";

Expand All @@ -40,17 +41,18 @@ function handleHeaderContextMenu(
) {
e.preventDefault();
e.stopPropagation();
const t = window.__waveI18n.t;
const magnified = globalStore.get(nodeModel.isMagnified);
const menu: ContextMenuItem[] = [
{
label: magnified ? "Un-Magnify Block" : "Magnify Block",
label: magnified ? t("app.unMagnifyBlock") : t("app.magnifyBlock"),
click: () => {
nodeModel.toggleMagnify();
},
},
{ type: "separator" },
{
label: "Copy BlockId",
label: t("app.copyBlockId"),
click: () => {
navigator.clipboard.writeText(blockId);
},
Expand All @@ -61,7 +63,7 @@ function handleHeaderContextMenu(
menu.push(
{ type: "separator" },
{
label: "Close Block",
label: t("app.closeBlock"),
click: () => uxCloseBlock(blockId),
}
);
Expand All @@ -76,6 +78,7 @@ type HeaderTextElemsProps = {
};

const HeaderTextElems = React.memo(({ viewModel, blockId, preview, error }: HeaderTextElemsProps) => {
const { t } = useTranslation();
const waveEnv = useWaveEnv<BlockEnv>();
const frameTextAtom = waveEnv.getBlockMetaKeyAtom(blockId, "frame:text");
const frameText = jotai.useAtomValue(frameTextAtom);
Expand All @@ -102,7 +105,7 @@ const HeaderTextElems = React.memo(({ viewModel, blockId, preview, error }: Head
<div className="iconbutton disabled" key="controller-status" onClick={copyHeaderErr}>
<i
className="fa-sharp fa-solid fa-triangle-exclamation"
title={"Error Rendering View Header: " + error.message}
title={t("app.errorRenderingViewHeader", { error: error.message })}
/>
</div>
);
Expand All @@ -119,6 +122,7 @@ type HeaderEndIconsProps = {
};

const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndIconsProps) => {
const { t } = useTranslation();
const blockEnv = useWaveEnv<BlockEnv>();
const endIconButtons = util.useAtomValueSafe(viewModel?.endIconButtons);
const magnified = jotai.useAtomValue(nodeModel.isMagnified);
Expand All @@ -136,7 +140,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI
const splitHorizontalDecl: IconButtonDecl = {
elemtype: "iconbutton",
icon: "columns",
title: "Split Horizontally",
title: t("app.splitHorizontally"),
click: (e) => {
e.stopPropagation();
const blockAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", blockId));
Expand All @@ -150,7 +154,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI
const splitVerticalDecl: IconButtonDecl = {
elemtype: "iconbutton",
icon: "grip-lines",
title: "Split Vertically",
title: t("app.splitVertically"),
click: (e) => {
e.stopPropagation();
const blockAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", blockId));
Expand All @@ -167,15 +171,15 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI
const settingsDecl: IconButtonDecl = {
elemtype: "iconbutton",
icon: "cog",
title: "Settings",
title: t("app.settings"),
click: (e) => handleHeaderContextMenu(e, blockId, viewModel, nodeModel, blockEnv),
};
endIconsElem.push(<IconButton key="settings" decl={settingsDecl} className="block-frame-settings" />);
if (ephemeral) {
const addToLayoutDecl: IconButtonDecl = {
elemtype: "iconbutton",
icon: "circle-plus",
title: "Add to Layout",
title: t("app.addToLayout"),
click: () => {
nodeModel.addEphemeralNodeToLayout();
},
Expand All @@ -198,7 +202,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI
const closeDecl: IconButtonDecl = {
elemtype: "iconbutton",
icon: "xmark-large",
title: "Close",
title: t("app.close"),
click: () => uxCloseBlock(nodeModel.blockId),
};
endIconsElem.push(<IconButton key="close" decl={closeDecl} className="block-frame-default-close" />);
Expand Down
27 changes: 27 additions & 0 deletions frontend/app/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import en from "./locales/en.json";
import zhCN from "./locales/zh-CN.json";

i18n.use(initReactI18next).init({
resources: {
en: { translation: en },
"zh-CN": { translation: zhCN },
},
lng: "zh-CN", // default language
fallbackLng: "en",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
interpolation: {
escapeValue: false,
},
});

export default i18n;

// Expose a global t function for use in non-React contexts (e.g. event handlers, menus)
declare global {
interface Window {
__waveI18n: { t: typeof i18n.t };
}
}
window.__waveI18n = { t: i18n.t.bind(i18n) };
73 changes: 73 additions & 0 deletions frontend/app/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"app.tabBarPosition": "Tab Bar Position",
"app.top": "Top",
"app.left": "Left",
"app.renameTab": "Rename Tab",
"app.copyTabId": "Copy TabId",
"app.flagTab": "Flag Tab",
"app.none": "None",
"app.green": "Green",
"app.teal": "Teal",
"app.blue": "Blue",
"app.purple": "Purple",
"app.red": "Red",
"app.orange": "Orange",
"app.yellow": "Yellow",
"app.backgrounds": "Backgrounds",
"app.default": "Default",
"app.closeTab": "Close Tab",
"app.magnifyBlock": "Magnify Block",
"app.unMagnifyBlock": "Un-Magnify Block",
"app.copyBlockId": "Copy BlockId",
"app.closeBlock": "Close Block",
"app.addToLayout": "Add to Layout",
"app.splitHorizontally": "Split Horizontally",
"app.splitVertically": "Split Vertically",
"app.settings": "Settings",
"app.close": "Close",
"app.connectTo": "Connect to (username@host)...",
"app.local": "Local",
"app.remote": "Remote",
"app.editConnections": "Edit Connections",
"app.reconnectTo": "Reconnect to {{connection}}",
"app.disconnect": "Disconnect {{connection}}",
"app.newConnection": "{{name}} (New Connection)",
"app.gitBash": "Git Bash",
"app.waveAI": "Wave AI",
"app.context": "Context",
"app.widgetContext": "Widget Context",
"app.widgetAccess": "Widget Access {{state}}",
"app.on": "ON",
"app.off": "OFF",
"app.moreOptions": "More options",
"app.newChat": "New Chat",
"app.maxOutputTokens": "Max Output Tokens",
"app.configureModes": "Configure Modes",
"app.hideWaveAI": "Hide Wave AI",
"app.about": "About",
"app.waveTerminal": "Wave Terminal",
"app.version": "Version",
"app.configFiles": "Config Files",
"app.connections": "Connections",
"app.themes": "Themes",
"app.keybindings": "Keybindings",
"app.errorRenderingViewHeader": "Error Rendering View Header: {{error}}",
"app.cancel": "Cancel",
"app.ok": "Ok",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
"app.aboutDescription": "Open-Source AI-Integrated Terminal",
"app.aboutTagline": "Built for Seamless Workflows",
"app.clientVersion": "Client Version",
"app.updateChannel": "Update Channel",
"app.github": "GitHub",
"app.website": "Website",
"app.openSource": "Open Source",
"app.sponsor": "Sponsor",
"app.viewDocumentation": "View documentation",
"app.visual": "Visual",
"app.rawJson": "Raw JSON",
"app.saving": "Saving...",
"app.save": "Save",
"app.unsavedChanges": "Unsaved changes",
"app.loading": "Loading...",
"app.configError": "Config Error"
}
73 changes: 73 additions & 0 deletions frontend/app/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"app.tabBarPosition": "标签页栏位置",
"app.top": "顶部",
"app.left": "左侧",
"app.renameTab": "重命名标签页",
"app.copyTabId": "复制标签页ID",
"app.flagTab": "标记标签页",
"app.none": "无",
"app.green": "绿色",
"app.teal": "青色",
"app.blue": "蓝色",
"app.purple": "紫色",
"app.red": "红色",
"app.orange": "橙色",
"app.yellow": "黄色",
"app.backgrounds": "背景",
"app.default": "默认",
"app.closeTab": "关闭标签页",
"app.magnifyBlock": "放大区块",
"app.unMagnifyBlock": "取消放大区块",
"app.copyBlockId": "复制区块ID",
"app.closeBlock": "关闭区块",
"app.addToLayout": "添加到布局",
"app.splitHorizontally": "水平拆分",
"app.splitVertically": "垂直拆分",
"app.settings": "设置",
"app.close": "关闭",
"app.connectTo": "连接到 (用户名@主机)...",
"app.local": "本地",
"app.remote": "远程",
"app.editConnections": "编辑连接",
"app.reconnectTo": "重新连接到 {{connection}}",
"app.disconnect": "断开连接 {{connection}}",
"app.newConnection": "{{name}} (新建连接)",
"app.gitBash": "Git Bash",
"app.waveAI": "Wave AI",
"app.context": "上下文",
"app.widgetContext": "小组件上下文",
"app.widgetAccess": "小组件访问 {{state}}",
"app.on": "开启",
"app.off": "关闭",
"app.moreOptions": "更多选项",
"app.newChat": "新建对话",
"app.maxOutputTokens": "最大输出令牌数",
"app.configureModes": "配置模式",
"app.hideWaveAI": "隐藏 Wave AI",
"app.about": "关于",
"app.waveTerminal": "Wave 终端",
"app.version": "版本",
"app.configFiles": "配置文件",
"app.connections": "连接",
"app.themes": "主题",
"app.keybindings": "快捷键",
"app.errorRenderingViewHeader": "渲染视图标题错误:{{error}}",
"app.cancel": "取消",
"app.ok": "确定",
"app.aboutDescription": "开源 AI 集成终端",
"app.aboutTagline": "为流畅工作流而生",
"app.clientVersion": "客户端版本",
"app.updateChannel": "更新频道",
"app.github": "GitHub",
"app.website": "官网",
"app.openSource": "开源",
"app.sponsor": "赞助",
"app.viewDocumentation": "查看文档",
"app.visual": "可视化",
"app.rawJson": "原始 JSON",
"app.saving": "保存中...",
"app.save": "保存",
"app.unsavedChanges": "未保存的更改",
"app.loading": "加载中...",
"app.configError": "配置错误"
}
Loading