@4399ywkf/cli 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/templates/AntdStaticMethods/index.tsx +20 -0
  2. package/dist/templates/AppTheme.tsx +136 -0
  3. package/dist/templates/DIRECTORY_STRUCTURE.md +141 -0
  4. package/dist/templates/GlobalProvider/AppTheme.tsx +136 -0
  5. package/dist/templates/GlobalProvider/Locale.tsx +84 -0
  6. package/dist/templates/GlobalProvider/Query.tsx +12 -0
  7. package/dist/templates/GlobalProvider/StyleRegistry.tsx +9 -0
  8. package/dist/templates/GlobalProvider/index.tsx +23 -0
  9. package/dist/templates/Locale.tsx +55 -56
  10. package/dist/templates/Query.tsx +12 -0
  11. package/dist/templates/StyleRegistry.tsx +9 -0
  12. package/dist/templates/analyzeUnusedKeys.ts +506 -0
  13. package/dist/templates/app/.i18nrc.js +57 -0
  14. package/dist/templates/app/docs/DIRECTORY_STRUCTURE.md +141 -0
  15. package/dist/templates/app/docs/glossary.md +11 -0
  16. package/dist/templates/app/package.json.tpl +7 -15
  17. package/dist/templates/app/scripts/i18nWorkflow/analyzeUnusedKeys.ts +506 -0
  18. package/dist/templates/app/scripts/i18nWorkflow/cleanUnusedKeys.ts +344 -0
  19. package/dist/templates/app/scripts/i18nWorkflow/const.ts +18 -0
  20. package/dist/templates/app/scripts/i18nWorkflow/flattenLocaleKeys.ts +139 -0
  21. package/dist/templates/app/scripts/i18nWorkflow/genDefaultLocale.ts +19 -0
  22. package/dist/templates/app/scripts/i18nWorkflow/genDiff.ts +49 -0
  23. package/dist/templates/app/scripts/i18nWorkflow/i18nConfig.ts +7 -0
  24. package/dist/templates/app/scripts/i18nWorkflow/index.ts +11 -0
  25. package/dist/templates/app/scripts/i18nWorkflow/protectedPatterns.ts +91 -0
  26. package/dist/templates/app/scripts/i18nWorkflow/utils.ts +76 -0
  27. package/dist/templates/app/src/components/AntdStaticMethods/index.tsx +20 -0
  28. package/dist/templates/app/src/index.tsx +0 -4
  29. package/dist/templates/app/src/layout/GlobalProvider/AppTheme.tsx +136 -0
  30. package/dist/templates/app/src/layout/GlobalProvider/Locale.tsx +84 -0
  31. package/dist/templates/app/src/layout/GlobalProvider/Query.tsx +12 -0
  32. package/dist/templates/app/src/layout/GlobalProvider/StyleRegistry.tsx +9 -0
  33. package/dist/templates/app/src/layout/GlobalProvider/index.tsx +23 -0
  34. package/dist/templates/app/src/locales/utils.ts +23 -0
  35. package/dist/templates/app/src/pages/base/index.tsx +170 -79
  36. package/dist/templates/app/src/routes.tsx +2 -2
  37. package/dist/templates/app/tsconfig.json +19 -3
  38. package/dist/templates/base/index.tsx +170 -79
  39. package/dist/templates/cleanUnusedKeys.ts +344 -0
  40. package/dist/templates/components/AntdStaticMethods/index.tsx +20 -0
  41. package/dist/templates/const.ts +18 -0
  42. package/dist/templates/docs/DIRECTORY_STRUCTURE.md +141 -0
  43. package/dist/templates/docs/glossary.md +11 -0
  44. package/dist/templates/flattenLocaleKeys.ts +139 -0
  45. package/dist/templates/genDefaultLocale.ts +19 -0
  46. package/dist/templates/genDiff.ts +49 -0
  47. package/dist/templates/glossary.md +11 -0
  48. package/dist/templates/i18nConfig.ts +7 -0
  49. package/dist/templates/i18nWorkflow/analyzeUnusedKeys.ts +506 -0
  50. package/dist/templates/i18nWorkflow/cleanUnusedKeys.ts +344 -0
  51. package/dist/templates/i18nWorkflow/const.ts +18 -0
  52. package/dist/templates/i18nWorkflow/flattenLocaleKeys.ts +139 -0
  53. package/dist/templates/i18nWorkflow/genDefaultLocale.ts +19 -0
  54. package/dist/templates/i18nWorkflow/genDiff.ts +49 -0
  55. package/dist/templates/i18nWorkflow/i18nConfig.ts +7 -0
  56. package/dist/templates/i18nWorkflow/index.ts +11 -0
  57. package/dist/templates/i18nWorkflow/protectedPatterns.ts +91 -0
  58. package/dist/templates/i18nWorkflow/utils.ts +76 -0
  59. package/dist/templates/index.tsx +170 -79
  60. package/dist/templates/layout/GlobalProvider/AppTheme.tsx +136 -0
  61. package/dist/templates/layout/GlobalProvider/Locale.tsx +84 -0
  62. package/dist/templates/layout/GlobalProvider/Query.tsx +12 -0
  63. package/dist/templates/layout/GlobalProvider/StyleRegistry.tsx +9 -0
  64. package/dist/templates/layout/GlobalProvider/index.tsx +23 -0
  65. package/dist/templates/locales/utils.ts +23 -0
  66. package/dist/templates/package.json.tpl +7 -15
  67. package/dist/templates/pages/base/index.tsx +170 -79
  68. package/dist/templates/protectedPatterns.ts +91 -0
  69. package/dist/templates/routes.tsx +2 -2
  70. package/dist/templates/scripts/i18nWorkflow/analyzeUnusedKeys.ts +506 -0
  71. package/dist/templates/scripts/i18nWorkflow/cleanUnusedKeys.ts +344 -0
  72. package/dist/templates/scripts/i18nWorkflow/const.ts +18 -0
  73. package/dist/templates/scripts/i18nWorkflow/flattenLocaleKeys.ts +139 -0
  74. package/dist/templates/scripts/i18nWorkflow/genDefaultLocale.ts +19 -0
  75. package/dist/templates/scripts/i18nWorkflow/genDiff.ts +49 -0
  76. package/dist/templates/scripts/i18nWorkflow/i18nConfig.ts +7 -0
  77. package/dist/templates/scripts/i18nWorkflow/index.ts +11 -0
  78. package/dist/templates/scripts/i18nWorkflow/protectedPatterns.ts +91 -0
  79. package/dist/templates/scripts/i18nWorkflow/utils.ts +76 -0
  80. package/dist/templates/src/components/AntdStaticMethods/index.tsx +20 -0
  81. package/dist/templates/src/index.tsx +0 -4
  82. package/dist/templates/src/layout/GlobalProvider/AppTheme.tsx +136 -0
  83. package/dist/templates/src/layout/GlobalProvider/Locale.tsx +84 -0
  84. package/dist/templates/src/layout/GlobalProvider/Query.tsx +12 -0
  85. package/dist/templates/src/layout/GlobalProvider/StyleRegistry.tsx +9 -0
  86. package/dist/templates/src/layout/GlobalProvider/index.tsx +23 -0
  87. package/dist/templates/src/locales/utils.ts +23 -0
  88. package/dist/templates/src/pages/base/index.tsx +170 -79
  89. package/dist/templates/src/routes.tsx +2 -2
  90. package/dist/templates/tsconfig.json +19 -3
  91. package/dist/templates/type.ts +23 -24
  92. package/dist/templates/utils.ts +23 -0
  93. package/package.json +19 -21
@@ -0,0 +1,76 @@
1
+ import { consola } from "consola";
2
+ import { colors } from "consola/utils";
3
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
+ import { resolve, dirname } from "node:path";
5
+ import prettier from "@prettier/sync";
6
+ import i18nConfig from "./i18nConfig";
7
+
8
+ let prettierOptions = prettier.resolveConfig(
9
+ resolve(__dirname, "../../.prettierrc.js"),
10
+ );
11
+
12
+ export const readJSON = (filePath: string) => {
13
+ const data = readFileSync(filePath, "utf8");
14
+ return JSON.parse(data);
15
+ };
16
+
17
+ export const writeJSON = (filePath: string, data: any) => {
18
+ const jsonStr = JSON.stringify(data, null, 2);
19
+ writeFileSync(filePath, jsonStr, "utf8");
20
+ };
21
+
22
+ export const writeJSONWithPrettier = (filePath: string, data: any) => {
23
+ mkdirSync(dirname(filePath), { recursive: true });
24
+ const jsonStr = JSON.stringify(data, null, 2);
25
+ const formatted = prettier.format(jsonStr, {
26
+ ...prettierOptions,
27
+ parser: "json",
28
+ });
29
+ writeFileSync(filePath, formatted, "utf8");
30
+ };
31
+
32
+ export const genResourcesContent = (locales: string[]) => {
33
+ let index = "";
34
+ let indexObj = "";
35
+
36
+ for (const locale of locales) {
37
+ index += `import ${locale} from "./${locale}";\n`;
38
+ indexObj += ` "${locale.replace("_", "-")}": ${locale},\n`;
39
+ }
40
+
41
+ return `${index}
42
+ const resources = {
43
+ ${indexObj}} as const;
44
+ export default resources;
45
+ export const defaultResources = ${i18nConfig.entryLocale};
46
+ export type Resources = typeof resources;
47
+ export type DefaultResources = typeof defaultResources;
48
+ export type Namespaces = keyof DefaultResources;
49
+ export type Locales = keyof Resources;
50
+ `;
51
+ };
52
+
53
+ export const genNamespaceList = (files: string[], locale: string) => {
54
+ return files.map((file) => ({
55
+ name: file.replace(".json", ""),
56
+ path: resolve(i18nConfig.output, locale, file),
57
+ }));
58
+ };
59
+
60
+ export const tagBlue = (text: string) =>
61
+ colors.bgBlueBright(colors.black(` ${text} `));
62
+ export const tagYellow = (text: string) =>
63
+ colors.bgYellowBright(colors.black(` ${text} `));
64
+ export const tagGreen = (text: string) =>
65
+ colors.bgGreenBright(colors.black(` ${text} `));
66
+ export const tagWhite = (text: string) =>
67
+ colors.bgWhiteBright(colors.black(` ${text} `));
68
+
69
+ export const split = (name: string) => {
70
+ consola.log("");
71
+ consola.log(
72
+ colors.gray(
73
+ `========================== ${name} ==============================`,
74
+ ),
75
+ );
76
+ };
@@ -11,14 +11,117 @@ import {
11
11
  import React from "react";
12
12
  import { createStyles } from "antd-style";
13
13
 
14
- const { Title, Paragraph, Text, Link } = Typography;
14
+ const { Title, Paragraph, Text } = Typography;
15
15
 
16
- const useStyles = createStyles(({ css, token }) => ({
17
- app: css`
16
+ const px = (value: number | string) =>
17
+ typeof value === "number" ? `${value}px` : value;
18
+
19
+ const useStyles = createStyles(({ token, css }) => ({
20
+ root: css`
18
21
  width: 100%;
19
- height: 100%;
22
+ min-height: 100vh;
23
+ padding: ${px(token.paddingLG)};
24
+ background: linear-gradient(180deg, ${token.colorFillTertiary}, ${token.colorPrimaryHover});
20
25
  overflow: auto;
21
26
  `,
27
+ container: css`
28
+ margin: 0 auto;
29
+ max-width: 960px;
30
+ `,
31
+ hero: css`
32
+ text-align: center;
33
+ margin-bottom: ${px(token.marginLG)};
34
+ `,
35
+ heroIcon: css`
36
+ font-size: 3.5rem;
37
+ color: ${token.colorPrimary};
38
+ animation: bounce 1.2s infinite alternate;
39
+
40
+ @keyframes bounce {
41
+ from {
42
+ transform: translateY(0);
43
+ }
44
+ to {
45
+ transform: translateY(-6px);
46
+ }
47
+ }
48
+ `,
49
+ tagSpace: css`
50
+ margin-top: ${px(token.marginMD)};
51
+ `,
52
+ featuresGrid: css`
53
+ display: grid;
54
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
55
+ gap: ${px(token.marginSM)};
56
+ margin-bottom: ${px(token.marginLG)};
57
+ `,
58
+ featureCard: css`
59
+ text-align: center;
60
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
61
+
62
+ &:hover {
63
+ transform: translateY(-6px);
64
+ box-shadow: ${token.boxShadowSecondary};
65
+ }
66
+ `,
67
+ quickLinkGrid: css`
68
+ display: grid;
69
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
70
+ gap: ${px(token.marginSM)};
71
+ `,
72
+ quickLink: css`
73
+ padding: ${px(token.paddingMD)};
74
+ border-radius: ${px(token.borderRadiusLG)};
75
+ background: ${token.colorBgContainer};
76
+ cursor: pointer;
77
+ transition: background 0.2s ease;
78
+
79
+ &:hover {
80
+ background: ${token.colorBgElevated};
81
+ }
82
+ `,
83
+ footerActions: css`
84
+ margin-top: ${px(token.marginMD)};
85
+ text-align: center;
86
+ `,
87
+ cardTitle: css`
88
+ display: inline-flex;
89
+ align-items: center;
90
+ gap: ${px(token.marginXS)};
91
+ font-size: ${px(token.fontSizeHeading4)};
92
+ `,
93
+ codeBlock: css`
94
+ margin-top: ${px(token.marginXS)};
95
+ padding: ${px(token.paddingSM)};
96
+ background: ${token.colorFillAlter};
97
+ border-radius: ${px(token.borderRadiusLG)};
98
+ font-family: ${token.fontFamilyCode};
99
+ font-size: ${px(token.fontSizeSM)};
100
+ `,
101
+ stepLabel: css`
102
+ font-size: ${px(token.fontSizeBase)};
103
+ `,
104
+ quickLinkLabel: css`
105
+ font-size: ${px(token.fontSizeHeading2)};
106
+ margin-bottom: ${px(token.marginXS)};
107
+ `,
108
+ quickLinkDescription: css`
109
+ color: ${token.colorTextTertiary};
110
+ `,
111
+ panelCard: css`
112
+ margin-bottom: ${px(token.marginLG)};
113
+ background: ${token.colorBgElevated};
114
+ `,
115
+ featureIcon: css`
116
+ margin-bottom: ${px(token.marginMD)};
117
+ `,
118
+ footerParagraph: css`
119
+ margin-top: ${px(token.marginMD)};
120
+ color: ${token.colorTextTertiary};
121
+ `,
122
+ textCenterCard: css`
123
+ text-align: center;
124
+ `,
22
125
  }));
23
126
 
24
127
  export default function BasePage() {
@@ -28,17 +131,17 @@ export default function BasePage() {
28
131
 
29
132
  const features = [
30
133
  {
31
- icon: <ThunderboltOutlined className="text-4xl text-blue-500" />,
134
+ icon: <ThunderboltOutlined className={styles.heroIcon} />,
32
135
  title: "Rspack 构建",
33
136
  description: "基于 Rspack 的极速构建体验,开发效率翻倍",
34
137
  },
35
138
  {
36
- icon: <BulbOutlined className="text-4xl text-yellow-500" />,
139
+ icon: <BulbOutlined className={styles.heroIcon} />,
37
140
  title: "React 19",
38
141
  description: "使用最新的 React 19 特性,享受并发渲染",
39
142
  },
40
143
  {
41
- icon: <SettingOutlined className="text-4xl text-green-500" />,
144
+ icon: <SettingOutlined className={styles.heroIcon} />,
42
145
  title: "TypeScript",
43
146
  description: "完整的 TypeScript 支持,类型安全有保障",
44
147
  },
@@ -52,107 +155,94 @@ export default function BasePage() {
52
155
  ];
53
156
 
54
157
  return (
55
- <div
56
- className={cx(
57
- styles.app,
58
- "min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-gray-900 dark:to-gray-800 p-8"
59
- )}
60
- >
61
- <div className="max-w-6xl mx-auto">
158
+ <div className={cx(styles.root)}>
159
+ <div className={styles.container}>
62
160
  <Flex vertical gap={12}>
63
- {/* 头部欢迎区域 */}
64
- <div className="text-center mb-12 animate-fade-in">
65
- <div className="inline-block mb-4">
66
- <RocketOutlined className="text-6xl text-blue-500 animate-bounce" />
161
+ <section className={styles.hero}>
162
+ <div>
163
+ <RocketOutlined className={styles.heroIcon} />
67
164
  </div>
68
- <Title level={1} className="!mb-4">
165
+ <Title level={1} style={{ marginBottom: 16 }}>
69
166
  🎉 恭喜!项目创建成功
70
167
  </Title>
71
- <Paragraph className="text-lg text-gray-600 dark:text-gray-300">
168
+ <Paragraph>
72
169
  你的项目已经准备就绪,现在可以开始开发了
73
170
  </Paragraph>
74
- <Space size="middle" className="mt-4">
171
+ <Space size="middle" className={styles.tagSpace}>
75
172
  <Tag color="blue">React 19</Tag>
76
173
  <Tag color="green">TypeScript</Tag>
77
174
  <Tag color="orange">Rspack</Tag>
78
175
  <Tag color="purple">Ant Design</Tag>
79
176
  <Tag color="cyan">Tailwind CSS</Tag>
80
177
  </Space>
81
- </div>
178
+ </section>
82
179
 
83
- {/* 特性卡片 */}
84
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
180
+ <div className={styles.featuresGrid}>
85
181
  {features.map((feature, index) => (
86
182
  <Card
87
183
  key={index}
88
184
  hoverable
89
- className="text-center transition-all duration-300 hover:shadow-xl dark:bg-gray-800"
185
+ className={cx(styles.featureCard)}
186
+ bodyStyle={{ padding: 24 }}
90
187
  >
91
- <div className="mb-4">{feature.icon}</div>
188
+ <div className={styles.featureIcon}>{feature.icon}</div>
92
189
  <Title level={4}>{feature.title}</Title>
93
- <Paragraph className="text-gray-600 dark:text-gray-400">
94
- {feature.description}
95
- </Paragraph>
190
+ <Paragraph type="secondary">{feature.description}</Paragraph>
96
191
  </Card>
97
192
  ))}
98
193
  </div>
99
194
 
100
- {/* 快速开始 */}
101
195
  <Card
102
196
  title={
103
- <span className="text-xl">
104
- <BookOutlined className="mr-2" />
197
+ <span className={styles.cardTitle}>
198
+ <BookOutlined />
105
199
  快速开始
106
200
  </span>
107
201
  }
108
- className="mb-8 dark:bg-gray-800"
202
+ className={styles.panelCard}
109
203
  >
110
- <Space direction="vertical" size="large" className="w-full">
204
+ <Space direction="vertical" size="large">
111
205
  <div>
112
- <Text strong className="text-base">
206
+ <Text strong className={styles.stepLabel}>
113
207
  1. 安装依赖
114
208
  </Text>
115
- <div className="mt-2 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg font-mono text-sm">
209
+ <div className={styles.codeBlock}>
116
210
  <code>pnpm install</code>
117
211
  </div>
118
212
  </div>
119
213
  <div>
120
- <Text strong className="text-base">
214
+ <Text strong className={styles.stepLabel}>
121
215
  2. 启动开发服务器
122
216
  </Text>
123
- <div className="mt-2 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg font-mono text-sm">
217
+ <div className={styles.codeBlock}>
124
218
  <code>pnpm dev</code>
125
219
  </div>
126
220
  </div>
127
221
  <div>
128
- <Text strong className="text-base">
222
+ <Text strong className={styles.stepLabel}>
129
223
  3. 构建生产版本
130
224
  </Text>
131
- <div className="mt-2 p-4 bg-gray-100 dark:bg-gray-900 rounded-lg font-mono text-sm">
225
+ <div className={styles.codeBlock}>
132
226
  <code>pnpm build</code>
133
227
  </div>
134
228
  </div>
135
229
  </Space>
136
230
  </Card>
137
231
 
138
- {/* 快捷链接 */}
139
232
  <Card
140
233
  title={
141
- <span className="text-xl">
142
- <GithubOutlined className="mr-2" />
234
+ <span className={styles.cardTitle}>
235
+ <GithubOutlined />
143
236
  快捷链接
144
237
  </span>
145
238
  }
146
- className="mb-8 dark:bg-gray-800"
239
+ className={styles.panelCard}
147
240
  >
148
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
241
+ <div className={styles.quickLinkGrid}>
149
242
  {quickLinks.map((link, index) => (
150
- <div
151
- key={index}
152
- className="p-4 bg-gray-50 dark:bg-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer transition-colors"
153
- >
154
- <div className="text-2xl mb-2">{link.label}</div>
155
- <Text className="text-sm text-gray-600 dark:text-gray-400">
243
+ <div key={index} className={styles.quickLink}>
244
+ <div className={styles.quickLinkLabel}>{link.label}</div>
245
+ <Text className={styles.quickLinkDescription}>
156
246
  {link.description}
157
247
  </Text>
158
248
  </div>
@@ -160,34 +250,35 @@ export default function BasePage() {
160
250
  </div>
161
251
  </Card>
162
252
 
163
- {/* 底部操作区 */}
164
- <Card className="text-center dark:bg-gray-800">
165
- <Space size="large" wrap>
166
- <Button
167
- type="primary"
168
- size="large"
169
- icon={<RocketOutlined />}
170
- onClick={() => window.open("https://ant.design", "_blank")}
171
- >
172
- 查看 Ant Design 文档
173
- </Button>
174
- <Button
175
- size="large"
176
- icon={<BookOutlined />}
177
- onClick={() => window.open("https://react.dev", "_blank")}
178
- >
179
- React 文档
180
- </Button>
181
- <Button
182
- size="large"
183
- onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
184
- >
185
- 切换主题 ({theme === "dark" ? "🌙 暗色" : "☀️ 亮色"})
186
- </Button>
187
- </Space>
188
- <Paragraph className="mt-6 text-gray-500 dark:text-gray-400">
189
- 祝你开发愉快!如有问题,请查阅文档或联系技术支持 💪
190
- </Paragraph>
253
+ <Card className={cx(styles.panelCard, styles.textCenterCard)}>
254
+ <div className={styles.footerActions}>
255
+ <Space size="large" wrap>
256
+ <Button
257
+ type="primary"
258
+ size="large"
259
+ icon={<RocketOutlined />}
260
+ onClick={() => window.open("https://ant.design", "_blank")}
261
+ >
262
+ 查看 Ant Design 文档
263
+ </Button>
264
+ <Button
265
+ size="large"
266
+ icon={<BookOutlined />}
267
+ onClick={() => window.open("https://react.dev", "_blank")}
268
+ >
269
+ React 文档
270
+ </Button>
271
+ <Button
272
+ size="large"
273
+ onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
274
+ >
275
+ 切换主题 ({theme === "dark" ? "🌙 暗色" : "☀️ 亮色"})
276
+ </Button>
277
+ </Space>
278
+ <Paragraph className={styles.footerParagraph}>
279
+ 祝你开发愉快!如有问题,请查阅文档或联系技术支持 💪
280
+ </Paragraph>
281
+ </div>
191
282
  </Card>
192
283
  </Flex>
193
284
  </div>
@@ -0,0 +1,136 @@
1
+ import ThemeProvider from "@/components/ThemeProvider";
2
+ import { CLOUD_THEME_APPEARANCE } from "@/const/theme";
3
+ import { setCookie } from "@/utils/cookie";
4
+
5
+ // import { useGlobalStore } from "@store/global";
6
+ import React, { memo, useEffect, type ReactNode } from "react";
7
+
8
+ import { SYSTEM_PREFIX } from "@/const/system";
9
+ import { GlobalStyle } from "@/styles";
10
+ import { useUserStore } from "@store/user";
11
+ import { createStaticStyles, cx } from "antd-style";
12
+ import HarmonyOS_Sans_Regular from "@public/fonts/HarmonyOS_Sans_Regular.woff2";
13
+ import AntdStaticMethods from "@/components/AntdStaticMethods";
14
+
15
+ const styles = createStaticStyles(({ css, cssVar }) => ({
16
+ app: css`
17
+ position: relative;
18
+
19
+ overscroll-behavior: none;
20
+ display: flex;
21
+ flex-direction: column;
22
+ align-items: center;
23
+
24
+ height: 100%;
25
+ min-height: 100dvh;
26
+ max-height: 100dvh;
27
+
28
+ @media (min-device-width: 576px) {
29
+ overflow: hidden;
30
+ }
31
+ `,
32
+ // scrollbar-width and scrollbar-color are supported from Chrome 121
33
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/scrollbar-color
34
+ scrollbar: css`
35
+ scrollbar-color: ${cssVar.colorFill} transparent;
36
+ scrollbar-width: thin;
37
+
38
+ #lobe-mobile-scroll-container {
39
+ scrollbar-width: none;
40
+
41
+ ::-webkit-scrollbar {
42
+ width: 0;
43
+ height: 0;
44
+ }
45
+ }
46
+ `,
47
+
48
+ // so this is a polyfill for older browsers
49
+ scrollbarPolyfill: css`
50
+ ::-webkit-scrollbar {
51
+ width: 0.75em;
52
+ height: 0.75em;
53
+ }
54
+
55
+ ::-webkit-scrollbar-thumb {
56
+ border-radius: 10px;
57
+ }
58
+
59
+ :hover::-webkit-scrollbar-thumb {
60
+ border: 3px solid transparent;
61
+ background-color: ${cssVar.colorText};
62
+ background-clip: content-box;
63
+ }
64
+
65
+ ::-webkit-scrollbar-track {
66
+ background-color: transparent;
67
+ }
68
+ `,
69
+ }));
70
+
71
+ interface AppThemeProps {
72
+ children: ReactNode;
73
+ }
74
+
75
+ const AppTheme = memo(({ children }: AppThemeProps) => {
76
+ const themeMode = useUserStore((s) => s.themeMode);
77
+ const animationMode = useUserStore((s) => s.animationMode);
78
+ const neutralColor = useUserStore((s) => s.neutralColor);
79
+ const primaryColor = useUserStore((s) => s.primaryColor);
80
+
81
+ useEffect(() => {
82
+ // Update data-theme accordingly if user selects light or dark
83
+ if (themeMode !== "auto") {
84
+ document.documentElement.dataset.theme = themeMode;
85
+ return;
86
+ }
87
+
88
+ // For auto mode, we need to watch system preferences
89
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
90
+
91
+ // Set initial theme based on system preference
92
+ document.documentElement.dataset.theme = mediaQuery.matches
93
+ ? "dark"
94
+ : "light";
95
+
96
+ // Update theme when system preference changes
97
+ function handleChange(e) {
98
+ document.documentElement.dataset.theme = e.matches ? "dark" : "light";
99
+ }
100
+
101
+ mediaQuery.addEventListener("change", handleChange);
102
+ return () => mediaQuery.removeEventListener("change", handleChange);
103
+ }, [themeMode]);
104
+
105
+ return (
106
+ <ThemeProvider
107
+ prefixCls={SYSTEM_PREFIX}
108
+ appearance={themeMode !== "auto" ? themeMode : undefined}
109
+ className={cx(styles.app, styles.scrollbar, styles.scrollbarPolyfill)}
110
+ customTheme={{
111
+ neutralColor: neutralColor,
112
+ primaryColor: primaryColor,
113
+ }}
114
+ customFonts={[HarmonyOS_Sans_Regular]}
115
+ theme={{
116
+ cssVar: true,
117
+ token: {
118
+ motion: animationMode !== "disabled",
119
+ motionUnit: animationMode === "agile" ? 0.05 : 0.1,
120
+ },
121
+ }}
122
+ themeMode={themeMode}
123
+ onAppearanceChange={(appearance) => {
124
+ if (themeMode !== "auto") return;
125
+
126
+ setCookie(CLOUD_THEME_APPEARANCE, appearance);
127
+ }}
128
+ >
129
+ {children}
130
+ <AntdStaticMethods />
131
+ <GlobalStyle />
132
+ </ThemeProvider>
133
+ );
134
+ });
135
+
136
+ export default AppTheme;
@@ -0,0 +1,84 @@
1
+ import { ConfigProvider } from 'antd';
2
+ import dayjs from 'dayjs';
3
+ import { type PropsWithChildren, memo, useEffect, useState } from 'react';
4
+ import { isRtlLang } from 'rtl-detect';
5
+
6
+ import { createI18nNext } from '@/locales/create';
7
+ import { getAntdLocale } from '@/utils/locale';
8
+
9
+
10
+ const updateDayjs = async (lang: string) => {
11
+ let dayJSLocale;
12
+ try {
13
+ // dayjs locale is using `en` instead of `en-US`
14
+ // refs: https://github.com/lobehub/lobe-chat/issues/3396
15
+ const locale = lang!.toLowerCase() === 'en-us' ? 'en' : lang!.toLowerCase();
16
+
17
+ dayJSLocale = await import(`dayjs/locale/${locale}.js`);
18
+ } catch {
19
+ console.warn(`dayjs locale for ${lang} not found, fallback to en`);
20
+ dayJSLocale = await import(`dayjs/locale/en.js`);
21
+ }
22
+
23
+ dayjs.locale(dayJSLocale.default);
24
+ };
25
+
26
+ interface LocaleLayoutProps extends PropsWithChildren {
27
+ antdLocale?: any;
28
+ defaultLang?: string;
29
+ }
30
+
31
+ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang, antdLocale }) => {
32
+ const [i18n] = useState(() => createI18nNext(defaultLang));
33
+ const [lang, setLang] = useState(defaultLang);
34
+ const [locale, setLocale] = useState(antdLocale);
35
+
36
+ if (!i18n.instance.isInitialized)
37
+ i18n.init().then(async () => {
38
+ if (!lang) return;
39
+
40
+ await updateDayjs(lang);
41
+ });
42
+
43
+ // handle i18n instance language change
44
+ useEffect(() => {
45
+ const handleLang = async (lng: string) => {
46
+ setLang(lng);
47
+
48
+ if (lang === lng) return;
49
+
50
+ const newLocale = await getAntdLocale(lng);
51
+ setLocale(newLocale);
52
+
53
+ await updateDayjs(lng);
54
+ };
55
+
56
+ i18n.instance.on('languageChanged', handleLang);
57
+ return () => {
58
+ i18n.instance.off('languageChanged', handleLang);
59
+ };
60
+ }, [i18n, lang]);
61
+
62
+ // detect document direction
63
+ const documentDir = isRtlLang(lang!) ? 'rtl' : 'ltr';
64
+
65
+ return (
66
+ <ConfigProvider
67
+ direction={documentDir}
68
+ locale={locale}
69
+ theme={{
70
+ components: {
71
+ Button: {
72
+ contentFontSizeSM: 12,
73
+ },
74
+ },
75
+ }}
76
+ >
77
+ {children}
78
+ </ConfigProvider>
79
+ );
80
+ });
81
+
82
+ Locale.displayName = 'Locale';
83
+
84
+ export default Locale;
@@ -0,0 +1,12 @@
1
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2
+ import React, { type PropsWithChildren, useEffect, useState } from 'react';
3
+
4
+ const QueryProvider = ({ children }: PropsWithChildren) => {
5
+ const [queryClient] = useState(() => new QueryClient());
6
+
7
+ return (
8
+ <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
9
+ );
10
+ };
11
+
12
+ export default QueryProvider;
@@ -0,0 +1,9 @@
1
+ import { StyleProvider } from 'antd-style';
2
+ import { type PropsWithChildren } from 'react';
3
+
4
+
5
+ const StyleRegistry = ({ children }: PropsWithChildren) => {
6
+ return <StyleProvider>{children}</StyleProvider>;
7
+ };
8
+
9
+ export default StyleRegistry;
@@ -0,0 +1,23 @@
1
+ import StyleRegistry from "./StyleRegistry";
2
+ import AppTheme from "./AppTheme";
3
+ import QueryProvider from "./Query";
4
+ import Locale from "./Locale";
5
+ import { Outlet } from "react-router";
6
+
7
+ interface GlobalProviderProps {
8
+
9
+ }
10
+
11
+ export default function GlobalProvider({ }: GlobalProviderProps) {
12
+ return (
13
+ <StyleRegistry>
14
+ <Locale>
15
+ <AppTheme>
16
+ <QueryProvider>
17
+ <Outlet />
18
+ </QueryProvider>
19
+ </AppTheme>
20
+ </Locale>
21
+ </StyleRegistry>
22
+ );
23
+ }