@lobehub/chat 0.120.2 → 0.120.3

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 (63) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +47 -22
  3. package/README.zh-CN.md +53 -26
  4. package/docs/Deployment/Upstream-Sync.md +8 -2
  5. package/docs/Deployment/Upstream-Sync.zh-CN.md +9 -3
  6. package/docs/Development/Add-New-Locale.md +9 -1
  7. package/docs/Development/Add-New-Locale.zh-CN.md +9 -1
  8. package/docs/Development/Contributing-Guidelines.md +83 -0
  9. package/docs/Development/Contributing-Guidelines.zh-CN.md +13 -0
  10. package/docs/Development/Internationalization-Implementation.md +125 -0
  11. package/docs/Development/Internationalization-Implementation.zh-CN.md +9 -1
  12. package/docs/Development/State-Management/index.md +224 -0
  13. package/docs/Development/State-Management/index.zh-CN.md +25 -28
  14. package/docs/Home.md +9 -6
  15. package/docs/_Sidebar.md +9 -6
  16. package/locales/ar/common.json +1 -0
  17. package/locales/ar/setting.json +1 -3
  18. package/locales/de-DE/common.json +1 -0
  19. package/locales/de-DE/setting.json +1 -3
  20. package/locales/en-US/common.json +1 -0
  21. package/locales/en-US/setting.json +1 -3
  22. package/locales/es-ES/common.json +1 -0
  23. package/locales/es-ES/setting.json +1 -3
  24. package/locales/fr-FR/common.json +1 -0
  25. package/locales/fr-FR/setting.json +1 -3
  26. package/locales/it-IT/common.json +1 -0
  27. package/locales/it-IT/setting.json +1 -3
  28. package/locales/ja-JP/common.json +1 -0
  29. package/locales/ja-JP/setting.json +1 -3
  30. package/locales/ko-KR/common.json +1 -0
  31. package/locales/ko-KR/setting.json +1 -3
  32. package/locales/nl-NL/common.json +1 -0
  33. package/locales/nl-NL/setting.json +1 -3
  34. package/locales/pl-PL/common.json +1 -0
  35. package/locales/pl-PL/setting.json +1 -3
  36. package/locales/pt-BR/common.json +1 -0
  37. package/locales/pt-BR/setting.json +1 -3
  38. package/locales/ru-RU/common.json +1 -0
  39. package/locales/ru-RU/setting.json +1 -3
  40. package/locales/tr-TR/common.json +1 -0
  41. package/locales/tr-TR/setting.json +1 -3
  42. package/locales/vi-VN/common.json +1 -0
  43. package/locales/vi-VN/setting.json +1 -3
  44. package/locales/zh-CN/common.json +1 -0
  45. package/locales/zh-CN/setting.json +1 -3
  46. package/locales/zh-TW/common.json +1 -0
  47. package/locales/zh-TW/setting.json +1 -3
  48. package/next.config.mjs +2 -2
  49. package/package.json +6 -6
  50. package/scripts/docsWorkflow/const.ts +1 -1
  51. package/scripts/docsWorkflow/index.ts +9 -3
  52. package/scripts/readmeWorkflow/syncAgentIndex.ts +2 -2
  53. package/scripts/readmeWorkflow/syncPluginIndex.ts +2 -2
  54. package/src/app/chat/features/SessionListContent/List/Item/Actions.tsx +0 -2
  55. package/src/app/settings/common/Common.tsx +5 -6
  56. package/src/const/locale.ts +2 -2
  57. package/src/features/ChatInput/ActionBar/Clear.tsx +0 -2
  58. package/src/features/PluginDetailModal/index.tsx +0 -2
  59. package/src/features/PluginDevModal/index.tsx +0 -1
  60. package/src/layout/GlobalLayout/Locale.tsx +37 -10
  61. package/src/layout/GlobalLayout/index.tsx +1 -1
  62. package/src/locales/default/setting.ts +0 -2
  63. package/src/locales/resources.ts +9 -7
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Taille de la police du contenu du chat",
254
254
  "marks": {
255
- "large": "Grand",
256
- "normal": "Normal",
257
- "small": "Petit"
255
+ "normal": "Normal"
258
256
  },
259
257
  "title": "Taille de la police"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Importa dati"
52
52
  },
53
53
  "lang": {
54
+ "ar": "Arabo",
54
55
  "bn": "Bengalese",
55
56
  "cs-CZ": "Ceco",
56
57
  "da-DK": "Danese",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Dimensione del carattere per i messaggi",
254
254
  "marks": {
255
- "large": "Grande",
256
- "normal": "Normale",
257
- "small": "Piccola"
255
+ "normal": "Normale"
258
256
  },
259
257
  "title": "Dimensione del carattere"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "データのインポート"
52
52
  },
53
53
  "lang": {
54
+ "ar": "アラビア語",
54
55
  "bn": "ベンガル語",
55
56
  "cs-CZ": "チェコ語",
56
57
  "da-DK": "デンマーク語",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "チャットのフォントサイズ",
254
254
  "marks": {
255
- "large": "大きい",
256
- "normal": "標準",
257
- "small": "小さい"
255
+ "normal": "標準"
258
256
  },
259
257
  "title": "フォントサイズ"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "데이터 가져오기"
52
52
  },
53
53
  "lang": {
54
+ "ar": "아랍어",
54
55
  "bn": "벵골어",
55
56
  "cs-CZ": "체코어",
56
57
  "da-DK": "덴마크어",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "채팅 내용의 글꼴 크기",
254
254
  "marks": {
255
- "large": "대",
256
- "normal": "표준",
257
- "small": "소"
255
+ "normal": "표준"
258
256
  },
259
257
  "title": "글꼴 크기"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Gegevens importeren"
52
52
  },
53
53
  "lang": {
54
+ "ar": "Arabisch",
54
55
  "bn": "Bengaals",
55
56
  "cs-CZ": "Tsjechisch",
56
57
  "da-DK": "Deens",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Lettergrootte van chatberichten",
254
254
  "marks": {
255
- "large": "Groot",
256
- "normal": "Normaal",
257
- "small": "Klein"
255
+ "normal": "Normaal"
258
256
  },
259
257
  "title": "Lettergrootte"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Import danych"
52
52
  },
53
53
  "lang": {
54
+ "ar": "arabski",
54
55
  "bn": "Bengalski",
55
56
  "cs-CZ": "Czeski",
56
57
  "da-DK": "Duński",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Rozmiar czcionki wiadomości",
254
254
  "marks": {
255
- "large": "Duży",
256
- "normal": "Standardowy",
257
- "small": "Mały"
255
+ "normal": "Standardowy"
258
256
  },
259
257
  "title": "Rozmiar czcionki"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Importar dados"
52
52
  },
53
53
  "lang": {
54
+ "ar": "árabe",
54
55
  "bn": "Bengali",
55
56
  "cs-CZ": "Tcheco",
56
57
  "da-DK": "Dinamarquês",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Tamanho da fonte do conteúdo do chat",
254
254
  "marks": {
255
- "large": "Grande",
256
- "normal": "Normal",
257
- "small": "Pequeno"
255
+ "normal": "Normal"
258
256
  },
259
257
  "title": "Tamanho da Fonte"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Импорт данных"
52
52
  },
53
53
  "lang": {
54
+ "ar": "арабский",
54
55
  "bn": "Бенгальский",
55
56
  "cs-CZ": "Чешский",
56
57
  "da-DK": "Датский",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Размер шрифта для чата",
254
254
  "marks": {
255
- "large": "большой",
256
- "normal": "нормальный",
257
- "small": "малый"
255
+ "normal": "нормальный"
258
256
  },
259
257
  "title": "Размер шрифта"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Veri İçe Aktar"
52
52
  },
53
53
  "lang": {
54
+ "ar": "Arapça",
54
55
  "bn": "Bengalce",
55
56
  "cs-CZ": "Çekçe",
56
57
  "da-DK": "Danca",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Sohbet içeriği için yazı boyutu",
254
254
  "marks": {
255
- "large": "Büyük",
256
- "normal": "Normal",
257
- "small": "Küçük"
255
+ "normal": "Normal"
258
256
  },
259
257
  "title": "Yazı Boyutu"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "Nhập dữ liệu"
52
52
  },
53
53
  "lang": {
54
+ "ar": "Tiếng Ả Rập",
54
55
  "bn": "Tiếng Bengal",
55
56
  "cs-CZ": "Tiếng Séc",
56
57
  "da-DK": "Tiếng Đan Mạch",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "Kích cỡ chữ của nội dung trò chuyện",
254
254
  "marks": {
255
- "large": "Lớn",
256
- "normal": "Bình thường",
257
- "small": "Nhỏ"
255
+ "normal": "Bình thường"
258
256
  },
259
257
  "title": "Kích cỡ chữ"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "导入数据"
52
52
  },
53
53
  "lang": {
54
+ "ar": "阿拉伯语",
54
55
  "bn": "孟加拉语",
55
56
  "cs-CZ": "捷克语",
56
57
  "da-DK": "丹麦语",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "聊天内容的字体大小",
254
254
  "marks": {
255
- "large": "大",
256
- "normal": "标准",
257
- "small": "小"
255
+ "normal": "标准"
258
256
  },
259
257
  "title": "字体大小"
260
258
  },
@@ -51,6 +51,7 @@
51
51
  "title": "匯入資料"
52
52
  },
53
53
  "lang": {
54
+ "ar": "阿拉伯語",
54
55
  "bn": "孟加拉語",
55
56
  "cs-CZ": "捷克語",
56
57
  "da-DK": "丹麥語",
@@ -252,9 +252,7 @@
252
252
  "fontSize": {
253
253
  "desc": "聊天內容的字體大小",
254
254
  "marks": {
255
- "large": "大",
256
- "normal": "標準",
257
- "small": "小"
255
+ "normal": "標準"
258
256
  },
259
257
  "title": "字體大小"
260
258
  },
package/next.config.mjs CHANGED
@@ -21,11 +21,11 @@ const nextConfig = {
21
21
  compress: isProd,
22
22
  experimental: {
23
23
  optimizePackageImports: [
24
- 'modern-screenshot',
25
24
  'emoji-mart',
26
25
  '@emoji-mart/react',
27
26
  '@emoji-mart/data',
28
27
  '@icons-pack/react-simple-icons',
28
+ '@lobehub/ui',
29
29
  'gpt-tokenizer',
30
30
  'chroma-js',
31
31
  ],
@@ -46,7 +46,7 @@ const nextConfig = {
46
46
 
47
47
  reactStrictMode: true,
48
48
 
49
- transpilePackages: ['antd-style', '@lobehub/ui', '@lobehub/tts'],
49
+ transpilePackages: ['antd-style'],
50
50
 
51
51
  webpack(config) {
52
52
  config.experiments = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.120.2",
3
+ "version": "0.120.3",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -32,7 +32,7 @@
32
32
  "dev": "next dev -p 3010",
33
33
  "docs": "lobe-i18n md && npm run workflow:docs",
34
34
  "i18n": "npm run workflow:i18n && lobe-i18n",
35
- "i18n-md": "lobe-i18n md",
35
+ "i18n:docs": "lobe-i18n md && npm run workflow:docs",
36
36
  "lint": "npm run lint:ts && npm run lint:style && npm run type-check && npm run lint:circular",
37
37
  "lint:circular": "dpdm src/**/*.ts --warning false --tree false --exit-code circular:1 -T true",
38
38
  "lint:md": "remark . --quiet --frail --output",
@@ -51,9 +51,9 @@
51
51
  "test:coverage-local": "vitest run --coverage",
52
52
  "test:update": "vitest -u",
53
53
  "type-check": "tsc --noEmit",
54
- "workflow:docs": "bun scripts/docsWorkflow/index.ts",
55
- "workflow:i18n": "ts-node --project ./tsconfig.json scripts/i18nWorkflow/index.ts",
56
- "workflow:readme": "bun scripts/readmeWorkflow/index.ts"
54
+ "workflow:docs": "tsx scripts/docsWorkflow/index.ts",
55
+ "workflow:i18n": "tsx scripts/i18nWorkflow/index.ts",
56
+ "workflow:readme": "tsx scripts/readmeWorkflow/index.ts"
57
57
  },
58
58
  "lint-staged": {
59
59
  "*.md": [
@@ -179,7 +179,7 @@
179
179
  "remark-parse": "^10",
180
180
  "semantic-release": "^21",
181
181
  "stylelint": "^15",
182
- "ts-node": "^10",
182
+ "tsx": "^4",
183
183
  "typescript": "^5",
184
184
  "unified": "^11",
185
185
  "unist-util-visit": "^5",
@@ -6,7 +6,7 @@ export const ROOT = resolve(__dirname, '../..');
6
6
  export const DOCS_DIR = resolve(ROOT, 'docs');
7
7
  export const HOME_PATH = resolve(DOCS_DIR, 'Home.md');
8
8
  export const SIDEBAR_PATH = resolve(DOCS_DIR, '_Sidebar.md');
9
- export const docsFiles = globSync(resolve(DOCS_DIR, '**/*.md'), {
9
+ export const docsFiles = globSync(resolve(DOCS_DIR, '**/*.md').replaceAll('\\', '/'), {
10
10
  ignore: ['Home.md', '_Sidebar.md', '_Footer.md'],
11
11
  nodir: true,
12
12
  }).filter((file) => !file.includes('.zh-CN.md'));
@@ -20,9 +20,15 @@ const run = async () => {
20
20
  const links = {
21
21
  cn: urlJoin(
22
22
  WIKI_URL,
23
- relative(DOCS_DIR, paths.cn).split('/')[1].replace('.zh-CN.md', '.zh-CN'),
23
+ relative(DOCS_DIR, paths.cn)
24
+ .replaceAll('\\', '/')
25
+ .split('/')[1]
26
+ .replace('.zh-CN.md', '.zh-CN'),
27
+ ),
28
+ en: urlJoin(
29
+ WIKI_URL,
30
+ relative(DOCS_DIR, paths.en).replaceAll('\\', '/').split('/')[1].replace('.md', ''),
24
31
  ),
25
- en: urlJoin(WIKI_URL, relative(DOCS_DIR, paths.en).split('/')[1].replace('.md', '')),
26
32
  };
27
33
  const titles = {
28
34
  cn: await getTitle(paths.cn),
@@ -59,7 +65,7 @@ const run = async () => {
59
65
  child.links.cn,
60
66
  )}\n`;
61
67
  sidebarContent += `- ${genMdLink(child.titles.en, child.links.en)} | ${genMdLink(
62
- '中文',
68
+ child.titles.cn,
63
69
  child.links.cn,
64
70
  )}\n`;
65
71
  });
@@ -10,7 +10,7 @@ const genAgentTable = (data: DataItem[], lang: string) => {
10
10
  const content = data.slice(0, 4).map((item) => [
11
11
  [
12
12
  genLink(
13
- item.meta.title,
13
+ item.meta.title.replaceAll('|', ','),
14
14
  qs.stringifyUrl({
15
15
  query: { agent: item.identifier },
16
16
  url: MARKET_URL,
@@ -18,7 +18,7 @@ const genAgentTable = (data: DataItem[], lang: string) => {
18
18
  ),
19
19
  `<sup>By **${genLink(item.author, item.homepage)}** on **${(item as any).createAt}**</sup>`,
20
20
  ].join('<br/>'),
21
- [item.meta.description, genTags(item.meta.tags)].join('<br/>'),
21
+ [item.meta.description.replaceAll('|', ','), genTags(item.meta.tags)].join('<br/>'),
22
22
  ]);
23
23
  return markdownTable([
24
24
  isCN ? ['最近新增', '助手说明'] : ['Recent Submits', 'Description'],
@@ -10,10 +10,10 @@ const genPluginTable = (data: DataItem[], lang: string) => {
10
10
  .slice(0, 4)
11
11
  .map((item) => [
12
12
  [
13
- genLink(item.meta.title, PLGUIN_URL),
13
+ genLink(item.meta.title.replaceAll('|', ','), PLGUIN_URL),
14
14
  `<sup>By **${item.author}** on **${item.createdAt}**</sup>`,
15
15
  ].join('<br/>'),
16
- [item.meta.description, genTags(item.meta.tags)].join('<br/>'),
16
+ [item.meta.description.replaceAll('|', ','), genTags(item.meta.tags)].join('<br/>'),
17
17
  ]);
18
18
  return markdownTable([
19
19
  isCN ? ['最近新增', '插件描述'] : ['Recent Submits', 'Description'],
@@ -94,10 +94,8 @@ const Actions = memo<ActionProps>(({ id, setOpen }) => {
94
94
  domEvent.stopPropagation();
95
95
 
96
96
  modal.confirm({
97
- cancelText: t('cancel'),
98
97
  centered: true,
99
98
  okButtonProps: { danger: true },
100
- okText: t('ok'),
101
99
  onOk: () => {
102
100
  removeSession(id);
103
101
  },
@@ -49,10 +49,8 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
49
49
 
50
50
  const handleReset = useCallback(() => {
51
51
  modal.confirm({
52
- cancelText: t('cancel', { ns: 'common' }),
53
52
  centered: true,
54
53
  okButtonProps: { danger: true },
55
- okText: t('ok', { ns: 'common' }),
56
54
  onOk: () => {
57
55
  resetSettings();
58
56
  form.setFieldsValue(DEFAULT_SETTINGS);
@@ -63,12 +61,10 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
63
61
 
64
62
  const handleClear = useCallback(() => {
65
63
  modal.confirm({
66
- cancelText: t('cancel', { ns: 'common' }),
67
64
  centered: true,
68
65
  okButtonProps: {
69
66
  danger: true,
70
67
  },
71
- okText: t('ok', { ns: 'common' }),
72
68
  onOk: async () => {
73
69
  await clearSessions();
74
70
  await removeAllPlugins();
@@ -136,20 +132,23 @@ const Common = memo<SettingsCommonProps>(({ showAccessCodeConfig }) => {
136
132
  <SliderWithInput
137
133
  marks={{
138
134
  12: {
139
- label: t('settingTheme.fontSize.marks.small'),
135
+ label: 'A',
140
136
  style: {
137
+ fontSize: 12,
141
138
  marginTop: 4,
142
139
  },
143
140
  },
144
141
  14: {
145
142
  label: t('settingTheme.fontSize.marks.normal'),
146
143
  style: {
144
+ fontSize: 14,
147
145
  marginTop: 4,
148
146
  },
149
147
  },
150
148
  18: {
151
- label: t('settingTheme.fontSize.marks.large'),
149
+ label: 'A',
152
150
  style: {
151
+ fontSize: 18,
153
152
  marginTop: 4,
154
153
  },
155
154
  },
@@ -1,4 +1,4 @@
1
- import { Locales, normalizeLocale, supportLocales } from '@/locales/resources';
1
+ import { normalizeLocale, supportLocales } from '@/locales/resources';
2
2
 
3
3
  export const DEFAULT_LANG = 'en-US';
4
4
  export const LOBE_LOCALE_COOKIE = 'LOBE_LOCALE';
@@ -7,6 +7,6 @@ export const LOBE_LOCALE_COOKIE = 'LOBE_LOCALE';
7
7
  * Check if the language is supported
8
8
  * @param locale
9
9
  */
10
- export const isLocaleNotSupport = (locale: Locales) => {
10
+ export const isLocaleNotSupport = (locale: string) => {
11
11
  return normalizeLocale(locale) === DEFAULT_LANG || !supportLocales.includes(locale);
12
12
  };
@@ -22,9 +22,7 @@ const Clear = memo(() => {
22
22
 
23
23
  return (
24
24
  <Popconfirm
25
- cancelText={t('cancel', { ns: 'common' })}
26
25
  okButtonProps={{ danger: true }}
27
- okText={t('ok', { ns: 'common' })}
28
26
  onConfirm={() => {
29
27
  resetConversation();
30
28
  }}
@@ -33,8 +33,6 @@ const PluginDetailModal = memo<PluginDetailModalProps>(
33
33
  return (
34
34
  <Modal
35
35
  allowFullscreen
36
- cancelText={t('cancel', { ns: 'common' })}
37
- okText={t('ok', { ns: 'common' })}
38
36
  onCancel={onClose}
39
37
  onOk={() => {
40
38
  onClose();
@@ -96,7 +96,6 @@ const DevModal = memo<DevModalProps>(
96
96
  >
97
97
  <Modal
98
98
  allowFullscreen
99
- cancelText={t('cancel', { ns: 'common' })}
100
99
  footer={footer}
101
100
  okText={t('dev.save')}
102
101
  onCancel={(e) => {
@@ -1,24 +1,39 @@
1
1
  import { ConfigProvider } from 'antd';
2
- import { PropsWithChildren, memo, useState } from 'react';
2
+ import { PropsWithChildren, memo, useEffect, useState } from 'react';
3
3
  import useSWR from 'swr';
4
4
 
5
5
  import { createI18nNext } from '@/locales/create';
6
+ import { normalizeLocale } from '@/locales/resources';
6
7
  import { useOnFinishHydrationGlobal } from '@/store/global';
7
8
  import { isOnServerSide } from '@/utils/env';
8
9
  import { switchLang } from '@/utils/switchLang';
9
10
 
11
+ const getAntdLocale = async (lang?: string) => {
12
+ let normalLang = normalizeLocale(lang);
13
+
14
+ // due to antd only have ar-EG locale, we need to convert ar to ar-EG
15
+ // refs: https://ant.design/docs/react/i18n
16
+
17
+ // And we don't want to handle it in `normalizeLocale` function
18
+ // because of other locale files are all `ar` not `ar-EG`
19
+ if (normalLang === 'ar') normalLang = 'ar-EG';
20
+
21
+ const { default: locale } = await import(`antd/locale/${normalLang.replace('-', '_')}.js`);
22
+
23
+ return locale;
24
+ };
25
+
10
26
  interface LocaleLayoutProps extends PropsWithChildren {
11
- lang?: string;
27
+ defaultLang?: string;
12
28
  }
13
29
 
14
- const Locale = memo<LocaleLayoutProps>(({ children, lang }) => {
15
- const { data: locale } = useSWR(
16
- lang,
17
- async () =>
18
- await import(`antd/locale/${lang?.includes('-') ? lang?.replace('-', '_') : 'en_US'}.js`),
19
- { revalidateOnFocus: false },
20
- );
21
- const [i18n] = useState(createI18nNext(lang));
30
+ const Locale = memo<LocaleLayoutProps>(({ children, defaultLang }) => {
31
+ const [i18n] = useState(createI18nNext(defaultLang));
32
+ const [lang, setLang] = useState(defaultLang);
33
+
34
+ const { data: locale } = useSWR(['antd-locale', lang], ([, key]) => getAntdLocale(key), {
35
+ revalidateOnFocus: false,
36
+ });
22
37
 
23
38
  // if run on server side, init i18n instance everytime
24
39
  if (isOnServerSide) {
@@ -38,6 +53,18 @@ const Locale = memo<LocaleLayoutProps>(({ children, lang }) => {
38
53
  }
39
54
  }, []);
40
55
 
56
+ // handle i18n instance language change
57
+ useEffect(() => {
58
+ const handleLang = (e: string) => {
59
+ setLang(e);
60
+ };
61
+
62
+ i18n.instance.on('languageChanged', handleLang);
63
+ return () => {
64
+ i18n.instance.off('languageChanged', handleLang);
65
+ };
66
+ }, [i18n]);
67
+
41
68
  return <ConfigProvider locale={locale}>{children}</ConfigProvider>;
42
69
  });
43
70
 
@@ -48,7 +48,7 @@ interface GlobalLayoutProps extends AppThemeProps {
48
48
 
49
49
  const GlobalLayout = ({ children, defaultLang, ...theme }: GlobalLayoutProps) => (
50
50
  <AppTheme {...theme}>
51
- <Locale lang={defaultLang}>
51
+ <Locale defaultLang={defaultLang}>
52
52
  <StoreHydration />
53
53
  <Container>{children}</Container>
54
54
  <DebugUI />
@@ -252,9 +252,7 @@ export default {
252
252
  fontSize: {
253
253
  desc: '聊天内容的字体大小',
254
254
  marks: {
255
- large: '大',
256
255
  normal: '标准',
257
- small: '小',
258
256
  },
259
257
  title: '字体大小',
260
258
  },
@@ -19,7 +19,9 @@ export const locales = [
19
19
  export type DefaultResources = typeof resources;
20
20
  export type Locales = (typeof locales)[number];
21
21
 
22
- export const normalizeLocale = (locale: string) => {
22
+ export const normalizeLocale = (locale?: string) => {
23
+ if (!locale) return 'en-US';
24
+
23
25
  switch (locale) {
24
26
  case 'zh-CN':
25
27
  case 'zh': {
@@ -43,8 +45,8 @@ type LocaleOptions = {
43
45
 
44
46
  export const localeOptions: LocaleOptions = [
45
47
  {
46
- label: 'العربية',
47
- value: 'ar',
48
+ label: 'English',
49
+ value: 'en-US',
48
50
  },
49
51
  {
50
52
  label: '简体中文',
@@ -54,10 +56,6 @@ export const localeOptions: LocaleOptions = [
54
56
  label: '繁體中文',
55
57
  value: 'zh-TW',
56
58
  },
57
- {
58
- label: 'English',
59
- value: 'en-US',
60
- },
61
59
  {
62
60
  label: '日本語',
63
61
  value: 'ja-JP',
@@ -74,6 +72,10 @@ export const localeOptions: LocaleOptions = [
74
72
  label: 'Español',
75
73
  value: 'es-ES',
76
74
  },
75
+ {
76
+ label: 'العربية',
77
+ value: 'ar',
78
+ },
77
79
  {
78
80
  label: 'Français',
79
81
  value: 'fr-FR',