feat(ui): antd i18n
This commit is contained in:
49
ui/src/App.tsx
Normal file
49
ui/src/App.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { App as AntdApp, ConfigProvider as AntdConfigProvider } from "antd";
|
||||
import { type Locale } from "antd/es/locale";
|
||||
import AntdLocaleEnUs from "antd/locale/en_US";
|
||||
import AntdLocaleZhCN from "antd/locale/zh_CN";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/zh-cn";
|
||||
|
||||
import { localeNames } from "./i18n";
|
||||
import { router } from "./router.tsx";
|
||||
import { ThemeProvider } from "./components/ThemeProvider.tsx";
|
||||
|
||||
const App = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const antdLocalesMap: Record<string, Locale> = {
|
||||
[localeNames.ZH]: AntdLocaleZhCN,
|
||||
[localeNames.EN]: AntdLocaleEnUs,
|
||||
};
|
||||
const [antdLocale, setAntdLocale] = useState(antdLocalesMap[i18n.language]);
|
||||
|
||||
const handleLanguageChanged = () => {
|
||||
setAntdLocale(antdLocalesMap[i18n.language]);
|
||||
dayjs.locale(i18n.language);
|
||||
};
|
||||
i18n.on("languageChanged", handleLanguageChanged);
|
||||
useLayoutEffect(handleLanguageChanged, [i18n]);
|
||||
|
||||
return (
|
||||
<AntdConfigProvider
|
||||
locale={antdLocale}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: "hsl(24.6 95% 53.1%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AntdApp>
|
||||
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
</AntdApp>
|
||||
</AntdConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Languages } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
|
||||
export default function LocaleToggle() {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="icon">
|
||||
<Languages className="h-[1.2rem] w-[1.2rem] dark:text-white" />
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{Object.keys(i18n.store.data).map((key) => (
|
||||
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>{i18n.store.data[key].name as string}</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -2,14 +2,14 @@ import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import resources from "./locales";
|
||||
import resources, { LOCALE_ZH_NAME, LOCALE_EN_NAME } from "./locales";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "zh",
|
||||
fallbackLng: LOCALE_ZH_NAME,
|
||||
debug: true,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
@@ -19,4 +19,9 @@ i18n
|
||||
},
|
||||
});
|
||||
|
||||
export const localeNames = {
|
||||
ZH: LOCALE_ZH_NAME,
|
||||
EN: LOCALE_EN_NAME,
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -3,12 +3,15 @@ import { Resource } from "i18next";
|
||||
import zh from "./zh";
|
||||
import en from "./en";
|
||||
|
||||
export const LOCALE_ZH_NAME = "zh" as const;
|
||||
export const LOCALE_EN_NAME = "en" as const;
|
||||
|
||||
const resources: Resource = {
|
||||
zh: {
|
||||
[LOCALE_ZH_NAME]: {
|
||||
name: "简体中文",
|
||||
translation: zh,
|
||||
},
|
||||
en: {
|
||||
[LOCALE_EN_NAME]: {
|
||||
name: "English",
|
||||
translation: en,
|
||||
},
|
||||
|
||||
@@ -1,31 +1,17 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { App, ConfigProvider } from "antd";
|
||||
import AntdLocaleZhCN from "antd/locale/zh_CN";
|
||||
import dayjs from "dayjs";
|
||||
import dayjsUtc from "dayjs/plugin/utc";
|
||||
import "dayjs/locale/zh-cn";
|
||||
|
||||
import { router } from "./router.tsx";
|
||||
import { ThemeProvider } from "./components/ThemeProvider.tsx";
|
||||
import App from "./App";
|
||||
import "./i18n";
|
||||
import "./global.css";
|
||||
|
||||
// TODO: antd i18n
|
||||
dayjs.extend(dayjsUtc);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App>
|
||||
<ConfigProvider
|
||||
locale={AntdLocaleZhCN}
|
||||
theme={{
|
||||
token: {
|
||||
colorPrimary: "hsl(24.6 95% 53.1%)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
||||
<RouterProvider router={router} />
|
||||
</ThemeProvider>
|
||||
</ConfigProvider>
|
||||
</App>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Avatar, Button, Empty, Modal, notification, Space, Table, Tooltip, Typography, type TableProps } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { Copy as CopyIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react";
|
||||
import moment from "moment";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import AccessEditDialog from "@/components/certimate/AccessEditDialog";
|
||||
@@ -50,7 +50,7 @@ const AccessList = () => {
|
||||
title: t("common.text.created_at"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return moment(record.created!).format("YYYY-MM-DD HH:mm:ss");
|
||||
return dayjs(record.created!).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -58,7 +58,7 @@ const AccessList = () => {
|
||||
title: t("common.text.updated_at"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return moment(record.updated!).format("YYYY-MM-DD HH:mm:ss");
|
||||
return dayjs(record.updated!).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { Button, Divider, Empty, Menu, notification, Radio, Space, Table, theme, Tooltip, Typography, type MenuProps, type TableProps } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { Eye as EyeIcon, Filter as FilterIcon } from "lucide-react";
|
||||
import moment from "moment";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import CertificateDetailDrawer from "@/components/certificate/CertificateDetailDrawer";
|
||||
@@ -87,8 +87,8 @@ const CertificateList = () => {
|
||||
},
|
||||
filterIcon: () => <FilterIcon size={14} />,
|
||||
render: (_, record) => {
|
||||
const total = moment(record.expireAt).diff(moment(record.created), "d") + 1;
|
||||
const left = moment(record.expireAt).diff(moment(), "d");
|
||||
const total = dayjs(record.expireAt).diff(dayjs(record.created), "d") + 1;
|
||||
const left = dayjs(record.expireAt).diff(dayjs(), "d");
|
||||
return (
|
||||
<Space className="max-w-full" direction="vertical" size={4}>
|
||||
{left > 0 ? (
|
||||
@@ -98,7 +98,7 @@ const CertificateList = () => {
|
||||
)}
|
||||
|
||||
<Typography.Text type="secondary">
|
||||
{t("certificate.props.expiry.expiration", { date: moment(record.expireAt).format("YYYY-MM-DD") })}
|
||||
{t("certificate.props.expiry.expiration", { date: dayjs(record.expireAt).format("YYYY-MM-DD") })}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
@@ -132,7 +132,7 @@ const CertificateList = () => {
|
||||
title: t("common.text.created_at"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return moment(record.created!).format("YYYY-MM-DD HH:mm:ss");
|
||||
return dayjs(record.created!).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -140,7 +140,7 @@ const CertificateList = () => {
|
||||
title: t("common.text.updated_at"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return moment(record.updated!).format("YYYY-MM-DD HH:mm:ss");
|
||||
return dayjs(record.updated!).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
} from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-components";
|
||||
import { Filter as FilterIcon, Pencil as PencilIcon, Plus as PlusIcon, Trash2 as Trash2Icon } from "lucide-react";
|
||||
import moment from "moment";
|
||||
import dayjs from "dayjs";
|
||||
import { ClientResponseError } from "pocketbase";
|
||||
|
||||
import { Workflow as WorkflowType } from "@/domain/workflow";
|
||||
@@ -153,7 +153,7 @@ const WorkflowList = () => {
|
||||
title: t("common.text.created_at"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return moment(record.created!).format("YYYY-MM-DD HH:mm:ss");
|
||||
return dayjs(record.created!).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -161,7 +161,7 @@ const WorkflowList = () => {
|
||||
title: t("common.text.updated_at"),
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
return moment(record.updated!).format("YYYY-MM-DD HH:mm:ss");
|
||||
return dayjs(record.updated!).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import moment from "moment";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import { type Access } from "@/domain/access";
|
||||
import { getPocketBase } from "./pocketbase";
|
||||
@@ -19,6 +19,6 @@ export const save = async (record: Access) => {
|
||||
};
|
||||
|
||||
export const remove = async (record: Access) => {
|
||||
record.deleted = moment.utc().format("YYYY-MM-DD HH:mm:ss");
|
||||
record.deleted = dayjs.utc().format("YYYY-MM-DD HH:mm:ss");
|
||||
return await getPocketBase().collection("access").update(record.id, record);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import dayjs from "dayjs";
|
||||
import { type RecordListOptions } from "pocketbase";
|
||||
import moment from "moment";
|
||||
|
||||
import { type Certificate } from "@/domain/certificate";
|
||||
import { getPocketBase } from "./pocketbase";
|
||||
@@ -23,7 +23,7 @@ export const list = async (req: CertificateListReq) => {
|
||||
|
||||
if (req.state === "expireSoon") {
|
||||
options.filter = pb.filter("expireAt<{:expiredAt}", {
|
||||
expiredAt: moment().add(15, "d").toDate(),
|
||||
expiredAt: dayjs().add(15, "d").toDate(),
|
||||
});
|
||||
} else if (req.state === "expired") {
|
||||
options.filter = pb.filter("expireAt<={:expiredAt}", {
|
||||
|
||||
Reference in New Issue
Block a user