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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/web/src/AppRoot.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { RouterProvider } from "@tanstack/react-router";
import { useEffect } from "react";

import { ElectronBrowserHost } from "./browser/ElectronBrowserHost";
import { PreviewAutomationHosts } from "./components/preview/PreviewAutomationHosts";
import { QuitHoldOverlay } from "./components/QuitHoldOverlay";
import { useClientSettings, useClientSettingsHydrated } from "./hooks/useSettings";
import { markInterfaceLanguageConfigured, setInterfaceLanguage } from "./i18n/translate";
import { AppAtomRegistryProvider } from "./rpc/atomRegistry";
import type { AppRouter } from "./router";

function InterfaceLanguageBootstrap() {
const interfaceLanguage = useClientSettings((settings) => settings.interfaceLanguage);
const settingsHydrated = useClientSettingsHydrated();

useEffect(() => {
if (!settingsHydrated) return;
setInterfaceLanguage(interfaceLanguage);
markInterfaceLanguageConfigured();
}, [interfaceLanguage, settingsHydrated]);

return null;
}

/**
* Owns renderer-wide providers. The Electron browser host intentionally sits
* outside the router so its webviews survive route transitions, but it must
Expand All @@ -14,6 +30,7 @@ import type { AppRouter } from "./router";
export function AppRoot({ router }: { readonly router: AppRouter }) {
return (
<AppAtomRegistryProvider>
<InterfaceLanguageBootstrap />
<RouterProvider router={router} />
<PreviewAutomationHosts />
<ElectronBrowserHost />
Expand Down
7 changes: 5 additions & 2 deletions apps/web/src/components/AppSidebarLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
useSidebarVisibility,
} from "./ui/sidebar";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
import { useTranslate } from "../i18n/translate";

const MACOS_TRAFFIC_LIGHTS_LEFT_INSET = "var(--desktop-window-controls-inset, 90px)";

Expand All @@ -74,6 +75,7 @@ function readInitialThreadSidebarWidth(): number {
}

function SidebarControl() {
const t = useTranslate();
const keybindings = useAtomValue(primaryServerKeybindingsAtom);
const { toggleSidebar } = useSidebar();
const isSidebarVisible = useSidebarVisibility();
Expand Down Expand Up @@ -134,12 +136,13 @@ function SidebarControl() {
stageBackdropVariant &&
resolveSidebarStageFocusRingOffsetClass(stageBackdropVariant),
)}
aria-label="Toggle main sidebar"
aria-label={t("Toggle main sidebar")}
/>
}
/>
<TooltipPopup side="bottom">
Toggle main sidebar{shortcutLabel ? ` (${shortcutLabel})` : ""}
{t("Toggle main sidebar")}
{shortcutLabel ? ` (${shortcutLabel})` : ""}
</TooltipPopup>
</Tooltip>
</div>
Expand Down
26 changes: 15 additions & 11 deletions apps/web/src/components/BranchToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { useComposerMenuProps } from "./chat/composerEventScope";
import { measureRestingComposerControls } from "./chat/restingComposerControlsMeasurement";
import { resolveRestingComposerControlsNaturalWidth } from "./composerFooterLayout";
import { cn } from "~/lib/utils";
import { useTranslate } from "../i18n/translate";

export interface BranchToolbarHandle {
openBranchPicker: () => void;
Expand Down Expand Up @@ -124,6 +125,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
previousWorktreeLabel,
onUsePreviousWorktree,
}: MobileRunContextSelectorProps) {
const t = useTranslate();
const composerFloatingLayerProps = useComposerMenuProps();
const activeEnvironment = useMemo(
() => availableEnvironments?.find((env) => env.environmentId === environmentId) ?? null,
Expand All @@ -136,12 +138,12 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
? FolderGitIcon
: FolderIcon;
const workspaceLabel = forceNewWorktree
? resolveEnvModeLabel("worktree")
? t(resolveEnvModeLabel("worktree"))
: envModeLocked
? resolveLockedWorkspaceLabel(activeWorktreePath)
? t(resolveLockedWorkspaceLabel(activeWorktreePath))
: effectiveEnvMode === "worktree"
? resolveEnvModeLabel("worktree")
: resolveCurrentWorkspaceLabel(activeWorktreePath);
? t(resolveEnvModeLabel("worktree"))
: t(resolveCurrentWorkspaceLabel(activeWorktreePath));
const isLocked = envLocked || envModeLocked;
const workspaceIcon = (
<Tooltip>
Expand All @@ -166,7 +168,9 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
/>
)}
</TooltipTrigger>
<TooltipPopup>{autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}</TooltipPopup>
<TooltipPopup>
{autoEnvironmentLabel ?? activeEnvironment?.label ?? t("Run on")}
</TooltipPopup>
</Tooltip>
{workspaceIcon}
</span>
Expand All @@ -185,7 +189,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
{autoEnvironmentLabel ??
(showEnvironmentIndicator ? (activeEnvironment?.label ?? "Run on") : workspaceLabel)}
(showEnvironmentIndicator ? (activeEnvironment?.label ?? t("Run on")) : workspaceLabel)}
</span>
</span>
</>
Expand Down Expand Up @@ -220,7 +224,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
{showEnvironmentPicker && availableEnvironments && onEnvironmentChange ? (
<>
<MenuGroup>
<MenuGroupLabel>Run on</MenuGroupLabel>
<MenuGroupLabel>{t("Run on")}</MenuGroupLabel>
<MenuRadioGroup
value={autoEnvironmentLabel ? "auto" : environmentId}
onValueChange={(value) =>
Expand All @@ -240,7 +244,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
<span className="flex min-w-0 items-center gap-1.5">
<ScaleIcon className="size-3" aria-hidden="true" />
<span className="min-w-0 truncate">
{autoEnvironmentLabel ?? "Auto balance"}
{autoEnvironmentLabel ?? t("Auto balance")}
</span>
</span>
</MenuRadioItem>
Expand All @@ -263,7 +267,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
</>
) : null}
<MenuGroup>
<MenuGroupLabel>Workspace</MenuGroupLabel>
<MenuGroupLabel>{t("Workspace")}</MenuGroupLabel>
<MenuRadioGroup
value={effectiveEnvMode}
onValueChange={(value) => {
Expand All @@ -282,14 +286,14 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({
<FolderIcon className="size-3" />
)}
<span className="min-w-0 truncate">
{resolveCurrentWorkspaceLabel(activeWorktreePath)}
{t(resolveCurrentWorkspaceLabel(activeWorktreePath))}
</span>
</span>
</MenuRadioItem>
<MenuRadioItem disabled={envModeLocked} value="worktree">
<span className="flex min-w-0 items-center gap-1.5">
<FolderGit2Icon className="size-3" />
<span className="min-w-0 truncate">{resolveEnvModeLabel("worktree")}</span>
<span className="min-w-0 truncate">{t(resolveEnvModeLabel("worktree"))}</span>
</span>
</MenuRadioItem>
{previousWorktreeLabel ? (
Expand Down
28 changes: 15 additions & 13 deletions apps/web/src/components/BranchToolbarEnvModeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type EnvMode,
} from "./BranchToolbar.logic";
import { useComposerMenuProps } from "./chat/composerEventScope";
import { useTranslate } from "../i18n/translate";
import {
Select,
SelectGroup,
Expand Down Expand Up @@ -40,17 +41,18 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
previousWorktreeLabel,
onUsePreviousWorktree,
}: BranchToolbarEnvModeSelectorProps) {
const t = useTranslate();
const composerFloatingLayerProps = useComposerMenuProps();
const showPreviousWorktree = Boolean(previousWorktreeLabel && onUsePreviousWorktree);
const envModeItems = useMemo(
() => [
{ value: "local", label: resolveCurrentWorkspaceLabel(activeWorktreePath) },
{ value: "worktree", label: resolveEnvModeLabel("worktree") },
{ value: "local", label: t(resolveCurrentWorkspaceLabel(activeWorktreePath)) },
{ value: "worktree", label: t(resolveEnvModeLabel("worktree")) },
...(showPreviousWorktree && previousWorktreeLabel
? [{ value: PREVIOUS_WORKTREE_SELECT_VALUE, label: previousWorktreeLabel }]
: []),
],
[activeWorktreePath, previousWorktreeLabel, showPreviousWorktree],
[activeWorktreePath, previousWorktreeLabel, showPreviousWorktree, t],
);

if (envLocked || forceNewWorktree) {
Expand All @@ -77,15 +79,15 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-[cubic-bezier(0.32,0.72,0,1)] group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none"
>
{forceNewWorktree
? resolveEnvModeLabel("worktree")
: resolveLockedWorkspaceLabel(activeWorktreePath)}
? t(resolveEnvModeLabel("worktree"))
: t(resolveLockedWorkspaceLabel(activeWorktreePath))}
</span>
</span>
</TooltipTrigger>
<TooltipPopup>
{forceNewWorktree
? "Each model starts in its own worktree."
: resolveLockedWorkspaceLabel(activeWorktreePath)}
? t("Each model starts in its own worktree.")
: t(resolveLockedWorkspaceLabel(activeWorktreePath))}
</TooltipPopup>
</Tooltip>
);
Expand All @@ -111,7 +113,7 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
variant="ghost"
size="xs"
className="min-w-0 shrink font-normal text-xs!"
aria-label="Workspace"
aria-label={t("Workspace")}
data-composer-shortcut="composer.workspace"
data-composer-context-control
/>
Expand All @@ -138,27 +140,27 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe
</TooltipTrigger>
<TooltipPopup>
{effectiveEnvMode === "worktree"
? resolveEnvModeLabel("worktree")
: resolveCurrentWorkspaceLabel(activeWorktreePath)}
? t(resolveEnvModeLabel("worktree"))
: t(resolveCurrentWorkspaceLabel(activeWorktreePath))}
</TooltipPopup>
</Tooltip>
<SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}>
<SelectGroup>
<SelectGroupLabel>Workspace</SelectGroupLabel>
<SelectGroupLabel>{t("Workspace")}</SelectGroupLabel>
<SelectItem value="local">
<span className="inline-flex items-center gap-1.5">
{activeWorktreePath ? (
<FolderGitIcon className="size-3" />
) : (
<FolderIcon className="size-3" />
)}
{resolveCurrentWorkspaceLabel(activeWorktreePath)}
{t(resolveCurrentWorkspaceLabel(activeWorktreePath))}
</span>
</SelectItem>
<SelectItem value="worktree">
<span className="inline-flex items-center gap-1.5">
<FolderGit2Icon className="size-3" />
{resolveEnvModeLabel("worktree")}
{t(resolveEnvModeLabel("worktree"))}
</span>
</SelectItem>
{showPreviousWorktree && previousWorktreeLabel ? (
Expand Down
23 changes: 13 additions & 10 deletions apps/web/src/components/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ import { resolveProjectSettings } from "@t3tools/shared/projectSettings";
import { sourceControlRepositorySelector } from "@t3tools/shared/sourceControl";
import { truncate } from "@t3tools/shared/String";
import { resolveThreadReferenceCopyTarget } from "@t3tools/shared/threadReference";
import { useTranslate } from "../i18n/translate";
import {
getTerminalLabel,
nextTerminalId,
Expand Down Expand Up @@ -1462,6 +1463,7 @@ function releaseChatTimelineAnchor<T extends { readonly messageId: MessageId | n
}

export default function ChatView(props: ChatViewProps) {
const t = useTranslate();
const {
environmentId,
threadId,
Expand Down Expand Up @@ -6312,12 +6314,12 @@ export default function ChatView(props: ChatViewProps) {
id: `thread-woke:${activeThread?.id ?? "unknown"}`,
variant: "info",
icon: <AlarmClockIcon />,
title: "Thread woke from snooze",
description: "Send a message to continue",
dismissLabel: "Dismiss Woke notification",
title: t("Thread woke from snooze"),
description: t("Send a message to continue"),
dismissLabel: t("Dismiss Woke notification"),
onDismiss: acknowledgeActiveThreadWoke,
};
}, [acknowledgeActiveThreadWoke, activeThread?.id, activeThreadWokeVisible]);
}, [acknowledgeActiveThreadWoke, activeThread?.id, activeThreadWokeVisible, t]);
const parkedThreadBannerItem = useMemo<ComposerBannerStackItem | null>(() => {
if (!activeThreadSnoozed && !activeThreadSettled) {
return null;
Expand All @@ -6327,8 +6329,8 @@ export default function ChatView(props: ChatViewProps) {
id: `thread-${isSnoozed ? "snoozed" : "settled"}:${activeThread?.id ?? "unknown"}`,
variant: "info",
icon: isSnoozed ? <AlarmClockIcon /> : <CheckCircle2Icon />,
title: `This thread is ${isSnoozed ? "snoozed" : "settled"}`,
description: `Send a message to ${isSnoozed ? "wake" : "unsettle"}`,
title: `${t("This thread is")} ${t(isSnoozed ? "snoozed" : "settled")}`,
description: `${t("Send a message to")} ${t(isSnoozed ? "wake" : "unsettle")}`,
actions: (
<Button
size="xs"
Expand All @@ -6340,11 +6342,11 @@ export default function ChatView(props: ChatViewProps) {
>
{isSnoozed
? isUnsnoozing
? "Waking..."
: "Wake now"
? t("Waking...")
: t("Wake now")
: isUnsettling
? "Un-settling..."
: "Un-settle"}
? t("Un-settling...")
: t("Un-settle")}
</Button>
),
};
Expand All @@ -6356,6 +6358,7 @@ export default function ChatView(props: ChatViewProps) {
handleUnsettleActiveThread,
isUnsnoozing,
isUnsettling,
t,
]);
// Session-scoped dismissals, one key per (thread, snapshot). A set rather
// than a single slot so dismissing the banner on one thread does not
Expand Down
Loading
Loading