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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ public/assets/material-icons/
# Nix
result
skills-lock.json
.vs/
8 changes: 8 additions & 0 deletions src-tauri/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,8 @@ pub(crate) struct AppSettings {
pub(crate) ui_scale: f64,
#[serde(default = "default_theme", rename = "theme")]
pub(crate) theme: String,
#[serde(default = "default_ui_language", rename = "uiLanguage")]
pub(crate) ui_language: String,
#[serde(
default = "default_usage_show_remaining",
rename = "usageShowRemaining"
Expand Down Expand Up @@ -698,6 +700,10 @@ fn default_theme() -> String {
"system".to_string()
}

fn default_ui_language() -> String {
"system".to_string()
}

fn default_usage_show_remaining() -> bool {
false
}
Expand Down Expand Up @@ -1142,6 +1148,7 @@ impl Default for AppSettings {
last_composer_reasoning_effort: None,
ui_scale: 1.0,
theme: default_theme(),
ui_language: default_ui_language(),
usage_show_remaining: default_usage_show_remaining(),
show_message_file_path: default_show_message_file_path(),
chat_history_scrollback_items: default_chat_history_scrollback_items(),
Expand Down Expand Up @@ -1306,6 +1313,7 @@ mod tests {
assert!(settings.last_composer_reasoning_effort.is_none());
assert!((settings.ui_scale - 1.0).abs() < f64::EPSILON);
assert_eq!(settings.theme, "system");
assert_eq!(settings.ui_language, "system");
assert!(!settings.usage_show_remaining);
assert!(settings.show_message_file_path);
assert_eq!(settings.chat_history_scrollback_items, Some(200));
Expand Down
17 changes: 12 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ import {
resolveWorkspaceRuntimeCodexArgsOverride,
} from "@threads/utils/threadCodexParamsSeed";
import { setWorkspaceRuntimeCodexArgs } from "@services/tauri";
import { I18nProvider } from "@/i18n/provider";

const AboutView = lazy(() =>
import("@/features/about/components/AboutView").then((module) => ({
Expand Down Expand Up @@ -2630,7 +2631,12 @@ function MainApp() {
);

return (
<div className={`${appClassName}${isResizing ? " is-resizing" : ""}`} style={appStyle} ref={appRef}>
<I18nProvider languagePreference={appSettings.uiLanguage}>
<div
className={`${appClassName}${isResizing ? " is-resizing" : ""}`}
style={appStyle}
ref={appRef}
>
<div className="drag-strip" id="titlebar" />
<TitlebarExpandControls {...sidebarToggleProps} />
<WindowCaptionControls />
Expand Down Expand Up @@ -2776,10 +2782,11 @@ function MainApp() {
onRemoveDictationModel: dictationModel.remove,
}}
/>
{showMobileSetupWizard && (
<MobileServerSetupWizard {...mobileSetupWizardProps} />
)}
</div>
{showMobileSetupWizard && (
<MobileServerSetupWizard {...mobileSetupWizardProps} />
)}
</div>
</I18nProvider>
);
}

Expand Down
28 changes: 15 additions & 13 deletions src/features/settings/components/SettingsNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ServerCog from "lucide-react/dist/esm/icons/server-cog";
import Bot from "lucide-react/dist/esm/icons/bot";
import Info from "lucide-react/dist/esm/icons/info";
import { PanelNavItem, PanelNavList } from "@/features/design-system/components/panel/PanelPrimitives";
import { useI18n } from "@/i18n/useI18n";
import type { CodexSection } from "./settingsTypes";

type SettingsNavProps = {
Expand All @@ -25,6 +26,7 @@ export function SettingsNav({
onSelectSection,
showDisclosure = false,
}: SettingsNavProps) {
const { t } = useI18n();
return (
<aside className="settings-sidebar">
<PanelNavList className="settings-nav-list">
Expand All @@ -35,7 +37,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("projects")}
>
Projects
{t("settings.nav.projects")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -44,7 +46,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("environments")}
>
Environments
{t("settings.nav.environments")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -53,7 +55,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("display")}
>
Display &amp; Sound
{t("settings.nav.display")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -62,7 +64,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("composer")}
>
Composer
{t("settings.nav.composer")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -71,7 +73,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("dictation")}
>
Dictation
{t("settings.nav.dictation")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -80,7 +82,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("shortcuts")}
>
Shortcuts
{t("settings.nav.shortcuts")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -89,7 +91,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("open-apps")}
>
Open in
{t("settings.nav.openApps")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -98,7 +100,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("git")}
>
Git
{t("settings.nav.git")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -107,7 +109,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("server")}
>
Server
{t("settings.nav.server")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -116,7 +118,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("agents")}
>
Agents
{t("settings.nav.agents")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -125,7 +127,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("codex")}
>
Codex
{t("settings.nav.codex")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -134,7 +136,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("features")}
>
Features
{t("settings.nav.features")}
</PanelNavItem>
<PanelNavItem
className="settings-nav"
Expand All @@ -143,7 +145,7 @@ export function SettingsNav({
showDisclosure={showDisclosure}
onClick={() => onSelectSection("about")}
>
About
{t("settings.nav.about")}
</PanelNavItem>
</PanelNavList>
</aside>
Expand Down
1 change: 1 addition & 0 deletions src/features/settings/components/SettingsView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const baseSettings: AppSettings = {
lastComposerReasoningEffort: null,
uiScale: 1,
theme: "system",
uiLanguage: "system",
usageShowRemaining: false,
showMessageFilePath: true,
chatHistoryScrollbackItems: 200,
Expand Down
12 changes: 7 additions & 5 deletions src/features/settings/components/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useSettingsViewCloseShortcuts } from "@settings/hooks/useSettingsViewCl
import { useSettingsViewNavigation } from "@settings/hooks/useSettingsViewNavigation";
import { useSettingsViewOrchestration } from "@settings/hooks/useSettingsViewOrchestration";
import { ModalShell } from "@/features/design-system/components/modal/ModalShell";
import { useI18n } from "@/i18n/useI18n";
import { SettingsNav } from "./SettingsNav";
import type { CodexSection } from "./settingsTypes";
import { SETTINGS_SECTION_LABELS } from "./settingsViewConstants";
Expand Down Expand Up @@ -97,6 +98,7 @@ export function SettingsView({
onRemoveDictationModel,
initialSection,
}: SettingsViewProps) {
const { t } = useI18n();
const {
activeSection,
showMobileDetail,
Expand Down Expand Up @@ -137,7 +139,7 @@ export function SettingsView({

useSettingsViewCloseShortcuts(onClose);

const activeSectionLabel = SETTINGS_SECTION_LABELS[activeSection];
const activeSectionLabel = t(`settings.nav.${SETTINGS_SECTION_LABELS[activeSection]}`);
const settingsBodyClassName = `settings-body${
useMobileMasterDetail ? " settings-body-mobile-master-detail" : ""
}${useMobileMasterDetail && showMobileDetail ? " is-detail-visible" : ""}`;
Expand All @@ -151,13 +153,13 @@ export function SettingsView({
>
<div className="settings-titlebar">
<div className="settings-title" id="settings-modal-title">
Settings
{t("settings.shell.title")}
</div>
<button
type="button"
className="ghost icon-button settings-close"
onClick={onClose}
aria-label="Close settings"
aria-label={t("settings.shell.close")}
>
<X aria-hidden />
</button>
Expand All @@ -180,10 +182,10 @@ export function SettingsView({
type="button"
className="settings-mobile-back"
onClick={() => setShowMobileDetail(false)}
aria-label="Back to settings sections"
aria-label={t("settings.shell.mobile.back")}
>
<ChevronLeft aria-hidden />
Sections
{t("settings.shell.mobile.sections")}
</button>
<div className="settings-mobile-detail-title">{activeSectionLabel}</div>
</div>
Expand Down
45 changes: 26 additions & 19 deletions src/features/settings/components/sections/SettingsAboutSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import {
isMobileRuntime,
type AppBuildType,
} from "@services/tauri";
import { useUpdater } from "@/features/update/hooks/useUpdater";
import { SettingsSection } from "@/features/design-system/components/settings/SettingsPrimitives";
import { useUpdater } from "@/features/update/hooks/useUpdater";
import { useI18n } from "@/i18n/useI18n";

function formatBytes(value: number) {
if (!Number.isFinite(value) || value <= 0) {
Expand All @@ -22,6 +23,7 @@ function formatBytes(value: number) {
}

export function SettingsAboutSection() {
const { t } = useI18n();
const [appBuildType, setAppBuildType] = useState<AppBuildType | "unknown">("unknown");
const [updaterEnabled, setUpdaterEnabled] = useState(false);
const { state: updaterState, checkForUpdates, startUpdate } = useUpdater({
Expand Down Expand Up @@ -72,42 +74,42 @@ export function SettingsAboutSection() {
const buildDateValue = __APP_BUILD_DATE__.trim();
const parsedBuildDate = Date.parse(buildDateValue);
const buildDateLabel = Number.isNaN(parsedBuildDate)
? buildDateValue || "unknown"
? buildDateValue || t("settings.about.unknown")
: new Date(parsedBuildDate).toLocaleString();

return (
<SettingsSection title="About" subtitle="App version, build metadata, and update controls.">
<SettingsSection title={t("settings.nav.about")}>
<div className="settings-field">
<div className="settings-help">
Version: <code>{__APP_VERSION__}</code>
{t("settings.about.version")}: <code>{__APP_VERSION__}</code>
</div>
<div className="settings-help">
Build type: <code>{appBuildType}</code>
{t("settings.about.buildType")}: <code>{appBuildType}</code>
</div>
<div className="settings-help">
Branch: <code>{__APP_GIT_BRANCH__ || "unknown"}</code>
{t("settings.about.branch")}: <code>{__APP_GIT_BRANCH__ || t("settings.about.unknown")}</code>
</div>
<div className="settings-help">
Commit: <code>{__APP_COMMIT_HASH__ || "unknown"}</code>
{t("settings.about.commit")}: <code>{__APP_COMMIT_HASH__ || t("settings.about.unknown")}</code>
</div>
<div className="settings-help">
Build date: <code>{buildDateLabel}</code>
{t("settings.about.buildDate")}: <code>{buildDateLabel}</code>
</div>
</div>
<div className="settings-field">
<div className="settings-label">App Updates</div>
<div className="settings-label">{t("settings.about.updates.title")}</div>
<div className="settings-help">
Currently running version <code>{__APP_VERSION__}</code>
{t("settings.about.updates.currentVersion")} <code>{__APP_VERSION__}</code>
</div>
{!updaterEnabled && (
<div className="settings-help">
Updates are unavailable in this runtime.
{t("settings.about.updates.unavailable")}
</div>
)}

{updaterState.stage === "error" && (
<div className="settings-help ds-text-danger">
Update failed: {updaterState.error}
{t("settings.about.updates.error")}: {updaterState.error}
</div>
)}

Expand All @@ -117,23 +119,26 @@ export function SettingsAboutSection() {
<div className="settings-help">
{updaterState.stage === "downloading" ? (
<>
Downloading update...{" "}
{t("settings.about.updates.downloading")}{" "}
{updaterState.progress?.totalBytes
? `${Math.round((updaterState.progress.downloadedBytes / updaterState.progress.totalBytes) * 100)}%`
: formatBytes(updaterState.progress?.downloadedBytes ?? 0)}
</>
) : updaterState.stage === "installing" ? (
"Installing update..."
t("settings.about.updates.installing")
) : (
"Restarting..."
t("settings.about.updates.restarting")
)}
</div>
) : updaterState.stage === "available" ? (
<div className="settings-help">
Version <code>{updaterState.version}</code> is available.
{t("settings.about.updates.available.prefix")} <code>{updaterState.version}</code>{" "}
{t("settings.about.updates.available.suffix")}
</div>
) : updaterState.stage === "latest" ? (
<div className="settings-help">You are on the latest version.</div>
<div className="settings-help">
{t("settings.about.updates.latest")}
</div>
) : null}

<div className="settings-controls">
Expand All @@ -144,7 +149,7 @@ export function SettingsAboutSection() {
disabled={!updaterEnabled}
onClick={() => void startUpdate()}
>
Download & Install
{t("settings.about.updates.downloadInstall")}
</button>
) : (
<button
Expand All @@ -159,7 +164,9 @@ export function SettingsAboutSection() {
}
onClick={() => void checkForUpdates({ announceNoUpdate: true })}
>
{updaterState.stage === "checking" ? "Checking..." : "Check for updates"}
{updaterState.stage === "checking"
? t("settings.about.updates.checking")
: t("settings.about.updates.check")}
</button>
)}
</div>
Expand Down
Loading