@qlover/create-app 0.10.1 → 0.10.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 (214) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
  3. package/dist/configs/_common/.github/workflows/release.yml +2 -2
  4. package/dist/index.cjs +4 -4
  5. package/dist/index.js +4 -4
  6. package/dist/templates/next-app/config/IOCIdentifier.ts +2 -2
  7. package/dist/templates/next-app/config/Identifier/common/common.ts +14 -0
  8. package/dist/templates/next-app/config/Identifier/pages/index.ts +1 -0
  9. package/dist/templates/next-app/config/Identifier/pages/page.about.ts +20 -0
  10. package/dist/templates/next-app/config/common.ts +1 -1
  11. package/dist/templates/next-app/config/cookies.ts +23 -0
  12. package/dist/templates/next-app/config/i18n/AboutI18n.ts +14 -0
  13. package/dist/templates/next-app/config/i18n/i18nConfig.ts +3 -1
  14. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  15. package/dist/templates/next-app/config/i18n/loginI18n.ts +8 -0
  16. package/dist/templates/next-app/config/theme.ts +4 -0
  17. package/dist/templates/next-app/eslint.config.mjs +9 -2
  18. package/dist/templates/next-app/next.config.ts +1 -0
  19. package/dist/templates/next-app/package.json +17 -6
  20. package/dist/templates/next-app/public/locales/en.json +5 -0
  21. package/dist/templates/next-app/public/locales/zh.json +5 -0
  22. package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +37 -0
  23. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +30 -6
  24. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +1 -1
  25. package/dist/templates/next-app/src/app/[locale]/layout.tsx +47 -10
  26. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
  27. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +22 -10
  28. package/dist/templates/next-app/src/app/[locale]/page.tsx +23 -8
  29. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +21 -9
  30. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +7 -28
  31. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +7 -34
  32. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +12 -34
  33. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +7 -26
  34. package/dist/templates/next-app/src/app/api/admin/users/route.ts +14 -33
  35. package/dist/templates/next-app/src/app/api/locales/json/route.ts +13 -25
  36. package/dist/templates/next-app/src/app/api/user/login/route.ts +6 -46
  37. package/dist/templates/next-app/src/app/api/user/logout/route.ts +5 -24
  38. package/dist/templates/next-app/src/app/api/user/register/route.ts +6 -45
  39. package/dist/templates/next-app/src/app/manifest.ts +16 -0
  40. package/dist/templates/next-app/src/app/robots.txt +2 -0
  41. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +3 -3
  42. package/dist/templates/next-app/src/base/cases/AppConfig.ts +14 -13
  43. package/dist/templates/next-app/src/base/cases/Datetime.ts +3 -3
  44. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +8 -2
  45. package/dist/templates/next-app/src/base/cases/DialogHandler.ts +30 -4
  46. package/dist/templates/next-app/src/base/cases/InversifyContainer.ts +16 -4
  47. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +20 -4
  48. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +8 -2
  49. package/dist/templates/next-app/src/base/cases/ResourceState.ts +3 -3
  50. package/dist/templates/next-app/src/base/cases/RouterService.ts +28 -10
  51. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +14 -2
  52. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +1 -1
  53. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +20 -5
  54. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +31 -8
  55. package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +4 -1
  56. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +22 -0
  57. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +27 -7
  58. package/dist/templates/next-app/src/base/port/IOCInterface.ts +9 -0
  59. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +1 -1
  60. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +3 -3
  61. package/dist/templates/next-app/src/base/services/I18nService.ts +20 -10
  62. package/dist/templates/next-app/src/base/services/ResourceService.ts +32 -11
  63. package/dist/templates/next-app/src/base/services/UserService.ts +12 -3
  64. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +9 -6
  65. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +20 -5
  66. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +21 -4
  67. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +12 -3
  68. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +10 -2
  69. package/dist/templates/next-app/src/base/types/{PageProps.ts → AppPageRouter.ts} +4 -1
  70. package/dist/templates/next-app/src/base/types/PagesRouter.ts +9 -0
  71. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +20 -6
  72. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +27 -9
  73. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +6 -4
  74. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +39 -9
  75. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +9 -2
  76. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +37 -10
  77. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +9 -2
  78. package/dist/templates/next-app/src/i18n/loadMessages.ts +103 -0
  79. package/dist/templates/next-app/src/i18n/request.ts +3 -22
  80. package/dist/templates/next-app/src/pages/[locale]/about.tsx +61 -0
  81. package/dist/templates/next-app/src/pages/_app.tsx +50 -0
  82. package/dist/templates/next-app/src/pages/_document.tsx +13 -0
  83. package/dist/templates/next-app/src/{middleware.ts → proxy.ts} +2 -1
  84. package/dist/templates/next-app/src/server/AppErrorApi.ts +1 -1
  85. package/dist/templates/next-app/src/server/AppPageRouteParams.ts +107 -0
  86. package/dist/templates/next-app/src/server/AppSuccessApi.ts +1 -1
  87. package/dist/templates/next-app/src/server/NextApiServer.ts +62 -0
  88. package/dist/templates/next-app/src/server/PagesRouteParams.ts +146 -0
  89. package/dist/templates/next-app/src/server/PasswordEncrypt.ts +8 -2
  90. package/dist/templates/next-app/src/server/ServerAuth.ts +20 -5
  91. package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts} +50 -8
  92. package/dist/templates/next-app/src/server/UserCredentialToken.ts +8 -2
  93. package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +86 -0
  94. package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +42 -0
  95. package/dist/templates/next-app/src/server/controllers/LocalesController.ts +36 -0
  96. package/dist/templates/next-app/src/server/controllers/UserController.ts +86 -0
  97. package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +8 -0
  98. package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +21 -0
  99. package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +11 -0
  100. package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +10 -0
  101. package/dist/templates/next-app/src/server/port/{ParamsHandlerInterface.ts → RouteParamsnHandlerInterface.ts} +9 -2
  102. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
  103. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +8 -0
  104. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +1 -1
  105. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +2 -2
  106. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +31 -9
  107. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +15 -6
  108. package/dist/templates/next-app/src/server/services/AIService.ts +3 -1
  109. package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +5 -2
  110. package/dist/templates/next-app/src/{base → server}/services/AdminLocalesService.ts +2 -2
  111. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +29 -14
  112. package/dist/templates/next-app/src/server/services/ApiUserService.ts +6 -3
  113. package/dist/templates/next-app/src/server/services/UserService.ts +12 -3
  114. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +24 -7
  115. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +17 -5
  116. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +17 -11
  117. package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  118. package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  119. package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  120. package/dist/templates/next-app/src/styles/css/index.css +1 -1
  121. package/dist/templates/next-app/src/styles/css/scrollbar.css +34 -0
  122. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +34 -11
  123. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +69 -39
  124. package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +64 -0
  125. package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +42 -0
  126. package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +34 -0
  127. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +7 -1
  128. package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +17 -0
  129. package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +112 -0
  130. package/dist/templates/next-app/src/uikit/{components → components-app}/LanguageSwitcher.tsx +15 -19
  131. package/dist/templates/next-app/src/uikit/{components → components-app}/ThemeSwitcher.tsx +53 -52
  132. package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +98 -0
  133. package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +93 -0
  134. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +16 -4
  135. package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +32 -0
  136. package/dist/templates/next-app/tsconfig.json +3 -2
  137. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +3 -1
  138. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfig.ts +19 -19
  139. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +8 -8
  140. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +9 -9
  141. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +1 -1
  142. package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +5 -5
  143. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +9 -5
  144. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +8 -4
  145. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +4 -4
  146. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +5 -5
  147. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +3 -3
  148. package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +1 -1
  149. package/dist/templates/react-app/docs/en/components/chat-message-component.md +35 -29
  150. package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +18 -5
  151. package/dist/templates/react-app/docs/en/components/message-base-list-component.md +11 -12
  152. package/dist/templates/react-app/docs/en/request.md +1 -3
  153. package/dist/templates/react-app/docs/zh/components/chat-message-component.md +35 -29
  154. package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +18 -5
  155. package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +11 -12
  156. package/dist/templates/react-app/docs/zh/request.md +1 -3
  157. package/dist/templates/react-app/eslint.config.mjs +10 -5
  158. package/dist/templates/react-app/src/App.tsx +1 -1
  159. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +2 -2
  160. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +14 -11
  161. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +10 -16
  162. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -10
  163. package/dist/templates/react-app/src/base/cases/AppConfig.ts +19 -19
  164. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +28 -5
  165. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +5 -2
  166. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +16 -4
  167. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +1 -1
  168. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +6 -3
  169. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +17 -8
  170. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +4 -6
  171. package/dist/templates/react-app/src/base/cases/ResourceState.ts +3 -3
  172. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +3 -3
  173. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +1 -1
  174. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -2
  175. package/dist/templates/react-app/src/base/port/IOCInterface.ts +4 -2
  176. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +5 -5
  177. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +7 -7
  178. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +8 -8
  179. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +4 -2
  180. package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +3 -3
  181. package/dist/templates/react-app/src/base/services/I18nService.ts +24 -13
  182. package/dist/templates/react-app/src/base/services/IdentifierService.ts +21 -26
  183. package/dist/templates/react-app/src/base/services/RouteService.ts +8 -8
  184. package/dist/templates/react-app/src/base/services/UserBootstrap.ts +2 -2
  185. package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +9 -5
  186. package/dist/templates/react-app/src/base/services/UserService.ts +10 -4
  187. package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +3 -1
  188. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +5 -2
  189. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +11 -4
  190. package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +5 -4
  191. package/dist/templates/react-app/src/pages/base/Layout.tsx +1 -1
  192. package/dist/templates/react-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  193. package/dist/templates/react-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  194. package/dist/templates/react-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  195. package/dist/templates/react-app/src/styles/css/index.css +1 -1
  196. package/dist/templates/react-app/src/styles/css/scrollbar.css +34 -0
  197. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +2 -2
  198. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +4 -4
  199. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +8 -2
  200. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +6 -6
  201. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +1 -1
  202. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +1 -3
  203. package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +4 -1
  204. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +39 -16
  205. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +4 -2
  206. package/package.json +3 -3
  207. package/dist/templates/next-app/src/server/PageParams.ts +0 -66
  208. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +0 -80
  209. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +0 -65
  210. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +0 -58
  211. package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +0 -21
  212. package/dist/templates/react-app/makes/eslint-utils.mjs +0 -195
  213. /package/dist/templates/next-app/{src/app/[locale] → public}/favicon.ico +0 -0
  214. /package/dist/templates/next-app/src/uikit/{components → components-app}/LogoutButton.tsx +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  } from '@qlover/corekit-bridge';
6
6
  import { Logger, ConsoleHandler, TimestampFormatter } from '@qlover/logger';
7
7
  import type { IocRegisterOptions } from '@/base/port/IOCInterface';
8
- import { SupabaseBridge } from '@/server/sqlBridges/SupabaseBridge';
8
+ import { SupabaseBridge } from '@/server/SupabaseBridge';
9
9
  import { IOCIdentifier as I } from '@config/IOCIdentifier';
10
10
 
11
11
  export class ServerIOCRegister implements IOCRegisterInterface<
@@ -19,6 +19,7 @@ export class ServerIOCRegister implements IOCRegisterInterface<
19
19
  *
20
20
  * 一般用于注册全局
21
21
  *
22
+ * @override
22
23
  * @param ioc - IOC container
23
24
  */
24
25
  protected registerGlobals(ioc: IOCContainerInterface): void {
@@ -45,16 +46,22 @@ export class ServerIOCRegister implements IOCRegisterInterface<
45
46
  );
46
47
  }
47
48
 
49
+ /**
50
+ * @override
51
+ */
48
52
  protected registerImplement(ioc: IOCContainerInterface): void {
49
53
  ioc.bind(I.DBBridgeInterface, ioc.get(SupabaseBridge));
50
54
  }
51
55
 
56
+ /**
57
+ * @override
58
+ */
52
59
  protected registerCommon(_ioc: IOCContainerInterface): void {}
53
60
 
54
61
  /**
55
62
  * @override
56
63
  */
57
- register(
64
+ public register(
58
65
  ioc: IOCContainerInterface,
59
66
  _manager: IOCManagerInterface<IOCContainerInterface>
60
67
  ): void {
@@ -0,0 +1,103 @@
1
+ import { useApiLocales } from '@config/common';
2
+
3
+ /**
4
+ * 加载 i18n 消息的公共方法
5
+ * 支持从 API 加载或从 JSON 文件加载
6
+ *
7
+ * @param locale - 要加载的语言代码
8
+ * @param namespace - 可选的命名空间(单个字符串或字符串数组),如果提供则只返回该命名空间下的消息,保留命名空间前缀
9
+ * @returns Promise<Record<string, string>> 返回翻译消息对象
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // 加载所有消息
14
+ * const allMessages = await loadMessages('en');
15
+ *
16
+ * // 加载单个命名空间
17
+ * const commonMessages = await loadMessages('en', 'common');
18
+ *
19
+ * // 加载多个命名空间
20
+ * const messages = await loadMessages('en', ['common', 'page_home']);
21
+ * ```
22
+ */
23
+ export async function loadMessages(
24
+ locale: string,
25
+ namespace?: string | string[]
26
+ ): Promise<Record<string, string>> {
27
+ let allMessages: Record<string, string>;
28
+
29
+ // 如果配置了使用 API 加载本地化数据
30
+ if (useApiLocales) {
31
+ try {
32
+ const app_host = process.env.APP_HOST;
33
+ const localeUrl = `${app_host}/api/locales/json?locale=${locale}`;
34
+ const response = await fetch(localeUrl);
35
+
36
+ if (!response.ok) {
37
+ throw new Error(
38
+ `Failed to fetch locale from API: ${response.statusText}`
39
+ );
40
+ }
41
+
42
+ allMessages = await response.json();
43
+ } catch (error) {
44
+ console.warn(`Failed to load locale from API for ${locale}`, error);
45
+ // 如果 API 加载失败,继续尝试从文件加载
46
+ allMessages = (await import(`../../public/locales/${locale}.json`))
47
+ .default;
48
+ }
49
+ } else {
50
+ allMessages = (await import(`../../public/locales/${locale}.json`)).default;
51
+ }
52
+
53
+ // 如果指定了命名空间,进行过滤
54
+ return filterMessagesByNamespace(allMessages, namespace);
55
+ }
56
+
57
+ /**
58
+ * 根据命名空间过滤消息,保留命名空间前缀
59
+ *
60
+ * @param messages - 所有消息对象
61
+ * @param namespace - 可选的命名空间(单个字符串或字符串数组),如果提供则只返回该命名空间下的消息,保留命名空间前缀
62
+ * @returns 过滤后的消息对象
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * const allMessages = { "common:save": "Save", "common:cancel": "Cancel", "page_home:title": "Home" };
67
+ *
68
+ * // 单个命名空间
69
+ * const commonMessages = filterMessagesByNamespace(allMessages, "common");
70
+ * // 返回: { "common:save": "Save", "common:cancel": "Cancel" }
71
+ *
72
+ * // 多个命名空间
73
+ * const messages = filterMessagesByNamespace(allMessages, ["common", "page_home"]);
74
+ * // 返回: { "common:save": "Save", "common:cancel": "Cancel", "page_home:title": "Home" }
75
+ * ```
76
+ */
77
+ export function filterMessagesByNamespace(
78
+ messages: Record<string, string>,
79
+ namespace?: string | string[]
80
+ ): Record<string, string> {
81
+ // 如果没有指定命名空间,返回所有消息
82
+ if (!namespace) {
83
+ return messages;
84
+ }
85
+
86
+ // 将单个字符串转换为数组,统一处理
87
+ const namespaces = Array.isArray(namespace) ? namespace : [namespace];
88
+ const filteredMessages: Record<string, string> = {};
89
+
90
+ // 遍历所有命名空间
91
+ for (const ns of namespaces) {
92
+ const namespacePrefix = `${ns}:`;
93
+
94
+ for (const [key, value] of Object.entries(messages)) {
95
+ if (key.startsWith(namespacePrefix)) {
96
+ // 保留完整的键名(包括命名空间前缀)
97
+ filteredMessages[key] = value;
98
+ }
99
+ }
100
+ }
101
+
102
+ return filteredMessages;
103
+ }
@@ -1,7 +1,7 @@
1
1
  // src/i18n/request.ts
2
2
 
3
3
  import { getRequestConfig } from 'next-intl/server';
4
- import { useApiLocales } from '@config/common';
4
+ import { loadMessages } from './loadMessages';
5
5
  import { routing, type Locale } from './routing';
6
6
 
7
7
  // Export a function to configure next-intl on each request (server-side)
@@ -14,30 +14,11 @@ export default getRequestConfig(async ({ requestLocale }) => {
14
14
  locale = routing.defaultLocale;
15
15
  }
16
16
 
17
- // use api locales
18
- if (useApiLocales) {
19
- const app_host = process.env.APP_HOST;
20
- const localeUrl = `${app_host}/api/locales/json?locale=${locale}`;
21
- const response = await fetch(localeUrl);
22
- const data = await response.json();
23
- return {
24
- locale,
25
- messages: data,
26
- // 将 MISSING_MESSAGE 错误转换为警告
27
- onError: (error) => {
28
- if (error.message.includes('MISSING_MESSAGE')) {
29
- console.warn(`[i18n] Missing translation: ${error.message}`);
30
- return error.message; // 返回 key 作为 fallback 文本
31
- }
32
- throw error; // 其他错误仍然抛出
33
- }
34
- };
35
- }
17
+ const messages = await loadMessages(locale);
36
18
 
37
- // Dynamically import the translation messages for the selected locale
38
19
  return {
39
20
  locale,
40
- messages: (await import(`../../public/locales/${locale}.json`)).default,
21
+ messages,
41
22
  // 将 MISSING_MESSAGE 错误转换为警告
42
23
  onError: (error) => {
43
24
  if (error.message.includes('MISSING_MESSAGE')) {
@@ -0,0 +1,61 @@
1
+ import { useMemo } from 'react';
2
+ import { PagesRouteParams } from '@/server/PagesRouteParams';
3
+ import type { PagesRouteParamsType } from '@/server/PagesRouteParams';
4
+ import { ClientSeo } from '@/uikit/components/ClientSeo';
5
+ import { PagesRoutePage } from '@/uikit/components-pages/PagesRoutePage';
6
+ import { useI18nInterface } from '@/uikit/hook/useI18nInterface';
7
+ import { aboutI18n, i18nConfig } from '@config/i18n';
8
+ import { COMMON_ADMIN_TITLE } from '@config/Identifier';
9
+ import type { GetStaticPropsContext } from 'next';
10
+
11
+ interface AboutProps {
12
+ messages: Record<string, string>;
13
+ }
14
+
15
+ const namespace = 'page_about';
16
+
17
+ export default function About({}: AboutProps) {
18
+ const i18nInterface = useMemo(() => {
19
+ return {
20
+ ...aboutI18n,
21
+ adminTitle: COMMON_ADMIN_TITLE
22
+ };
23
+ }, []);
24
+ const seoMetadata = useI18nInterface(i18nInterface);
25
+
26
+ return (
27
+ <PagesRoutePage
28
+ data-testid="AboutPage"
29
+ tt={{ title: seoMetadata.title, adminTitle: seoMetadata.adminTitle }}
30
+ showAdminButton={false}
31
+ >
32
+ <ClientSeo i18nInterface={seoMetadata} />
33
+ <div data-testid="About" className="bg-primary h-screen">
34
+ {seoMetadata.title}
35
+ </div>
36
+ </PagesRoutePage>
37
+ );
38
+ }
39
+
40
+ export async function getStaticProps({
41
+ params
42
+ }: GetStaticPropsContext<PagesRouteParamsType>) {
43
+ const pageParams = new PagesRouteParams(params);
44
+
45
+ const messages = await pageParams.getI18nMessages(namespace);
46
+
47
+ return {
48
+ props: {
49
+ messages
50
+ }
51
+ };
52
+ }
53
+
54
+ export async function getStaticPaths() {
55
+ return {
56
+ paths: i18nConfig.supportedLngs.map((locale) => ({
57
+ params: { locale }
58
+ })),
59
+ fallback: false
60
+ };
61
+ }
@@ -0,0 +1,50 @@
1
+ import dynamic from 'next/dynamic';
2
+ import { withRouter } from 'next/router';
3
+ import { NextIntlClientProvider } from 'next-intl';
4
+ import '@/styles/css/index.css';
5
+ import type { PagesRouterProps } from '@/base/types/PagesRouter';
6
+ import { BootstrapsProvider } from '@/uikit/components/BootstrapsProvider';
7
+ import { IOCProvider } from '@/uikit/components/IOCProvider';
8
+ import { i18nConfig } from '@config/i18n';
9
+ import { themeConfig } from '@config/theme';
10
+
11
+ /**
12
+ * 动态导入 ClientRootProvider,禁用 SSR,确保只在客户端渲染
13
+ *
14
+ * 为什么需要禁用 SSR?
15
+ * 1. ClientRootProvider 内部使用了 AntdRegistry(来自 @ant-design/nextjs-registry),
16
+ * 该组件主要针对 App Router 设计,在 Pages Router 的 SSR 环境中可能存在兼容性问题
17
+ * 2. ClientRootProvider 使用了 useIOC() hook,该 hook 在服务端渲染时可能无法正常工作
18
+ * 3. ThemeProvider 和 AntdThemeProvider 可能依赖浏览器 API(如 localStorage、window 等),
19
+ * 在服务端渲染时会导致错误或页面一直处于加载状态
20
+ *
21
+ * 通过在 Pages Router 中使用 dynamic import 并设置 ssr: false,
22
+ * 可以确保 ClientRootProvider 只在客户端渲染,避免 SSR 相关的问题
23
+ */
24
+ const ClientRootProvider = dynamic(
25
+ () =>
26
+ import('@/uikit/components/ClientRootProvider').then(
27
+ (mod) => mod.ClientRootProvider
28
+ ),
29
+ {
30
+ ssr: false
31
+ }
32
+ );
33
+
34
+ function App({ Component, pageProps, router }: PagesRouterProps) {
35
+ const locale = (router.query.locale as string) || i18nConfig.fallbackLng;
36
+
37
+ return (
38
+ <IOCProvider>
39
+ <NextIntlClientProvider locale={locale} messages={pageProps.messages}>
40
+ <BootstrapsProvider>
41
+ <ClientRootProvider themeConfig={themeConfig}>
42
+ <Component {...pageProps} />
43
+ </ClientRootProvider>
44
+ </BootstrapsProvider>
45
+ </NextIntlClientProvider>
46
+ </IOCProvider>
47
+ );
48
+ }
49
+
50
+ export default withRouter(App);
@@ -0,0 +1,13 @@
1
+ import { Html, Head, Main, NextScript } from 'next/document';
2
+
3
+ export default function Document() {
4
+ return (
5
+ <Html data-testid="PagesRootLayout">
6
+ <Head />
7
+ <body>
8
+ <Main />
9
+ <NextScript />
10
+ </body>
11
+ </Html>
12
+ );
13
+ }
@@ -20,6 +20,7 @@ export const config = {
20
20
  // - Next.js internals (_next/*)
21
21
  // - Static files (*.svg, *.png, *.jpg, *.jpeg, *.gif, *.ico)
22
22
  // - Other static assets and special files
23
- '/((?!api|_next|.*\\.(?:svg|png|jpg|jpeg|gif|ico)|favicon.ico|sitemap.xml|sitemap-0.xml).*)'
23
+ // - Manifest file (manifest.webmanifest)
24
+ '/((?!api|_next|.*\\.(?:svg|png|jpg|jpeg|gif|ico)|favicon.ico|sitemap.xml|sitemap-0.xml|manifest.webmanifest).*)'
24
25
  ]
25
26
  };
@@ -1,7 +1,7 @@
1
1
  import type { AppApiErrorInterface } from '@/base/port/AppApiInterface';
2
2
 
3
3
  export class AppErrorApi implements AppApiErrorInterface {
4
- readonly success = false;
4
+ public readonly success = false;
5
5
 
6
6
  constructor(
7
7
  public readonly id: string,
@@ -0,0 +1,107 @@
1
+ import { cookies } from 'next/headers';
2
+ import { notFound } from 'next/navigation';
3
+ import { getMessages, getTranslations } from 'next-intl/server';
4
+ import { TranslateI18nInterface } from '@/base/cases/TranslateI18nInterface';
5
+ import { filterMessagesByNamespace } from '@/i18n/loadMessages';
6
+ import { i18nConfig } from '@config/i18n';
7
+ import type { LocaleType, PageI18nInterface } from '@config/i18n';
8
+ import { themeConfig } from '@config/theme';
9
+ import type { RouteParamsnHandlerInterface } from './port/RouteParamsnHandlerInterface';
10
+
11
+ export interface PageWithParams {
12
+ params?: Promise<PageParamsType>;
13
+ }
14
+
15
+ export interface PageParamsType {
16
+ locale?: string;
17
+ }
18
+
19
+ /**
20
+ * 用于 src/app/page 路由的参数管理工具
21
+ */
22
+ export class AppPageRouteParams<
23
+ T extends PageParamsType
24
+ > implements RouteParamsnHandlerInterface {
25
+ private locale: string | null;
26
+
27
+ constructor(protected readonly params: T) {
28
+ this.locale = this.params.locale || i18nConfig.fallbackLng;
29
+ }
30
+
31
+ /**
32
+ * @override
33
+ */
34
+ public getLocale(defaultLocale?: string): string {
35
+ if (this.locale) {
36
+ return this.locale;
37
+ }
38
+
39
+ this.locale = this.params.locale || defaultLocale || i18nConfig.fallbackLng;
40
+
41
+ return this.locale;
42
+ }
43
+
44
+ /**
45
+ * @override
46
+ */
47
+ public getI18nWithNotFound(): string {
48
+ const locale = this.getLocale();
49
+
50
+ if (!i18nConfig.supportedLngs.includes(locale as LocaleType)) {
51
+ notFound();
52
+ }
53
+
54
+ return locale;
55
+ }
56
+
57
+ /**
58
+ * 获取 i18n 消息
59
+ * 使用 next-intl 的 getMessages 加载消息
60
+ *
61
+ * @override
62
+ * @param namespace - 可选的命名空间(单个字符串或字符串数组),会与默认命名空间 ['common', 'api'] 合并
63
+ * @returns Promise<Record<string, string>> 返回翻译消息对象
64
+ */
65
+ public async getI18nMessages(
66
+ namespace?: string | string[]
67
+ ): Promise<Record<string, string>> {
68
+ const locale = this.getLocale();
69
+ const messages = await getMessages({ locale });
70
+ // 将默认命名空间和用户提供的命名空间合并
71
+ const defaultNamespaces = [...i18nConfig.defaultNamespaces];
72
+ const userNamespaces = namespace
73
+ ? Array.isArray(namespace)
74
+ ? namespace
75
+ : [namespace]
76
+ : [];
77
+ // 合并并去重
78
+ const namespaces = [...new Set([...defaultNamespaces, ...userNamespaces])];
79
+ return filterMessagesByNamespace(messages, namespaces);
80
+ }
81
+
82
+ /**
83
+ * @override
84
+ */
85
+ public async getI18nInterface<T extends PageI18nInterface>(
86
+ i18nInterface: T,
87
+ _namespace?: string
88
+ ): Promise<T> {
89
+ const t = await getTranslations({
90
+ locale: this.getLocale()
91
+ // namespace: namespace
92
+ });
93
+
94
+ return TranslateI18nInterface.translate<T>(i18nInterface, t);
95
+ }
96
+
97
+ /**
98
+ * @override
99
+ */
100
+ public async getTheme(): Promise<string> {
101
+ const cookieStore = await cookies();
102
+ return (
103
+ cookieStore.get(themeConfig.storageKey)?.value ||
104
+ themeConfig.supportedThemes[0]
105
+ );
106
+ }
107
+ }
@@ -1,7 +1,7 @@
1
1
  import type { AppApiSuccessInterface } from '@/base/port/AppApiInterface';
2
2
 
3
3
  export class AppSuccessApi<T = unknown> implements AppApiSuccessInterface<T> {
4
- readonly success = true;
4
+ public readonly success = true;
5
5
 
6
6
  constructor(public readonly data?: T) {}
7
7
  }
@@ -0,0 +1,62 @@
1
+ import { ExecutorError } from '@qlover/fe-corekit';
2
+ import { NextResponse } from 'next/server';
3
+ import {
4
+ isAppApiErrorInterface,
5
+ isAppApiSuccessInterface,
6
+ type AppApiResult
7
+ } from '@/base/port/AppApiInterface';
8
+ import type { BootstrapServerContextValue } from '@/core/bootstraps/BootstrapServer';
9
+ import { BootstrapServer } from '@/core/bootstraps/BootstrapServer';
10
+ import { AppErrorApi } from './AppErrorApi';
11
+ import { AppSuccessApi } from './AppSuccessApi';
12
+ import type { PromiseTask } from '@qlover/fe-corekit';
13
+
14
+ export class NextApiServer extends BootstrapServer {
15
+ /**
16
+ * @override
17
+ */
18
+ protected isAppApiResult(result: unknown): result is AppApiResult {
19
+ return isAppApiSuccessInterface(result) || isAppApiErrorInterface(result);
20
+ }
21
+
22
+ /**
23
+ * @override
24
+ */
25
+ public async run<Result>(
26
+ task?: PromiseTask<Result | AppApiResult, BootstrapServerContextValue>
27
+ ): Promise<AppApiResult> {
28
+ const result = await this.execNoError(task);
29
+
30
+ // Is result is AppApiResult, return it directly
31
+ if (this.isAppApiResult(result)) {
32
+ return result;
33
+ }
34
+
35
+ // If result is ExecutorError, return AppErrorApi
36
+ if (result instanceof ExecutorError) {
37
+ return new AppErrorApi(result.id, result.message);
38
+ }
39
+
40
+ // If result is Error, return AppErrorApi
41
+ if (result instanceof Error) {
42
+ return new AppErrorApi('SERVER_ERROR', result.message);
43
+ }
44
+
45
+ return new AppSuccessApi(result);
46
+ }
47
+
48
+ /**
49
+ * @override
50
+ */
51
+ public async runWithJson<Result>(
52
+ task?: PromiseTask<Result | AppApiResult, BootstrapServerContextValue>
53
+ ): Promise<NextResponse> {
54
+ const result = await this.run(task);
55
+
56
+ if (!result.success) {
57
+ return NextResponse.json(result, { status: 400 });
58
+ }
59
+
60
+ return NextResponse.json(result);
61
+ }
62
+ }
@@ -0,0 +1,146 @@
1
+ import { TranslateI18nInterface } from '@/base/cases/TranslateI18nInterface';
2
+ import { loadMessages } from '@/i18n/loadMessages';
3
+ import { i18nConfig } from '@config/i18n';
4
+ import type { LocaleType, PageI18nInterface } from '@config/i18n';
5
+ import { themeConfig } from '@config/theme';
6
+ import type { RouteParamsnHandlerInterface } from './port/RouteParamsnHandlerInterface';
7
+ import type { useTranslations } from 'next-intl';
8
+ import type { ParsedUrlQuery } from 'querystring';
9
+
10
+ export interface PagesRouteParamsType extends ParsedUrlQuery {
11
+ locale?: string | string[];
12
+ }
13
+
14
+ /**
15
+ * 用于 src/pages/** 路由的参数管理工具
16
+ *
17
+ * ⚠️ 注意:此类仅在服务器端使用(getStaticProps、getServerSideProps 中)
18
+ * 此类直接从 JSON 文件加载翻译消息,不依赖 next-intl/server,因此可以在 pages 目录中安全使用
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * export async function getStaticProps({ params }: GetStaticPropsContext<PagesRouteParamsType>) {
23
+ * const pageParams = new PagesRouteParams(params);
24
+ * const messages = await pageParams.getI18nMessages();
25
+ * return { props: { messages } };
26
+ * }
27
+ * ```
28
+ */
29
+ export class PagesRouteParams implements RouteParamsnHandlerInterface {
30
+ private locale: string | null;
31
+
32
+ constructor(protected readonly params: PagesRouteParamsType = {}) {
33
+ const localeParam = Array.isArray(this.params.locale)
34
+ ? this.params.locale[0]
35
+ : this.params.locale;
36
+ this.locale = localeParam || i18nConfig.fallbackLng;
37
+ }
38
+
39
+ /**
40
+ * @override
41
+ */
42
+ public getLocale(defaultLocale?: string): string {
43
+ if (this.locale) {
44
+ return this.locale;
45
+ }
46
+
47
+ const localeParam = Array.isArray(this.params.locale)
48
+ ? this.params.locale[0]
49
+ : this.params.locale;
50
+ this.locale = localeParam || defaultLocale || i18nConfig.fallbackLng;
51
+
52
+ return this.locale;
53
+ }
54
+
55
+ /**
56
+ * 获取 locale,如果 locale 不支持则抛出错误
57
+ * 注意:在 pages 目录中,应该在 getStaticProps/getServerSideProps 中返回 { notFound: true } 来处理不支持的 locale
58
+ *
59
+ * @override
60
+ * @returns 支持的 locale 字符串
61
+ * @throws 如果 locale 不支持
62
+ */
63
+ public getI18nWithNotFound(): string {
64
+ const locale = this.getLocale();
65
+
66
+ if (!i18nConfig.supportedLngs.includes(locale as LocaleType)) {
67
+ // 在 pages 目录中,应该返回 { notFound: true } 而不是调用 notFound()
68
+ // 这里抛出错误,由调用方在 getStaticProps/getServerSideProps 中处理
69
+ throw new Error(`Unsupported locale: ${locale}`);
70
+ }
71
+
72
+ return locale;
73
+ }
74
+
75
+ /**
76
+ * 获取 i18n 消息
77
+ * 使用公共方法加载消息,支持从 API 加载或动态导入 JSON 文件
78
+ * 不依赖 next-intl/server,适用于 getStaticProps/getServerSideProps
79
+ *
80
+ * @override
81
+ * @param namespace - 可选的命名空间(单个字符串或字符串数组),会与默认命名空间 ['common', 'api'] 合并
82
+ * @returns Promise<Record<string, string>> 返回翻译消息对象
83
+ */
84
+ public async getI18nMessages(
85
+ namespace?: string | string[]
86
+ ): Promise<Record<string, string>> {
87
+ const locale = this.getLocale();
88
+ // 将默认命名空间和用户提供的命名空间合并
89
+ const defaultNamespaces = [...i18nConfig.defaultNamespaces];
90
+ const userNamespaces = namespace
91
+ ? Array.isArray(namespace)
92
+ ? namespace
93
+ : [namespace]
94
+ : [];
95
+ // 合并并去重
96
+ const namespaces = [...new Set([...defaultNamespaces, ...userNamespaces])];
97
+ return loadMessages(locale, namespaces);
98
+ }
99
+
100
+ /**
101
+ * 获取翻译后的 i18n 接口
102
+ * 创建一个简单的翻译函数,基于加载的 messages
103
+
104
+ * @override
105
+ */
106
+ public async getI18nInterface<T extends PageI18nInterface>(
107
+ i18nInterface: T,
108
+ namespace?: string
109
+ ): Promise<T> {
110
+ const messages = await this.getI18nMessages(namespace);
111
+
112
+ // 创建一个简单的翻译函数
113
+ const t = (
114
+ key: string,
115
+ params?: Record<string, string | number | Date>
116
+ ) => {
117
+ const fullKey = namespace ? `${namespace}:${key}` : key;
118
+
119
+ let message = messages[fullKey] || messages[key] || key;
120
+
121
+ // 简单的参数替换
122
+ if (params) {
123
+ Object.entries(params).forEach(([paramKey, paramValue]) => {
124
+ message = message.replace(
125
+ new RegExp(`\\{${paramKey}\\}`, 'g'),
126
+ String(paramValue)
127
+ );
128
+ });
129
+ }
130
+
131
+ return message;
132
+ };
133
+
134
+ return TranslateI18nInterface.translate<T>(
135
+ i18nInterface,
136
+ t as ReturnType<typeof useTranslations>
137
+ );
138
+ }
139
+
140
+ /**
141
+ * @override
142
+ */
143
+ public getTheme(): string {
144
+ return themeConfig.defaultTheme;
145
+ }
146
+ }
@@ -2,11 +2,17 @@ import crypto from 'crypto';
2
2
  import type { Encryptor } from '@qlover/fe-corekit';
3
3
 
4
4
  export class PasswordEncrypt implements Encryptor<string, string> {
5
- encrypt(password: string): string {
5
+ /**
6
+ * @override
7
+ */
8
+ public encrypt(password: string): string {
6
9
  return crypto.createHash('md5').update(password).digest('hex');
7
10
  }
8
11
 
9
- decrypt(): string {
12
+ /**
13
+ * @override
14
+ */
15
+ public decrypt(): string {
10
16
  throw new Error('Md5Encrypt is not decryptable');
11
17
  }
12
18
  }