feat(basic): remove Lingui bootstrap and English shell (batch A)

Made-with: Cursor
This commit is contained in:
zuiidea
2026-04-14 20:55:52 +08:00
parent b47e932d92
commit b6f98b71b1
18 changed files with 102 additions and 845 deletions
-13
View File
@@ -1,13 +0,0 @@
const config = {
locales: ["en", "zh"],
sourceLocale: "en",
catalogs: [
{
path: "src/locales/{locale}/messages",
include: ["src"],
},
],
format: "po",
};
export default config;
@@ -1,5 +1,4 @@
import { Flex, theme } from "antd";
import { useLingui } from "@lingui/react/macro";
import { BarChart3 } from "lucide-react";
import type { ReactElement } from "react";
@@ -7,7 +6,6 @@ import type { ReactElement } from "react";
* Table empty state: icon in soft tile + bold title + secondary description (dashboard-style).
*/
export function DataTableEmpty(): ReactElement {
const { t } = useLingui();
const { token } = theme.useToken();
return (
@@ -46,7 +44,7 @@ export function DataTableEmpty(): ReactElement {
lineHeight: token.lineHeightLG,
}}
>
{t`No data`}
No data
</span>
<span
style={{
@@ -56,7 +54,7 @@ export function DataTableEmpty(): ReactElement {
lineHeight: token.lineHeight,
}}
>
{t`Nothing to show in this list yet`}
Nothing to show in this list yet
</span>
</Flex>
</Flex>
@@ -1,12 +1,10 @@
import { Flex, Typography, theme } from "antd";
import { useLingui } from "@lingui/react/macro";
import { GitHub } from "@/components/Icon";
const ANTD_ADMIN_REPO = "https://github.com/zuiidea/antd-admin";
export function AppFooter() {
const { token } = theme.useToken();
const { t } = useLingui();
const iconSize = Math.max(12, Math.round(Number(token.fontSizeSM)));
return (
@@ -20,7 +18,7 @@ export function AppFooter() {
}}
>
<Typography.Text type="secondary" style={{ fontSize: token.fontSizeSM, marginBottom: 0 }}>
{t`Powered by`}
Powered by
</Typography.Text>
<a
href={ANTD_ADMIN_REPO}
@@ -1,9 +1,8 @@
import { Layout, Button, Space, theme, Breadcrumb, Flex, Divider, Grid } from "antd";
import type { ItemType } from "antd/es/breadcrumb/Breadcrumb";
import { useLingui } from "@lingui/react/macro";
import { useSettingsStore, type Locale } from "@/stores/settings";
import { useSettingsStore } from "@/stores/settings";
import { Link, useLocation, useMatches } from "@tanstack/react-router";
import { Home, Languages, PanelLeft, ShieldAlert, Users } from "lucide-react";
import { Home, PanelLeft, ShieldAlert, Users } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import type { ReactNode } from "react";
import { Theme } from "@/components/Icon";
@@ -32,11 +31,8 @@ export type HeaderProps = {
export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProps) {
const toggleSidebar = useSettingsStore((s) => s.toggleSidebar);
const toggleDarkMode = useSettingsStore((s) => s.toggleDarkMode);
const locale = useSettingsStore((s) => s.locale);
const setLocale = useSettingsStore((s) => s.setLocale);
const location = useLocation();
const matches = useMatches();
const { t } = useLingui();
const { token } = theme.useToken();
const screens = Grid.useBreakpoint();
const isMobile = !screens.lg;
@@ -74,14 +70,7 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
const leafIcon: LucideIcon =
firstSegmentPath === "/users" ? Users : firstSegmentPath === "/403" ? ShieldAlert : Home;
const leafLabel =
leafLabelKey === "Dashboard"
? t`Dashboard`
: leafLabelKey === "Users"
? t`Users`
: leafLabelKey === "403"
? t`403`
: leafLabelKey;
const leafLabel = leafLabelKey;
const breadcrumbItems: ItemType[] = [];
@@ -89,11 +78,11 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
if (onDashboard) {
breadcrumbItems.push({
title: crumb(Home, t`Dashboard`),
title: crumb(Home, "Dashboard"),
});
} else {
breadcrumbItems.push({
title: crumb(Home, t`Dashboard`, "/dashboard"),
title: crumb(Home, "Dashboard", "/dashboard"),
});
breadcrumbItems.push({
@@ -113,11 +102,6 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
const showBreadcrumb =
showBreadcrumbProp && !hideBreadcrumbFromRoute && breadcrumbItems.length > 0;
const toggleLocale = () => {
const next: Locale = locale === "en" ? "zh" : "en";
setLocale(next);
};
return (
<AntHeader
style={{
@@ -135,7 +119,7 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
size="small"
onClick={toggleSidebar}
icon={<PanelLeft size={token.size} />}
aria-label={t`Toggle sidebar`}
aria-label="Toggle sidebar"
/>
) : null}
{showBreadcrumb ? (
@@ -146,17 +130,11 @@ export function Header({ showBreadcrumb: showBreadcrumbProp = true }: HeaderProp
) : null}
</Flex>
<Space>
<Button
type="text"
onClick={toggleLocale}
icon={<Languages size={token.size} />}
aria-label={t`Switch language`}
/>
<Button
type="text"
onClick={toggleDarkMode}
icon={<Theme size={token.size} />}
aria-label={t`Toggle Theme`}
aria-label="Toggle Theme"
/>
</Space>
</AntHeader>
@@ -1,8 +1,6 @@
import { Menu, Layout, theme, Flex, Grid, Drawer, Button } from "antd";
import { useEffect, useMemo, useState } from "react";
import { useNavigate, useLocation } from "@tanstack/react-router";
import { useLingui } from "@lingui/react/macro";
import { msg } from "@lingui/core/macro";
import {
Book,
Briefcase,
@@ -26,14 +24,14 @@ import { UserMenu } from "../UserMenu";
import "./index.css";
const { Sider } = Layout;
/** API menu `name` → Lingui descriptors (extracted into catalogs). */
const MENU_LABELS: Record<string, ReturnType<typeof msg>> = {
Platform: msg`Platform`,
Projects: msg`Projects`,
Dashboard: msg`Dashboard`,
Users: msg`Users`,
"Design Engineering": msg`Design Engineering`,
"Sales & Marketing": msg`Sales & Marketing`,
/** API menu `name` → English labels for known keys; unknown keys pass through as `menu.name`. */
const MENU_LABELS: Record<string, string> = {
Platform: "Platform",
Projects: "Projects",
Dashboard: "Dashboard",
Users: "Users",
"Design Engineering": "Design Engineering",
"Sales & Marketing": "Sales & Marketing",
};
type AntMenuItem = Required<MenuProps>["items"][number];
@@ -64,7 +62,6 @@ function renderMenuIcon(icon: string | null, size = 16) {
function buildMenuItems(
menus: MenuItemType[],
t: ReturnType<typeof useLingui>["t"],
token: ReturnType<typeof theme.useToken>["token"],
collapsed = false,
iconSize = 16,
@@ -76,11 +73,11 @@ function buildMenuItems(
const items: AntMenuItem[] = [];
for (const menu of sorted) {
const label = MENU_LABELS[menu.name] ? t(MENU_LABELS[menu.name]) : menu.name;
const label = MENU_LABELS[menu.name] ?? menu.name;
const key = menu.id;
if (menu.kind === "group") {
const built = buildMenuItems(menu.children, t, token, collapsed, iconSize, parentKeys);
const built = buildMenuItems(menu.children, token, collapsed, iconSize, parentKeys);
Object.assign(keyToPath, built.keyToPath);
Object.assign(pathToKeyChain, built.pathToKeyChain);
if (built.items.length > 0) {
@@ -131,7 +128,7 @@ function buildMenuItems(
let children: AntMenuItem[] | undefined;
if (menu.children?.length) {
const built = buildMenuItems(menu.children, t, token, collapsed, iconSize, nextParents);
const built = buildMenuItems(menu.children, token, collapsed, iconSize, nextParents);
Object.assign(keyToPath, built.keyToPath);
Object.assign(pathToKeyChain, built.pathToKeyChain);
children = built.items.length ? built.items : undefined;
@@ -157,14 +154,13 @@ export function Sidebar() {
const toggleSidebar = useSettingsStore((s) => s.toggleSidebar);
const navigate = useNavigate();
const location = useLocation();
const { t } = useLingui();
const { token } = theme.useToken();
const screens = Grid.useBreakpoint();
const isMobile = !screens.lg;
const mobileOpen = collapsed;
const builtMenu = useMemo(
() => buildMenuItems(menus, t, token, !isMobile && collapsed, token.size),
[menus, t, token, isMobile, collapsed, token.size],
() => buildMenuItems(menus, token, !isMobile && collapsed, token.size),
[menus, token, isMobile, collapsed, token.size],
);
const { selectedKey, routeOpenKeys } = useMemo(() => {
const chain = builtMenu.pathToKeyChain[location.pathname] ?? [];
@@ -191,7 +187,7 @@ export function Sidebar() {
const userMenuItems: MenuProps["items"] = [
{
key: "logout",
label: t`Sign Out`,
label: "Sign Out",
onClick: () => {
if (isMobile) {
setSidebarCollapsed(false);
@@ -265,7 +261,7 @@ export function Sidebar() {
className="sidebar-collapsed-brand__toggle"
onClick={toggleSidebar}
icon={<PanelLeft size={token.size} />}
aria-label={t`Toggle sidebar`}
aria-label="Toggle sidebar"
/>
</div>
</div>
@@ -313,7 +309,7 @@ export function Sidebar() {
size="small"
onClick={toggleSidebar}
icon={<PanelLeft size={token.size} />}
aria-label={t`Toggle sidebar`}
aria-label="Toggle sidebar"
style={{ flexShrink: 0 }}
/>
) : null}
+3 -5
View File
@@ -1,12 +1,10 @@
import { useNavigate } from "@tanstack/react-router";
import { useLingui } from "@lingui/react/macro";
import { ArrowLeft, Home, SearchX } from "lucide-react";
import { Button, Flex, Result, Space, theme } from "antd";
/** Shared 404 UI for `/404` route and root `notFoundComponent`. */
export function NotFound() {
const navigate = useNavigate();
const { t } = useLingui();
const { token } = theme.useToken();
const goDashboard = () => {
@@ -52,14 +50,14 @@ export function NotFound() {
<SearchX size={64} strokeWidth={1.25} color={token.colorTextQuaternary} aria-hidden />
}
title="404"
subTitle={t`Sorry, the page you visited does not exist.`}
subTitle="Sorry, the page you visited does not exist."
extra={
<Space wrap size="middle">
<Button type="primary" icon={<Home size={16} aria-hidden />} onClick={goDashboard}>
{t`Back to Home`}
Back to Home
</Button>
<Button icon={<ArrowLeft size={16} aria-hidden />} onClick={goBack}>
{t`Go back`}
Go back
</Button>
</Space>
}
-4
View File
@@ -1,4 +0,0 @@
declare module "*.po" {
import type { Messages } from "@lingui/core";
export const messages: Messages;
}
-317
View File
@@ -1,317 +0,0 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2026-04-07 15:30+0800\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: en\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/routes/_auth/users/index.tsx:464
#~ msgid "{n} rows"
#~ msgstr "{n} rows"
#: src/routes/_auth/users/index.tsx:386
msgid "{total} rows"
msgstr "{total} rows"
#: src/routes/_auth/dashboard/index.tsx:35
msgid "+180.1% from last month"
msgstr "+180.1% from last month"
#: src/routes/_auth/dashboard/index.tsx:41
msgid "+19% from last month"
msgstr "+19% from last month"
#: src/routes/_auth/dashboard/index.tsx:29
msgid "+20.1% from last month"
msgstr "+20.1% from last month"
#: src/routes/_auth/dashboard/index.tsx:47
msgid "+201 since last hour"
msgstr "+201 since last hour"
#: src/components/Layout/Header/index.tsx:78
msgid "403"
msgstr "403"
#: src/routes/_auth/dashboard/index.tsx:45
msgid "Active Now"
msgstr "Active Now"
#: src/routes/_auth/users/-FormModal.tsx:58
#: src/routes/_auth/users/-Toolbar.tsx:64
msgid "Admin"
msgstr "Admin"
#: src/routes/_auth/users/index.tsx:164
msgid "Are you absolutely sure?"
msgstr "Are you absolutely sure?"
#: src/routes/login/index.tsx:185
msgid "Auto login"
msgstr "Auto login"
#: src/components/NotFound/index.tsx:54
#: src/routes/_auth/403/index.tsx:40
msgid "Back to Home"
msgstr "Back to Home"
#: src/routes/_auth/users/-FormModal.tsx:32
#: src/routes/_auth/users/index.tsx:168
msgid "Cancel"
msgstr "Cancel"
#: src/routes/_auth/dashboard/index.tsx:134
msgid "Chart Placeholder"
msgstr "Chart Placeholder"
#: src/routes/_auth/users/index.tsx:106
msgid "Create failed"
msgstr "Create failed"
#: src/routes/_auth/users/-Toolbar.tsx:89
msgid "Create User"
msgstr "Create User"
#: src/routes/_auth/users/index.tsx:100
msgid "Created successfully"
msgstr "Created successfully"
#: src/components/Layout/Header/index.tsx:74
#: src/components/Layout/Header/index.tsx:87
#: src/components/Layout/Header/index.tsx:91
#: src/components/Layout/Sidebar/index.tsx:32
msgid "Dashboard"
msgstr "Dashboard"
#: src/routes/_auth/users/index.tsx:166
#: src/routes/_auth/users/index.tsx:257
msgid "Delete"
msgstr "Delete"
#: src/routes/_auth/users/index.tsx:156
msgid "Delete failed"
msgstr "Delete failed"
#: src/routes/_auth/users/index.tsx:149
msgid "Deleted successfully"
msgstr "Deleted successfully"
#: src/routes/_auth/users/index.tsx:142
msgid "Deleting…"
msgstr "Deleting…"
#: src/components/Layout/Sidebar/index.tsx:35
msgid "Design Engineering"
msgstr "Design Engineering"
#: src/routes/_auth/users/index.tsx:247
msgid "Edit"
msgstr "Edit"
#: src/routes/_auth/users/-FormModal.tsx:29
msgid "Edit User"
msgstr "Edit User"
#: src/routes/_auth/users/-FormModal.tsx:59
#: src/routes/_auth/users/-Toolbar.tsx:65
msgid "Editor"
msgstr "Editor"
#: src/routes/_auth/users/-FormModal.tsx:63
#: src/routes/_auth/users/index.tsx:202
msgid "Email"
msgstr "Email"
#: src/routes/_auth/users.tsx:353
#~ msgid "Filter by role"
#~ msgstr "Filter by role"
#: src/routes/login/index.tsx:192
msgid "Forgot password?"
msgstr "Forgot password?"
#: src/components/NotFound/index.tsx:57
msgid "Go back"
msgstr "Go back"
#: src/routes/login/index.tsx:68
msgid "Login failed"
msgstr "Login failed"
#: src/routes/login/index.tsx:64
msgid "Login successful"
msgstr "Login successful"
#: src/routes/_auth/users/-Toolbar.tsx:92
#: src/routes/_auth/users/-Toolbar.tsx:93
msgid "More filters"
msgstr "More filters"
#: src/routes/_auth/users/-FormModal.tsx:29
msgid "New User"
msgstr "New User"
#: src/components/DataTable/DataTableEmpty.tsx:49
msgid "No data"
msgstr "No data"
#: src/components/DataTable/DataTableEmpty.tsx:59
msgid "Nothing to show in this list yet"
msgstr "Nothing to show in this list yet"
#: src/routes/_auth/users/-FormModal.tsx:31
msgid "OK"
msgstr "OK"
#: src/routes/_auth/dashboard/index.tsx:115
msgid "Overview"
msgstr "Overview"
#: src/routes/404/index.tsx:87
#~ msgid "Page not found"
#~ msgstr "Page not found"
#: src/routes/login/index.tsx:171
msgid "Password"
msgstr "Password"
#: src/components/Layout/Sidebar/index.tsx:30
msgid "Platform"
msgstr "Platform"
#: src/routes/login/index.tsx:172
msgid "Please enter password"
msgstr "Please enter password"
#: src/routes/_auth/users/-FormModal.tsx:46
#: src/routes/login/index.tsx:164
msgid "Please enter username"
msgstr "Please enter username"
#: src/routes/_auth/users/-FormModal.tsx:53
msgid "Please select roles"
msgstr "Please select roles"
#: src/components/Layout/AppFooter/index.tsx:36
msgid "Powered by"
msgstr "Powered by"
#: src/components/Layout/Sidebar/index.tsx:31
msgid "Projects"
msgstr "Projects"
#: src/routes/_auth/dashboard/index.tsx:142
msgid "Recent Sales"
msgstr "Recent Sales"
#: src/routes/_auth/users/-Toolbar.tsx:58
msgid "Role"
msgstr "Role"
#: src/routes/_auth/users/-FormModal.tsx:52
#: src/routes/_auth/users/index.tsx:209
msgid "Roles"
msgstr "Roles"
#: src/routes/_auth/dashboard/index.tsx:39
msgid "Sales"
msgstr "Sales"
#: src/components/Layout/Sidebar/index.tsx:36
msgid "Sales & Marketing"
msgstr "Sales & Marketing"
#: src/routes/_auth/users/-Toolbar.tsx:43
msgid "Search User"
msgstr "Search User"
#: src/routes/_auth/users.tsx:341
#~ msgid "Search username or email"
#~ msgstr "Search username or email"
#: src/routes/login/index.tsx:204
msgid "Sign In"
msgstr "Sign In"
#: src/routes/login.tsx:100
#~ msgid "Sign in with your credentials."
#~ msgstr "Sign in with your credentials."
#: src/components/Layout/Sidebar/index.tsx:194
msgid "Sign Out"
msgstr "Sign Out"
#: src/components/NotFound/index.tsx:50
msgid "Sorry, the page you visited does not exist."
msgstr "Sorry, the page you visited does not exist."
#: src/routes/_auth/403/index.tsx:37
msgid "Sorry, you don't have permission to access this page."
msgstr "Sorry, you don't have permission to access this page."
#: src/routes/_auth/dashboard/index.tsx:33
msgid "Subscriptions"
msgstr "Subscriptions"
#: src/components/Layout/Header/index.tsx:145
#: src/routes/login/index.tsx:225
msgid "Switch language"
msgstr "Switch language"
#: src/routes/_auth/users/index.tsx:165
msgid "This action cannot be undone. This will permanently delete the user."
msgstr "This action cannot be undone. This will permanently delete the user."
#: src/components/Layout/Header/index.tsx:133
#: src/components/Layout/Sidebar/index.tsx:263
#: src/components/Layout/Sidebar/index.tsx:311
msgid "Toggle sidebar"
msgstr "Toggle sidebar"
#: src/components/Layout/Header/index.tsx:151
#: src/routes/login/index.tsx:232
msgid "Toggle Theme"
msgstr "Toggle Theme"
#: src/routes/_auth/dashboard/index.tsx:27
msgid "Total Revenue"
msgstr "Total Revenue"
#: src/routes/_auth/users/index.tsx:132
msgid "Update failed"
msgstr "Update failed"
#: src/routes/_auth/users/index.tsx:122
msgid "Updated successfully"
msgstr "Updated successfully"
#: src/routes/_auth/users/index.tsx:115
msgid "Updating…"
msgstr "Updating…"
#: src/components/Layout/Sidebar/index.tsx:34
msgid "User List"
msgstr "User List"
#: src/routes/_auth/users/-FormModal.tsx:45
#: src/routes/_auth/users/index.tsx:182
#: src/routes/login/index.tsx:163
msgid "Username"
msgstr "Username"
#: src/components/Layout/Header/index.tsx:76
#: src/components/Layout/Sidebar/index.tsx:33
msgid "Users"
msgstr "Users"
#: src/routes/login.tsx:101
#~ msgid "Welcome back"
#~ msgstr "Welcome back"
-317
View File
@@ -1,317 +0,0 @@
msgid ""
msgstr ""
"POT-Creation-Date: 2026-04-07 15:30+0800\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: zh\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
#: src/routes/_auth/users/index.tsx:464
#~ msgid "{n} rows"
#~ msgstr "{n} 行"
#: src/routes/_auth/users/index.tsx:386
msgid "{total} rows"
msgstr "共 {total} 条"
#: src/routes/_auth/dashboard/index.tsx:35
msgid "+180.1% from last month"
msgstr "较上月 +180.1%"
#: src/routes/_auth/dashboard/index.tsx:41
msgid "+19% from last month"
msgstr "较上月 +19%"
#: src/routes/_auth/dashboard/index.tsx:29
msgid "+20.1% from last month"
msgstr "较上月 +20.1%"
#: src/routes/_auth/dashboard/index.tsx:47
msgid "+201 since last hour"
msgstr "较上一小时 +201"
#: src/components/Layout/Header/index.tsx:78
msgid "403"
msgstr "403"
#: src/routes/_auth/dashboard/index.tsx:45
msgid "Active Now"
msgstr "当前在线"
#: src/routes/_auth/users/-FormModal.tsx:58
#: src/routes/_auth/users/-Toolbar.tsx:64
msgid "Admin"
msgstr "管理员"
#: src/routes/_auth/users/index.tsx:164
msgid "Are you absolutely sure?"
msgstr "确定要执行此操作吗?"
#: src/routes/login/index.tsx:185
msgid "Auto login"
msgstr "自动登录"
#: src/components/NotFound/index.tsx:54
#: src/routes/_auth/403/index.tsx:40
msgid "Back to Home"
msgstr "返回首页"
#: src/routes/_auth/users/-FormModal.tsx:32
#: src/routes/_auth/users/index.tsx:168
msgid "Cancel"
msgstr "取消"
#: src/routes/_auth/dashboard/index.tsx:134
msgid "Chart Placeholder"
msgstr "图表占位"
#: src/routes/_auth/users/index.tsx:106
msgid "Create failed"
msgstr "创建失败"
#: src/routes/_auth/users/-Toolbar.tsx:89
msgid "Create User"
msgstr "创建用户"
#: src/routes/_auth/users/index.tsx:100
msgid "Created successfully"
msgstr "创建成功"
#: src/components/Layout/Header/index.tsx:74
#: src/components/Layout/Header/index.tsx:87
#: src/components/Layout/Header/index.tsx:91
#: src/components/Layout/Sidebar/index.tsx:32
msgid "Dashboard"
msgstr "仪表盘"
#: src/routes/_auth/users/index.tsx:166
#: src/routes/_auth/users/index.tsx:257
msgid "Delete"
msgstr "删除"
#: src/routes/_auth/users/index.tsx:156
msgid "Delete failed"
msgstr "删除失败"
#: src/routes/_auth/users/index.tsx:149
msgid "Deleted successfully"
msgstr "删除成功"
#: src/routes/_auth/users/index.tsx:142
msgid "Deleting…"
msgstr "正在删除…"
#: src/components/Layout/Sidebar/index.tsx:35
msgid "Design Engineering"
msgstr "设计工程"
#: src/routes/_auth/users/index.tsx:247
msgid "Edit"
msgstr "编辑"
#: src/routes/_auth/users/-FormModal.tsx:29
msgid "Edit User"
msgstr "编辑用户"
#: src/routes/_auth/users/-FormModal.tsx:59
#: src/routes/_auth/users/-Toolbar.tsx:65
msgid "Editor"
msgstr "编辑者"
#: src/routes/_auth/users/-FormModal.tsx:63
#: src/routes/_auth/users/index.tsx:202
msgid "Email"
msgstr "邮箱"
#: src/routes/_auth/users.tsx:353
#~ msgid "Filter by role"
#~ msgstr "按角色筛选"
#: src/routes/login/index.tsx:192
msgid "Forgot password?"
msgstr "忘记密码?"
#: src/components/NotFound/index.tsx:57
msgid "Go back"
msgstr "返回上一页"
#: src/routes/login/index.tsx:68
msgid "Login failed"
msgstr "登录失败"
#: src/routes/login/index.tsx:64
msgid "Login successful"
msgstr "登录成功"
#: src/routes/_auth/users/-Toolbar.tsx:92
#: src/routes/_auth/users/-Toolbar.tsx:93
msgid "More filters"
msgstr "更多筛选"
#: src/routes/_auth/users/-FormModal.tsx:29
msgid "New User"
msgstr "新建用户"
#: src/components/DataTable/DataTableEmpty.tsx:49
msgid "No data"
msgstr "暂无数据"
#: src/components/DataTable/DataTableEmpty.tsx:59
msgid "Nothing to show in this list yet"
msgstr "当前列表为空,暂无可展示的记录"
#: src/routes/_auth/users/-FormModal.tsx:31
msgid "OK"
msgstr "确定"
#: src/routes/_auth/dashboard/index.tsx:115
msgid "Overview"
msgstr "概览"
#: src/routes/404/index.tsx:87
#~ msgid "Page not found"
#~ msgstr "页面不存在"
#: src/routes/login/index.tsx:171
msgid "Password"
msgstr "密码"
#: src/components/Layout/Sidebar/index.tsx:30
msgid "Platform"
msgstr "平台"
#: src/routes/login/index.tsx:172
msgid "Please enter password"
msgstr "请输入密码"
#: src/routes/_auth/users/-FormModal.tsx:46
#: src/routes/login/index.tsx:164
msgid "Please enter username"
msgstr "请输入用户名"
#: src/routes/_auth/users/-FormModal.tsx:53
msgid "Please select roles"
msgstr "请选择角色"
#: src/components/Layout/AppFooter/index.tsx:36
msgid "Powered by"
msgstr "由"
#: src/components/Layout/Sidebar/index.tsx:31
msgid "Projects"
msgstr "项目"
#: src/routes/_auth/dashboard/index.tsx:142
msgid "Recent Sales"
msgstr "近期销售"
#: src/routes/_auth/users/-Toolbar.tsx:58
msgid "Role"
msgstr "角色"
#: src/routes/_auth/users/-FormModal.tsx:52
#: src/routes/_auth/users/index.tsx:209
msgid "Roles"
msgstr "角色"
#: src/routes/_auth/dashboard/index.tsx:39
msgid "Sales"
msgstr "销售额"
#: src/components/Layout/Sidebar/index.tsx:36
msgid "Sales & Marketing"
msgstr "销售与市场"
#: src/routes/_auth/users/-Toolbar.tsx:43
msgid "Search User"
msgstr "搜索用户"
#: src/routes/_auth/users.tsx:341
#~ msgid "Search username or email"
#~ msgstr "搜索用户名或邮箱"
#: src/routes/login/index.tsx:204
msgid "Sign In"
msgstr "登录"
#: src/routes/login.tsx:100
#~ msgid "Sign in with your credentials."
#~ msgstr "凭用户名与密码登录"
#: src/components/Layout/Sidebar/index.tsx:194
msgid "Sign Out"
msgstr "退出登录"
#: src/components/NotFound/index.tsx:50
msgid "Sorry, the page you visited does not exist."
msgstr "抱歉,您访问的页面不存在。"
#: src/routes/_auth/403/index.tsx:37
msgid "Sorry, you don't have permission to access this page."
msgstr "抱歉,您没有权限访问此页面。"
#: src/routes/_auth/dashboard/index.tsx:33
msgid "Subscriptions"
msgstr "订阅"
#: src/components/Layout/Header/index.tsx:145
#: src/routes/login/index.tsx:225
msgid "Switch language"
msgstr "切换语言"
#: src/routes/_auth/users/index.tsx:165
msgid "This action cannot be undone. This will permanently delete the user."
msgstr "此操作无法撤销,将永久删除该用户。"
#: src/components/Layout/Header/index.tsx:133
#: src/components/Layout/Sidebar/index.tsx:263
#: src/components/Layout/Sidebar/index.tsx:311
msgid "Toggle sidebar"
msgstr "折叠侧栏"
#: src/components/Layout/Header/index.tsx:151
#: src/routes/login/index.tsx:232
msgid "Toggle Theme"
msgstr "切换主题"
#: src/routes/_auth/dashboard/index.tsx:27
msgid "Total Revenue"
msgstr "总收入"
#: src/routes/_auth/users/index.tsx:132
msgid "Update failed"
msgstr "更新失败"
#: src/routes/_auth/users/index.tsx:122
msgid "Updated successfully"
msgstr "更新成功"
#: src/routes/_auth/users/index.tsx:115
msgid "Updating…"
msgstr "正在更新…"
#: src/components/Layout/Sidebar/index.tsx:34
msgid "User List"
msgstr "用户列表"
#: src/routes/_auth/users/-FormModal.tsx:45
#: src/routes/_auth/users/index.tsx:182
#: src/routes/login/index.tsx:163
msgid "Username"
msgstr "用户名"
#: src/components/Layout/Header/index.tsx:76
#: src/components/Layout/Sidebar/index.tsx:33
msgid "Users"
msgstr "用户"
#: src/routes/login.tsx:101
#~ msgid "Welcome back"
#~ msgstr "欢迎回来"
-8
View File
@@ -2,17 +2,9 @@ import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";
import { i18n } from "@lingui/core";
import { messages as enMessages } from "./locales/en/messages.po";
import { messages as zhMessages } from "./locales/zh/messages.po";
import { useSettingsStore } from "./stores/settings";
import { useAuthStore } from "./stores/auth";
import { fetchSessionAndApplyToStore } from "./utils/session";
i18n.load("en", enMessages);
i18n.load("zh", zhMessages);
i18n.activate(useSettingsStore.getState().locale);
const router = createRouter({ routeTree });
declare module "@tanstack/react-router" {
+9 -18
View File
@@ -1,18 +1,13 @@
import { useEffect } from "react";
import { useEffect, useLayoutEffect } from "react";
import { createRootRoute, Outlet } from "@tanstack/react-router";
import { ConfigProvider, App } from "antd";
import enUS from "antd/locale/en_US";
import zhCN from "antd/locale/zh_CN";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { i18n } from "@lingui/core";
import { I18nProvider } from "@lingui/react";
import { useSettingsStore } from "@/stores/settings";
import { useAppTheme } from "@/hooks/useAppTheme";
import { NotFound } from "@/components/NotFound";
import "@/index.css";
const antdLocaleMap = { en: enUS, zh: zhCN } as const;
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: 1, refetchOnWindowFocus: false },
@@ -20,14 +15,12 @@ const queryClient = new QueryClient({
});
function RootComponent() {
const locale = useSettingsStore((s) => s.locale);
const darkMode = useSettingsStore((s) => s.darkMode);
const configProviderProps = useAppTheme();
useEffect(() => {
i18n.activate(locale);
document.documentElement.lang = locale === "zh" ? "zh" : "en";
}, [locale]);
useLayoutEffect(() => {
document.documentElement.lang = "en";
}, []);
useEffect(() => {
document.documentElement.setAttribute("data-theme", darkMode ? "dark" : "light");
@@ -35,13 +28,11 @@ function RootComponent() {
return (
<QueryClientProvider client={queryClient}>
<I18nProvider i18n={i18n}>
<ConfigProvider {...configProviderProps} locale={antdLocaleMap[locale]}>
<App>
<Outlet />
</App>
</ConfigProvider>
</I18nProvider>
<ConfigProvider {...configProviderProps} locale={enUS}>
<App>
<Outlet />
</App>
</ConfigProvider>
</QueryClientProvider>
);
}
+2 -4
View File
@@ -1,5 +1,4 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { useLingui } from "@lingui/react/macro";
import { Home, ShieldAlert } from "lucide-react";
import { Button, Flex, Result, theme } from "antd";
@@ -9,7 +8,6 @@ export const Route = createFileRoute("/_auth/403/")({
function ForbiddenPage() {
const navigate = useNavigate();
const { t } = useLingui();
const { token } = theme.useToken();
const goDashboard = () => {
@@ -29,10 +27,10 @@ function ForbiddenPage() {
<ShieldAlert size={64} strokeWidth={1.25} color={token.colorTextQuaternary} aria-hidden />
}
title="403"
subTitle={t`Sorry, you don't have permission to access this page.`}
subTitle="Sorry, you don't have permission to access this page."
extra={
<Button type="primary" icon={<Home size={16} aria-hidden />} onClick={goDashboard}>
{t`Back to Home`}
Back to Home
</Button>
}
/>
+12 -14
View File
@@ -3,7 +3,6 @@ import { useMemo } from "react";
import { createFileRoute } from "@tanstack/react-router";
import { Card, Col, Row, Typography, Avatar, theme, Flex, Skeleton } from "antd";
import { useQuery } from "@tanstack/react-query";
import { useLingui } from "@lingui/react/macro";
import { DollarSign, Users, CreditCard, Activity } from "lucide-react";
import "./index.css";
@@ -115,7 +114,6 @@ function DashboardSkeleton() {
}
function DashboardPage() {
const { t } = useLingui();
const { token } = theme.useToken();
const { isPending } = useQuery({
queryKey: ["dashboard"],
@@ -133,31 +131,31 @@ function DashboardPage() {
const stats = useMemo(
() => [
{
title: t`Total Revenue`,
title: "Total Revenue",
value: "$45,231.89",
description: t`+20.1% from last month`,
description: "+20.1% from last month",
icon: <DollarSign style={{ color: token.colorTextSecondary }} />,
},
{
title: t`Subscriptions`,
title: "Subscriptions",
value: "+2350",
description: t`+180.1% from last month`,
description: "+180.1% from last month",
icon: <Users style={{ color: token.colorTextSecondary }} />,
},
{
title: t`Sales`,
title: "Sales",
value: "+12,234",
description: t`+19% from last month`,
description: "+19% from last month",
icon: <CreditCard style={{ color: token.colorTextSecondary }} />,
},
{
title: t`Active Now`,
title: "Active Now",
value: "+573",
description: t`+201 since last hour`,
description: "+201 since last hour",
icon: <Activity style={{ color: token.colorTextSecondary }} />,
},
],
[t, token.colorTextSecondary],
[token.colorTextSecondary],
);
const recentSales = useMemo(
@@ -230,7 +228,7 @@ function DashboardPage() {
<Card
className="dash-card-interactive"
style={{ ...cardHoverStyle, height: "100%" }}
title={<Title level={5} style={{ margin: 0 }}>{t`Overview`}</Title>}
title={<Title level={5} style={{ margin: 0 }}>Overview</Title>}
>
<Flex
className="dash-chart-placeholder"
@@ -247,7 +245,7 @@ function DashboardPage() {
}}
>
<Text type="secondary" style={{ fontSize: token.fontSizeSM }}>
{t`Chart Placeholder`}
Chart Placeholder
</Text>
</Flex>
</Card>
@@ -256,7 +254,7 @@ function DashboardPage() {
<Card
className="dash-card-interactive"
style={{ ...cardHoverStyle, height: "100%" }}
title={<Title level={5} style={{ margin: 0 }}>{t`Recent Sales`}</Title>}
title={<Title level={5} style={{ margin: 0 }}>Recent Sales</Title>}
>
<Flex
vertical
@@ -1,6 +1,5 @@
import { Form, Input, Select } from "antd";
import type { FormInstance } from "antd/es/form";
import { useLingui } from "@lingui/react/macro";
import type { CreateUserRequest, User } from "@/api/schemas";
import { BaseFormModal } from "@/components/FormModal";
@@ -21,14 +20,12 @@ export function FormModal({
onCancel,
onFinish,
}: FormModalProps) {
const { t } = useLingui();
return (
<BaseFormModal<CreateUserRequest>
open={open}
title={editingUser ? t`Edit User` : t`New User`}
okText={t`OK`}
cancelText={t`Cancel`}
title={editingUser ? "Edit User" : "New User"}
okText="OK"
cancelText="Cancel"
form={form}
confirmLoading={confirmLoading}
onCancel={onCancel}
@@ -36,25 +33,25 @@ export function FormModal({
>
<Form.Item
name="username"
label={t`Username`}
rules={[{ required: true, message: t`Please enter username` }]}
label="Username"
rules={[{ required: true, message: "Please enter username" }]}
>
<Input />
</Form.Item>
<Form.Item
name="roles"
label={t`Roles`}
rules={[{ required: true, message: t`Please select roles` }]}
label="Roles"
rules={[{ required: true, message: "Please select roles" }]}
>
<Select
mode="multiple"
options={[
{ label: t`Admin`, value: "admin" },
{ label: t`Editor`, value: "editor" },
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
]}
/>
</Form.Item>
<Form.Item name="email" label={t`Email`}>
<Form.Item name="email" label="Email">
<Input />
</Form.Item>
</BaseFormModal>
+8 -19
View File
@@ -1,5 +1,4 @@
import { Button, Input, Select, theme } from "antd";
import { useLingui } from "@lingui/react/macro";
import { Plus, UserRound } from "lucide-react";
import { forwardRef, useMemo } from "react";
import { FilterToolbar } from "@/components/FilterToolbar";
@@ -29,7 +28,6 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
},
ref,
) {
const { t } = useLingui();
const { token } = theme.useToken();
const slots = useMemo(
@@ -40,7 +38,7 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
children: (
<Input.Search
allowClear
placeholder={t`Search User`}
placeholder="Search User"
style={{ width: FILTER_CONTROL_WIDTH }}
value={keywordInput}
onChange={(e) => onKeywordChange(e.target.value)}
@@ -55,29 +53,20 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
children: (
<Select
allowClear
placeholder={t`Role`}
placeholder="Role"
style={{ width: FILTER_CONTROL_WIDTH }}
prefix={<UserRound size={token.fontSize} />}
value={roleValue}
onChange={(v) => onRoleChange(v ?? "")}
options={[
{ label: t`Admin`, value: "admin" },
{ label: t`Editor`, value: "editor" },
{ label: "Admin", value: "admin" },
{ label: "Editor", value: "editor" },
]}
/>
),
},
],
[
keywordInput,
onClearSearch,
onKeywordChange,
onRoleChange,
onSearch,
roleValue,
t,
token.fontSize,
],
[keywordInput, onClearSearch, onKeywordChange, onRoleChange, onSearch, roleValue, token.fontSize],
);
return (
@@ -86,11 +75,11 @@ export const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>(function Toolbar
slots={slots}
actions={
<Button type="primary" icon={<Plus size={token.fontSize} />} onClick={onCreateClick}>
{t`Create User`}
Create User
</Button>
}
moreFiltersLabel={t`More filters`}
moreFiltersTitle={t`More filters`}
moreFiltersLabel="More filters"
moreFiltersTitle="More filters"
/>
);
});
+19 -21
View File
@@ -1,7 +1,6 @@
import { createFileRoute, useNavigate } from "@tanstack/react-router";
import { Avatar, Button, Space, Form, App, Dropdown, theme, Tag, Flex } from "antd";
import type { TablePaginationConfig } from "antd/es/table/interface";
import { useLingui } from "@lingui/react/macro";
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { httpClient } from "@/utils/http";
import { USER_ENDPOINTS } from "@/api/user";
@@ -38,7 +37,6 @@ function UsersPage() {
const search = Route.useSearch();
const navigate = useNavigate({ from: Route.fullPath });
const { message, modal } = App.useApp();
const { t } = useLingui();
const { token } = theme.useToken();
const [modalOpen, setModalOpen] = useState(false);
const pageShellRef = useRef<HTMLDivElement>(null);
@@ -101,7 +99,7 @@ function UsersPage() {
createLifecycle: {
onSuccess: () => {
message.success({
content: t`Created successfully`,
content: "Created successfully",
key: MESSAGE_KEY_USER_CREATE,
});
setModalOpen(false);
@@ -109,7 +107,7 @@ function UsersPage() {
},
onError: () => {
message.error({
content: t`Create failed`,
content: "Create failed",
key: MESSAGE_KEY_USER_CREATE,
});
},
@@ -117,14 +115,14 @@ function UsersPage() {
updateLifecycle: {
onMutate: () => {
message.loading({
content: t`Updating…`,
content: "Updating…",
key: MESSAGE_KEY_USER_UPDATE,
duration: 0,
});
},
onSuccess: () => {
message.success({
content: t`Updated successfully`,
content: "Updated successfully",
key: MESSAGE_KEY_USER_UPDATE,
});
setModalOpen(false);
@@ -133,7 +131,7 @@ function UsersPage() {
},
onError: () => {
message.error({
content: t`Update failed`,
content: "Update failed",
key: MESSAGE_KEY_USER_UPDATE,
});
},
@@ -141,20 +139,20 @@ function UsersPage() {
deleteLifecycle: {
onMutate: () => {
message.loading({
content: t`Deleting…`,
content: "Deleting…",
key: MESSAGE_KEY_USER_DELETE,
duration: 0,
});
},
onSuccess: () => {
message.success({
content: t`Deleted successfully`,
content: "Deleted successfully",
key: MESSAGE_KEY_USER_DELETE,
});
},
onError: () => {
message.error({
content: t`Delete failed`,
content: "Delete failed",
key: MESSAGE_KEY_USER_DELETE,
});
},
@@ -163,11 +161,11 @@ function UsersPage() {
const confirmDelete = (record: User) => {
modal.confirm({
title: t`Are you absolutely sure?`,
content: t`This action cannot be undone. This will permanently delete the user.`,
okText: t`Delete`,
title: "Are you absolutely sure?",
content: "This action cannot be undone. This will permanently delete the user.",
okText: "Delete",
okType: "danger",
cancelText: t`Cancel`,
cancelText: "Cancel",
onOk: () => deleteMutation.mutate(record.id),
});
};
@@ -181,7 +179,7 @@ function UsersPage() {
sortOrder: search.sortField === "id" ? search.sortOrder : null,
},
{
title: t`Username`,
title: "Username",
dataIndex: "username",
key: "username",
sorter: true,
@@ -207,14 +205,14 @@ function UsersPage() {
},
},
{
title: t`Email`,
title: "Email",
dataIndex: "email",
key: "email",
sorter: true,
sortOrder: search.sortField === "email" ? search.sortOrder : null,
},
{
title: t`Roles`,
title: "Roles",
dataIndex: "roles",
key: "roles",
sorter: true,
@@ -252,7 +250,7 @@ function UsersPage() {
{
key: "edit",
icon: <Pencil size={token.fontSize} />,
label: t`Edit`,
label: "Edit",
onClick: () => {
setEditingUser(record);
form.setFieldsValue(record);
@@ -262,7 +260,7 @@ function UsersPage() {
{
key: "delete",
icon: <Trash2 size={token.fontSize} />,
label: t`Delete`,
label: "Delete",
danger: true,
onClick: () => confirmDelete(record),
},
@@ -391,7 +389,7 @@ function UsersPage() {
current: currentPage,
pageSize: search.limit,
showSizeChanger: true,
showTotal: (total) => t`${total} rows`,
showTotal: (total) => `${total} rows`,
onChange: (page, pageSize) => {
void navigate({
search: {
@@ -403,7 +401,7 @@ function UsersPage() {
},
}
: false,
[showPagination, data?.total, currentPage, search, navigate, t],
[showPagination, data?.total, currentPage, search, navigate],
);
return (
+12 -28
View File
@@ -2,7 +2,6 @@ import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
import { Form, Input, Button, Card, App, theme, Typography, Flex, Checkbox, Space } from "antd";
import type { CSSProperties } from "react";
import { useMutation } from "@tanstack/react-query";
import { useLingui } from "@lingui/react/macro";
import { httpClient } from "@/utils/http";
import { useAuthStore } from "@/stores/auth";
import { useSettingsStore } from "@/stores/settings";
@@ -11,7 +10,6 @@ import { LoginRequestSchema, AuthTokensSchema } from "@/api/schemas";
import { fetchSessionAndApplyToStore } from "@/utils/session";
import type { LoginRequest } from "@/api/schemas";
import { APP_BRAND_NAME, APP_FAVICON_SRC } from "@/utils/constants";
import { Languages } from "lucide-react";
import { Theme } from "@/components/Icon";
import { AppFooter } from "@/components/Layout/AppFooter";
import { Aurora } from "@/components/Aurora";
@@ -30,10 +28,7 @@ export const Route = createFileRoute("/login/")({
function LoginPage() {
const navigate = useNavigate();
const { message } = App.useApp();
const { t } = useLingui();
const setTokens = useAuthStore((s) => s.setTokens);
const locale = useSettingsStore((s) => s.locale);
const setLocale = useSettingsStore((s) => s.setLocale);
const toggleDarkMode = useSettingsStore((s) => s.toggleDarkMode);
const darkMode = useSettingsStore((s) => s.darkMode);
const { token } = theme.useToken();
@@ -47,18 +42,14 @@ function LoginPage() {
await fetchSessionAndApplyToStore();
},
onSuccess: () => {
message.success(t`Login successful`);
message.success("Login successful");
void navigate({ to: "/dashboard" });
},
onError: (err) => {
message.error(err instanceof Error ? err.message : t`Login failed`);
message.error(err instanceof Error ? err.message : "Login failed");
},
});
const toggleLocale = () => {
setLocale(locale === "en" ? "zh" : "en");
};
const shellStyle: CSSProperties = {
position: "relative",
isolation: "isolate",
@@ -146,12 +137,12 @@ function LoginPage() {
>
<Form.Item
name="username"
label={<span style={{ fontWeight: 500 }}>{t`Username`}</span>}
rules={[{ required: true, message: t`Please enter username` }]}
label={<span style={{ fontWeight: 500 }}>Username</span>}
rules={[{ required: true, message: "Please enter username" }]}
>
<Input
id="login-username"
aria-label={t`Username`}
aria-label="Username"
placeholder="admin"
size="large"
/>
@@ -159,13 +150,13 @@ function LoginPage() {
<Form.Item
name="password"
label={<span style={{ fontWeight: 500 }}>{t`Password`}</span>}
rules={[{ required: true, message: t`Please enter password` }]}
label={<span style={{ fontWeight: 500 }}>Password</span>}
rules={[{ required: true, message: "Please enter password" }]}
style={{ marginBottom: token.marginLG }}
>
<Input.Password
id="login-password"
aria-label={t`Password`}
aria-label="Password"
placeholder="admin"
size="large"
/>
@@ -178,14 +169,14 @@ function LoginPage() {
wrap="wrap"
>
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>{t`Auto login`}</Checkbox>
<Checkbox>Auto login</Checkbox>
</Form.Item>
<Typography.Link
href="#"
onClick={(e) => e.preventDefault()}
style={{ fontSize: token.fontSizeSM }}
>
{t`Forgot password?`}
Forgot password?
</Typography.Link>
</Flex>
@@ -197,7 +188,7 @@ function LoginPage() {
block
size="large"
>
{t`Sign In`}
Sign In
</Button>
</Form.Item>
</Form>
@@ -213,19 +204,12 @@ function LoginPage() {
>
<Flex justify="center" style={{ marginBottom: token.marginSM }}>
<Space>
<Button
type="text"
size="small"
onClick={toggleLocale}
icon={<Languages size={token.size} />}
aria-label={t`Switch language`}
/>
<Button
type="text"
size="small"
onClick={toggleDarkMode}
icon={<Theme size={token.size} />}
aria-label={t`Toggle Theme`}
aria-label="Toggle Theme"
/>
</Space>
</Flex>
+1 -8
View File
@@ -1,7 +1,5 @@
import { createPersistentStore } from "./createPersistentStore";
export type Locale = "en" | "zh";
function getInitialDarkMode() {
if (typeof window === "undefined") {
return false;
@@ -12,29 +10,24 @@ function getInitialDarkMode() {
interface SettingsState {
darkMode: boolean;
sidebarCollapsed: boolean;
locale: Locale;
toggleDarkMode: () => void;
toggleSidebar: () => void;
setSidebarCollapsed: (collapsed: boolean) => void;
setLocale: (locale: Locale) => void;
}
export const useSettingsStore = createPersistentStore<SettingsState>(
(set) => ({
darkMode: getInitialDarkMode(),
sidebarCollapsed: false,
locale: "en",
toggleDarkMode: () => set((s) => ({ darkMode: !s.darkMode })),
toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })),
setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
setLocale: (locale) => set({ locale }),
}),
{
name: "settings-storage",
name: "settings-storage-basic",
partialize: (state) => ({
darkMode: state.darkMode,
sidebarCollapsed: state.sidebarCollapsed,
locale: state.locale,
}),
},
);