diff --git a/windows/tauri/src/App.tsx b/windows/tauri/src/App.tsx index 83c1560c..b58a4787 100644 --- a/windows/tauri/src/App.tsx +++ b/windows/tauri/src/App.tsx @@ -6,6 +6,7 @@ import { traceWindowOpen, traceWindowOpenAfterFrame, } from "@/features/window/utils/window-open-diagnostics"; +import { LocaleProvider } from "./i18n/locale-provider"; const WorkbenchApp = lazy(() => import("./workbench-app")); @@ -92,7 +93,9 @@ function App() { return ( }> - + + + ); } diff --git a/windows/tauri/src/config/backend-capabilities.test.ts b/windows/tauri/src/config/backend-capabilities.test.ts new file mode 100644 index 00000000..132dd6e0 --- /dev/null +++ b/windows/tauri/src/config/backend-capabilities.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, test } from "bun:test"; +import { BACKEND_UNAVAILABLE_TOOLTIP, backendCapabilities } from "./backend-capabilities"; +import { defaultSettings } from "@/features/settings/config/default-settings"; + +describe("default Windows workbench capability policy", () => { + test("does not enable unavailable feature families by default", () => { + expect(BACKEND_UNAVAILABLE_TOOLTIP).toBe("待开发"); + + for (const capability of ["github", "remote", "docker", "agent", "collaboration"] as const) { + expect(backendCapabilities[capability]).toBe(false); + } + + expect(defaultSettings.coreFeatures.github).toBe(false); + expect(defaultSettings.coreFeatures.remote).toBe(false); + expect(defaultSettings.coreFeatures.docker).toBe(false); + expect(defaultSettings.coreFeatures.aiChat).toBe(false); + expect(defaultSettings.coreFeatures.teamCollaboration).toBe(false); + }); +}); diff --git a/windows/tauri/src/features/command-palette/components/command-palette.tsx b/windows/tauri/src/features/command-palette/components/command-palette.tsx index a85c1865..e52686b3 100644 --- a/windows/tauri/src/features/command-palette/components/command-palette.tsx +++ b/windows/tauri/src/features/command-palette/components/command-palette.tsx @@ -1,11 +1,9 @@ import { appDataDir } from "@tauri-apps/api/path"; -import { ClockCounterClockwiseIcon as History, PuzzlePieceIcon as Puzzle } from "@/ui/icons"; +import { ClockCounterClockwiseIcon as History } from "@/ui/icons"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useUIExtensionStore } from "@/extensions/ui/stores/ui-extension-store"; import { IconThemeSelectorContent } from "@/features/command-palette/components/icon-theme-selector"; import { ThemeSelectorContent } from "@/features/command-palette/components/theme-selector"; import { useEditorSettingsStore } from "@/features/editor/stores/settings.store"; -import { DatabaseCommandContent } from "@/features/database/components/database-sidebar"; import { useLspStore } from "@/features/editor/lsp/stores/lsp.store"; import { useBufferStore } from "@/features/editor/stores/buffer.store"; import { isMarkdownFile } from "@/features/editor/utils/lines"; @@ -20,7 +18,6 @@ import { unstageAllFiles, } from "@/features/git/api/git-status-api"; import { useRepositoryStore } from "@/features/git/stores/git-repository.store"; -import { useGitHubStore } from "@/features/github/stores/github.store"; import { useToast } from "@/features/layout/contexts/toast-context"; import { useOnboardingStore } from "@/features/onboarding/stores/onboarding.store"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; @@ -42,11 +39,9 @@ import Command, { import Keybinding from "@/features/keymaps/components/keybinding"; import { matchesSearchQuery } from "@/utils/search-match"; import { createAdvancedActions } from "../constants/advanced-actions"; -import { createDatabaseActions } from "../constants/database-actions"; import { createFileActions } from "../constants/file-actions"; import { createGenerateActions } from "../constants/generate-actions"; import { createGitActions } from "../constants/git-actions"; -import { createGitHubActions } from "../constants/github-actions"; import { createMarkdownActions } from "../constants/markdown-actions"; import { createNavigationActions } from "../constants/navigation-actions"; import { createPaneActions } from "../constants/pane-actions"; @@ -154,8 +149,6 @@ const CommandPaletteContent = ({ commandPaletteInitialView }: CommandPaletteCont const lspStatus = useLspStore.use.lspStatus(); const rootFolderPath = useFileSystemStore((state) => state.rootFolderPath); const activeRepoPath = useRepositoryStore.use.activeRepoPath(); - const { checkAuth: checkGitHubAuth } = useGitHubStore.use.actions(); - const extensionCommands = useUIExtensionStore.use.commands(); const extensionViews = useCommandPaletteViews(); const { showToast } = useToast(); const openWhatsNew = useWhatsNewStore((state) => state.actions.open); @@ -170,7 +163,6 @@ const CommandPaletteContent = ({ commandPaletteInitialView }: CommandPaletteCont switchToPreviousBuffer, reopenClosedTab, openWebViewerBuffer, - openGitHubFormBuffer, openContent, } = useBufferStore.use.actions(); const { zoomIn, zoomOut, resetZoom } = useZoomStore.use.actions(); @@ -289,8 +281,6 @@ const CommandPaletteContent = ({ commandPaletteInitialView }: CommandPaletteCont ...createNavigationActions({ setIsSidebarVisible, setActiveView, - setIsBottomPaneVisible, - setBottomPaneActiveTab, setIsQuickOpenVisible, openCommandPaletteView, openSettingsDialog, @@ -314,26 +304,6 @@ const CommandPaletteContent = ({ commandPaletteInitialView }: CommandPaletteCont ...createGenerateActions({ onClose, }), - ...Array.from(extensionCommands.values()).map( - (command): Action => ({ - id: `extension-command:${command.id}`, - label: command.title, - description: command.category - ? `${command.category} extension command` - : "Installed extension command", - icon: , - category: command.category ?? "Extensions", - action: () => { - onClose(); - void Promise.resolve(command.execute()).catch((error) => { - showToast({ - message: error instanceof Error ? error.message : "Extension command failed", - type: "error", - }); - }); - }, - }), - ), ...createWindowActions({ onClose, }), @@ -354,27 +324,6 @@ const CommandPaletteContent = ({ commandPaletteInitialView }: CommandPaletteCont }, onClose, }), - ...createGitHubActions({ - repoPath: activeRepoPath ?? rootFolderPath ?? null, - setIsSidebarVisible, - setActiveView, - settings: { - showGitHubPullRequests: commandSettings.showGitHubPullRequests, - showGitHubIssues: commandSettings.showGitHubIssues, - showGitHubActions: commandSettings.showGitHubActions, - }, - updateSetting: useSettingsStore.getState().actions.updateSetting as ( - key: string, - value: any, - ) => void | Promise, - checkAuth: checkGitHubAuth, - showToast, - openGitHubFormBuffer, - onClose, - }), - ...createDatabaseActions({ - openDatabaseCommand: () => pushView("databases"), - }), ...createAdvancedActions({ lspStatus, vimMode: commandSettings.vimMode, @@ -481,12 +430,6 @@ const CommandPaletteContent = ({ commandPaletteInitialView }: CommandPaletteCont onBack={popView} onClose={onClose} /> - ) : currentView === "databases" ? ( - ) : extensionView ? ( extensionView.render({ isActive: true, diff --git a/windows/tauri/src/features/command-palette/constants/advanced-actions.tsx b/windows/tauri/src/features/command-palette/constants/advanced-actions.tsx index 062fc9b8..cdbcfec0 100644 --- a/windows/tauri/src/features/command-palette/constants/advanced-actions.tsx +++ b/windows/tauri/src/features/command-palette/constants/advanced-actions.tsx @@ -10,7 +10,6 @@ import { stopAllLanguageServers, } from "@/features/keymaps/commands/lsp-command-actions"; import { openLitheLogBuffer } from "@/features/settings/services/lithe-log-service"; -import { useUIState } from "@/features/window/stores/ui-state.store"; import { showAlertDialog } from "@/ui/dialog"; import type { Action } from "../types/action.types"; @@ -36,18 +35,6 @@ export const createAdvancedActions = (params: AdvancedActionsParams): Action[] = const { lspStatus, vimMode, vimCommands, setMode, openQuickEdit, showToast, onClose } = params; const baseActions: Action[] = [ - { - id: "ai-new-agent", - label: "AI: New Agent", - description: "Open the unified agent launcher", - icon: , - category: "AI", - commandId: "workbench.agentLauncher", - action: () => { - useUIState.getState().setIsAgentLauncherVisible(true); - onClose(); - }, - }, { id: "ai-quick-edit", label: "AI: Quick Edit Selection", diff --git a/windows/tauri/src/features/command-palette/constants/navigation-actions.tsx b/windows/tauri/src/features/command-palette/constants/navigation-actions.tsx index 9e39e06b..57876730 100644 --- a/windows/tauri/src/features/command-palette/constants/navigation-actions.tsx +++ b/windows/tauri/src/features/command-palette/constants/navigation-actions.tsx @@ -1,27 +1,19 @@ import { FileTextIcon as FileText, FolderOpenIcon as FolderOpen, - BugBeetleIcon as BugBeetle, GitBranchIcon as GitBranch, - GitPullRequestIcon as GitPullRequest, HashIcon as Hash, ListBulletsIcon as ListBullets, - PackageIcon as Package, MagnifyingGlassIcon as Search, } from "@/ui/icons"; import { useBufferStore } from "@/features/editor/stores/buffer.store"; import type { SidebarView } from "@/features/layout/utils/sidebar-pane-utils"; -import type { - BottomPaneTab, - SettingsTab, -} from "@/features/window/stores/ui-state/types/ui-state.types"; +import type { SettingsTab } from "@/features/window/stores/ui-state/types/ui-state.types"; import type { Action } from "../types/action.types"; interface NavigationActionsParams { setIsSidebarVisible: (v: boolean) => void; setActiveView: (view: SidebarView) => void; - setIsBottomPaneVisible: (v: boolean) => void; - setBottomPaneActiveTab: (tab: BottomPaneTab) => void; setIsQuickOpenVisible: (v: boolean) => void; openCommandPaletteView?: (view: "outline") => void; openSettingsDialog: (tab?: SettingsTab) => void; @@ -33,8 +25,6 @@ export const createNavigationActions = (params: NavigationActionsParams): Action const { setIsSidebarVisible, setActiveView, - setIsBottomPaneVisible, - setBottomPaneActiveTab, setIsQuickOpenVisible, openCommandPaletteView, coreFeatures, @@ -68,32 +58,6 @@ export const createNavigationActions = (params: NavigationActionsParams): Action onClose(); }, }, - { - id: "view-show-github-prs", - label: "View: Show Pull Requests", - description: "Switch to GitHub Pull Requests view", - icon: , - category: "Navigation", - commandId: "workbench.showGitHub", - action: () => { - setIsSidebarVisible(true); - setActiveView("github-prs"); - onClose(); - }, - }, - { - id: "view-show-debugger", - label: "View: Show Run and Debug", - description: "Switch to debugger view", - icon: , - category: "Navigation", - commandId: "workbench.showDebugger", - action: () => { - setBottomPaneActiveTab("debugger"); - setIsBottomPaneVisible(true); - onClose(); - }, - }, ...(coreFeatures.outline ? [ { @@ -123,17 +87,6 @@ export const createNavigationActions = (params: NavigationActionsParams): Action useBufferStore.getState().actions.openGlobalSearchBuffer(); }, }, - { - id: "view-show-extensions", - label: "View: Show Extensions", - description: "Open the extensions tab", - icon: , - category: "Navigation", - action: () => { - onClose(); - useBufferStore.getState().actions.openExtensionsBuffer(); - }, - }, { id: "go-to-line", label: "Go: Go to Line", diff --git a/windows/tauri/src/features/command-palette/constants/settings-actions.tsx b/windows/tauri/src/features/command-palette/constants/settings-actions.tsx index 160d287c..de4eb3ab 100644 --- a/windows/tauri/src/features/command-palette/constants/settings-actions.tsx +++ b/windows/tauri/src/features/command-palette/constants/settings-actions.tsx @@ -79,13 +79,19 @@ const settingsTabLabels: Record = { const settingsTabCommands = (Object.entries(settingsTabLabels) as Array<[SettingsTab, string]>) .map(([tab, label]) => ({ tab, label })) - .filter(({ tab }) => tab !== "language"); + .filter( + ({ tab }) => + !["account", "ai", "collaboration", "enterprise", "language"].includes(tab), + ); function getMatchingSettingsRecords(query: string) { const trimmedQuery = query.trim(); if (trimmedQuery.length < 2) return []; return settingsSearchIndex + .filter( + (record) => !["account", "ai", "collaboration", "enterprise"].includes(record.tab), + ) .filter((record) => record.id !== "editor-vim-mode") .map((record) => { const score = scoreSearchQuery(trimmedQuery, [ diff --git a/windows/tauri/src/features/keymaps/utils/matcher.test.ts b/windows/tauri/src/features/keymaps/utils/matcher.test.ts new file mode 100644 index 00000000..fd38ad7e --- /dev/null +++ b/windows/tauri/src/features/keymaps/utils/matcher.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, test } from "bun:test"; +import { matchKeybinding } from "./matcher"; + +function keyboardEvent(overrides: Partial): KeyboardEvent { + return { + altKey: false, + code: "", + ctrlKey: false, + key: "", + metaKey: false, + shiftKey: false, + ...overrides, + } as KeyboardEvent; +} + +describe("keymap matcher", () => { + test("matches Ctrl+Shift+F by physical key while an IME changes event.key", () => { + const event = keyboardEvent({ + code: "KeyF", + ctrlKey: true, + key: "ㄈ", + shiftKey: true, + }); + + expect(matchKeybinding(event, "cmd+shift+f").matched).toBe(true); + }); + + test("does not treat a modifier-only key as global search", () => { + const event = keyboardEvent({ code: "ShiftLeft", key: "Shift", shiftKey: true }); + + expect(matchKeybinding(event, "cmd+shift+f").matched).toBe(false); + }); +}); diff --git a/windows/tauri/src/features/keymaps/utils/matcher.ts b/windows/tauri/src/features/keymaps/utils/matcher.ts index 251e0d1a..899e55d5 100644 --- a/windows/tauri/src/features/keymaps/utils/matcher.ts +++ b/windows/tauri/src/features/keymaps/utils/matcher.ts @@ -42,7 +42,9 @@ export function eventToKey(event: KeyboardEvent): ParsedKey { // where the character at that physical position differs from the US layout. let key = event.key; const hasModifier = event.metaKey || event.ctrlKey || event.altKey; - if (key === "Dead" || key === "Unidentified" || (hasModifier && CODE_TO_KEY[event.code])) { + if (hasModifier && /^Key[A-Z]$/.test(event.code)) { + key = event.code.slice(3).toLowerCase(); + } else if (key === "Dead" || key === "Unidentified" || (hasModifier && CODE_TO_KEY[event.code])) { key = CODE_TO_KEY[event.code] || event.code; } diff --git a/windows/tauri/src/features/layout/components/bottom-pane/bottom-pane.tsx b/windows/tauri/src/features/layout/components/bottom-pane/bottom-pane.tsx index 04d7a53f..af0b7d07 100644 --- a/windows/tauri/src/features/layout/components/bottom-pane/bottom-pane.tsx +++ b/windows/tauri/src/features/layout/components/bottom-pane/bottom-pane.tsx @@ -1,5 +1,6 @@ import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { isBackendCapabilityAvailable } from "@/config/backend-capabilities"; import DebuggerView from "@/features/debugger/components/debugger-view"; import { useBufferStore } from "@/features/editor/stores/buffer.store"; import { BOTTOM_PANE_ID } from "@/features/panes/constants/pane"; @@ -62,11 +63,25 @@ const BottomPane = () => { }, [bottomPaneActiveTab, isBottomPaneVisible]); useEffect(() => { - if (isBottomPaneVisible && bottomPaneActiveTab === "debugger" && !debuggerEnabled) { + if ( + isBottomPaneVisible && + bottomPaneActiveTab === "debugger" && + (!debuggerEnabled || !isBackendCapabilityAvailable("debugger")) + ) { useUIState.getState().setIsBottomPaneVisible(false); } }, [bottomPaneActiveTab, isBottomPaneVisible, debuggerEnabled]); + useEffect(() => { + if ( + isBottomPaneVisible && + bottomPaneActiveTab === "terminal" && + (!terminalEnabled || !isBackendCapabilityAvailable("terminal")) + ) { + useUIState.getState().setIsBottomPaneVisible(false); + } + }, [bottomPaneActiveTab, isBottomPaneVisible, terminalEnabled]); + useEffect(() => { if ( isBottomPaneVisible && @@ -226,7 +241,7 @@ const BottomPane = () => { > {/* Terminal Container - Always mounted to preserve terminal sessions */} - {terminalEnabled && ( + {terminalEnabled && isBackendCapabilityAvailable("terminal") && ( { /> )} - {debuggerEnabled && bottomPaneActiveTab === "debugger" && ( + {debuggerEnabled && + isBackendCapabilityAvailable("debugger") && + bottomPaneActiveTab === "debugger" && ( diff --git a/windows/tauri/src/features/layout/components/footer/footer.tsx b/windows/tauri/src/features/layout/components/footer/footer.tsx index 18cda54c..2219431e 100644 --- a/windows/tauri/src/features/layout/components/footer/footer.tsx +++ b/windows/tauri/src/features/layout/components/footer/footer.tsx @@ -5,11 +5,8 @@ import { } from "@/config/backend-capabilities"; import { useDiagnosticsStore } from "@/features/diagnostics/stores/diagnostics.store"; import { useBufferStore } from "@/features/editor/stores/buffer.store"; -import { useExtensionStore } from "@/extensions/registry/extension-store"; -import { useSidebarPaneController } from "@/features/layout/hooks/use-sidebar-pane-controller"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; import { useUIState } from "@/features/window/stores/ui-state.store"; -import { useAuthStore } from "@/features/window/stores/auth.store"; import { NotificationsTrigger } from "@/features/notifications/components/notifications-trigger"; import { FOOTER_TRAILING_ITEM_IDS, @@ -18,35 +15,24 @@ import { type FooterTrailingItemId, } from "@/features/layout/config/item-order"; import { orderChromeItems, type ChromeItem } from "@/features/layout/utils/chrome-items"; -import { useFooterDebuggerItem } from "./footer-debugger-item"; import { useFooterGitBranchItem } from "./footer-git-branch-item"; -import { FooterControlBadge, FooterTabControl } from "./footer-tab-control"; +import { FooterTabControl } from "./footer-tab-control"; import { DatabaseIcon, - ExtensionsIcon, - ListIcon, TerminalWindowIcon, - UsersThreeIcon, WarningIcon, } from "@/ui/icons"; import { ChromeBar, ChromeGroup } from "@/ui/chrome"; const Footer = () => { const terminalEnabled = useSettingsStore((state) => state.settings.coreFeatures.terminal); - const debuggerEnabled = useSettingsStore((state) => state.settings.coreFeatures.debugger); const diagnosticsEnabled = useSettingsStore((state) => state.settings.coreFeatures.diagnostics); - const outlineEnabled = useSettingsStore((state) => state.settings.coreFeatures.outline); - const teamCollaborationEnabled = useSettingsStore( - (state) => state.settings.coreFeatures.teamCollaboration, - ); const footerLeadingItemsOrder = useSettingsStore( (state) => state.settings.footerLeadingItemsOrder, ); const footerTrailingItemsOrder = useSettingsStore( (state) => state.settings.footerTrailingItemsOrder, ); - const isRightSidebarVisible = useUIState((state) => state.isRightSidebarVisible); - const activeRightSidebarView = useUIState((state) => state.activeRightSidebarView); const isCommandPaletteVisible = useUIState((state) => state.isCommandPaletteVisible); const commandPaletteInitialView = useUIState((state) => state.commandPaletteInitialView); const isBottomPaneVisible = useUIState((state) => state.isBottomPaneVisible); @@ -54,11 +40,6 @@ const Footer = () => { const setIsBottomPaneVisible = useUIState((state) => state.setIsBottomPaneVisible); const setBottomPaneActiveTab = useUIState((state) => state.setBottomPaneActiveTab); const openCommandPaletteView = useUIState((state) => state.openCommandPaletteView); - const hasTeamsCollaborationAccess = useAuthStore( - (state) => state.subscription?.collaboration?.enabled === true, - ); - const isCollaborationFeatureEnabled = hasTeamsCollaborationAccess && teamCollaborationEnabled; - const { openSidebarView } = useSidebarPaneController(); const isDiagnosticsBufferActive = useBufferStore((state) => { if (!state.activeBufferId) return false; return state.buffers.some( @@ -66,20 +47,7 @@ const Footer = () => { ); }); const openDiagnosticsBuffer = useBufferStore.use.actions().openDiagnosticsBuffer; - const openExtensionsBuffer = useBufferStore.use.actions().openExtensionsBuffer; - const isExtensionsBufferActive = useBufferStore((state) => { - if (!state.activeBufferId) return false; - return state.buffers.some( - (buffer) => buffer.id === state.activeBufferId && buffer.type === "extensions", - ); - }); const branchItem = useFooterGitBranchItem(); - - const debuggerItem = useFooterDebuggerItem( - debuggerEnabled || !isBackendCapabilityAvailable("debugger"), - footerLeadingItemsOrder, - ); - const extensionUpdatesCount = useExtensionStore.use.extensionsWithUpdates().size; const diagnosticsByFile = useDiagnosticsStore.use.diagnosticsByFile(); const diagnosticsCount = Array.from(diagnosticsByFile.values()).reduce( (total, diagnostics) => total + diagnostics.length, @@ -112,7 +80,6 @@ const Footer = () => { ), } : null, - debuggerItem, diagnosticsEnabled ? { id: "diagnostics", @@ -135,33 +102,11 @@ const Footer = () => { ), } : null, - extensionUpdatesCount > 0 - ? { - id: "extensions", - label: "Extension updates", - content: ( - openExtensionsBuffer()} - > - - - {extensionUpdatesCount > 9 ? "9+" : extensionUpdatesCount} - - - ), - } - : null, ]; const footerLeadingItems = footerLeadingItemsSource.filter( (item): item is ChromeItem => item !== null, ); - const shouldShowOutline = outlineEnabled; - const isOutlineActive = isRightSidebarVisible && activeRightSidebarView === "outline"; const isDatabasesActive = isCommandPaletteVisible && commandPaletteInitialView === "databases"; - const isCollaborationActive = isRightSidebarVisible && activeRightSidebarView === "collaboration"; const footerTrailingOrder = useMemo(() => { return normalizeItemOrder( footerTrailingItemsOrder, @@ -170,26 +115,6 @@ const Footer = () => { }, [footerTrailingItemsOrder]); const footerTrailingItems: Array> = [ - ...(shouldShowOutline - ? [ - { - id: "outline" as const, - label: "Outline", - content: ( - { - openSidebarView("outline"); - }} - > - - - ), - }, - ] - : []), { id: "databases", label: "Databases", @@ -209,32 +134,6 @@ const Footer = () => { ), }, - ...(teamCollaborationEnabled - ? [ - { - id: "collaboration" as const, - label: "Collaboration", - content: ( - { - openSidebarView("collaboration"); - }} - > - - - ), - }, - ] - : []), { id: "notifications", label: "Notifications", diff --git a/windows/tauri/src/features/layout/components/main-layout.tsx b/windows/tauri/src/features/layout/components/main-layout.tsx index bf198c0c..74f6de62 100644 --- a/windows/tauri/src/features/layout/components/main-layout.tsx +++ b/windows/tauri/src/features/layout/components/main-layout.tsx @@ -1,20 +1,15 @@ import { lazy, Suspense, useEffect, useRef, useState } from "react"; -import { useChatInitialization } from "@/features/ai/hooks/use-chat-initialization"; -import { useCollaborationPresence } from "@/features/collaboration/hooks/use-collaboration-presence"; import { initializeDebuggerEventBridge } from "@/features/debugger/services/debug-adapter-events"; -import { useBufferStore } from "@/features/editor/stores/buffer.store"; -import { getBufferById } from "@/features/editor/utils/buffer-index"; import { getSymlinkInfo } from "@/features/file-system/controllers/platform"; -import type { FileEntry } from "@/features/file-system/types/app.types"; import { useFileSystemStore } from "@/features/file-system/stores/file-system.store"; import { useFileSystemFolderDrop } from "@/features/file-system/hooks/use-file-system-folder-drop"; import { openDroppedWorkspacePaths } from "@/features/file-system/utils/open-dropped-workspace-paths"; import { useGitStore } from "@/features/git/stores/git.store"; import { isGitChangeRelevant, subscribeToGitChanges } from "@/features/git/events/git-events"; +import { useBufferStore } from "@/features/editor/stores/buffer.store"; import { useOnboardingStore } from "@/features/onboarding/stores/onboarding.store"; import { CachedWorkspaceSplitViews } from "@/features/panes/components/split-view-root"; import { usePaneKeyboard } from "@/features/panes/hooks/use-pane-keyboard"; -import type { PaneContent } from "@/features/panes/types/pane-content.types"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; import { useVimStore } from "@/features/vim/stores/vim.store"; import { isWslPath } from "@/features/wsl/utils/wsl-path"; @@ -35,13 +30,8 @@ import { MainSidebar, SidebarActivityRail, } from "./sidebar/main-sidebar"; +import { WelcomeScreen } from "./welcome-screen"; -const AIChat = lazy(() => import("@/features/ai/components/chat/ai-chat")); -const AgentLauncher = lazy(() => - import("@/features/ai/components/agent-launcher").then((module) => ({ - default: module.AgentLauncher, - })), -); const CommandPalette = lazy(() => import("@/features/command-palette/components/command-palette")); const ConnectionDialog = lazy(() => import("@/features/database/components/connection/connection-dialog").then((module) => ({ @@ -79,58 +69,31 @@ const TerminalHost = lazy(() => ); const BottomPane = lazy(() => import("./bottom-pane/bottom-pane")); -const EMPTY_PROJECT_FILES: FileEntry[] = []; -const EMPTY_BUFFERS: PaneContent[] = []; export function MainLayout() { const [deferredSurfacesReady, setDeferredSurfacesReady] = useState(false); - useChatInitialization(); usePaneKeyboard(); - useCollaborationPresence(); const isSidebarVisible = useUIState((state) => state.isSidebarVisible); const activityRailExpanded = useSettingsStore((state) => state.settings.activityRailExpanded); const activityRailWidth = useSettingsStore((state) => state.settings.activityRailWidth); const sidebarWidth = useSettingsStore((state) => state.settings.sidebarWidth); - const aiChatWidth = useSettingsStore((state) => state.settings.aiChatWidth); const showStatusBar = useSettingsStore((state) => state.settings.showStatusBar); - const isRightSidebarVisible = useUIState((state) => state.isRightSidebarVisible); - const activeRightSidebarView = useUIState((state) => state.activeRightSidebarView); const isDatabaseConnectionVisible = useUIState((state) => state.isDatabaseConnectionVisible); const setIsDatabaseConnectionVisible = useUIState( (state) => state.setIsDatabaseConnectionVisible, ); - const showInlineAiChat = useSettingsStore((state) => state.settings.isAIChatVisible); const renderedActivityRailWidth = activityRailExpanded ? activityRailWidth : COLLAPSED_ACTIVITY_RAIL_WIDTH; - const visibleInlineAiChat = showInlineAiChat && deferredSurfacesReady; const leftPaneReservedWidth = - renderedActivityRailWidth + - (isRightSidebarVisible ? sidebarWidth : 0) + - (visibleInlineAiChat ? aiChatWidth : 0); - const aiPaneReservedWidth = - renderedActivityRailWidth + - (isSidebarVisible ? sidebarWidth : 0) + - (isRightSidebarVisible ? sidebarWidth : 0); - const rightPaneReservedWidth = - renderedActivityRailWidth + - (isSidebarVisible ? sidebarWidth : 0) + - (visibleInlineAiChat ? aiChatWidth : 0); + renderedActivityRailWidth + (isSidebarVisible ? sidebarWidth : 0); const vimRelativeLineNumbers = useSettingsStore((state) => state.settings.vimRelativeLineNumbers); const relativeLineNumbers = useVimStore.use.relativeLineNumbers(); const { setRelativeLineNumbers } = useVimStore.use.actions(); - const buffers = useBufferStore((state) => (showInlineAiChat ? state.buffers : EMPTY_BUFFERS)); - const activeBuffer = useBufferStore((state) => { - if (!showInlineAiChat || !state.activeBufferId) return null; - return getBufferById(state.buffers, state.activeBufferId); - }); const handleOpenFolderByPath = useFileSystemStore.use.handleOpenFolderByPath?.(); const handleFileOpen = useFileSystemStore.use.handleFileOpen?.(); const rootFolderPath = useFileSystemStore.use.rootFolderPath?.(); - const allProjectFiles = useFileSystemStore( - (state) => state.projectFilesCache?.files ?? EMPTY_PROJECT_FILES, - ); const switchToProject = useFileSystemStore.use.switchToProject?.(); const setIsSwitchingProject = useFileSystemStore.use.setIsSwitchingProject?.(); const refreshWorkspaceGitStatus = useGitStore((state) => state.actions.refreshWorkspaceGitStatus); @@ -301,83 +264,56 @@ export function MainLayout() { - - - - - - - - + {rootFolderPath ? ( + <> + - - - {terminalWidthMode === "editor" && deferredSurfacesReady && ( - - - - )} - + + + + + + + + + + {terminalWidthMode === "editor" && deferredSurfacesReady && ( + + + + )} + - {/* Right side panes are ordered from inner to edge. */} - {visibleInlineAiChat ? ( - - - - - - ) : null} - - - - - + - {terminalWidthMode === "full" && deferredSurfacesReady && ( - - - - + {terminalWidthMode === "full" && deferredSurfacesReady && ( + + + + + + )} - )} - - {showStatusBar ? : null} + {showStatusBar ? : null} + > + ) : ( + + )} {/* Global modals and overlays */} {deferredSurfacesReady ? ( @@ -385,7 +321,6 @@ export function MainLayout() { - - - {label} - - - ); -} - interface MainSidebarProps { - paneLevel?: "primary" | "edge"; activeView?: SidebarView; isGitActive?: boolean; - isGitHubPRsActive?: boolean; } interface SidebarPaneEntry { @@ -152,29 +103,11 @@ const waitForProjectCarouselPaint = () => export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRailProps) => { const { openSidebarView } = useSidebarPaneController(); const isGitViewActive = useUIState((state) => state.isGitViewActive); - const isGitHubPRsViewActive = useUIState((state) => state.isGitHubPRsViewActive); const isSidebarVisible = useUIState((state) => state.isSidebarVisible); const activeSidebarView = useUIState((state) => state.activeSidebarView); const setIsProjectPickerVisible = useUIState((state) => state.setIsProjectPickerVisible); + const openSettingsDialog = useUIState((state) => state.openSettingsDialog); const openGlobalSearchBuffer = useBufferStore.use.actions().openGlobalSearchBuffer; - const openExtensionsBuffer = useBufferStore.use.actions().openExtensionsBuffer; - const handleNewAgent = useNewAgentAction(); - const handleNewTerminal = useCallback(() => { - const uiState = useUIState.getState(); - uiState.setBottomPaneActiveTab("terminal"); - uiState.setIsBottomPaneVisible(true); - window.dispatchEvent(new CustomEvent("terminal-new")); - }, []); - const handleNewWorktree = useCallback(() => { - openSidebarView("git"); - window.setTimeout(() => { - window.dispatchEvent( - new CustomEvent("lithe:git-palette-action", { - detail: { type: "manage-branches", tab: "worktrees" }, - }), - ); - }, 0); - }, [openSidebarView]); const configuredActivityRailWidth = useSettingsStore((state) => state.settings.activityRailWidth); const openFoldersInNewWindow = useSettingsStore((state) => state.settings.openFoldersInNewWindow); const hiddenSidebarActivityItems = useSettingsStore( @@ -183,15 +116,6 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa const showActivityRailProjectSwitcher = useSettingsStore( (state) => state.settings.showActivityRailProjectSwitcher, ); - const showActivityRailAgentHistory = useSettingsStore( - (state) => state.settings.showActivityRailAgentHistory, - ); - const showActivityRailTerminals = useSettingsStore( - (state) => state.settings.showActivityRailTerminals, - ); - const showActivityRailWorktrees = useSettingsStore( - (state) => state.settings.showActivityRailWorktrees, - ); const showActivityRailProjectIcons = useSettingsStore( (state) => state.settings.showActivityRailProjectIcons, ); @@ -213,12 +137,7 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa const isResizingRef = useRef(false); const isProjectGestureSettlingRef = useRef(false); const projectWheelEndTimerRef = useRef | null>(null); - const isExtensionsBufferActive = useBufferStore((state) => { - const activeBuffer = state.buffers.find((buffer) => buffer.id === state.activeBufferId); - return activeBuffer?.type === "extensions"; - }); const coreFeatures = useSettingsStore((state) => state.settings.coreFeatures); - const extensionViews = useExtensionViews(); const projectTabs = useWorkspaceTabsStore.use.projectTabs(); const activeProject = projectTabs.find((project) => project.isActive); const carouselProject = @@ -246,7 +165,7 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa () => [ { id: "files", - label: "Files", + label: "Project", icon: , }, ...(coreFeatures.search @@ -262,46 +181,25 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa ? [ { id: "git", - label: "Source Control", + label: "Changes", icon: , }, ] : []), - ...(coreFeatures.github - ? [ - { - id: "github-prs", - label: "Pull Requests", - icon: , - }, - ] - : []), - ...(coreFeatures.docker - ? [ - { - id: "docker", - label: "Docker", - icon: , - }, - ] - : []), { - id: "extensions", - label: "Extensions", - icon: , + id: "database", + label: "Database", + icon: , + }, + { + id: "settings", + label: "Settings", + icon: , }, - ...Array.from(extensionViews.values()).map((view) => ({ - id: view.id, - label: view.title, - icon: , - })), ], [ - coreFeatures.docker, coreFeatures.git, - coreFeatures.github, coreFeatures.search, - extensionViews, ], ); @@ -320,17 +218,11 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa const hasHiddenActivityRailItems = hiddenSidebarActivityItems.length > 0 || !showActivityRailProjectSwitcher || - !showActivityRailAgentHistory || - (coreFeatures.terminal && !showActivityRailTerminals) || - (coreFeatures.git && !showActivityRailWorktrees) || !showActivityRailProjectIcons; const showAllActivityRailItems = useCallback(() => { void updateSetting("hiddenSidebarActivityItems", []); void updateSetting("showActivityRailProjectSwitcher", true); - void updateSetting("showActivityRailAgentHistory", true); - void updateSetting("showActivityRailTerminals", true); - void updateSetting("showActivityRailWorktrees", true); void updateSetting("showActivityRailProjectIcons", true); }, [updateSetting]); @@ -741,67 +633,15 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa openGlobalSearchBuffer()} - onExtensionsClick={() => openExtensionsBuffer()} - isExtensionsActive={isExtensionsBufferActive} + onSettingsClick={() => openSettingsDialog()} compact={!expanded} showLabels={expanded} orientation="vertical" /> - - {showActivityRailAgentHistory ? ( - isBackendCapabilityAvailable("agent") ? ( - - ) : ( - } - label="Agents" - /> - ) - ) : null} - {coreFeatures.terminal && showActivityRailTerminals ? ( - isBackendCapabilityAvailable("terminal") ? ( - - ) : ( - } - label="Terminals" - /> - ) - ) : null} - {coreFeatures.git && showActivityRailWorktrees ? ( - isBackendCapabilityAvailable("git") ? ( - - ) : ( - } - label="Worktrees" - /> - ) - ) : null} )} > @@ -858,42 +698,6 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa Actions - - - New Agent - - {coreFeatures.terminal ? ( - - - New Terminal - - ) : null} - {coreFeatures.git ? ( - - - New Worktree - - ) : null} setIsProjectPickerVisible(true)}> Open Project… @@ -902,18 +706,6 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa Search - openExtensionsBuffer()} - disabled={!isBackendCapabilityAvailable("extensions")} - title={ - isBackendCapabilityAvailable("extensions") - ? "Extensions" - : BACKEND_UNAVAILABLE_TOOLTIP - } - > - - Extensions - @@ -943,37 +735,6 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa {item.label} ))} - - void updateSetting("showActivityRailAgentHistory", checked) - } - > - - Agents - - {coreFeatures.terminal ? ( - - void updateSetting("showActivityRailTerminals", checked) - } - > - - Terminals - - ) : null} - {coreFeatures.git ? ( - - void updateSetting("showActivityRailWorktrees", checked) - } - > - - Worktrees - - ) : null} @@ -1001,30 +762,19 @@ export const SidebarActivityRail = memo(({ expanded = false }: SidebarActivityRa }); export const MainSidebar = memo( - ({ paneLevel = "primary", activeView, isGitActive, isGitHubPRsActive }: MainSidebarProps) => { + ({ activeView, isGitActive }: MainSidebarProps) => { const uiGitViewActive = useUIState((state) => state.isGitViewActive); - const uiGitHubPRsViewActive = useUIState((state) => state.isGitHubPRsViewActive); const uiActiveSidebarView = useUIState((state) => state.activeSidebarView); const isGitViewActive = isGitActive ?? uiGitViewActive; - const isGitHubPRsViewActive = isGitHubPRsActive ?? uiGitHubPRsViewActive; const activeSidebarView = activeView ?? uiActiveSidebarView; - const extensionViews = useExtensionViews(); const handleFileSelect = useFileSystemStore.use.handleFileSelect?.(); const rootFolderPath = useFileSystemStore.use.rootFolderPath?.(); const coreFeatures = useSettingsStore((state) => state.settings.coreFeatures); - const hasTeamsCollaborationAccess = useAuthStore( - (state) => state.subscription?.collaboration?.enabled === true, - ); - const isCollaborationFeatureEnabled = - hasTeamsCollaborationAccess && coreFeatures.teamCollaboration; - const isOutlineFeatureEnabled = coreFeatures.outline; const activePaneId: SidebarView = isGitViewActive ? "git" - : isGitHubPRsViewActive - ? "github-prs" - : activeSidebarView; + : activeSidebarView; const allPaneEntries: SidebarPaneEntry[] = [ ...(coreFeatures.git ? [ @@ -1040,57 +790,12 @@ export const MainSidebar = memo( }, ] : []), - ...(coreFeatures.github - ? [ - { - id: "github-prs" as const, - content: , - }, - ] - : []), - ...(coreFeatures.docker - ? [ - { - id: "docker" as const, - content: , - }, - ] - : []), { id: "files", content: , }, - ...(isOutlineFeatureEnabled - ? [ - { - id: "outline" as const, - content: , - }, - ] - : []), - ...(isCollaborationFeatureEnabled - ? [ - { - id: "collaboration" as const, - content: , - }, - ] - : []), - ...Array.from(extensionViews).map( - ([viewId, view]) => - ({ - id: viewId, - content: ( - - {view.render()} - - ), - }) satisfies SidebarPaneEntry, - ), ]; - const paneEntries = allPaneEntries.filter( - (pane) => pane.id === activeSidebarView || getSidebarPaneLevel(pane.id) === paneLevel, - ); + const paneEntries = allPaneEntries; const activePane = (() => { const requestedIndex = paneEntries.findIndex((pane) => pane.id === activePaneId); if (requestedIndex >= 0) return paneEntries[requestedIndex]; diff --git a/windows/tauri/src/features/layout/components/sidebar/sidebar-pane-selector.tsx b/windows/tauri/src/features/layout/components/sidebar/sidebar-pane-selector.tsx index e58d0a2e..6f383b20 100644 --- a/windows/tauri/src/features/layout/components/sidebar/sidebar-pane-selector.tsx +++ b/windows/tauri/src/features/layout/components/sidebar/sidebar-pane-selector.tsx @@ -1,21 +1,16 @@ import { useMemo, type ReactNode } from "react"; -import { - BACKEND_UNAVAILABLE_TOOLTIP, - isBackendCapabilityAvailable, -} from "@/config/backend-capabilities"; +import { BACKEND_UNAVAILABLE_TOOLTIP } from "@/config/backend-capabilities"; +import { useTranslation } from "@/i18n/locale-provider"; import type { CoreFeaturesState } from "@/features/settings/types/feature.types"; -import { useExtensionViews } from "@/extensions/ui/hooks/use-extension-views"; -import { DynamicIcon } from "@/extensions/ui/components/dynamic-icon"; import { normalizeItemOrder } from "@/features/layout/config/item-order"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; import { SidebarListItem } from "@/ui/sidebar"; import { Tabs, TabsList, TabsTrigger } from "@/ui/tabs"; import { - BoxIcon, + DatabaseIcon, + GearIcon, GitBranchIcon, - ExtensionsIcon, FilesIcon, - GitPullRequestIcon, MagnifyingGlassIcon, } from "@/ui/icons"; import Tooltip from "@/ui/tooltip"; @@ -52,14 +47,12 @@ function orderItems(items: T[], orderedIds: string[]) interface SidebarPaneSelectorProps { activeSidebarView: SidebarView; isGitViewActive: boolean; - isGitHubPRsViewActive: boolean; isSidebarVisible?: boolean; coreFeatures: CoreFeaturesState; onViewChange: (view: SidebarView) => void; onSearchClick?: () => void; - onExtensionsClick?: () => void; isSearchActive?: boolean; - isExtensionsActive?: boolean; + onSettingsClick?: () => void; compact?: boolean; showLabels?: boolean; orientation?: "horizontal" | "vertical"; @@ -68,29 +61,26 @@ interface SidebarPaneSelectorProps { export const SidebarPaneSelector = ({ activeSidebarView, isGitViewActive, - isGitHubPRsViewActive, isSidebarVisible = true, coreFeatures, onViewChange, onSearchClick, - onExtensionsClick, isSearchActive = false, - isExtensionsActive = false, + onSettingsClick, compact = false, showLabels = false, orientation = "horizontal", }: SidebarPaneSelectorProps) => { + const { t } = useTranslation(); const isVertical = orientation === "vertical"; const tooltipSide = isVertical ? "right" : "bottom"; const iconClassName = compact || isVertical ? "size-4" : undefined; - const isBufferOwnedSurfaceActive = isSearchActive || isExtensionsActive; + const isBufferOwnedSurfaceActive = isSearchActive; const isPrimarySidebarItemActive = isSidebarVisible && !isBufferOwnedSurfaceActive; const isFilesActive = isPrimarySidebarItemActive && !isGitViewActive && - !isGitHubPRsViewActive && activeSidebarView === "files"; - const extensionViews = useExtensionViews(); const sidebarActivityItemsOrder = useSettingsStore( (state) => state.settings.sidebarActivityItemsOrder, ); @@ -102,13 +92,13 @@ export const SidebarPaneSelector = ({ () => [ { id: "files", - label: showLabels ? "Files" : undefined, + label: showLabels ? t("workbench.project") : undefined, icon: , isActive: isFilesActive, onClick: () => onViewChange("files"), - ariaLabel: "Files", + ariaLabel: t("workbench.project"), tooltip: { - content: "Files", + content: t("workbench.project"), shortcut: "Mod+Shift+E", side: tooltipSide, }, @@ -117,13 +107,13 @@ export const SidebarPaneSelector = ({ ? [ { id: "search", - label: showLabels ? "Search" : undefined, + label: showLabels ? t("workbench.search") : undefined, icon: , isActive: isSearchActive, onClick: onSearchClick, - ariaLabel: "Search", + ariaLabel: t("workbench.search"), tooltip: { - content: "Search", + content: t("workbench.search"), shortcut: "Mod+Shift+F", side: tooltipSide, }, @@ -134,113 +124,59 @@ export const SidebarPaneSelector = ({ ? [ { id: "git", - label: showLabels ? "Source Control" : undefined, + label: showLabels ? t("workbench.changes") : undefined, icon: , isActive: isPrimarySidebarItemActive && isGitViewActive, onClick: () => onViewChange("git"), - disabled: !isBackendCapabilityAvailable("git"), - ariaLabel: "Git Source Control", + ariaLabel: t("workbench.changes"), tooltip: { - content: isBackendCapabilityAvailable("git") - ? "Source Control" - : BACKEND_UNAVAILABLE_TOOLTIP, + content: t("workbench.changes"), shortcut: "Mod+Shift+G", side: tooltipSide, }, } satisfies SidebarPaneItem, ] : []), - ...(coreFeatures.github - ? [ - { - id: "github-prs", - label: showLabels ? "Pull Requests" : undefined, - icon: , - isActive: isPrimarySidebarItemActive && isGitHubPRsViewActive, - onClick: () => onViewChange("github-prs"), - disabled: !isBackendCapabilityAvailable("github"), - ariaLabel: "GitHub Pull Requests", - tooltip: { - content: isBackendCapabilityAvailable("github") - ? "Pull Requests" - : BACKEND_UNAVAILABLE_TOOLTIP, - side: tooltipSide, - }, - } satisfies SidebarPaneItem, - ] - : []), - ...(coreFeatures.docker + { + id: "database", + label: showLabels ? t("workbench.database") : undefined, + icon: , + disabled: true, + ariaLabel: t("workbench.database"), + tooltip: { + content: BACKEND_UNAVAILABLE_TOOLTIP, + side: tooltipSide, + }, + }, + ...(onSettingsClick ? [ { - id: "docker", - label: showLabels ? "Docker" : undefined, - icon: , - isActive: isPrimarySidebarItemActive && activeSidebarView === "docker", - onClick: () => onViewChange("docker"), - disabled: !isBackendCapabilityAvailable("docker"), - ariaLabel: "Docker", + id: "settings", + label: showLabels ? t("workbench.settings") : undefined, + icon: , + onClick: onSettingsClick, + ariaLabel: t("workbench.settings"), tooltip: { - content: isBackendCapabilityAvailable("docker") - ? "Docker" - : BACKEND_UNAVAILABLE_TOOLTIP, + content: t("workbench.settings"), side: tooltipSide, }, } satisfies SidebarPaneItem, ] : []), - { - id: "extensions", - label: showLabels ? "Extensions" : undefined, - icon: , - isActive: isExtensionsActive, - onClick: onExtensionsClick ?? (() => onViewChange("extensions")), - disabled: !isBackendCapabilityAvailable("extensions"), - ariaLabel: "Extensions", - tooltip: { - content: isBackendCapabilityAvailable("extensions") - ? "Extensions" - : BACKEND_UNAVAILABLE_TOOLTIP, - side: tooltipSide, - }, - }, - ...Array.from(extensionViews.values()).map( - (view) => - ({ - id: view.id, - label: showLabels ? view.title : undefined, - icon: , - isActive: isPrimarySidebarItemActive && activeSidebarView === view.id, - onClick: () => onViewChange(view.id), - disabled: !isBackendCapabilityAvailable("extensions"), - ariaLabel: view.title, - tooltip: { - content: isBackendCapabilityAvailable("extensions") - ? view.title - : BACKEND_UNAVAILABLE_TOOLTIP, - side: tooltipSide, - }, - }) satisfies SidebarPaneItem, - ), ], [ - activeSidebarView, coreFeatures.git, - coreFeatures.github, - coreFeatures.docker, coreFeatures.search, - extensionViews, iconClassName, isFilesActive, isPrimarySidebarItemActive, - isGitHubPRsViewActive, isGitViewActive, isSearchActive, - isExtensionsActive, - isSidebarVisible, - onExtensionsClick, onSearchClick, + onSettingsClick, onViewChange, showLabels, + t, tooltipSide, ], ); @@ -263,6 +199,7 @@ export const SidebarPaneSelector = ({ {visibleItems.map((item) => { const itemNode = ( { const tabNode = ( state.recentFolders); + const openRecentFolder = useRecentFoldersStore((state) => state.actions.openRecentFolder); + const removeFromRecents = useRecentFoldersStore((state) => state.actions.removeFromRecents); + const handleOpenFolder = useFileSystemStore((state) => state.handleOpenFolder); + const setIsProjectPickerVisible = useUIState((state) => state.setIsProjectPickerVisible); + const setIsSettingsDialogVisible = useUIState((state) => state.setIsSettingsDialogVisible); + + const visibleRecentFolders = useMemo(() => { + const normalizedQuery = query.trim().toLowerCase(); + const sortedFolders = [...recentFolders].sort( + (left, right) => (right.lastOpenedAt ?? 0) - (left.lastOpenedAt ?? 0), + ); + + if (!normalizedQuery) return sortedFolders; + + return sortedFolders.filter((folder) => { + return ( + folder.name.toLowerCase().includes(normalizedQuery) || + folder.path.toLowerCase().includes(normalizedQuery) + ); + }); + }, [query, recentFolders]); + + return ( + + + + + + {t("welcome.title")} + + + + + + setQuery(event.target.value)} + className="min-w-0 flex-1 bg-transparent outline-none placeholder:text-subtle-foreground" + placeholder={t("welcome.searchProjects")} + aria-label={t("welcome.searchProjects")} + /> + + setIsProjectPickerVisible(true)} + > + + {t("welcome.clone")} + + void handleOpenFolder()}> + + {t("welcome.open")} + + + + + {visibleRecentFolders.length > 0 ? ( + + {visibleRecentFolders.map((folder) => ( + + + + + void openRecentFolder(folder.path)} + > + + + {folder.name} + + {folder.path} + + + removeFromRecents(folder.path)} + aria-label={t("welcome.removeRecent", { name: folder.name })} + > + + + + ))} + + ) : ( + + + + {t("welcome.noRecentProjects")} + + {t("welcome.openFolderHint")} + + )} + + + + ); +} diff --git a/windows/tauri/src/features/layout/config/item-order.ts b/windows/tauri/src/features/layout/config/item-order.ts index 02a22c36..ad9aeb7e 100644 --- a/windows/tauri/src/features/layout/config/item-order.ts +++ b/windows/tauri/src/features/layout/config/item-order.ts @@ -1,29 +1,24 @@ -export const HEADER_TRAILING_ITEM_IDS = ["run-actions", "ai-chat", "account"] as const; +export const HEADER_TRAILING_ITEM_IDS = [] as const; export const SIDEBAR_ACTIVITY_ITEM_IDS = [ "files", - "search", "git", - "github-prs", - "docker", - "extensions", + "search", + "database", + "settings", ] as const; export const FOOTER_LEADING_ITEM_IDS = [ "branch", "terminal", - "debugger", "diagnostics", - "extensions", ] as const; export const FOOTER_TRAILING_ITEM_IDS = [ - "outline", "databases", - "collaboration", "notifications", ] as const; -export type HeaderTrailingItemId = (typeof HEADER_TRAILING_ITEM_IDS)[number]; +export type HeaderTrailingItemId = "account"; export type SidebarActivityItemId = (typeof SIDEBAR_ACTIVITY_ITEM_IDS)[number]; -export type FooterLeadingItemId = (typeof FOOTER_LEADING_ITEM_IDS)[number]; +export type FooterLeadingItemId = (typeof FOOTER_LEADING_ITEM_IDS)[number] | "debugger"; export type FooterTrailingItemId = (typeof FOOTER_TRAILING_ITEM_IDS)[number]; export function normalizeItemOrder( diff --git a/windows/tauri/src/features/settings/components/settings-dialog.tsx b/windows/tauri/src/features/settings/components/settings-dialog.tsx index 76f62c17..456f33b6 100644 --- a/windows/tauri/src/features/settings/components/settings-dialog.tsx +++ b/windows/tauri/src/features/settings/components/settings-dialog.tsx @@ -1,6 +1,7 @@ import { CaretDownIcon as CaretDown, MagnifyingGlassIcon as Search } from "@/ui/icons"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; +import { useTranslation } from "@/i18n/locale-provider"; import { resolveSettingsAccess, resolveVisibleSettingsSection, @@ -10,24 +11,18 @@ import { getSettingSearchTargetKey, SETTINGS_SEARCH_TAB_LABELS, } from "@/features/settings/lib/settings-search"; -import { useAuthStore } from "@/features/window/stores/auth.store"; import { type SettingsTab, useUIState } from "@/features/window/stores/ui-state.store"; import { Card } from "@/ui/card"; import Dialog from "@/ui/dialog"; import { Dropdown, type MenuItem } from "@/ui/dropdown"; import { Empty, EmptyDescription } from "@/ui/empty"; import Input from "@/ui/input"; -import { ScrollArea } from "@/ui/scroll-area"; import type { SearchResult } from "../types/search.types"; import { SETTINGS_TAB_ITEMS, SettingsVerticalTabs } from "./settings-vertical-tabs"; import { AdvancedSettings } from "./tabs/advanced-settings"; -import { AccountSettings } from "./tabs/account-settings"; -import { AISettings } from "./tabs/ai-settings"; import { AppearanceSettings } from "./tabs/appearance-settings"; -import { CollaborationSettings } from "./tabs/collaboration-settings"; import { EditorSettings } from "./tabs/editor-settings"; -import { EnterpriseSettings } from "./tabs/enterprise-settings"; import { GeneralSettings } from "./tabs/general-settings"; import { GitSettings } from "./tabs/git-settings"; import { KeyboardSettings } from "./tabs/keyboard-settings"; @@ -39,13 +34,17 @@ interface SettingsDialogProps { onClose: () => void; } +function getSettingsTabLabelKey(tab: SettingsTab) { + return `settings.tabs.${tab === "file-explorer" ? "files" : tab}`; +} + const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { const { settingsInitialTab, setSettingsInitialTab } = useUIState(); + const { t } = useTranslation(); const [activeTab, setActiveTab] = useState("general"); const lastSettingsTab = useSettingsStore((state) => state.settings.lastSettingsTab); const updateSetting = useSettingsStore((state) => state.actions.updateSetting); - const subscription = useAuthStore((state) => state.subscription); - const settingsAccess = resolveSettingsAccess(subscription); + const settingsAccess = resolveSettingsAccess(null); const { canShowEnterpriseSettings, canShowCollaborationSettings } = settingsAccess; const clearSearch = useSettingsStore((state) => state.actions.clearSearch); @@ -120,7 +119,7 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { const Icon = tab.icon; return { id: tab.id, - label: tab.label, + label: t(getSettingsTabLabelKey(tab.id)), icon: , className: tab.id === activeTab ? "bg-accent text-foreground" : undefined, onClick: () => handleTabChange(tab.id), @@ -194,8 +193,6 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { const renderTabContent = () => { switch (activeTab) { - case "account": - return ; case "general": return ; case "editor": @@ -204,14 +201,8 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { return ; case "appearance": return ; - case "ai": - return ; case "keyboard": return ; - case "collaboration": - return canShowCollaborationSettings ? : ; - case "enterprise": - return canShowEnterpriseSettings ? : ; case "advanced": return ; case "terminal": @@ -234,7 +225,7 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { onClose={onClose} title={ <> - Settings + {t("workbench.settings")} { onClick={() => setIsTabDropdownOpen(true)} > - {activeTabItem.label} + {t(getSettingsTabLabelKey(activeTabItem.id))} > @@ -251,7 +242,7 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { { setSearchQuery(e.target.value); @@ -302,21 +293,17 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { size="flush" className="@container/settings mt-0 mr-2 mb-2 ml-0 min-w-0 flex-1 bg-background max-[720px]:ml-2" > - {renderTabContent()} - + @@ -362,7 +349,7 @@ const SettingsDialog = ({ isOpen, onClose }: SettingsDialogProps) => { }) ) : ( - No matching settings + {t("settings.noMatching")} )} diff --git a/windows/tauri/src/features/settings/components/settings-vertical-tabs.tsx b/windows/tauri/src/features/settings/components/settings-vertical-tabs.tsx index 1bc11a32..b7e35a3c 100644 --- a/windows/tauri/src/features/settings/components/settings-vertical-tabs.tsx +++ b/windows/tauri/src/features/settings/components/settings-vertical-tabs.tsx @@ -1,5 +1,4 @@ import { - ArrowSquareUpIcon as ArrowSquareUp, CodeBlockIcon as CodeBlock, GearIcon as Gear, GearSixIcon as GearSix, @@ -14,13 +13,9 @@ import { UsersThreeIcon as UsersThree, } from "@/ui/icons"; import type { ComponentType } from "react"; -import { useUpgradeToPro } from "@/features/settings/hooks/use-upgrade-to-pro"; -import { resolveSettingsAccess } from "@/features/settings/lib/settings-access"; import { filterVisibleSettingsTabs } from "@/features/settings/lib/settings-tab-visibility"; -import { useAuthStore } from "@/features/window/stores/auth.store"; +import { useTranslation } from "@/i18n/locale-provider"; import type { SettingsTab } from "@/features/window/stores/ui-state.store"; -import { useProFeature } from "@/extensions/ui/hooks/use-pro-feature"; -import { Button } from "@/ui/button"; import { Empty, EmptyDescription } from "@/ui/empty"; import { ScrollArea } from "@/ui/scroll-area"; import { Tabs, TabsList, TabsTrigger } from "@/ui/tabs"; @@ -110,12 +105,10 @@ export const SettingsVerticalTabs = ({ onTabChange, panelIdForTab = (tab) => `settings-panel-${tab}`, }: SettingsVerticalTabsProps) => { - const subscription = useAuthStore((state) => state.subscription); - const { hasSettingsSync } = useProFeature(); - const { promptUpgrade } = useUpgradeToPro(); - const settingsAccess = resolveSettingsAccess(subscription); + const { t } = useTranslation(); const visibleTabs = filterVisibleSettingsTabs(SETTINGS_TAB_ITEMS, { - ...settingsAccess, + canShowCollaborationSettings: false, + canShowEnterpriseSettings: false, matchingTabs: null, }); @@ -158,7 +151,13 @@ export const SettingsVerticalTabs = ({ )} > - {item.label} + + {t( + `settings.tabs.${ + item.id === "file-explorer" ? "files" : item.id + }`, + )} + ); }) @@ -170,21 +169,6 @@ export const SettingsVerticalTabs = ({ - - {!hasSettingsSync ? ( - - - - Upgrade to Pro - - - ) : null} ); }; diff --git a/windows/tauri/src/features/settings/components/tabs/advanced-settings.tsx b/windows/tauri/src/features/settings/components/tabs/advanced-settings.tsx index d090a70e..9b31f76c 100644 --- a/windows/tauri/src/features/settings/components/tabs/advanced-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/advanced-settings.tsx @@ -19,12 +19,21 @@ import { Empty, EmptyDescription } from "@/ui/empty"; import Switch from "@/ui/switch"; import Section, { SettingsView, SettingRow } from "../settings-section"; import { getServiceUrls } from "@/config/services"; +import { useTranslation } from "@/i18n/locale-provider"; + +const UNSUPPORTED_FEATURE_IDS = new Set([ + "github", + "remote", + "debugger", + "aiChat", + "teamCollaboration", + "webViewer", +]); -const telemetryDescription = - "Lithe sends anonymous operational metadata for updates and, when enabled, heartbeats, extensions, and crashes; it never sends file paths, project names, prompts, or editor content."; const telemetryLearnMoreUrl = getServiceUrls().telemetryDocsUrl; export const AdvancedSettings = () => { + const { t } = useTranslation(); const coreFeatures = useSettingsStore((state) => state.settings.coreFeatures); const telemetry = useSettingsStore((state) => state.settings.telemetry); const updateSetting = useSettingsStore((state) => state.actions.updateSetting); @@ -40,11 +49,11 @@ export const AdvancedSettings = () => { const handleResetSettings = () => { resetToDefaults(); - showToast({ message: "Settings reset to defaults", type: "success" }); + showToast({ message: t("settings.advanced.settingsReset"), type: "success" }); }; const defaultCoreFeatures = getDefaultSetting("coreFeatures"); const coreFeaturesList = createCoreFeaturesList(coreFeatures).filter( - (feature: CoreFeature) => feature.id !== "git", + (feature: CoreFeature) => feature.id !== "git" && !UNSUPPORTED_FEATURE_IDS.has(feature.id), ); const handleCoreFeatureToggle = (featureId: string, enabled: boolean) => { @@ -63,7 +72,7 @@ export const AdvancedSettings = () => { const handleClearTelemetryLog = async () => { await clearTelemetryLogEntries(); - showToast({ message: "Telemetry log cleared", type: "success" }); + showToast({ message: t("settings.advanced.telemetryCleared"), type: "success" }); }; const handleExportSettings = async () => { @@ -71,8 +80,8 @@ export const AdvancedSettings = () => { const targetPath = await save({ defaultPath: "lithe-settings.json", filters: [ - { name: "JSON", extensions: ["json"] }, - { name: "All Files", extensions: ["*"] }, + { name: t("settings.common.json"), extensions: ["json"] }, + { name: t("settings.common.allFiles"), extensions: ["*"] }, ], }); @@ -82,7 +91,7 @@ export const AdvancedSettings = () => { const payload = createSettingsExportPayload(useSettingsStore.getState().settings); await writeTextFile(targetPath, JSON.stringify(payload, null, 2)); - showToast({ message: "Settings exported", type: "success" }); + showToast({ message: t("settings.advanced.settingsExported"), type: "success" }); } catch (error) { console.error("Failed to export settings:", error); const message = @@ -93,7 +102,7 @@ export const AdvancedSettings = () => { : JSON.stringify(error); showToast({ - message: `Failed to export settings: ${message}`, + message: t("settings.advanced.exportFailed", { error: message }), type: "error", }); } @@ -115,14 +124,17 @@ export const AdvancedSettings = () => { const imported = useSettingsStore.getState().actions.updateSettingsFromJSON(text); if (!imported) { - showToast({ message: "Invalid settings file format", type: "error" }); + showToast({ message: t("settings.advanced.invalidFile"), type: "error" }); return; } - showToast({ message: "Settings imported", type: "success" }); + showToast({ message: t("settings.advanced.settingsImported"), type: "success" }); } catch (error) { console.error("Failed to import settings:", error); - showToast({ message: `Failed to import settings: ${error}`, type: "error" }); + showToast({ + message: t("settings.advanced.importFailed", { error: String(error) }), + type: "error", + }); } }; input.click(); @@ -130,19 +142,22 @@ export const AdvancedSettings = () => { return ( - + {coreFeaturesList.map((feature: CoreFeature) => ( - Experimental + {t("settings.advanced.experimental")} ) : undefined } - description={feature.description} + description={t(`settings.advanced.feature.${feature.id}.description`)} onReset={() => handleResetFeature(feature.id)} canReset={ feature.enabled !== @@ -157,37 +172,43 @@ export const AdvancedSettings = () => { ))} - - + + void handleExportSettings()} size="sm"> - Export + {t("settings.keyboard.export")} - Import + {t("settings.keyboard.import")} - - + + - + - {telemetryDescription}{" "} + {t("settings.advanced.telemetryDescription")} {" "} - Learn more + {t("settings.advanced.learnMore")} > } @@ -199,8 +220,8 @@ export const AdvancedSettings = () => { /> { onClick={() => setShowTelemetryLog((value) => !value)} size="sm" > - {showTelemetryLog ? "Hide Log" : "Open Log"} + {showTelemetryLog ? t("settings.advanced.hideLog") : t("settings.advanced.openLog")} - Clear + {t("settings.advanced.clear")} @@ -219,7 +240,7 @@ export const AdvancedSettings = () => { {telemetryLog.length === 0 ? ( - No telemetry entries yet. + {t("settings.advanced.noTelemetry")} ) : ( diff --git a/windows/tauri/src/features/settings/components/tabs/appearance-settings.tsx b/windows/tauri/src/features/settings/components/tabs/appearance-settings.tsx index f21471cc..1cea7460 100644 --- a/windows/tauri/src/features/settings/components/tabs/appearance-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/appearance-settings.tsx @@ -33,8 +33,10 @@ import { deleteCustomTheme, uploadTheme, } from "@/features/settings/utils/theme-upload"; +import { useTranslation } from "@/i18n/locale-provider"; export const AppearanceSettings = () => { + const { t } = useTranslation(); const settings = useSettingsStore( useShallow((state) => ({ autoThemeDark: state.settings.autoThemeDark, @@ -147,7 +149,7 @@ export const AppearanceSettings = () => { chooseThemeFile((file) => { void uploadTheme(file).then((result) => { if (!result.success || !result.theme) { - toast.error(result.error ?? "Failed to import theme", { + toast.error(result.error ?? t("settings.appearance.importThemeFailed"), { description: result.details?.slice(0, 4).join("\n"), }); return; @@ -155,8 +157,8 @@ export const AppearanceSettings = () => { toast.success( result.themes?.length === 1 - ? `Imported ${result.theme.name}` - : `Imported ${result.themes?.length ?? 0} theme variants`, + ? t("settings.appearance.importedTheme", { theme: result.theme.name }) + : t("settings.appearance.importedThemeVariants", { count: result.themes?.length ?? 0 }), ); selectImportedTheme(result.theme.id); }); @@ -177,9 +179,9 @@ export const AppearanceSettings = () => { } await Promise.all(fallbackUpdates); await deleteCustomTheme(themeId); - toast.success("Custom theme removed"); + toast.success(t("settings.appearance.customThemeRemoved")); } catch (error) { - toast.error("Failed to remove custom theme", { + toast.error(t("settings.appearance.removeCustomThemeFailed"), { description: error instanceof Error ? error.message : String(error), }); } @@ -191,10 +193,10 @@ export const AppearanceSettings = () => { return ( - + updateSetting("syncSystemTheme", getDefaultSetting("syncSystemTheme"))} canReset={settings.syncSystemTheme !== getDefaultSetting("syncSystemTheme")} > @@ -207,8 +209,8 @@ export const AppearanceSettings = () => { {!settings.syncSystemTheme ? ( updateSetting("theme", getDefaultSetting("theme"))} canReset={settings.theme !== getDefaultSetting("theme")} > @@ -228,8 +230,8 @@ export const AppearanceSettings = () => { {settings.syncSystemTheme ? ( <> updateSetting("autoThemeLight", getDefaultSetting("autoThemeLight"))} canReset={settings.autoThemeLight !== getDefaultSetting("autoThemeLight")} > @@ -246,8 +248,8 @@ export const AppearanceSettings = () => { updateSetting("autoThemeDark", getDefaultSetting("autoThemeDark"))} canReset={settings.autoThemeDark !== getDefaultSetting("autoThemeDark")} > @@ -266,8 +268,8 @@ export const AppearanceSettings = () => { ) : null} updateSetting("iconTheme", getDefaultSetting("iconTheme"))} canReset={settings.iconTheme !== getDefaultSetting("iconTheme")} > @@ -284,17 +286,17 @@ export const AppearanceSettings = () => { - Import Lithe theme JSON or create one from an installed theme.{" "} + {t("settings.appearance.customThemesDescription")} {" "} - Format guide + {t("settings.appearance.formatGuide")} > } @@ -302,11 +304,11 @@ export const AppearanceSettings = () => { setIsThemeCreatorOpen(true)}> - Create + {t("settings.appearance.create")} - Import + {t("settings.appearance.import")} @@ -321,7 +323,7 @@ export const AppearanceSettings = () => { type="button" size="icon-xs" variant="danger" - tooltip={`Remove ${theme.name}`} + tooltip={t("settings.appearance.removeTheme", { theme: theme.name })} onClick={() => void handleRemoveCustomTheme(theme.id)} > @@ -330,10 +332,10 @@ export const AppearanceSettings = () => { ))} - + updateSetting("uiFontFamily", getDefaultSetting("uiFontFamily"))} canReset={settings.uiFontFamily !== getDefaultSetting("uiFontFamily")} > @@ -346,8 +348,8 @@ export const AppearanceSettings = () => { updateSetting("uiFontSize", getDefaultSetting("uiFontSize"))} canReset={settings.uiFontSize !== getDefaultSetting("uiFontSize")} > @@ -359,15 +361,17 @@ export const AppearanceSettings = () => { onChange={(value) => updateSetting("uiFontSize", value)} className={cn(SETTINGS_CONTROL_WIDTHS.number, "tabular-nums")} size="sm" - aria-label={`UI font size: ${formatUiFontSize(settings.uiFontSize)} pixels`} + aria-label={t("settings.appearance.uiFontSizeAria", { + size: formatUiFontSize(settings.uiFontSize), + })} /> - + updateSetting("reduceMotion", getDefaultSetting("reduceMotion"))} canReset={settings.reduceMotion !== getDefaultSetting("reduceMotion")} > @@ -379,8 +383,8 @@ export const AppearanceSettings = () => { updateSetting("showStatusBar", getDefaultSetting("showStatusBar"))} canReset={settings.showStatusBar !== getDefaultSetting("showStatusBar")} > @@ -392,8 +396,8 @@ export const AppearanceSettings = () => { updateSetting("showTabIcons", getDefaultSetting("showTabIcons"))} canReset={settings.showTabIcons !== getDefaultSetting("showTabIcons")} > @@ -405,8 +409,8 @@ export const AppearanceSettings = () => { updateSetting("tabCloseButtonVisibility", getDefaultSetting("tabCloseButtonVisibility")) } @@ -417,9 +421,9 @@ export const AppearanceSettings = () => { updateSetting("tabCloseButtonVisibility", value as TabCloseButtonVisibility) @@ -431,10 +435,10 @@ export const AppearanceSettings = () => { - + updateSetting("windowChromeDensity", getDefaultSetting("windowChromeDensity")) } @@ -443,8 +447,8 @@ export const AppearanceSettings = () => { updateSetting("windowChromeDensity", value as WindowChromeDensity)} className={SETTINGS_CONTROL_WIDTHS.wide} @@ -454,8 +458,8 @@ export const AppearanceSettings = () => { updateSetting("activityRailExpanded", getDefaultSetting("activityRailExpanded")) } @@ -469,8 +473,8 @@ export const AppearanceSettings = () => { updateSetting("activityRailWidth", getDefaultSetting("activityRailWidth"))} canReset={settings.activityRailWidth !== getDefaultSetting("activityRailWidth")} > @@ -483,13 +487,13 @@ export const AppearanceSettings = () => { className={SETTINGS_CONTROL_WIDTHS.number} size="sm" disabled={!settings.activityRailExpanded} - aria-label={`Activity bar width: ${settings.activityRailWidth} pixels`} + aria-label={t("settings.appearance.activityBarWidthAria", { size: settings.activityRailWidth })} /> updateSetting("sidebarWidth", getDefaultSetting("sidebarWidth"))} canReset={settings.sidebarWidth !== getDefaultSetting("sidebarWidth")} > @@ -501,14 +505,14 @@ export const AppearanceSettings = () => { onChange={(value) => updateSetting("sidebarWidth", value)} className={SETTINGS_CONTROL_WIDTHS.number} size="sm" - aria-label={`Sidebar width: ${settings.sidebarWidth} pixels`} + aria-label={t("settings.appearance.sidebarWidthAria", { size: settings.sidebarWidth })} /> {!IS_MAC && !IS_WINDOWS && !IS_LINUX && ( updateSetting("nativeMenuBar", getDefaultSetting("nativeMenuBar"))} canReset={settings.nativeMenuBar !== getDefaultSetting("nativeMenuBar")} > @@ -525,8 +529,8 @@ export const AppearanceSettings = () => { {!IS_MAC && ( updateSetting("compactMenuBar", getDefaultSetting("compactMenuBar"))} canReset={settings.compactMenuBar !== getDefaultSetting("compactMenuBar")} > @@ -540,8 +544,8 @@ export const AppearanceSettings = () => { )} updateSetting("windowTransparency", getDefaultSetting("windowTransparency")) } @@ -555,8 +559,8 @@ export const AppearanceSettings = () => { updateSetting("openFoldersInNewWindow", getDefaultSetting("openFoldersInNewWindow")) } diff --git a/windows/tauri/src/features/settings/components/tabs/editor-settings.tsx b/windows/tauri/src/features/settings/components/tabs/editor-settings.tsx index 85b7d5ee..e7315334 100644 --- a/windows/tauri/src/features/settings/components/tabs/editor-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/editor-settings.tsx @@ -7,8 +7,10 @@ import Section, { SETTINGS_CONTROL_WIDTHS, SettingsView, SettingRow } from "../s import Select from "@/ui/select"; import Switch from "@/ui/switch"; import { FontSelector } from "../font-selector"; +import { useTranslation } from "@/i18n/locale-provider"; export const EditorSettings = () => { + const { t } = useTranslation(); const settings = useSettingsStore( useShallow((state) => ({ autoCompletion: state.settings.autoCompletion, @@ -48,26 +50,26 @@ export const EditorSettings = () => { const updateSetting = useSettingsStore((state) => state.actions.updateSetting); const languageOptions = useMemo( () => [ - { value: "auto", label: "Auto Detect" }, + { value: "auto", label: t("settings.editor.autoDetect") }, ...getAllLanguages().map((language) => ({ value: language.id, label: language.displayName, })), ], - [], + [t], ); const renderWhitespaceOptions = [ - { value: "none", label: "None" }, - { value: "boundary", label: "Boundary" }, - { value: "trailing", label: "Trailing" }, - { value: "all", label: "All" }, + { value: "none", label: t("settings.editor.whitespaceNone") }, + { value: "boundary", label: t("settings.editor.whitespaceBoundary") }, + { value: "trailing", label: t("settings.editor.whitespaceTrailing") }, + { value: "all", label: t("settings.editor.whitespaceAll") }, ]; return ( - + updateSetting("fontFamily", getDefaultSetting("fontFamily"))} canReset={settings.fontFamily !== getDefaultSetting("fontFamily")} > @@ -80,8 +82,8 @@ export const EditorSettings = () => { updateSetting("fontSize", getDefaultSetting("fontSize"))} canReset={settings.fontSize !== getDefaultSetting("fontSize")} > @@ -96,8 +98,8 @@ export const EditorSettings = () => { updateSetting("editorFontLigatures", getDefaultSetting("editorFontLigatures")) } @@ -111,8 +113,8 @@ export const EditorSettings = () => { updateSetting("editorItalicComments", getDefaultSetting("editorItalicComments")) } @@ -126,8 +128,8 @@ export const EditorSettings = () => { updateSetting("editorLineHeight", getDefaultSetting("editorLineHeight"))} canReset={settings.editorLineHeight !== getDefaultSetting("editorLineHeight")} > @@ -143,8 +145,8 @@ export const EditorSettings = () => { updateSetting("tabSize", getDefaultSetting("tabSize"))} canReset={settings.tabSize !== getDefaultSetting("tabSize")} > @@ -158,8 +160,8 @@ export const EditorSettings = () => { /> updateSetting("wordWrap", getDefaultSetting("wordWrap"))} canReset={settings.wordWrap !== getDefaultSetting("wordWrap")} > @@ -171,8 +173,8 @@ export const EditorSettings = () => { updateSetting("lineNumbers", getDefaultSetting("lineNumbers"))} canReset={settings.lineNumbers !== getDefaultSetting("lineNumbers")} > @@ -184,8 +186,8 @@ export const EditorSettings = () => { updateSetting("renderWhitespace", getDefaultSetting("renderWhitespace"))} canReset={settings.renderWhitespace !== getDefaultSetting("renderWhitespace")} > @@ -202,8 +204,8 @@ export const EditorSettings = () => { updateSetting("renderIndentGuides", getDefaultSetting("renderIndentGuides")) } @@ -217,8 +219,8 @@ export const EditorSettings = () => { updateSetting("highlightOccurrences", getDefaultSetting("highlightOccurrences")) } @@ -232,8 +234,8 @@ export const EditorSettings = () => { updateSetting("vimRelativeLineNumbers", getDefaultSetting("vimRelativeLineNumbers")) } @@ -248,8 +250,8 @@ export const EditorSettings = () => { updateSetting("showMinimap", getDefaultSetting("showMinimap"))} canReset={settings.showMinimap !== getDefaultSetting("showMinimap")} > @@ -261,8 +263,8 @@ export const EditorSettings = () => { updateSetting("editorStickyScroll", getDefaultSetting("editorStickyScroll")) } @@ -276,8 +278,8 @@ export const EditorSettings = () => { updateSetting( "editorBracketPairColorization", @@ -297,8 +299,8 @@ export const EditorSettings = () => { updateSetting("editorSmoothScrolling", getDefaultSetting("editorSmoothScrolling")) } @@ -312,8 +314,8 @@ export const EditorSettings = () => { updateSetting( "editorScrollBeyondLastLine", @@ -332,20 +334,20 @@ export const EditorSettings = () => { updateSetting("editorCursorStyle", getDefaultSetting("editorCursorStyle"))} canReset={settings.editorCursorStyle !== getDefaultSetting("editorCursorStyle")} > updateSetting("editorCursorStyle", value as typeof settings.editorCursorStyle) @@ -357,8 +359,8 @@ export const EditorSettings = () => { updateSetting("editorCursorBlinking", getDefaultSetting("editorCursorBlinking")) } @@ -367,11 +369,11 @@ export const EditorSettings = () => { updateSetting("editorCursorBlinking", value as typeof settings.editorCursorBlinking) @@ -383,8 +385,8 @@ export const EditorSettings = () => { updateSetting("maxOpenTabs", getDefaultSetting("maxOpenTabs"))} canReset={settings.maxOpenTabs !== getDefaultSetting("maxOpenTabs")} > @@ -399,8 +401,8 @@ export const EditorSettings = () => { updateSetting("horizontalTabScroll", getDefaultSetting("horizontalTabScroll")) } @@ -413,8 +415,8 @@ export const EditorSettings = () => { /> updateSetting("autoSave", getDefaultSetting("autoSave"))} canReset={settings.autoSave !== getDefaultSetting("autoSave")} > @@ -425,8 +427,8 @@ export const EditorSettings = () => { /> updateSetting("defaultLanguage", getDefaultSetting("defaultLanguage"))} canReset={settings.defaultLanguage !== getDefaultSetting("defaultLanguage")} > @@ -443,8 +445,8 @@ export const EditorSettings = () => { updateSetting("autoDetectLanguage", getDefaultSetting("autoDetectLanguage")) } @@ -458,8 +460,8 @@ export const EditorSettings = () => { updateSetting("formatOnSave", getDefaultSetting("formatOnSave"))} canReset={settings.formatOnSave !== getDefaultSetting("formatOnSave")} > @@ -471,8 +473,8 @@ export const EditorSettings = () => { updateSetting("lintOnSave", getDefaultSetting("lintOnSave"))} canReset={settings.lintOnSave !== getDefaultSetting("lintOnSave")} > @@ -484,8 +486,8 @@ export const EditorSettings = () => { updateSetting("autoCompletion", getDefaultSetting("autoCompletion"))} canReset={settings.autoCompletion !== getDefaultSetting("autoCompletion")} > @@ -497,8 +499,8 @@ export const EditorSettings = () => { updateSetting("parameterHints", getDefaultSetting("parameterHints"))} canReset={settings.parameterHints !== getDefaultSetting("parameterHints")} > @@ -510,8 +512,8 @@ export const EditorSettings = () => { updateSetting("inlayHints", getDefaultSetting("inlayHints"))} canReset={settings.inlayHints !== getDefaultSetting("inlayHints")} > @@ -523,8 +525,8 @@ export const EditorSettings = () => { updateSetting("codeLens", getDefaultSetting("codeLens"))} canReset={settings.codeLens !== getDefaultSetting("codeLens")} > @@ -536,8 +538,8 @@ export const EditorSettings = () => { updateSetting("semanticTokens", getDefaultSetting("semanticTokens"))} canReset={settings.semanticTokens !== getDefaultSetting("semanticTokens")} > @@ -549,8 +551,8 @@ export const EditorSettings = () => { updateSetting("breadcrumbShowSymbols", getDefaultSetting("breadcrumbShowSymbols")) } diff --git a/windows/tauri/src/features/settings/components/tabs/file-tree-settings.tsx b/windows/tauri/src/features/settings/components/tabs/file-tree-settings.tsx index 21ed4066..812833f4 100644 --- a/windows/tauri/src/features/settings/components/tabs/file-tree-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/file-tree-settings.tsx @@ -7,8 +7,10 @@ import Select from "@/ui/select"; import Textarea from "@/ui/textarea"; import Section, { SETTINGS_CONTROL_WIDTHS, SettingsView, SettingRow } from "../settings-section"; import Switch from "@/ui/switch"; +import { useTranslation } from "@/i18n/locale-provider"; export const FileTreeSettings = () => { + const { t } = useTranslation(); const settings = useSettingsStore( useShallow((state) => ({ autoRevealActiveFileInFileTree: state.settings.autoRevealActiveFileInFileTree, @@ -59,18 +61,18 @@ export const FileTreeSettings = () => { return ( - + updateSetting("fileTreeSortOrder", getDefaultSetting("fileTreeSortOrder"))} canReset={settings.fileTreeSortOrder !== getDefaultSetting("fileTreeSortOrder")} > updateSetting("fileTreeSortOrder", value as FileTreeSortOrder)} className={SETTINGS_CONTROL_WIDTHS.default} @@ -80,8 +82,8 @@ export const FileTreeSettings = () => { updateSetting("fileTreeIndentSize", getDefaultSetting("fileTreeIndentSize")) } @@ -98,8 +100,8 @@ export const FileTreeSettings = () => { updateSetting("showFileIconsInFileTree", getDefaultSetting("showFileIconsInFileTree")) } @@ -115,8 +117,8 @@ export const FileTreeSettings = () => { updateSetting( "showIndentGuidesInFileTree", @@ -135,8 +137,8 @@ export const FileTreeSettings = () => { updateSetting("compactFoldersInFileTree", getDefaultSetting("compactFoldersInFileTree")) } @@ -152,8 +154,8 @@ export const FileTreeSettings = () => { updateSetting("hideRootFolderInFileTree", getDefaultSetting("hideRootFolderInFileTree")) } @@ -169,8 +171,8 @@ export const FileTreeSettings = () => { updateSetting( "showHiddenFilesInFileTree", @@ -189,8 +191,8 @@ export const FileTreeSettings = () => { updateSetting( "showGitignoredFilesInFileTree", @@ -210,8 +212,8 @@ export const FileTreeSettings = () => { updateSetting("showGitStatusInFileTree", getDefaultSetting("showGitStatusInFileTree")) } @@ -227,10 +229,10 @@ export const FileTreeSettings = () => { - + updateSetting( "autoRevealActiveFileInFileTree", @@ -250,8 +252,8 @@ export const FileTreeSettings = () => { updateSetting("confirmBeforeFileDelete", getDefaultSetting("confirmBeforeFileDelete")) } @@ -267,10 +269,10 @@ export const FileTreeSettings = () => { - + updateSetting("hiddenFilePatterns", getDefaultSetting("hiddenFilePatterns")) } @@ -297,8 +299,8 @@ export const FileTreeSettings = () => { updateSetting("hiddenDirectoryPatterns", getDefaultSetting("hiddenDirectoryPatterns")) } diff --git a/windows/tauri/src/features/settings/components/tabs/general-settings.tsx b/windows/tauri/src/features/settings/components/tabs/general-settings.tsx index d2f9ecc9..684c2727 100644 --- a/windows/tauri/src/features/settings/components/tabs/general-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/general-settings.tsx @@ -5,6 +5,8 @@ import { IdeSettingsImportDialog } from "@/features/file-system/components/ide-s import { useToast } from "@/features/layout/contexts/toast-context"; import { TypedConfirmAction } from "@/features/settings/components/typed-confirm-action"; import { useUpdater } from "@/features/settings/hooks/use-updater"; +import { useSettingsStore } from "@/features/settings/stores/settings.store"; +import { useTranslation } from "@/i18n/locale-provider"; import { Button } from "@/ui/button"; import Command, { CommandEmpty, @@ -14,15 +16,16 @@ import Command, { CommandList, } from "@/ui/command"; import { Progress } from "@/ui/progress"; +import Select from "@/ui/select"; import { writeClipboardText } from "@/utils/clipboard"; import { matchesSearchQuery } from "@/utils/search-match"; -import { SettingsView, SettingRow } from "../settings-section"; +import { SETTINGS_CONTROL_WIDTHS, SettingsView, SettingRow } from "../settings-section"; const REPORT_BUG_CHANNELS = [ { id: "github", label: "GitHub", - detail: "Open a bug report issue", + detailKey: "settings.general.githubReportDescription", url: "https://github.com/1lck/Lithe-IDEA/issues/new", }, ] as const; @@ -30,6 +33,9 @@ const REPORT_BUG_CHANNELS = [ type ReportBugChannel = (typeof REPORT_BUG_CHANNELS)[number]; export const GeneralSettings = () => { + const displayLanguage = useSettingsStore((state) => state.settings.displayLanguage); + const updateSetting = useSettingsStore((state) => state.actions.updateSetting); + const { t } = useTranslation(); const { available, checking, @@ -77,7 +83,7 @@ export const GeneralSettings = () => { setCliInstalled(true); } catch (error) { showToast({ - message: `Failed to install CLI: ${error}. You may need administrator privileges.`, + message: t("settings.general.cliInstallFailed", { error: String(error) }), type: "error", }); } finally { @@ -92,7 +98,7 @@ export const GeneralSettings = () => { showToast({ message: result, type: "success" }); setCliInstalled(false); } catch (error) { - showToast({ message: `Failed to uninstall CLI: ${error}`, type: "error" }); + showToast({ message: t("settings.general.cliUninstallFailed", { error: String(error) }), type: "error" }); } finally { setCliInstalling(false); } @@ -102,16 +108,16 @@ export const GeneralSettings = () => { try { const command = await invoke("get_cli_install_command"); await writeClipboardText(command); - showToast({ message: "Install command copied to clipboard", type: "success" }); + showToast({ message: t("settings.general.installCommandCopied"), type: "success" }); } catch (error) { - showToast({ message: `Failed to copy command: ${error}`, type: "error" }); + showToast({ message: t("settings.general.copyCommandFailed", { error: String(error) }), type: "error" }); } }; const handleCheckForUpdates = async () => { const hasUpdate = await checkForUpdates({ ignoreSuppression: true }); if (!hasUpdate) { - showToast({ message: "You're on the latest version", type: "success" }); + showToast({ message: t("settings.general.latestVersion"), type: "success" }); } }; @@ -121,7 +127,7 @@ export const GeneralSettings = () => { const plat = os.platform(); const ver = os.version(); - return `Environment\n\n- App: Lithe ${version}\n- OS: ${plat} ${ver}\n\nProblem\n\nDescribe the issue here. Steps to reproduce, expected vs actual.\n`; + return t("settings.general.bugReportTemplate", { version, platform: plat, osVersion: ver }); }; const handleReportBug = async (channel: ReportBugChannel) => { @@ -131,20 +137,36 @@ export const GeneralSettings = () => { await writeClipboardText(report); await openUrl(channel.url); - showToast({ message: "Report template copied", type: "success" }); + showToast({ message: t("settings.general.reportTemplateCopied"), type: "success" }); setIsReportBugDialogOpen(false); } catch (err) { console.error("Failed to prepare bug report:", err); - showToast({ message: "Failed to prepare bug report", type: "error" }); + showToast({ message: t("settings.general.prepareReportFailed"), type: "error" }); } }; return ( + updateSetting("displayLanguage", value as "en-US" | "zh-CN")} + className={SETTINGS_CONTROL_WIDTHS.default} + size="md" + variant="default" + /> + + {available ? ( @@ -155,10 +177,12 @@ export const GeneralSettings = () => { size="sm" > {downloading - ? "Downloading..." + ? t("settings.general.downloading") : installing - ? "Installing..." - : `Install ${updateInfo?.version ?? "update"}`} + ? t("settings.general.installing") + : t("settings.general.installUpdate", { + version: updateInfo?.version ?? t("settings.general.update"), + })} ) : ( { variant="default" size="sm" > - {checking ? "Checking..." : "Check"} + {checking ? t("settings.general.checking") : t("settings.general.check")} )} @@ -175,20 +199,26 @@ export const GeneralSettings = () => { {downloading - ? `Lithe ${appVersion || "..."} · Downloading ${downloadProgress?.percentage ?? 0}%` + ? t("settings.general.updateProgress", { + version: appVersion || "...", + percentage: downloadProgress?.percentage ?? 0, + }) : installing - ? `Lithe ${appVersion || "..."} · Installing update...` + ? t("settings.general.updateInstalling", { version: appVersion || "..." }) : available - ? `Lithe ${appVersion || "..."} · Version ${updateInfo?.version} available` + ? t("settings.general.updateAvailable", { + version: appVersion || "...", + availableVersion: updateInfo?.version ?? "", + }) : error - ? `Lithe ${appVersion || "..."} · Failed to check for updates` - : `Lithe ${appVersion || "..."} · App is up to date`} + ? t("settings.general.updateCheckFailed", { version: appVersion || "..." }) + : t("settings.general.upToDate", { version: appVersion || "..." })} {downloading && downloadProgress ? ( ) : null} @@ -196,14 +226,14 @@ export const GeneralSettings = () => { {error && {error}} {cliInstalled ? ( @@ -215,16 +245,16 @@ export const GeneralSettings = () => { variant="default" size="sm" > - {cliInstalling ? "Installing..." : "Install"} + {cliInstalling ? t("settings.general.installing") : t("settings.general.install")} - Copy + {t("settings.general.copy")} > )} @@ -233,24 +263,27 @@ export const GeneralSettings = () => { {cliChecking - ? "Checking..." + ? t("settings.general.checking") : cliInstalled - ? "CLI command is installed at $HOME/.local/bin/lithe" - : "CLI command is not installed."} + ? t("settings.general.cliInstalled") + : t("settings.general.cliNotInstalled")} - + setIsImportDialogOpen(true)} variant="default" size="sm"> - Import + {t("settings.general.importSettings")} setIsReportBugDialogOpen(true)} variant="default" size="sm"> - Open + {t("settings.general.open")} @@ -274,6 +307,7 @@ function ReportBugCommandDialog({ onClose: () => void; onSelect: (channel: ReportBugChannel) => void; }) { + const { t } = useTranslation(); const inputRef = useRef(null); const [query, setQuery] = useState(""); const [selectedIndex, setSelectedIndex] = useState(0); @@ -282,12 +316,8 @@ function ReportBugCommandDialog({ inputRef.current?.focus(); }, []); - const channels = useMemo( - () => - REPORT_BUG_CHANNELS.filter((channel) => - matchesSearchQuery(query, [channel.label, channel.detail]), - ), - [query], + const channels = REPORT_BUG_CHANNELS.filter((channel) => + matchesSearchQuery(query, [channel.label, t(channel.detailKey)]), ); useEffect(() => { @@ -316,19 +346,19 @@ function ReportBugCommandDialog({ }; return ( - + {channels.length === 0 ? ( - No report channel matches "{query}". + {t("settings.general.noReportChannel", { query })} ) : ( channels.map((channel, index) => ( onSelect(channel)} onMouseEnter={() => setSelectedIndex(index)} title={channel.label} - description={channel.detail} + description={t(channel.detailKey)} /> )) )} diff --git a/windows/tauri/src/features/settings/components/tabs/git-settings.tsx b/windows/tauri/src/features/settings/components/tabs/git-settings.tsx index 46f51502..35ceef9c 100644 --- a/windows/tauri/src/features/settings/components/tabs/git-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/git-settings.tsx @@ -1,10 +1,12 @@ import { useShallow } from "zustand/react/shallow"; import { getDefaultSetting, useSettingsStore } from "@/features/settings/stores/settings.store"; +import { useTranslation } from "@/i18n/locale-provider"; import Section, { SETTINGS_CONTROL_WIDTHS, SettingsView, SettingRow } from "../settings-section"; import Select from "@/ui/select"; import Switch from "@/ui/switch"; export const GitSettings = () => { + const { t } = useTranslation(); const settings = useSettingsStore( useShallow((state) => ({ autoRefreshGitStatus: state.settings.autoRefreshGitStatus, @@ -32,10 +34,10 @@ export const GitSettings = () => { return ( - + updateSetting("coreFeatures", getDefaultSetting("coreFeatures"))} canReset={settings.coreFeatures.git !== getDefaultSetting("coreFeatures").git} > @@ -43,8 +45,8 @@ export const GitSettings = () => { updateSetting("autoRefreshGitStatus", getDefaultSetting("autoRefreshGitStatus")) } @@ -58,8 +60,8 @@ export const GitSettings = () => { updateSetting("confirmBeforeDiscard", getDefaultSetting("confirmBeforeDiscard")) } @@ -73,10 +75,10 @@ export const GitSettings = () => { - + updateSetting("gitChangesFolderView", getDefaultSetting("gitChangesFolderView")) } @@ -90,8 +92,8 @@ export const GitSettings = () => { updateSetting("showUntrackedFiles", getDefaultSetting("showUntrackedFiles")) } @@ -105,8 +107,8 @@ export const GitSettings = () => { updateSetting("showStagedFirst", getDefaultSetting("showStagedFirst"))} canReset={settings.showStagedFirst !== getDefaultSetting("showStagedFirst")} > @@ -118,8 +120,8 @@ export const GitSettings = () => { updateSetting("openDiffOnClick", getDefaultSetting("openDiffOnClick"))} canReset={settings.openDiffOnClick !== getDefaultSetting("openDiffOnClick")} > @@ -131,8 +133,8 @@ export const GitSettings = () => { updateSetting("compactGitStatusBadges", getDefaultSetting("compactGitStatusBadges")) } @@ -146,8 +148,8 @@ export const GitSettings = () => { updateSetting("collapseEmptyGitSections", getDefaultSetting("collapseEmptyGitSections")) } @@ -163,8 +165,8 @@ export const GitSettings = () => { updateSetting("rememberLastGitPanelMode", getDefaultSetting("rememberLastGitPanelMode")) } @@ -180,8 +182,8 @@ export const GitSettings = () => { updateSetting("gitDefaultDiffView", getDefaultSetting("gitDefaultDiffView")) } @@ -190,8 +192,8 @@ export const GitSettings = () => { updateSetting("gitDefaultDiffView", value as "unified" | "split")} className={SETTINGS_CONTROL_WIDTHS.default} @@ -203,10 +205,10 @@ export const GitSettings = () => { - + updateSetting("enableInlineGitBlame", getDefaultSetting("enableInlineGitBlame")) } diff --git a/windows/tauri/src/features/settings/components/tabs/keyboard-settings.tsx b/windows/tauri/src/features/settings/components/tabs/keyboard-settings.tsx index 6512e5dc..4325b610 100644 --- a/windows/tauri/src/features/settings/components/tabs/keyboard-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/keyboard-settings.tsx @@ -45,6 +45,7 @@ import { quickTransition } from "@/utils/motion"; import { matchesSearchQuery } from "@/utils/search-match"; import { TypedConfirmAction } from "../typed-confirm-action"; import { SettingsView, SettingRow } from "../settings-section"; +import { useTranslation } from "@/i18n/locale-provider"; type FilterType = "all" | "user" | "default" | "preset" | "preset-changes" | "extension"; @@ -63,6 +64,7 @@ const summaryStepTransition = { }; export const KeyboardSettings = () => { + const { t } = useTranslation(); const [searchQuery, setSearchQuery] = useState(""); const [filterType, setFilterType] = useState("all"); const [isEditingKeybindings, setIsEditingKeybindings] = useState(false); @@ -140,7 +142,7 @@ export const KeyboardSettings = () => { const handleResetAll = () => { resetToDefaults(); - showToast({ message: "Keybindings reset to defaults", type: "success" }); + showToast({ message: t("settings.keyboard.keybindingsReset"), type: "success" }); }; const handleExport = async () => { @@ -150,8 +152,8 @@ export const KeyboardSettings = () => { const targetPath = await save({ defaultPath: "keybindings.json", filters: [ - { name: "JSON", extensions: ["json"] }, - { name: "All Files", extensions: ["*"] }, + { name: t("settings.common.json"), extensions: ["json"] }, + { name: t("settings.common.allFiles"), extensions: ["*"] }, ], }); @@ -165,7 +167,7 @@ export const KeyboardSettings = () => { }); await writeTextFile(targetPath, JSON.stringify(payload, null, 2)); - showToast({ message: "Keybindings exported", type: "success" }); + showToast({ message: t("settings.keyboard.keybindingsExported"), type: "success" }); } catch (error) { console.error("Failed to export keybindings:", error); const message = @@ -176,7 +178,7 @@ export const KeyboardSettings = () => { : JSON.stringify(error); showToast({ - message: `Failed to export keybindings: ${message}`, + message: t("settings.keyboard.exportFailed", { error: message }), type: "error", }); } @@ -195,7 +197,7 @@ export const KeyboardSettings = () => { const imported = parseKeybindingsImportJson(text); if (!imported) { - showToast({ message: "Invalid keybindings file format", type: "error" }); + showToast({ message: t("settings.keyboard.invalidFile"), type: "error" }); return; } @@ -209,13 +211,17 @@ export const KeyboardSettings = () => { } showToast({ - message: `Imported ${imported.keybindings.length} keybindings${ - imported.keybindingPreset ? " and preset" : "" - }`, + message: t("settings.keyboard.keybindingsImported", { + count: imported.keybindings.length, + preset: imported.keybindingPreset ? t("settings.keyboard.andPreset") : "", + }), type: "success", }); } catch (error) { - showToast({ message: `Failed to import keybindings: ${error}`, type: "error" }); + showToast({ + message: t("settings.keyboard.importFailed", { error: String(error) }), + type: "error", + }); } }; input.click(); @@ -238,22 +244,25 @@ export const KeyboardSettings = () => { size="sm" > - Back + {t("settings.keyboard.back")} - + - Import + {t("settings.keyboard.import")} void handleExport()} size="sm"> - Export + {t("settings.keyboard.export")} setSearchQuery(event.target.value)} leftIcon={Search} @@ -266,36 +275,36 @@ export const KeyboardSettings = () => { , }, { value: "user", - label: "User", + label: t("settings.keyboard.user"), icon: , }, { value: "default", - label: "Default", + label: t("settings.keyboard.default"), icon: , }, { value: "preset", - label: "Preset", + label: t("settings.keyboard.preset"), icon: , }, { value: "preset-changes", - label: "Preset Changes", + label: t("settings.keyboard.presetChanges"), icon: , }, { value: "extension", - label: "Extension", + label: t("settings.keyboard.extension"), icon: , }, ]} @@ -314,11 +323,11 @@ export const KeyboardSettings = () => { - Command - Keybinding - When - Source - Actions + {t("settings.keyboard.command")} + {t("settings.keyboard.keybinding")} + {t("settings.keyboard.when")} + {t("settings.keyboard.source")} + {t("settings.keyboard.actions")} @@ -326,7 +335,7 @@ export const KeyboardSettings = () => { - No keybindings found + {t("settings.keyboard.noKeybindings")} @@ -346,8 +355,8 @@ export const KeyboardSettings = () => { ) : ( updateSetting("vimMode", getDefaultSetting("vimMode"))} canReset={vimMode !== getDefaultSetting("vimMode")} > @@ -359,8 +368,8 @@ export const KeyboardSettings = () => { updateSetting("keybindingPreset", getDefaultSetting("keybindingPreset")) } @@ -369,12 +378,16 @@ export const KeyboardSettings = () => { updateSetting("keybindingPreset", value as KeybindingPreset)} - options={keybindingPresetOptions} + options={keybindingPresetOptions.map((option) => + option.value === "none" + ? { ...option, label: t("settings.keyboard.none") } + : option, + )} size="md" variant="default" searchable searchableTrigger="input" - aria-label="Keybinding preset" + aria-label={t("settings.keyboard.presetAria")} /> @@ -382,23 +395,31 @@ export const KeyboardSettings = () => { - This preset is incomplete. {selectedPresetCoverage.missingCommandIds.length}{" "} - built-in command - {selectedPresetCoverage.missingCommandIds.length === 1 ? " is" : "s are"} still - missing preset coverage. + {t("settings.keyboard.presetIncomplete", { + count: selectedPresetCoverage.missingCommandIds.length, + suffix: + selectedPresetCoverage.missingCommandIds.length === 1 + ? t("settings.keyboard.presetSingularSuffix") + : t("settings.keyboard.presetPluralSuffix"), + })} ) : null} - + setIsEditingKeybindings(true)} size="sm"> - Open Editor + {t("settings.keyboard.openEditor")} {userOverrideCount > 0 ? ( - {userOverrideCount} user override{userOverrideCount === 1 ? "" : "s"} currently - saved. + {t("settings.keyboard.userOverrides", { + count: userOverrideCount, + suffix: userOverrideCount === 1 ? "" : t("settings.keyboard.pluralSuffix"), + })} ) : null} diff --git a/windows/tauri/src/features/settings/components/tabs/terminal-settings.tsx b/windows/tauri/src/features/settings/components/tabs/terminal-settings.tsx index 3f6a0dc5..5a0b147f 100644 --- a/windows/tauri/src/features/settings/components/tabs/terminal-settings.tsx +++ b/windows/tauri/src/features/settings/components/tabs/terminal-settings.tsx @@ -20,11 +20,10 @@ import Select from "@/ui/select"; import Switch from "@/ui/switch"; import Textarea from "@/ui/textarea"; import Tooltip from "@/ui/tooltip"; - -const FONT_HELP_TEXT = - "Note: Selected font must be installed on your system to work correctly. If icons are missing, try installing a Nerd Font."; +import { useTranslation } from "@/i18n/locale-provider"; export const TerminalSettings = () => { + const { t } = useTranslation(); const settings = useSettingsStore((state) => state.settings); const updateSetting = useSettingsStore((state) => state.actions.updateSetting); const monospaceFonts = useFontStore.use.monospaceFonts(); @@ -47,7 +46,7 @@ export const TerminalSettings = () => { const fontOptions = [ ...installedNerdFonts.map((font) => ({ value: font, - label: `${font} (Nerd Font)`, + label: `${font} (${t("settings.terminal.nerdFont")})`, })), ...monospaceFonts .filter((f) => !COMMON_TERMINAL_NERD_FONTS.includes(f.family)) @@ -61,12 +60,12 @@ export const TerminalSettings = () => { ) { fontOptions.unshift({ value: settings.terminalFontFamily, - label: `${settings.terminalFontFamily} (Custom)`, + label: `${settings.terminalFontFamily} (${t("settings.terminal.custom")})`, }); } const shellOptions = [ - { value: DEFAULT_SHELL_OPTION_VALUE, label: "System Default" }, + { value: DEFAULT_SHELL_OPTION_VALUE, label: t("settings.terminal.systemDefault") }, ...shells.map((shell) => ({ value: shell.id, label: shell.name, @@ -110,12 +109,12 @@ export const TerminalSettings = () => { return ( updateSetting("terminalDefaultShellId", getDefaultSetting("terminalDefaultShellId")) } @@ -139,8 +138,8 @@ export const TerminalSettings = () => { updateSetting("terminalDefaultProfileId", getDefaultSetting("terminalDefaultProfileId")) } @@ -167,20 +166,19 @@ export const TerminalSettings = () => { - Built-in profiles are generated from detected shells. Custom profiles appear in the - terminal toolbar profile picker. + {t("settings.terminal.profilesHelp")} profileActions.addProfile({ - name: `Custom Profile ${profiles.length + 1}`, + name: t("settings.terminal.customProfile", { number: profiles.length + 1 }), shell: settings.terminalDefaultShellId || undefined, startupCommands: [], }) @@ -188,14 +186,14 @@ export const TerminalSettings = () => { size="sm" > - Add Profile + {t("settings.terminal.addProfile")} {profiles.length === 0 ? ( - No custom terminal profiles yet. + {t("settings.terminal.noCustomProfiles")} ) : ( @@ -210,13 +208,13 @@ export const TerminalSettings = () => { {profile.name} - Visible in the terminal profile picker. + {t("settings.terminal.profilePickerDescription")} profileActions.deleteProfile(profile.id)} - aria-label={`Delete ${profile.name}`} + aria-label={t("settings.terminal.deleteProfile", { profile: profile.name })} size="icon-sm" > @@ -225,7 +223,9 @@ export const TerminalSettings = () => { - Name + + {t("settings.terminal.profileName")} + { name: event.target.value, }) } - placeholder="My Profile" + placeholder={t("settings.terminal.profileNamePlaceholder")} size="md" /> - Shell + + {t("settings.terminal.profileShell")} + { - Startup Directory + {t("settings.terminal.startupDirectory")} { startupDirectory: event.target.value || undefined, }) } - placeholder="Leave empty to use the current workspace directory" + placeholder={t("settings.terminal.startupDirectoryPlaceholder")} size="md" /> - Leave empty to use the current workspace directory. + {t("settings.terminal.startupDirectoryDescription")} - Startup Commands + {t("settings.terminal.startupCommands")} { .filter(Boolean), }) } - placeholder="One command per line" + placeholder={t("settings.terminal.startupCommandsPlaceholder")} rows={3} size="md" /> - Enter one command per line. + {t("settings.terminal.startupCommandsDescription")} )) @@ -305,10 +307,10 @@ export const TerminalSettings = () => { - + updateSetting("terminalFontFamily", getDefaultSetting("terminalFontFamily")) } @@ -324,17 +326,17 @@ export const TerminalSettings = () => { variant="default" searchable searchableTrigger="input" - placeholder="Select font..." + placeholder={t("settings.terminal.selectFont")} /> - + updateSetting("terminalFontSize", getDefaultSetting("terminalFontSize"))} canReset={settings.terminalFontSize !== getDefaultSetting("terminalFontSize")} > @@ -349,8 +351,8 @@ export const TerminalSettings = () => { updateSetting("terminalLineHeight", getDefaultSetting("terminalLineHeight")) } @@ -368,8 +370,8 @@ export const TerminalSettings = () => { updateSetting("terminalLetterSpacing", getDefaultSetting("terminalLetterSpacing")) } @@ -387,8 +389,8 @@ export const TerminalSettings = () => { updateSetting("terminalScrollback", getDefaultSetting("terminalScrollback")) } @@ -406,10 +408,10 @@ export const TerminalSettings = () => { - + updateSetting( "terminalAltClickMovesCursor", @@ -429,8 +431,8 @@ export const TerminalSettings = () => { updateSetting("terminalMacOptionIsMeta", getDefaultSetting("terminalMacOptionIsMeta")) } @@ -446,8 +448,8 @@ export const TerminalSettings = () => { updateSetting( "terminalRightClickSelectsWord", @@ -467,10 +469,10 @@ export const TerminalSettings = () => { - + updateSetting("terminalCursorStyle", getDefaultSetting("terminalCursorStyle")) } @@ -479,9 +481,9 @@ export const TerminalSettings = () => { updateSetting("terminalCursorStyle", val as "block" | "underline" | "bar") @@ -495,8 +497,8 @@ export const TerminalSettings = () => { updateSetting("terminalCursorBlink", getDefaultSetting("terminalCursorBlink")) } @@ -510,8 +512,8 @@ export const TerminalSettings = () => { updateSetting("terminalCursorWidth", getDefaultSetting("terminalCursorWidth")) } @@ -528,8 +530,8 @@ export const TerminalSettings = () => { updateSetting( "terminalCursorInactiveStyle", @@ -544,11 +546,11 @@ export const TerminalSettings = () => { updateSetting( diff --git a/windows/tauri/src/features/settings/config/default-settings.ts b/windows/tauri/src/features/settings/config/default-settings.ts index 53ba7038..e10e3b02 100644 --- a/windows/tauri/src/features/settings/config/default-settings.ts +++ b/windows/tauri/src/features/settings/config/default-settings.ts @@ -64,6 +64,7 @@ export const defaultSettings: Settings = { // UI uiFontFamily: DEFAULT_UI_FONT_FAMILY, uiFontSize: UI_FONT_SIZE_DEFAULT, + displayLanguage: "en-US", reduceMotion: false, showStatusBar: true, showTabIcons: true, @@ -105,10 +106,10 @@ export const defaultSettings: Settings = { activityRailExpanded: false, activityRailWidth: 180, showActivityRailProjectSwitcher: true, - showActivityRailAgentHistory: true, - showActivityRailTerminals: true, - showActivityRailWorktrees: true, - showActivityRailProjectIcons: true, + showActivityRailAgentHistory: false, + showActivityRailTerminals: false, + showActivityRailWorktrees: false, + showActivityRailProjectIcons: false, collapsedActivityRailSections: [], sidebarWidth: 220, showGitHubPullRequests: true, @@ -132,16 +133,16 @@ export const defaultSettings: Settings = { // Features coreFeatures: { git: true, - github: true, - remote: true, + github: false, + remote: false, terminal: true, search: true, diagnostics: true, debugger: false, - docker: true, + docker: false, outline: true, - aiChat: true, - teamCollaboration: true, + aiChat: false, + teamCollaboration: false, breadcrumbs: true, persistentCommands: true, webViewer: false, diff --git a/windows/tauri/src/features/settings/lib/settings-access.ts b/windows/tauri/src/features/settings/lib/settings-access.ts index eca064fc..1819f164 100644 --- a/windows/tauri/src/features/settings/lib/settings-access.ts +++ b/windows/tauri/src/features/settings/lib/settings-access.ts @@ -28,7 +28,7 @@ export function resolveVisibleSettingsSection( tab: SettingsTab, access: SettingsAccess, ): SettingsSection { - if (tab === "language") { + if (tab === "language" || tab === "account" || tab === "ai") { return "editor"; } diff --git a/windows/tauri/src/features/settings/lib/settings-tab-visibility.test.ts b/windows/tauri/src/features/settings/lib/settings-tab-visibility.test.ts new file mode 100644 index 00000000..b9698759 --- /dev/null +++ b/windows/tauri/src/features/settings/lib/settings-tab-visibility.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, test } from "bun:test"; +import { SETTINGS_TAB_ITEMS } from "../components/settings-vertical-tabs"; +import { filterVisibleSettingsTabs } from "./settings-tab-visibility"; + +describe("supported Windows settings tabs", () => { + test("excludes account and unavailable feature settings", () => { + const visibleTabs = filterVisibleSettingsTabs(SETTINGS_TAB_ITEMS, { + canShowCollaborationSettings: false, + canShowEnterpriseSettings: false, + matchingTabs: null, + }); + + expect(visibleTabs.map((tab) => tab.id)).toEqual([ + "general", + "appearance", + "editor", + "file-explorer", + "git", + "terminal", + "keyboard", + "advanced", + ]); + }); +}); diff --git a/windows/tauri/src/features/settings/lib/settings-tab-visibility.ts b/windows/tauri/src/features/settings/lib/settings-tab-visibility.ts index e23c0a10..24ebefa0 100644 --- a/windows/tauri/src/features/settings/lib/settings-tab-visibility.ts +++ b/windows/tauri/src/features/settings/lib/settings-tab-visibility.ts @@ -8,6 +8,10 @@ export function filterVisibleSettingsTabs( }, ) { return tabs.filter((item) => { + if (item.id === "account" || item.id === "ai") { + return false; + } + if (!params.canShowEnterpriseSettings && item.id === "enterprise") { return false; } diff --git a/windows/tauri/src/features/settings/types/settings.types.ts b/windows/tauri/src/features/settings/types/settings.types.ts index ee83dd5d..5b13cf8c 100644 --- a/windows/tauri/src/features/settings/types/settings.types.ts +++ b/windows/tauri/src/features/settings/types/settings.types.ts @@ -1,4 +1,5 @@ import type { CoreFeaturesState } from "./feature.types"; +import type { DisplayLanguage } from "@/i18n/locale"; import type { V0DesignSystemProfile } from "@/extensions/v0/types/v0-design-system.types"; import type { AIChatSkill } from "@/features/ai/types/skills.types"; import type { @@ -81,6 +82,7 @@ export interface Settings { // UI uiFontFamily: string; uiFontSize: number; + displayLanguage: DisplayLanguage; reduceMotion: boolean; showStatusBar: boolean; showTabIcons: boolean; diff --git a/windows/tauri/src/features/window/components/title-bar/title-bar.tsx b/windows/tauri/src/features/window/components/title-bar/title-bar.tsx index 94c645e3..9c931f51 100644 --- a/windows/tauri/src/features/window/components/title-bar/title-bar.tsx +++ b/windows/tauri/src/features/window/components/title-bar/title-bar.tsx @@ -1,17 +1,13 @@ import { getCurrentWindow, type Window as TauriWindow } from "@tauri-apps/api/window"; import { useCallback, useEffect, useState } from "react"; import { createPortal } from "react-dom"; +import { BACKEND_UNAVAILABLE_TOOLTIP } from "@/config/backend-capabilities"; +import { useTranslation } from "@/i18n/locale-provider"; import { openFolder } from "@/features/file-system/controllers/platform"; -import { - BACKEND_UNAVAILABLE_TOOLTIP, - isBackendCapabilityAvailable, -} from "@/config/backend-capabilities"; import { useFileSystemStore } from "@/features/file-system/stores/file-system.store"; -import { AppUpdateControl } from "@/features/layout/components/app-update-control"; -import { GitHubNotificationsMenu } from "@/features/github/components/github-notifications-menu"; import type { HeaderTrailingItemId } from "@/features/layout/config/item-order"; import { orderChromeItems, type ChromeItem } from "@/features/layout/utils/chrome-items"; -import RunActionsButton from "@/features/run-actions/components/run-actions-button"; +import { useFooterGitBranchItem } from "@/features/layout/components/footer/footer-git-branch-item"; import SettingsDialog from "@/features/settings/components/settings-dialog"; import { useSettingsStore } from "@/features/settings/stores/settings.store"; import { useUIState } from "@/features/window/stores/ui-state.store"; @@ -31,8 +27,9 @@ import { FilesIcon, FolderOpenIcon, ListIcon, + MagnifyingGlassIcon, + PlayIcon, SidebarSimpleIcon, - SparkleIcon, TrashIcon, WindowExpandIcon, } from "@/ui/icons"; @@ -40,7 +37,6 @@ import { Toggle } from "@/ui/toggle"; import Tooltip from "@/ui/tooltip"; import { cn } from "@/utils/cn"; import { IS_LINUX, IS_MAC, IS_WINDOWS } from "@/utils/platform"; -import { AccountMenu } from "../account-menu"; import ProjectPicker from "../project-picker"; import { WindowControls } from "./window-controls"; import WindowMenuBar from "../window-menu-bar"; @@ -49,29 +45,9 @@ interface TitleBarProps { showMinimal?: boolean; } -function placeAgentBeforeAccount(items: Array>) { - const accountIndex = items.findIndex((item) => item.id === "account"); - if (accountIndex < 0) return items; - - const nextItems = [...items]; - const itemIndex = nextItems.findIndex((item) => item.id === "ai-chat"); - const nextAccountIndex = nextItems.findIndex((item) => item.id === "account"); - if (itemIndex < 0 || nextAccountIndex < 0 || itemIndex === nextAccountIndex - 1) { - return nextItems; - } - - const [item] = nextItems.splice(itemIndex, 1); - const insertionIndex = nextItems.findIndex((candidate) => candidate.id === "account"); - nextItems.splice(insertionIndex, 0, item); - - return nextItems; -} - function TitleBarTrailingActions({ items }: { items: Array> }) { return ( - - {items.map((item) => item.content ? ( @@ -84,9 +60,9 @@ function TitleBarTrailingActions({ items }: { items: Array { + const { t } = useTranslation(); const nativeMenuBar = useSettingsStore((state) => state.settings.nativeMenuBar); const compactMenuBar = useSettingsStore((state) => state.settings.compactMenuBar); - const isAIChatVisible = useSettingsStore((state) => state.settings.isAIChatVisible); const activityRailExpanded = useSettingsStore((state) => state.settings.activityRailExpanded); const headerTrailingItemsOrder = useSettingsStore( (state) => state.settings.headerTrailingItemsOrder, @@ -96,6 +72,8 @@ const TitleBar = ({ showMinimal = false }: TitleBarProps) => { const closeProject = useFileSystemStore((state) => state.closeProject); const projectTabs = useWorkspaceTabsStore.use.projectTabs(); const setIsProjectPickerVisible = useUIState((state) => state.setIsProjectPickerVisible); + const setIsGlobalSearchVisible = useUIState((state) => state.setIsGlobalSearchVisible); + const branchItem = useFooterGitBranchItem(); const [menuBarActiveMenu, setMenuBarActiveMenu] = useState(null); const [isCompactMenuVisible, setIsCompactMenuVisible] = useState(false); @@ -286,41 +264,66 @@ const TitleBar = ({ showMinimal = false }: TitleBarProps) => { ); const headerTrailingItems: Array> = [ - { id: "run-actions", label: "Run actions", content: }, - { - id: "ai-chat", - label: "Agent", - content: ( - { - useSettingsStore.getState().actions.toggleAIChatVisible(); - }} - aria-label={ - isBackendCapabilityAvailable("agent") ? "Toggle Agent" : BACKEND_UNAVAILABLE_TOOLTIP - } - size="icon-xs" - > - - - ), - }, - { - id: "account", - label: "Account", - content: , - }, ]; - const orderedTrailingItems = placeAgentBeforeAccount( - orderChromeItems(headerTrailingItems, headerTrailingItemsOrder), + const orderedTrailingItems = orderChromeItems(headerTrailingItems, headerTrailingItemsOrder); + + const activeProject = projectTabs.find((project) => project.isActive); + const projectLabel = activeProject?.name ?? t("workbench.openProject"); + const macOSAlignedControls = ( + + setIsProjectPickerVisible(true)} + > + + {projectLabel} + + {branchItem?.content} + + ); + + const workbenchActions = ( + + + + + + {t("workbench.currentFile")} + + + + setIsGlobalSearchVisible(true)} + aria-label={t("workbench.search")} + > + + + setIsProjectPickerVisible(true)} + aria-label={t("workbench.moreProjectActions")} + > + + + ); if (showMinimal) { @@ -359,9 +362,11 @@ const TitleBar = ({ showMinimal = false }: TitleBarProps) => { {menuItem} {sidebarToggle} + {macOSAlignedControls} + {workbenchActions} @@ -382,9 +387,11 @@ const TitleBar = ({ showMinimal = false }: TitleBarProps) => { {menuItem} {sidebarToggle} + {macOSAlignedControls} + {workbenchActions} {showAppWindowControls && ( diff --git a/windows/tauri/src/features/window/components/window-menu-bar.tsx b/windows/tauri/src/features/window/components/window-menu-bar.tsx index adc4b481..90624090 100644 --- a/windows/tauri/src/features/window/components/window-menu-bar.tsx +++ b/windows/tauri/src/features/window/components/window-menu-bar.tsx @@ -423,22 +423,6 @@ const WindowMenuBar = ({ ), - Agent: ( - - handleClickEmit("menu_toggle_ai_chat")}> - Toggle Agent - - handleCommand("workbench.agentLauncher")} - > - New Agent - - handleCommand("editor.inlineEdit")}> - Inline Edit - - - ), Tools: ( handleClickEmit("menu_open_settings")}> Preferences - handleClickEmit("menu_open_extensions")} - disabled={!isBackendCapabilityAvailable("extensions")} - title={BACKEND_UNAVAILABLE_TOOLTIP} - > - Extensions - handleCommand("workbench.openKeyboardShortcuts")}> Keyboard Shortcuts @@ -560,13 +537,11 @@ const WindowMenuBar = ({ ) => string; +} + +const LocaleContext = createContext(null); + +export function LocaleProvider({ children }: { children: ReactNode }) { + const language = useSettingsStore((state) => state.settings.displayLanguage); + + return ( + + {children} + + ); +} + +export function useTranslation() { + const context = useContext(LocaleContext); + + if (!context) { + throw new Error("useTranslation must be used inside LocaleProvider"); + } + + return context; +} diff --git a/windows/tauri/src/i18n/locale.test.ts b/windows/tauri/src/i18n/locale.test.ts new file mode 100644 index 00000000..68f88e9b --- /dev/null +++ b/windows/tauri/src/i18n/locale.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test } from "bun:test"; +import { defaultSettings } from "@/features/settings/config/default-settings"; +import { createTranslator, getLocaleCatalog } from "./locale"; + +describe("Windows display language", () => { + test("uses Simplified Chinese strings and falls back to English", () => { + const translate = createTranslator("zh-CN"); + + expect(translate("workbench.project")).toBe("项目"); + expect(translate("welcome.openProject")).toBe("打开项目"); + expect(translate("missing.key")).toBe("missing.key"); + expect(getLocaleCatalog("en-US")).toHaveProperty(["settings.displayLanguage"]); + expect(defaultSettings.displayLanguage).toBe("en-US"); + }); +}); diff --git a/windows/tauri/src/i18n/locale.ts b/windows/tauri/src/i18n/locale.ts new file mode 100644 index 00000000..08c9c12c --- /dev/null +++ b/windows/tauri/src/i18n/locale.ts @@ -0,0 +1,840 @@ +export const DISPLAY_LANGUAGES = ["en-US", "zh-CN"] as const; + +export type DisplayLanguage = (typeof DISPLAY_LANGUAGES)[number]; + +const catalogs = { + "en-US": { + "workbench.project": "Project", + "workbench.changes": "Changes", + "workbench.search": "Search", + "workbench.database": "Database", + "workbench.settings": "Settings", + "workbench.openProject": "Open Project", + "workbench.currentFile": "Current File", + "workbench.moreProjectActions": "More project actions", + "welcome.openProject": "Open Project", + "welcome.recentProjects": "Recent Projects", + "settings.displayLanguage": "Display Language", + "settings.displayLanguageDescription": "Choose the language used throughout Lithe.", + "settings.languageEnglish": "English", + "settings.languageChinese": "Simplified Chinese", + "settings.search": "Search settings...", + "settings.noMatching": "No matching settings", + "settings.tabs.general": "General", + "settings.tabs.appearance": "Appearance", + "settings.tabs.editor": "Editor", + "settings.tabs.files": "Files", + "settings.tabs.git": "Git", + "settings.tabs.terminal": "Terminal", + "settings.tabs.keyboard": "Keybindings", + "settings.tabs.advanced": "Advanced", + "settings.git.integration": "Integration", + "settings.git.gitIntegration": "Git Integration", + "settings.git.gitIntegrationDescription": "Enable source control management with Git repositories", + "settings.git.autoRefresh": "Auto Refresh Git Status", + "settings.git.autoRefreshDescription": "Refresh the Git view automatically after relevant file changes and Git events", + "settings.git.confirmDiscard": "Confirm Before Discard", + "settings.git.confirmDiscardDescription": "Show a confirmation before discarding file or repository changes", + "settings.git.view": "Git View", + "settings.git.folderChanges": "Folder-Based Changes", + "settings.git.folderChangesDescription": "Show Git changes in a folder tree, similar to Files", + "settings.git.untracked": "Show Untracked Files", + "settings.git.untrackedDescription": "Display untracked files in the Git status panel", + "settings.git.stagedFirst": "Show Staged First", + "settings.git.stagedFirstDescription": "Render staged changes above unstaged changes in the Git panel", + "settings.git.openDiff": "Open Diff On Click", + "settings.git.openDiffDescription": "Open the diff when clicking a changed file instead of opening the file directly", + "settings.git.compactBadges": "Compact Git Status Badges", + "settings.git.compactBadgesDescription": "Use a denser layout for diff stats and staged labels in the Git panel", + "settings.git.collapseEmpty": "Collapse Empty Sections", + "settings.git.collapseEmptyDescription": "Hide empty Git sections like Staged Changes when they have no items", + "settings.git.rememberPanel": "Remember Last Git Panel Mode", + "settings.git.rememberPanelDescription": "Restore the last open bottom Git panel section when reopening the Git view", + "settings.git.defaultDiff": "Default Diff View", + "settings.git.defaultDiffDescription": "Choose the default layout for Git diffs", + "settings.git.unified": "Unified", + "settings.git.split": "Split", + "settings.git.editor": "Editor", + "settings.git.inlineBlame": "Enable Inline Blame", + "settings.git.inlineBlameDescription": "Show inline Git blame metadata for the current line in the editor", + "settings.general.version": "Version", + "settings.general.versionDescription": "Check for updates and install the latest app version.", + "settings.general.downloading": "Downloading...", + "settings.general.installing": "Installing...", + "settings.general.installUpdate": "Install {version}", + "settings.general.update": "update", + "settings.general.checking": "Checking...", + "settings.general.check": "Check", + "settings.general.updateProgress": "Lithe {version} · Downloading {percentage}%", + "settings.general.updateInstalling": "Lithe {version} · Installing update...", + "settings.general.updateAvailable": "Lithe {version} · Version {availableVersion} available", + "settings.general.updateCheckFailed": "Lithe {version} · Failed to check for updates", + "settings.general.upToDate": "Lithe {version} · App is up to date", + "settings.general.updateDownloadProgress": "Lithe update download progress", + "settings.general.terminalCommand": "Terminal Command", + "settings.general.terminalCommandDescription": "Install the `lithe` command to open folders and files from your terminal.", + "settings.general.uninstall": "Uninstall", + "settings.general.uninstalling": "Uninstalling...", + "settings.general.install": "Install", + "settings.general.copy": "Copy", + "settings.general.copyInstallCommand": "Copy install command to clipboard", + "settings.general.cliInstalled": "CLI command is installed at $HOME/.local/bin/lithe", + "settings.general.cliNotInstalled": "CLI command is not installed.", + "settings.general.importSettings": "Import Settings", + "settings.general.importSettingsDescription": "Import matching setup from another editor.", + "settings.general.reportBug": "Report a Bug", + "settings.general.reportBugDescription": "Choose where to report an issue with environment details.", + "settings.general.open": "Open", + "settings.general.reportVia": "Report via...", + "settings.general.noReportChannel": "No report channel matches \"{query}\".", + "settings.general.githubReportDescription": "Open a bug report issue", + "settings.general.cliInstallFailed": "Failed to install CLI: {error}. You may need administrator privileges.", + "settings.general.cliUninstallFailed": "Failed to uninstall CLI: {error}", + "settings.general.installCommandCopied": "Install command copied to clipboard", + "settings.general.copyCommandFailed": "Failed to copy command: {error}", + "settings.general.latestVersion": "You're on the latest version", + "settings.general.reportTemplateCopied": "Report template copied", + "settings.general.prepareReportFailed": "Failed to prepare bug report", + "settings.general.bugReportTemplate": "Environment\n\n- App: Lithe {version}\n- OS: {platform} {osVersion}\n\nProblem\n\nDescribe the issue here. Steps to reproduce, expected vs actual.\n", + "settings.files.display": "Display", + "settings.files.sortOrder": "Sort Order", + "settings.files.sortOrderDescription": "Choose whether folders stay above files or everything sorts by name", + "settings.files.foldersFirst": "Folders First", + "settings.files.name": "Name", + "settings.files.indentSize": "Indent Size", + "settings.files.indentSizeDescription": "Pixels per nesting level", + "settings.files.showFileIcons": "Show File Icons", + "settings.files.showFileIconsDescription": "Show themed file and folder icons", + "settings.files.showIndentGuides": "Show Indent Guides", + "settings.files.showIndentGuidesDescription": "Show vertical guides for nested folders", + "settings.files.compactFolders": "Compact Folders", + "settings.files.compactFoldersDescription": "Collapse single-child folder chains", + "settings.files.hideRootFolder": "Hide Root Folder", + "settings.files.hideRootFolderDescription": "Show project files directly at the top level", + "settings.files.showHiddenFiles": "Show Hidden Files", + "settings.files.showHiddenFilesDescription": "Show dotfiles and hidden directories", + "settings.files.respectGitignore": "Respect .gitignore", + "settings.files.respectGitignoreDescription": "Hide files matched by root and nested .gitignore files", + "settings.files.showGitStatus": "Show Git Status", + "settings.files.showGitStatusDescription": "Display Git color decorations beside changed files", + "settings.files.behavior": "Behavior", + "settings.files.autoReveal": "Auto Reveal Active File", + "settings.files.autoRevealDescription": "Expand and scroll Files to the active editor file", + "settings.files.confirmBeforeDelete": "Confirm Before Delete", + "settings.files.confirmBeforeDeleteDescription": "Ask for confirmation before deleting a file or folder", + "settings.files.filters": "Filters", + "settings.files.hiddenFiles": "Hidden Files", + "settings.files.hiddenDirectories": "Hidden Directories", + "settings.files.globPatterns": "Comma-separated glob patterns", + "settings.editor.section": "Editor", + "settings.editor.fontFamily": "Editor Font Family", + "settings.editor.fontFamilyDescription": "Font family for code editor", + "settings.editor.fontSize": "Font Size", + "settings.editor.fontSizeDescription": "Editor font size in pixels", + "settings.editor.fontLigatures": "Font Ligatures", + "settings.editor.fontLigaturesDescription": "Use programming ligatures provided by the selected editor font", + "settings.editor.italicComments": "Italic Comments", + "settings.editor.italicCommentsDescription": "Render code comments in italics", + "settings.editor.lineHeight": "Line Height", + "settings.editor.lineHeightDescription": "Editor line height multiplier", + "settings.editor.tabSize": "Tab Size", + "settings.editor.tabSizeDescription": "Number of spaces per tab", + "settings.editor.wordWrap": "Word Wrap", + "settings.editor.wordWrapDescription": "Wrap lines that exceed viewport width", + "settings.editor.lineNumbers": "Line Numbers", + "settings.editor.lineNumbersDescription": "Show line numbers in the editor", + "settings.editor.renderWhitespace": "Render Whitespace", + "settings.editor.renderWhitespaceDescription": "Show visible markers for spaces and tabs", + "settings.editor.whitespaceNone": "None", + "settings.editor.whitespaceBoundary": "Boundary", + "settings.editor.whitespaceTrailing": "Trailing", + "settings.editor.whitespaceAll": "All", + "settings.editor.indentGuides": "Indent Guides", + "settings.editor.indentGuidesDescription": "Show vertical guides for indentation levels", + "settings.editor.highlightOccurrences": "Highlight Occurrences", + "settings.editor.highlightOccurrencesDescription": "Highlight visible matches for the word under the cursor", + "settings.editor.relativeLineNumbers": "Relative Line Numbers", + "settings.editor.relativeLineNumbersDescription": "Show relative numbers when Vim mode is active", + "settings.editor.showMinimap": "Show Minimap", + "settings.editor.showMinimapDescription": "Show a minimap overview on the right side of the editor", + "settings.editor.stickyScroll": "Sticky Scroll", + "settings.editor.stickyScrollDescription": "Keep containing scopes visible at the top while scrolling", + "settings.editor.bracketPairColorization": "Bracket Pair Colorization", + "settings.editor.bracketPairColorizationDescription": "Use matching colors to distinguish nested bracket pairs", + "settings.editor.smoothScrolling": "Smooth Scrolling", + "settings.editor.smoothScrollingDescription": "Animate editor scrolling between positions", + "settings.editor.scrollBeyondLastLine": "Scroll Beyond Last Line", + "settings.editor.scrollBeyondLastLineDescription": "Allow scrolling the final line above the bottom of the editor", + "settings.editor.cursorStyle": "Cursor Style", + "settings.editor.cursorStyleDescription": "Shape of the editor cursor outside Vim normal mode", + "settings.editor.cursorLine": "Line", + "settings.editor.cursorThinLine": "Thin Line", + "settings.editor.cursorBlock": "Block", + "settings.editor.cursorBlockOutline": "Block Outline", + "settings.editor.cursorUnderline": "Underline", + "settings.editor.cursorThinUnderline": "Thin Underline", + "settings.editor.cursorBlinking": "Cursor Blinking", + "settings.editor.cursorBlinkingDescription": "Animation used by the editor cursor outside Vim normal mode", + "settings.editor.blink": "Blink", + "settings.editor.smooth": "Smooth", + "settings.editor.phase": "Phase", + "settings.editor.expand": "Expand", + "settings.editor.solid": "Solid", + "settings.editor.maxOpenTabs": "Max Open Tabs", + "settings.editor.maxOpenTabsDescription": "Maximum number of tabs before oldest closes", + "settings.editor.bufferCarousel": "Buffer Carousel", + "settings.editor.bufferCarouselDescription": "Show open buffers as a horizontally scrollable carousel in the main view", + "settings.editor.autoSave": "Auto Save", + "settings.editor.autoSaveDescription": "Automatically save files when editing", + "settings.editor.defaultLanguage": "Default Language", + "settings.editor.defaultLanguageDescription": "Default syntax highlighting for new files", + "settings.editor.autoDetect": "Auto Detect", + "settings.editor.autoDetectLanguage": "Auto-detect Language", + "settings.editor.autoDetectLanguageDescription": "Automatically detect file language from extension", + "settings.editor.formatOnSave": "Format on Save", + "settings.editor.formatOnSaveDescription": "Automatically format code when saving", + "settings.editor.lintOnSave": "Lint on Save", + "settings.editor.lintOnSaveDescription": "Run linter when saving files", + "settings.editor.autoCompletion": "Auto Completion", + "settings.editor.autoCompletionDescription": "Show completion suggestions while typing", + "settings.editor.parameterHints": "Parameter Hints", + "settings.editor.parameterHintsDescription": "Show function parameter hints", + "settings.editor.inlayHints": "Inlay Hints", + "settings.editor.inlayHintsDescription": "Show inline type and parameter hints from language servers", + "settings.editor.codeLens": "Code Lens", + "settings.editor.codeLensDescription": "Show inline code actions above symbols", + "settings.editor.semanticTokens": "Semantic Tokens", + "settings.editor.semanticTokensDescription": "Use language server semantic highlighting", + "settings.editor.symbolBreadcrumb": "Show Symbol in Breadcrumb", + "settings.editor.symbolBreadcrumbDescription": "Show the containing function/class for the cursor position in the breadcrumb bar", + "settings.appearance.theme": "Theme", + "settings.appearance.syncWithOs": "Sync With OS", + "settings.appearance.syncWithOsDescription": "Automatically switch between your preferred light and dark themes", + "settings.appearance.colorTheme": "Color Theme", + "settings.appearance.colorThemeDescription": "Choose your preferred color theme", + "settings.appearance.preferredLightTheme": "Preferred Light Theme", + "settings.appearance.preferredLightThemeDescription": "Used when Sync With OS is enabled and the system appearance is light", + "settings.appearance.preferredDarkTheme": "Preferred Dark Theme", + "settings.appearance.preferredDarkThemeDescription": "Used when Sync With OS is enabled and the system appearance is dark", + "settings.appearance.iconTheme": "Icon Theme", + "settings.appearance.iconThemeDescription": "Icons displayed in the file tree and tabs", + "settings.appearance.customThemes": "Custom Themes", + "settings.appearance.customThemesDescription": "Import Lithe theme JSON or create one from an installed theme.", + "settings.appearance.formatGuide": "Format guide", + "settings.appearance.create": "Create", + "settings.appearance.import": "Import", + "settings.appearance.removeTheme": "Remove {theme}", + "settings.appearance.importThemeFailed": "Failed to import theme", + "settings.appearance.importedTheme": "Imported {theme}", + "settings.appearance.importedThemeVariants": "Imported {count} theme variants", + "settings.appearance.customThemeRemoved": "Custom theme removed", + "settings.appearance.removeCustomThemeFailed": "Failed to remove custom theme", + "settings.appearance.typography": "Typography", + "settings.appearance.uiFontFamily": "UI Font Family", + "settings.appearance.uiFontFamilyDescription": "Font family for UI elements (file tree, markdown, etc.)", + "settings.appearance.uiFontSize": "UI Font Size", + "settings.appearance.uiFontSizeDescription": "Adjust UI text and icon scale in 0.5px steps", + "settings.appearance.uiFontSizeAria": "UI font size: {size} pixels", + "settings.appearance.interface": "Interface", + "settings.appearance.reduceMotion": "Reduce Motion", + "settings.appearance.reduceMotionDescription": "Reduce non-essential interface animations while keeping state changes visible", + "settings.appearance.showStatusBar": "Show Status Bar", + "settings.appearance.showStatusBarDescription": "Show app controls and status information along the bottom edge", + "settings.appearance.showTabIcons": "Show Tab Icons", + "settings.appearance.showTabIconsDescription": "Show file and view icons in editor tabs", + "settings.appearance.tabCloseButtons": "Tab Close Buttons", + "settings.appearance.tabCloseButtonsDescription": "Choose when unpinned tabs show their close button", + "settings.appearance.activeAndHovered": "Active and Hovered", + "settings.appearance.hoveredOnly": "Hovered Only", + "settings.appearance.always": "Always", + "settings.appearance.layout": "Layout", + "settings.appearance.windowChromeDensity": "Window Chrome Density", + "settings.appearance.windowChromeDensityDescription": "Choose a focused or roomier scale for title bars, tabs, sidebars, and the footer", + "settings.appearance.focused": "Focused", + "settings.appearance.comfortable": "Comfortable", + "settings.appearance.expandedActivityBar": "Expanded Activity Bar", + "settings.appearance.expandedActivityBarDescription": "Show labels beside icons in the activity bar", + "settings.appearance.activityBarWidth": "Activity Bar Width", + "settings.appearance.activityBarWidthDescription": "Set the width of the expanded activity bar", + "settings.appearance.activityBarWidthAria": "Activity bar width: {size} pixels", + "settings.appearance.sidebarWidth": "Sidebar Width", + "settings.appearance.sidebarWidthDescription": "Set the default width used by left and right sidebars", + "settings.appearance.sidebarWidthAria": "Sidebar width: {size} pixels", + "settings.appearance.nativeMenuBar": "Native Menu Bar", + "settings.appearance.nativeMenuBarDescription": "Use the native menu bar or a custom UI menu bar", + "settings.appearance.compactMenuBar": "Compact Menu Bar", + "settings.appearance.compactMenuBarDescription": "Requires UI menu bar; compact hamburger or full UI menu", + "settings.appearance.windowTransparency": "Window Transparency", + "settings.appearance.windowTransparencyDescription": "Use translucent app chrome and transparent native windows where supported", + "settings.appearance.openProjectsNewWindow": "Open Projects In New Window", + "settings.appearance.openProjectsNewWindowDescription": "Open each new project in a separate window and disable activity-bar project switching", + "settings.terminal.nerdFont": "Nerd Font", + "settings.terminal.custom": "Custom", + "settings.terminal.systemDefault": "System Default", + "settings.terminal.fontHelp": "Note: Selected font must be installed on your system to work correctly. If icons are missing, try installing a Nerd Font.", + "settings.terminal.launch": "Launch", + "settings.terminal.launchDescription": "Choose which shell and profile new terminal tabs should use by default.", + "settings.terminal.defaultShell": "Default Shell", + "settings.terminal.defaultShellDescription": "Fallback shell when a terminal profile does not override it.", + "settings.terminal.defaultProfile": "Default Profile", + "settings.terminal.defaultProfileDescription": "Used by the terminal toolbar button and Cmd+T when the terminal is focused.", + "settings.terminal.profiles": "Profiles", + "settings.terminal.profilesDescription": "Create reusable launch presets with a shell override, startup directory, and optional startup commands.", + "settings.terminal.profilesHelp": "Built-in profiles are generated from detected shells. Custom profiles appear in the terminal toolbar profile picker.", + "settings.terminal.addProfile": "Add Profile", + "settings.terminal.customProfile": "Custom Profile {number}", + "settings.terminal.noCustomProfiles": "No custom terminal profiles yet.", + "settings.terminal.profilePickerDescription": "Visible in the terminal profile picker.", + "settings.terminal.deleteProfile": "Delete {profile}", + "settings.terminal.profileName": "Name", + "settings.terminal.profileNamePlaceholder": "My Profile", + "settings.terminal.profileShell": "Shell", + "settings.terminal.startupDirectory": "Startup Directory", + "settings.terminal.startupDirectoryPlaceholder": "Leave empty to use the current workspace directory", + "settings.terminal.startupDirectoryDescription": "Leave empty to use the current workspace directory.", + "settings.terminal.startupCommands": "Startup Commands", + "settings.terminal.startupCommandsPlaceholder": "One command per line", + "settings.terminal.startupCommandsDescription": "Enter one command per line.", + "settings.terminal.typography": "Typography", + "settings.terminal.fontFamily": "Font Family", + "settings.terminal.fontFamilyDescription": "Font family for the integrated terminal. Select a Nerd Font for best icon support.", + "settings.terminal.selectFont": "Select font...", + "settings.terminal.fontSize": "Font Size", + "settings.terminal.fontSizeDescription": "Terminal font size in pixels", + "settings.terminal.lineHeight": "Line Height", + "settings.terminal.lineHeightDescription": "Line height multiplier", + "settings.terminal.letterSpacing": "Letter Spacing", + "settings.terminal.letterSpacingDescription": "Additional spacing between characters", + "settings.terminal.scrollback": "Scrollback", + "settings.terminal.scrollbackDescription": "How many lines of terminal history to keep in memory", + "settings.terminal.interaction": "Interaction", + "settings.terminal.altClickMovesCursor": "Alt Click Moves Cursor", + "settings.terminal.altClickMovesCursorDescription": "Move the shell prompt cursor to the clicked position when supported", + "settings.terminal.optionAsMeta": "Option as Meta", + "settings.terminal.optionAsMetaDescription": "Treat the Option key as Meta in terminal applications on macOS", + "settings.terminal.rightClickSelectsWord": "Right Click Selects Word", + "settings.terminal.rightClickSelectsWordDescription": "Select the word under the pointer before opening the context menu", + "settings.terminal.cursor": "Cursor", + "settings.terminal.cursorStyle": "Cursor Style", + "settings.terminal.cursorStyleDescription": "Shape of the cursor", + "settings.terminal.block": "Block", + "settings.terminal.underline": "Underline", + "settings.terminal.bar": "Bar", + "settings.terminal.blinkingCursor": "Blinking Cursor", + "settings.terminal.blinkingCursorDescription": "Whether the cursor should blink", + "settings.terminal.cursorWidth": "Cursor Width", + "settings.terminal.cursorWidthDescription": "Thickness of the bar or block cursor", + "settings.terminal.inactiveCursorStyle": "Inactive Cursor Style", + "settings.terminal.inactiveCursorStyleDescription": "Appearance of the terminal cursor when the terminal is not focused", + "settings.terminal.outline": "Outline", + "settings.terminal.hidden": "Hidden", + "settings.keyboard.resetToDefaults": "Reset to Defaults", + "settings.keyboard.import": "Import", + "settings.keyboard.export": "Export", + "settings.keyboard.back": "Back", + "settings.keyboard.search": "Search keybindings...", + "settings.keyboard.filter": "Keybinding filter", + "settings.keyboard.all": "All", + "settings.keyboard.user": "User", + "settings.keyboard.default": "Default", + "settings.keyboard.preset": "Preset", + "settings.keyboard.presetChanges": "Preset Changes", + "settings.keyboard.extension": "Extension", + "settings.keyboard.command": "Command", + "settings.keyboard.keybinding": "Keybinding", + "settings.keyboard.when": "When", + "settings.keyboard.source": "Source", + "settings.keyboard.actions": "Actions", + "settings.keyboard.noKeybindings": "No keybindings found", + "settings.keyboard.vimMode": "Vim Mode", + "settings.keyboard.vimModeDescription": "Enable vim keybindings and commands", + "settings.keyboard.presetLabel": "Keybinding Preset", + "settings.keyboard.presetDescription": "Apply a base shortcut style before your custom overrides.", + "settings.keyboard.presetAria": "Keybinding preset", + "settings.keyboard.presetIncomplete": "This preset is incomplete. {count} built-in command{suffix} still missing preset coverage.", + "settings.keyboard.presetSingularSuffix": " is", + "settings.keyboard.presetPluralSuffix": "s are", + "settings.keyboard.editKeybindings": "Edit Keybindings", + "settings.keyboard.editKeybindingsDescription": "Customize shortcuts individually.", + "settings.keyboard.openEditor": "Open Editor", + "settings.keyboard.userOverrides": "{count} user override{suffix} currently saved.", + "settings.keyboard.pluralSuffix": "s", + "settings.keyboard.keybindingsReset": "Keybindings reset to defaults", + "settings.keyboard.keybindingsExported": "Keybindings exported", + "settings.keyboard.exportFailed": "Failed to export keybindings: {error}", + "settings.keyboard.invalidFile": "Invalid keybindings file format", + "settings.keyboard.keybindingsImported": "Imported {count} keybindings{preset}", + "settings.keyboard.andPreset": " and preset", + "settings.keyboard.importFailed": "Failed to import keybindings: {error}", + "settings.keyboard.none": "None", + "settings.advanced.features": "Features", + "settings.advanced.featuresDescription": "Toggle application features on or off", + "settings.advanced.experimental": "Experimental", + "settings.advanced.data": "Data", + "settings.advanced.exportSettings": "Export Settings", + "settings.advanced.exportSettingsDescription": "Save all app settings to a JSON file.", + "settings.advanced.importSettings": "Import Settings", + "settings.advanced.importSettingsDescription": "Restore app settings from an Lithe settings JSON file.", + "settings.advanced.resetSettings": "Reset Settings", + "settings.advanced.resetSettingsDescription": "Reset all settings to their default values", + "settings.advanced.reset": "Reset", + "settings.advanced.telemetry": "Telemetry", + "settings.advanced.anonymousTelemetry": "Anonymous Usage Telemetry", + "settings.advanced.telemetryDescription": "Lithe sends anonymous operational metadata for updates and, when enabled, heartbeats, extensions, and crashes; it never sends file paths, project names, prompts, or editor content.", + "settings.advanced.learnMore": "Learn more", + "settings.advanced.telemetryLog": "Telemetry Log", + "settings.advanced.telemetryLogDescription": "Inspect the local queue and recent telemetry delivery results.", + "settings.advanced.hideLog": "Hide Log", + "settings.advanced.openLog": "Open Log", + "settings.advanced.clear": "Clear", + "settings.advanced.noTelemetry": "No telemetry entries yet.", + "settings.advanced.settingsReset": "Settings reset to defaults", + "settings.advanced.telemetryCleared": "Telemetry log cleared", + "settings.advanced.settingsExported": "Settings exported", + "settings.advanced.exportFailed": "Failed to export settings: {error}", + "settings.advanced.invalidFile": "Invalid settings file format", + "settings.advanced.settingsImported": "Settings imported", + "settings.advanced.importFailed": "Failed to import settings: {error}", + "settings.advanced.feature.terminal.name": "Integrated Terminal", + "settings.advanced.feature.terminal.description": "Built-in terminal for command line operations", + "settings.advanced.feature.search.name": "Global Search", + "settings.advanced.feature.search.description": "Search across files and folders in workspace", + "settings.advanced.feature.diagnostics.name": "Diagnostics & Problems", + "settings.advanced.feature.diagnostics.description": "Code diagnostics and error reporting", + "settings.advanced.feature.outline.name": "Outline", + "settings.advanced.feature.outline.description": "Document symbols and quick navigation for the active file", + "settings.advanced.feature.breadcrumbs.name": "Breadcrumbs", + "settings.advanced.feature.breadcrumbs.description": "File path navigation breadcrumbs in editor", + "settings.advanced.feature.persistentCommands.name": "Persistent Commands", + "settings.advanced.feature.persistentCommands.description": "The last used commands appear at the top of the command palette", + "settings.common.json": "JSON", + "settings.common.allFiles": "All Files", + }, + "zh-CN": { + "workbench.project": "项目", + "workbench.changes": "更改", + "workbench.search": "搜索", + "workbench.database": "数据库", + "workbench.settings": "设置", + "workbench.openProject": "打开项目", + "workbench.currentFile": "当前文件", + "workbench.moreProjectActions": "更多项目操作", + "welcome.openProject": "打开项目", + "welcome.recentProjects": "最近项目", + "settings.displayLanguage": "显示语言", + "settings.displayLanguageDescription": "选择 Lithe 界面使用的语言。", + "settings.languageEnglish": "English", + "settings.languageChinese": "简体中文", + "settings.search": "搜索设置...", + "settings.noMatching": "没有匹配的设置", + "settings.tabs.general": "常规", + "settings.tabs.appearance": "外观", + "settings.tabs.editor": "编辑器", + "settings.tabs.files": "文件", + "settings.tabs.git": "Git", + "settings.tabs.terminal": "终端", + "settings.tabs.keyboard": "快捷键", + "settings.tabs.advanced": "高级", + "settings.git.integration": "集成", + "settings.git.gitIntegration": "Git 集成", + "settings.git.gitIntegrationDescription": "启用 Git 仓库的源代码管理功能", + "settings.git.autoRefresh": "自动刷新 Git 状态", + "settings.git.autoRefreshDescription": "相关文件或 Git 事件发生变化后自动刷新 Git 视图", + "settings.git.confirmDiscard": "丢弃前确认", + "settings.git.confirmDiscardDescription": "丢弃文件或仓库更改前显示确认提示", + "settings.git.view": "Git 视图", + "settings.git.folderChanges": "基于文件夹的更改", + "settings.git.folderChangesDescription": "以类似文件视图的文件夹树形式显示 Git 更改", + "settings.git.untracked": "显示未跟踪文件", + "settings.git.untrackedDescription": "在 Git 状态面板中显示未跟踪文件", + "settings.git.stagedFirst": "优先显示已暂存项", + "settings.git.stagedFirstDescription": "在 Git 面板中将已暂存更改显示在未暂存更改之前", + "settings.git.openDiff": "单击打开差异", + "settings.git.openDiffDescription": "单击已更改文件时打开差异,而不是直接打开文件", + "settings.git.compactBadges": "紧凑 Git 状态标记", + "settings.git.compactBadgesDescription": "在 Git 面板中使用更紧凑的差异统计和暂存标签布局", + "settings.git.collapseEmpty": "折叠空分区", + "settings.git.collapseEmptyDescription": "没有项目时隐藏已暂存更改等空 Git 分区", + "settings.git.rememberPanel": "记住上次 Git 面板模式", + "settings.git.rememberPanelDescription": "重新打开 Git 视图时恢复上次打开的底部 Git 面板分区", + "settings.git.defaultDiff": "默认差异视图", + "settings.git.defaultDiffDescription": "选择 Git 差异的默认布局", + "settings.git.unified": "统一视图", + "settings.git.split": "拆分视图", + "settings.git.editor": "编辑器", + "settings.git.inlineBlame": "启用行内 Blame", + "settings.git.inlineBlameDescription": "在编辑器中显示当前行的 Git Blame 元数据", + "settings.general.version": "版本", + "settings.general.versionDescription": "检查更新并安装最新应用版本。", + "settings.general.downloading": "正在下载...", + "settings.general.installing": "正在安装...", + "settings.general.installUpdate": "安装 {version}", + "settings.general.update": "更新", + "settings.general.checking": "正在检查...", + "settings.general.check": "检查", + "settings.general.updateProgress": "Lithe {version} · 正在下载 {percentage}%", + "settings.general.updateInstalling": "Lithe {version} · 正在安装更新...", + "settings.general.updateAvailable": "Lithe {version} · 版本 {availableVersion} 可用", + "settings.general.updateCheckFailed": "Lithe {version} · 检查更新失败", + "settings.general.upToDate": "Lithe {version} · 应用已是最新版本", + "settings.general.updateDownloadProgress": "Lithe 更新下载进度", + "settings.general.terminalCommand": "终端命令", + "settings.general.terminalCommandDescription": "安装 `lithe` 命令,以便从终端打开文件夹和文件。", + "settings.general.uninstall": "卸载", + "settings.general.uninstalling": "正在卸载...", + "settings.general.install": "安装", + "settings.general.copy": "复制", + "settings.general.copyInstallCommand": "复制安装命令到剪贴板", + "settings.general.cliInstalled": "CLI 命令已安装到 $HOME/.local/bin/lithe", + "settings.general.cliNotInstalled": "CLI 命令未安装。", + "settings.general.importSettings": "导入设置", + "settings.general.importSettingsDescription": "从其他编辑器导入相匹配的配置。", + "settings.general.reportBug": "报告问题", + "settings.general.reportBugDescription": "选择提交包含环境信息的问题的位置。", + "settings.general.open": "打开", + "settings.general.reportVia": "通过以下方式报告...", + "settings.general.noReportChannel": "没有匹配“{query}”的报告渠道。", + "settings.general.githubReportDescription": "打开问题报告 Issue", + "settings.general.cliInstallFailed": "安装 CLI 失败:{error}。您可能需要管理员权限。", + "settings.general.cliUninstallFailed": "卸载 CLI 失败:{error}", + "settings.general.installCommandCopied": "安装命令已复制到剪贴板", + "settings.general.copyCommandFailed": "复制命令失败:{error}", + "settings.general.latestVersion": "您使用的是最新版本", + "settings.general.reportTemplateCopied": "报告模板已复制", + "settings.general.prepareReportFailed": "准备问题报告失败", + "settings.general.bugReportTemplate": "环境\n\n- 应用:Lithe {version}\n- 操作系统:{platform} {osVersion}\n\n问题\n\n请在此描述问题。请提供复现步骤、预期结果和实际结果。\n", + "settings.files.display": "显示", + "settings.files.sortOrder": "排序顺序", + "settings.files.sortOrderDescription": "选择文件夹始终排在文件之前,还是全部按名称排序", + "settings.files.foldersFirst": "文件夹优先", + "settings.files.name": "名称", + "settings.files.indentSize": "缩进大小", + "settings.files.indentSizeDescription": "每个嵌套层级的像素数", + "settings.files.showFileIcons": "显示文件图标", + "settings.files.showFileIconsDescription": "显示主题化的文件和文件夹图标", + "settings.files.showIndentGuides": "显示缩进参考线", + "settings.files.showIndentGuidesDescription": "显示嵌套文件夹的垂直参考线", + "settings.files.compactFolders": "紧凑文件夹", + "settings.files.compactFoldersDescription": "折叠只有一个子项的连续文件夹", + "settings.files.hideRootFolder": "隐藏根文件夹", + "settings.files.hideRootFolderDescription": "在顶层直接显示项目文件", + "settings.files.showHiddenFiles": "显示隐藏文件", + "settings.files.showHiddenFilesDescription": "显示点文件和隐藏目录", + "settings.files.respectGitignore": "遵循 .gitignore", + "settings.files.respectGitignoreDescription": "隐藏由根目录和嵌套 .gitignore 文件匹配的文件", + "settings.files.showGitStatus": "显示 Git 状态", + "settings.files.showGitStatusDescription": "在已更改文件旁显示 Git 颜色标记", + "settings.files.behavior": "行为", + "settings.files.autoReveal": "自动显示活动文件", + "settings.files.autoRevealDescription": "展开并滚动文件视图至当前编辑器文件", + "settings.files.confirmBeforeDelete": "删除前确认", + "settings.files.confirmBeforeDeleteDescription": "删除文件或文件夹前请求确认", + "settings.files.filters": "筛选", + "settings.files.hiddenFiles": "隐藏文件", + "settings.files.hiddenDirectories": "隐藏目录", + "settings.files.globPatterns": "以逗号分隔的 glob 模式", + "settings.editor.section": "编辑器", + "settings.editor.fontFamily": "编辑器字体", + "settings.editor.fontFamilyDescription": "代码编辑器使用的字体", + "settings.editor.fontSize": "字体大小", + "settings.editor.fontSizeDescription": "编辑器字体大小(像素)", + "settings.editor.fontLigatures": "字体连字", + "settings.editor.fontLigaturesDescription": "使用所选编辑器字体提供的编程连字", + "settings.editor.italicComments": "斜体注释", + "settings.editor.italicCommentsDescription": "以斜体显示代码注释", + "settings.editor.lineHeight": "行高", + "settings.editor.lineHeightDescription": "编辑器行高倍数", + "settings.editor.tabSize": "制表符大小", + "settings.editor.tabSizeDescription": "每个制表符占用的空格数", + "settings.editor.wordWrap": "自动换行", + "settings.editor.wordWrapDescription": "换行显示超出视口宽度的行", + "settings.editor.lineNumbers": "行号", + "settings.editor.lineNumbersDescription": "在编辑器中显示行号", + "settings.editor.renderWhitespace": "显示空白字符", + "settings.editor.renderWhitespaceDescription": "为空格和制表符显示可见标记", + "settings.editor.whitespaceNone": "无", + "settings.editor.whitespaceBoundary": "边界", + "settings.editor.whitespaceTrailing": "行尾", + "settings.editor.whitespaceAll": "全部", + "settings.editor.indentGuides": "缩进参考线", + "settings.editor.indentGuidesDescription": "显示缩进层级的垂直参考线", + "settings.editor.highlightOccurrences": "突出显示匹配项", + "settings.editor.highlightOccurrencesDescription": "突出显示光标下单词的可见匹配项", + "settings.editor.relativeLineNumbers": "相对行号", + "settings.editor.relativeLineNumbersDescription": "Vim 模式激活时显示相对行号", + "settings.editor.showMinimap": "显示缩略图", + "settings.editor.showMinimapDescription": "在编辑器右侧显示缩略图概览", + "settings.editor.stickyScroll": "粘滞滚动", + "settings.editor.stickyScrollDescription": "滚动时在顶部保持显示包含范围", + "settings.editor.bracketPairColorization": "括号对着色", + "settings.editor.bracketPairColorizationDescription": "使用匹配颜色区分嵌套的括号对", + "settings.editor.smoothScrolling": "平滑滚动", + "settings.editor.smoothScrollingDescription": "在编辑器位置之间滚动时显示动画", + "settings.editor.scrollBeyondLastLine": "滚动至末行之外", + "settings.editor.scrollBeyondLastLineDescription": "允许将最后一行滚动到编辑器底部上方", + "settings.editor.cursorStyle": "光标样式", + "settings.editor.cursorStyleDescription": "Vim 普通模式外编辑器光标的形状", + "settings.editor.cursorLine": "竖线", + "settings.editor.cursorThinLine": "细竖线", + "settings.editor.cursorBlock": "块", + "settings.editor.cursorBlockOutline": "块轮廓", + "settings.editor.cursorUnderline": "下划线", + "settings.editor.cursorThinUnderline": "细下划线", + "settings.editor.cursorBlinking": "光标闪烁", + "settings.editor.cursorBlinkingDescription": "Vim 普通模式外编辑器光标使用的动画", + "settings.editor.blink": "闪烁", + "settings.editor.smooth": "平滑", + "settings.editor.phase": "渐变", + "settings.editor.expand": "展开", + "settings.editor.solid": "常亮", + "settings.editor.maxOpenTabs": "最大打开标签数", + "settings.editor.maxOpenTabsDescription": "达到此数量后关闭最早的标签页", + "settings.editor.bufferCarousel": "缓冲区轮播", + "settings.editor.bufferCarouselDescription": "在主视图中将打开的缓冲区显示为可横向滚动的轮播", + "settings.editor.autoSave": "自动保存", + "settings.editor.autoSaveDescription": "编辑时自动保存文件", + "settings.editor.defaultLanguage": "默认语言", + "settings.editor.defaultLanguageDescription": "新文件默认使用的语法高亮", + "settings.editor.autoDetect": "自动检测", + "settings.editor.autoDetectLanguage": "自动检测语言", + "settings.editor.autoDetectLanguageDescription": "根据扩展名自动检测文件语言", + "settings.editor.formatOnSave": "保存时格式化", + "settings.editor.formatOnSaveDescription": "保存时自动格式化代码", + "settings.editor.lintOnSave": "保存时检查", + "settings.editor.lintOnSaveDescription": "保存文件时运行检查器", + "settings.editor.autoCompletion": "自动补全", + "settings.editor.autoCompletionDescription": "输入时显示补全建议", + "settings.editor.parameterHints": "参数提示", + "settings.editor.parameterHintsDescription": "显示函数参数提示", + "settings.editor.inlayHints": "内嵌提示", + "settings.editor.inlayHintsDescription": "显示语言服务器提供的内嵌类型和参数提示", + "settings.editor.codeLens": "代码镜头", + "settings.editor.codeLensDescription": "在符号上方显示内嵌代码操作", + "settings.editor.semanticTokens": "语义令牌", + "settings.editor.semanticTokensDescription": "使用语言服务器的语义高亮", + "settings.editor.symbolBreadcrumb": "在面包屑中显示符号", + "settings.editor.symbolBreadcrumbDescription": "在面包屑栏中显示光标位置所在的函数或类", + "settings.appearance.theme": "主题", + "settings.appearance.syncWithOs": "与操作系统同步", + "settings.appearance.syncWithOsDescription": "在偏好的浅色和深色主题之间自动切换", + "settings.appearance.colorTheme": "颜色主题", + "settings.appearance.colorThemeDescription": "选择偏好的颜色主题", + "settings.appearance.preferredLightTheme": "首选浅色主题", + "settings.appearance.preferredLightThemeDescription": "启用与操作系统同步且系统外观为浅色时使用", + "settings.appearance.preferredDarkTheme": "首选深色主题", + "settings.appearance.preferredDarkThemeDescription": "启用与操作系统同步且系统外观为深色时使用", + "settings.appearance.iconTheme": "图标主题", + "settings.appearance.iconThemeDescription": "在文件树和标签页中显示的图标", + "settings.appearance.customThemes": "自定义主题", + "settings.appearance.customThemesDescription": "导入 Lithe 主题 JSON,或从已安装主题创建主题。", + "settings.appearance.formatGuide": "格式指南", + "settings.appearance.create": "创建", + "settings.appearance.import": "导入", + "settings.appearance.removeTheme": "移除 {theme}", + "settings.appearance.importThemeFailed": "导入主题失败", + "settings.appearance.importedTheme": "已导入 {theme}", + "settings.appearance.importedThemeVariants": "已导入 {count} 个主题变体", + "settings.appearance.customThemeRemoved": "自定义主题已移除", + "settings.appearance.removeCustomThemeFailed": "移除自定义主题失败", + "settings.appearance.typography": "字体排印", + "settings.appearance.uiFontFamily": "界面字体", + "settings.appearance.uiFontFamilyDescription": "界面元素使用的字体(文件树、Markdown 等)", + "settings.appearance.uiFontSize": "界面字体大小", + "settings.appearance.uiFontSizeDescription": "以 0.5 像素为单位调整界面文本和图标缩放", + "settings.appearance.uiFontSizeAria": "界面字体大小:{size} 像素", + "settings.appearance.interface": "界面", + "settings.appearance.reduceMotion": "减少动态效果", + "settings.appearance.reduceMotionDescription": "减少非必要的界面动画,同时保持状态变化可见", + "settings.appearance.showStatusBar": "显示状态栏", + "settings.appearance.showStatusBarDescription": "在底部边缘显示应用控件和状态信息", + "settings.appearance.showTabIcons": "显示标签页图标", + "settings.appearance.showTabIconsDescription": "在编辑器标签页中显示文件和视图图标", + "settings.appearance.tabCloseButtons": "标签页关闭按钮", + "settings.appearance.tabCloseButtonsDescription": "选择未固定标签页显示关闭按钮的时机", + "settings.appearance.activeAndHovered": "活动和悬停时", + "settings.appearance.hoveredOnly": "仅悬停时", + "settings.appearance.always": "始终", + "settings.appearance.layout": "布局", + "settings.appearance.windowChromeDensity": "窗口框架密度", + "settings.appearance.windowChromeDensityDescription": "为标题栏、标签页、侧边栏和页脚选择更紧凑或更宽松的尺寸", + "settings.appearance.focused": "紧凑", + "settings.appearance.comfortable": "舒适", + "settings.appearance.expandedActivityBar": "展开活动栏", + "settings.appearance.expandedActivityBarDescription": "在活动栏图标旁显示标签", + "settings.appearance.activityBarWidth": "活动栏宽度", + "settings.appearance.activityBarWidthDescription": "设置展开活动栏的宽度", + "settings.appearance.activityBarWidthAria": "活动栏宽度:{size} 像素", + "settings.appearance.sidebarWidth": "侧边栏宽度", + "settings.appearance.sidebarWidthDescription": "设置左右侧边栏使用的默认宽度", + "settings.appearance.sidebarWidthAria": "侧边栏宽度:{size} 像素", + "settings.appearance.nativeMenuBar": "原生菜单栏", + "settings.appearance.nativeMenuBarDescription": "使用原生菜单栏或自定义界面菜单栏", + "settings.appearance.compactMenuBar": "紧凑菜单栏", + "settings.appearance.compactMenuBarDescription": "需要界面菜单栏;使用紧凑汉堡菜单或完整界面菜单", + "settings.appearance.windowTransparency": "窗口透明度", + "settings.appearance.windowTransparencyDescription": "在支持时使用半透明应用框架和透明原生窗口", + "settings.appearance.openProjectsNewWindow": "在新窗口中打开项目", + "settings.appearance.openProjectsNewWindowDescription": "在单独窗口中打开每个新项目,并禁用活动栏项目切换", + "settings.terminal.nerdFont": "Nerd Font", + "settings.terminal.custom": "自定义", + "settings.terminal.systemDefault": "系统默认", + "settings.terminal.fontHelp": "注意:所选字体必须已安装在系统中才能正常工作。如果缺少图标,请尝试安装 Nerd Font。", + "settings.terminal.launch": "启动", + "settings.terminal.launchDescription": "选择新终端标签页默认使用的 Shell 和配置文件。", + "settings.terminal.defaultShell": "默认 Shell", + "settings.terminal.defaultShellDescription": "终端配置文件未覆盖时使用的备用 Shell。", + "settings.terminal.defaultProfile": "默认配置文件", + "settings.terminal.defaultProfileDescription": "终端获得焦点时由终端工具栏按钮和 Cmd+T 使用。", + "settings.terminal.profiles": "配置文件", + "settings.terminal.profilesDescription": "创建可复用的启动预设,其中包含 Shell 覆盖、启动目录和可选启动命令。", + "settings.terminal.profilesHelp": "内置配置文件根据检测到的 Shell 生成。自定义配置文件显示在终端工具栏的配置文件选择器中。", + "settings.terminal.addProfile": "添加配置文件", + "settings.terminal.customProfile": "自定义配置文件 {number}", + "settings.terminal.noCustomProfiles": "尚无自定义终端配置文件。", + "settings.terminal.profilePickerDescription": "显示在终端配置文件选择器中。", + "settings.terminal.deleteProfile": "删除 {profile}", + "settings.terminal.profileName": "名称", + "settings.terminal.profileNamePlaceholder": "我的配置文件", + "settings.terminal.profileShell": "Shell", + "settings.terminal.startupDirectory": "启动目录", + "settings.terminal.startupDirectoryPlaceholder": "留空以使用当前工作区目录", + "settings.terminal.startupDirectoryDescription": "留空以使用当前工作区目录。", + "settings.terminal.startupCommands": "启动命令", + "settings.terminal.startupCommandsPlaceholder": "每行一个命令", + "settings.terminal.startupCommandsDescription": "每行输入一个命令。", + "settings.terminal.typography": "字体排印", + "settings.terminal.fontFamily": "字体", + "settings.terminal.fontFamilyDescription": "集成终端使用的字体。选择 Nerd Font 以获得最佳图标支持。", + "settings.terminal.selectFont": "选择字体...", + "settings.terminal.fontSize": "字体大小", + "settings.terminal.fontSizeDescription": "终端字体大小(像素)", + "settings.terminal.lineHeight": "行高", + "settings.terminal.lineHeightDescription": "行高倍数", + "settings.terminal.letterSpacing": "字母间距", + "settings.terminal.letterSpacingDescription": "字符之间的额外间距", + "settings.terminal.scrollback": "回滚缓冲区", + "settings.terminal.scrollbackDescription": "在内存中保留的终端历史行数", + "settings.terminal.interaction": "交互", + "settings.terminal.altClickMovesCursor": "Alt 单击移动光标", + "settings.terminal.altClickMovesCursorDescription": "在支持时将 Shell 提示符光标移动到单击位置", + "settings.terminal.optionAsMeta": "Option 作为 Meta", + "settings.terminal.optionAsMetaDescription": "在 macOS 终端应用中将 Option 键视为 Meta 键", + "settings.terminal.rightClickSelectsWord": "右键选择单词", + "settings.terminal.rightClickSelectsWordDescription": "打开上下文菜单前选择指针下的单词", + "settings.terminal.cursor": "光标", + "settings.terminal.cursorStyle": "光标样式", + "settings.terminal.cursorStyleDescription": "光标的形状", + "settings.terminal.block": "块", + "settings.terminal.underline": "下划线", + "settings.terminal.bar": "竖线", + "settings.terminal.blinkingCursor": "光标闪烁", + "settings.terminal.blinkingCursorDescription": "是否让光标闪烁", + "settings.terminal.cursorWidth": "光标宽度", + "settings.terminal.cursorWidthDescription": "竖线或块光标的粗细", + "settings.terminal.inactiveCursorStyle": "非活动光标样式", + "settings.terminal.inactiveCursorStyleDescription": "终端未获得焦点时光标的外观", + "settings.terminal.outline": "轮廓", + "settings.terminal.hidden": "隐藏", + "settings.keyboard.resetToDefaults": "恢复默认值", + "settings.keyboard.import": "导入", + "settings.keyboard.export": "导出", + "settings.keyboard.back": "返回", + "settings.keyboard.search": "搜索快捷键...", + "settings.keyboard.filter": "快捷键筛选", + "settings.keyboard.all": "全部", + "settings.keyboard.user": "用户", + "settings.keyboard.default": "默认", + "settings.keyboard.preset": "预设", + "settings.keyboard.presetChanges": "预设更改", + "settings.keyboard.extension": "扩展", + "settings.keyboard.command": "命令", + "settings.keyboard.keybinding": "快捷键", + "settings.keyboard.when": "条件", + "settings.keyboard.source": "来源", + "settings.keyboard.actions": "操作", + "settings.keyboard.noKeybindings": "未找到快捷键", + "settings.keyboard.vimMode": "Vim 模式", + "settings.keyboard.vimModeDescription": "启用 Vim 快捷键和命令", + "settings.keyboard.presetLabel": "快捷键预设", + "settings.keyboard.presetDescription": "在自定义覆盖前应用基础快捷键样式。", + "settings.keyboard.presetAria": "快捷键预设", + "settings.keyboard.presetIncomplete": "此预设不完整。仍有 {count} 个内置命令{suffix}缺少预设覆盖。", + "settings.keyboard.presetSingularSuffix": "", + "settings.keyboard.presetPluralSuffix": "", + "settings.keyboard.editKeybindings": "编辑快捷键", + "settings.keyboard.editKeybindingsDescription": "逐项自定义快捷键。", + "settings.keyboard.openEditor": "打开编辑器", + "settings.keyboard.userOverrides": "当前保存了 {count} 个用户覆盖{suffix}。", + "settings.keyboard.pluralSuffix": "", + "settings.keyboard.keybindingsReset": "快捷键已恢复默认值", + "settings.keyboard.keybindingsExported": "快捷键已导出", + "settings.keyboard.exportFailed": "导出快捷键失败:{error}", + "settings.keyboard.invalidFile": "快捷键文件格式无效", + "settings.keyboard.keybindingsImported": "已导入 {count} 个快捷键{preset}", + "settings.keyboard.andPreset": "及预设", + "settings.keyboard.importFailed": "导入快捷键失败:{error}", + "settings.keyboard.none": "无", + "settings.advanced.features": "功能", + "settings.advanced.featuresDescription": "打开或关闭应用功能", + "settings.advanced.experimental": "实验性", + "settings.advanced.data": "数据", + "settings.advanced.exportSettings": "导出设置", + "settings.advanced.exportSettingsDescription": "将全部应用设置保存到 JSON 文件。", + "settings.advanced.importSettings": "导入设置", + "settings.advanced.importSettingsDescription": "从 Lithe 设置 JSON 文件恢复应用设置。", + "settings.advanced.resetSettings": "重置设置", + "settings.advanced.resetSettingsDescription": "将所有设置重置为默认值", + "settings.advanced.reset": "重置", + "settings.advanced.telemetry": "遥测", + "settings.advanced.anonymousTelemetry": "匿名使用情况遥测", + "settings.advanced.telemetryDescription": "Lithe 会发送用于更新的匿名运行元数据;启用后还会发送心跳、扩展和崩溃信息。它绝不会发送文件路径、项目名称、提示词或编辑器内容。", + "settings.advanced.learnMore": "了解更多", + "settings.advanced.telemetryLog": "遥测日志", + "settings.advanced.telemetryLogDescription": "检查本地队列和最近的遥测发送结果。", + "settings.advanced.hideLog": "隐藏日志", + "settings.advanced.openLog": "打开日志", + "settings.advanced.clear": "清除", + "settings.advanced.noTelemetry": "暂无遥测条目。", + "settings.advanced.settingsReset": "设置已重置为默认值", + "settings.advanced.telemetryCleared": "遥测日志已清除", + "settings.advanced.settingsExported": "设置已导出", + "settings.advanced.exportFailed": "导出设置失败:{error}", + "settings.advanced.invalidFile": "设置文件格式无效", + "settings.advanced.settingsImported": "设置已导入", + "settings.advanced.importFailed": "导入设置失败:{error}", + "settings.advanced.feature.terminal.name": "集成终端", + "settings.advanced.feature.terminal.description": "用于命令行操作的内置终端", + "settings.advanced.feature.search.name": "全局搜索", + "settings.advanced.feature.search.description": "在工作区中的文件和文件夹内搜索", + "settings.advanced.feature.diagnostics.name": "诊断和问题", + "settings.advanced.feature.diagnostics.description": "代码诊断和错误报告", + "settings.advanced.feature.outline.name": "大纲", + "settings.advanced.feature.outline.description": "活动文件的文档符号和快速导航", + "settings.advanced.feature.breadcrumbs.name": "面包屑", + "settings.advanced.feature.breadcrumbs.description": "编辑器中的文件路径导航面包屑", + "settings.advanced.feature.persistentCommands.name": "常用命令", + "settings.advanced.feature.persistentCommands.description": "最近使用的命令显示在命令面板顶部", + "settings.common.json": "JSON", + "settings.common.allFiles": "所有文件", + }, +} as const; + +export type TranslationKey = keyof (typeof catalogs)["en-US"]; + +export function getLocaleCatalog(language: DisplayLanguage) { + return catalogs[language]; +} + +export function createTranslator(language: DisplayLanguage) { + const catalog = getLocaleCatalog(language); + + return (key: string, values?: Record): string => { + let translatedText: string = catalog[key as TranslationKey] ?? key; + + for (const [name, value] of Object.entries(values ?? {})) { + translatedText = translatedText.split(`{${name}}`).join(String(value)); + } + + return translatedText; + }; +}
{t("welcome.openFolderHint")}