diff --git a/src/components/AppSidebar.tsx b/src/components/AppSidebar.tsx
index 05aede9e..bf9c4ddc 100644
--- a/src/components/AppSidebar.tsx
+++ b/src/components/AppSidebar.tsx
@@ -9,6 +9,7 @@ import { SidebarSearch } from "./SidebarSearch";
import { SpaceBar } from "./SpaceBar";
import { UpdateBanner } from "./UpdateBanner";
import { ProjectSection } from "./sidebar/ProjectSection";
+import { useLanguage } from "@/lib/i18n";
interface AppSidebarProps {
isOpen: boolean;
@@ -73,6 +74,7 @@ export const AppSidebar = memo(function AppSidebar({
onOpenSettings,
agents,
}: AppSidebarProps) {
+ const { t } = useLanguage();
// Load default chat limit from main-process settings
const [defaultChatLimit, setDefaultChatLimit] = useState(10);
useEffect(() => {
@@ -199,7 +201,7 @@ export const AppSidebar = memo(function AppSidebar({
className="no-drag flex items-center gap-1.5 rounded-full px-3 py-1.5 text-[13px] font-medium text-sidebar-foreground/70 transition-all hover:bg-black/5 hover:text-sidebar-foreground dark:hover:bg-white/10"
>
{projects.length === 0 - ? "Add a project to get started" - : "No projects in this space"} + ? t("Add a project to get started") + : t("No projects in this space")}
)} @@ -269,7 +271,7 @@ export const AppSidebar = memo(function AppSidebar({Searching...
+{t("Searching")}...
)} {!isSearching && !hasResults && ( -No results found
+{t("No results found")}
)} {/* Session results */} {sessionResults.length > 0 && (- Chats + {t("Chats")}
{sessionResults.map((r) => ( {jiraBoardEnabled && ( diff --git a/src/lib/i18n.test.ts b/src/lib/i18n.test.ts new file mode 100644 index 00000000..d41d86e0 --- /dev/null +++ b/src/lib/i18n.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { translate } from "./i18n"; + +describe("i18n", () => { + it("translates supported interface labels to Chinese", () => { + expect(translate("Settings", "zh")).toBe("设置"); + expect(translate("Search chats...", "zh")).toBe("搜索聊天记录…"); + }); + + it("keeps English labels unchanged", () => { + expect(translate("Settings", "en")).toBe("Settings"); + }); + + it("leaves user-generated text unchanged when no translation exists", () => { + expect(translate("my-project-name", "zh")).toBe("my-project-name"); + }); +}); diff --git a/src/lib/i18n.tsx b/src/lib/i18n.tsx new file mode 100644 index 00000000..44940f46 --- /dev/null +++ b/src/lib/i18n.tsx @@ -0,0 +1,448 @@ +import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react"; + +export type Language = "en" | "zh"; + +const LANGUAGE_STORAGE_KEY = "harnss-language"; +const SOURCE_TEXT_BY_NODE = new WeakMap