@qlover/create-app 0.6.3 → 0.7.0

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 (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
  5. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
  6. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
  7. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
  8. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
  9. package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
  10. package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
  11. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
  12. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
  13. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
  14. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
  15. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
  16. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
  17. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
  18. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
  19. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
  20. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
  21. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
  22. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
  23. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
  24. package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
  25. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
  26. package/dist/templates/react-app/config/app.router.ts +155 -0
  27. package/dist/templates/react-app/config/common.ts +9 -1
  28. package/dist/templates/react-app/docs/en/test-guide.md +782 -0
  29. package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
  30. package/dist/templates/react-app/package.json +8 -19
  31. package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
  32. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
  33. package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
  34. package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
  35. package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
  36. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
  37. package/dist/templates/react-app/src/core/globals.ts +1 -3
  38. package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
  39. package/dist/templates/react-app/src/main.tsx +6 -1
  40. package/dist/templates/react-app/src/pages/404.tsx +0 -1
  41. package/dist/templates/react-app/src/pages/500.tsx +1 -1
  42. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
  43. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
  44. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
  45. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
  46. package/dist/templates/react-app/tsconfig.json +2 -1
  47. package/dist/templates/react-app/tsconfig.test.json +13 -0
  48. package/dist/templates/react-app/vite.config.ts +3 -2
  49. package/package.json +1 -1
@@ -7,34 +7,21 @@
7
7
  "homepage": "",
8
8
  "author": "qlover",
9
9
  "license": "ISC",
10
- "main": "./dist/es/index.js",
11
- "module": "./dist/es/index.js",
12
- "types": "./dist/es/index.d.ts",
13
- "exports": {
14
- ".": {
15
- "types": "./dist/es/index.d.ts",
16
- "import": "./dist/es/index.js",
17
- "require": "./dist/cjs/index.js"
18
- },
19
- "./cjs/*": "./dist/cjs/*",
20
- "./es/*": "./dist/es/*",
21
- "./package.json": "./package.json"
22
- },
23
10
  "repository": {
24
11
  "type": "git",
25
12
  "url": "git+https://github.com/qlover/fe-base.git",
26
13
  "directory": ""
27
14
  },
28
15
  "files": [
29
- "bin",
30
16
  "dist",
31
17
  "package.json",
32
- "README.md"
18
+ "README.md",
19
+ "README.en.md",
20
+ "docs"
33
21
  ],
34
22
  "keywords": [
35
- "scripts",
36
- "release",
37
- "fe-release"
23
+ "react-app",
24
+ "react-app-template"
38
25
  ],
39
26
  "publishConfig": {
40
27
  "access": "public"
@@ -57,7 +44,7 @@
57
44
  "@qlover/corekit-bridge": "latest",
58
45
  "@qlover/fe-corekit": "latest",
59
46
  "@qlover/logger": "^0.1.1",
60
- "@qlover/slice-store-react": "^1.0.8",
47
+ "@qlover/slice-store-react": "^1.2.6",
61
48
  "@tailwindcss/postcss": "^4.1.8",
62
49
  "@tailwindcss/vite": "^4.1.8",
63
50
  "antd": "^5.25.3",
@@ -80,6 +67,7 @@
80
67
  "@qlover/eslint-plugin-fe-dev": "^0.2.0",
81
68
  "@qlover/fe-scripts": "latest",
82
69
  "@qlover/fe-standard": "^0.0.4",
70
+ "@testing-library/react": "^16.3.0",
83
71
  "@types/lodash": "^4.17.13",
84
72
  "@types/react": "^18.3.11",
85
73
  "@types/react-dom": "^18.3.0",
@@ -95,6 +83,7 @@
95
83
  "eslint-plugin-react-refresh": "^0.4.14",
96
84
  "eslint-plugin-vitest": "^0.5.4",
97
85
  "globals": "^15.12.0",
86
+ "jsdom": "^26.1.0",
98
87
  "postcss": "^8.5.4",
99
88
  "prettier": "^3.5.3",
100
89
  "sass-embedded": "^1.79.4",
@@ -28,6 +28,17 @@ import type { EnvConfigInterface } from '@qlover/corekit-bridge';
28
28
  * console.log(config.aiApiBaseUrl); // Value from VITE_AI_API_BASE_URL
29
29
  */
30
30
  export class AppConfig implements EnvConfigInterface {
31
+ constructor(
32
+ /**
33
+ * Current environment mode for Vite
34
+ * @description Represents the running environment (development, production, etc.)
35
+ * Automatically set based on the current .env file being used
36
+ *
37
+ * from vite.config `VITE_USER_NODE_ENV`
38
+ */
39
+ readonly env: string = import.meta.env.VITE_USER_NODE_ENV
40
+ ) {}
41
+
31
42
  /**
32
43
  * Application name identifier
33
44
  * @description Injected from VITE_APP_NAME environment variable
@@ -40,15 +51,6 @@ export class AppConfig implements EnvConfigInterface {
40
51
  */
41
52
  readonly appVersion = '';
42
53
 
43
- /**
44
- * Current environment mode for Vite
45
- * @description Represents the running environment (development, production, etc.)
46
- * Automatically set based on the current .env file being used
47
- *
48
- * from vite.config `mode`
49
- */
50
- readonly env: string = import.meta.env.MODE;
51
-
52
54
  /**
53
55
  * Storage key for user authentication token
54
56
  * @description Injected from VITE_USER_TOKEN_STORAGE_KEY environment variable
@@ -113,4 +115,9 @@ export class AppConfig implements EnvConfigInterface {
113
115
  * Project startup href, usually from window.location.href
114
116
  */
115
117
  readonly bootHref = '';
118
+
119
+ /** Flag indicating if the current environment is production */
120
+ get isProduction(): boolean {
121
+ return this.env === 'production';
122
+ }
116
123
  }
@@ -12,6 +12,12 @@ export class PublicAssetsPath {
12
12
  constructor(protected prefix: string = routerPrefix) {}
13
13
 
14
14
  getPath(path: string): string {
15
- return this.prefix + `/${path}`;
15
+ if (!this.prefix) {
16
+ return path.startsWith('/') ? path : `/${path}`;
17
+ }
18
+ const prefix = this.prefix.endsWith('/')
19
+ ? this.prefix.slice(0, -1)
20
+ : this.prefix;
21
+ return prefix + (path.startsWith('/') ? path : `/${path}`);
16
22
  }
17
23
  }
@@ -9,6 +9,8 @@ import {
9
9
  type StoreStateInterface,
10
10
  StoreInterface
11
11
  } from '@qlover/corekit-bridge';
12
+ import { useLocaleRoutes } from '@config/common';
13
+
12
14
  const { supportedLngs, fallbackLng } = i18nConfig;
13
15
 
14
16
  export type I18nServiceLocale = (typeof supportedLngs)[number];
@@ -46,8 +48,10 @@ export class I18nService
46
48
  merge({}, i18nConfig, {
47
49
  debug,
48
50
  detection: {
49
- order: ['pathLanguageDetector', 'navigator'], // use custom detector
50
- caches: []
51
+ order: useLocaleRoutes
52
+ ? ['pathLanguageDetector', 'navigator', 'localStorage']
53
+ : ['localStorage', 'navigator'],
54
+ caches: useLocaleRoutes ? [] : ['localStorage']
51
55
  }
52
56
  })
53
57
  );
@@ -67,8 +71,11 @@ export class I18nService
67
71
 
68
72
  return fallbackLng;
69
73
  },
70
- cacheUserLanguage() {
71
- // no cache, because we get language from URL
74
+ cacheUserLanguage(lng: string) {
75
+ // Only cache language if not using locale routes
76
+ if (!useLocaleRoutes) {
77
+ localStorage.setItem('i18nextLng', lng);
78
+ }
72
79
  }
73
80
  };
74
81
  i18n.services.languageDetector.addDetector(pathLanguageDetector);
@@ -76,6 +83,10 @@ export class I18nService
76
83
 
77
84
  async changeLanguage(language: I18nServiceLocale): Promise<void> {
78
85
  await i18n.changeLanguage(language);
86
+ // 如果不使用本地化路由,则保存语言设置到本地存储
87
+ if (!useLocaleRoutes) {
88
+ localStorage.setItem('i18nextLng', language);
89
+ }
79
90
  }
80
91
 
81
92
  changeLoading(loading: boolean): void {
@@ -14,11 +14,20 @@ export type RouterServiceDependencies = {
14
14
 
15
15
  export type RouterServiceOptions = {
16
16
  routes: RouteConfigValue[];
17
+ /**
18
+ * Whether to use locale routes
19
+ *
20
+ * @default `false`
21
+ */
22
+ hasLocalRoutes?: boolean;
17
23
  logger: LoggerInterface;
18
24
  };
19
25
 
20
26
  export class RouterServiceState implements StoreStateInterface {
21
- constructor(public routes: RouteConfigValue[] = []) {}
27
+ constructor(
28
+ public routes: RouteConfigValue[] = [],
29
+ public localeRoutes: boolean = false
30
+ ) {}
22
31
  }
23
32
 
24
33
  export class RouteService
@@ -31,7 +40,9 @@ export class RouteService
31
40
  dependencies?: RouterServiceDependencies;
32
41
 
33
42
  constructor(private options: RouterServiceOptions) {
34
- super(() => new RouterServiceState(options.routes));
43
+ super(
44
+ () => new RouterServiceState(options.routes, !!options.hasLocalRoutes)
45
+ );
35
46
  }
36
47
 
37
48
  get logger(): LoggerInterface {
@@ -51,8 +62,11 @@ export class RouteService
51
62
  }
52
63
 
53
64
  composePath(path: string): string {
54
- const targetLang = I18nService.getCurrentLanguage();
55
- return `/${targetLang}${path}`;
65
+ if (this.state.localeRoutes) {
66
+ const targetLang = I18nService.getCurrentLanguage();
67
+ return `/${targetLang}${path}`;
68
+ }
69
+ return path.startsWith('/') ? path : `/${path}`;
56
70
  }
57
71
 
58
72
  /**
@@ -70,13 +84,19 @@ export class RouteService
70
84
  }
71
85
 
72
86
  changeRoutes(routes: RouteConfigValue[]): void {
73
- this.emit({ routes });
87
+ this.emit({ routes, localeRoutes: this.state.localeRoutes });
74
88
  }
75
89
 
76
- goto(path: string, options?: NavigateOptions): void {
90
+ goto(
91
+ path: string,
92
+ options?: NavigateOptions & {
93
+ navigate?: NavigateFunction;
94
+ }
95
+ ): void {
96
+ const { navigate, ...rest } = options || {};
77
97
  path = this.composePath(path);
78
98
  this.logger.debug('Goto path => ', path);
79
- this.navigate?.(path, options);
99
+ (navigate || this.navigate)?.(path, rest);
80
100
  }
81
101
 
82
102
  gotoLogin(): void {
@@ -86,4 +106,20 @@ export class RouteService
86
106
  replaceToHome(): void {
87
107
  this.goto('/', { replace: true });
88
108
  }
109
+
110
+ redirectToDefault(navigate: NavigateFunction): void {
111
+ this.goto('/', { replace: true, navigate });
112
+ }
113
+
114
+ i18nGuard(lng: string, navigate: NavigateFunction): void {
115
+ if (!this.state.localeRoutes) {
116
+ return;
117
+ }
118
+
119
+ if (!lng) {
120
+ this.goto('/404', { replace: true, navigate });
121
+ } else if (!I18nService.isValidLanguage(lng)) {
122
+ this.goto('/404', { replace: true, navigate });
123
+ }
124
+ }
89
125
  }
@@ -1,34 +1,52 @@
1
- import { Bootstrap } from '@qlover/corekit-bridge';
1
+ import {
2
+ Bootstrap,
3
+ IOCContainerInterface,
4
+ IOCFunctionInterface
5
+ } from '@qlover/corekit-bridge';
2
6
  import { envBlackList, envPrefix, browserGlobalsName } from '@config/common';
3
- import { IOC } from '../IOC';
4
7
  import * as globals from '../globals';
5
- import { GLOBAL_NO_WINDOW } from '@config/Identifier/common.error';
6
8
  import { IocRegisterImpl } from '../registers/IocRegisterImpl';
7
9
  import { BootstrapsRegistry } from './BootstrapsRegistry';
10
+ import { isObject } from 'lodash';
11
+ import { IOCIdentifierMap } from '../IOC';
12
+
13
+ export type BootstrapAppArgs = {
14
+ /**
15
+ * 启动的根节点,通常是window
16
+ */
17
+ root: unknown;
18
+ /**
19
+ * 启动的web地址
20
+ */
21
+ bootHref: string;
22
+ /**
23
+ * IOC容器
24
+ */
25
+ IOC: IOCFunctionInterface<IOCIdentifierMap, IOCContainerInterface>;
26
+ };
8
27
 
9
28
  export class BootstrapApp {
10
- static async main(): Promise<void> {
11
- const root = window;
29
+ static async main(args: BootstrapAppArgs): Promise<BootstrapAppArgs> {
30
+ const { root, bootHref, IOC } = args;
12
31
 
13
- if (!(typeof root !== 'undefined' && root instanceof Window)) {
14
- throw new Error(GLOBAL_NO_WINDOW);
32
+ if (!isObject(root)) {
33
+ throw new Error('root is not an object');
15
34
  }
16
35
 
17
36
  const { logger, appConfig } = globals;
18
- const { pathname } = root.location;
19
37
 
20
38
  const bootstrap = new Bootstrap({
21
39
  root,
22
40
  logger,
23
41
  ioc: {
24
42
  manager: IOC,
25
- register: new IocRegisterImpl({ pathname, appConfig })
43
+ register: new IocRegisterImpl({ pathname: bootHref, appConfig })
26
44
  },
27
45
  envOptions: {
28
46
  target: appConfig,
29
47
  source: {
30
48
  ...import.meta.env,
31
- [envPrefix + 'BOOT_HREF']: root.location.href
49
+ [envPrefix + 'BOOT_HREF']: bootHref
32
50
  },
33
51
  prefix: envPrefix,
34
52
  blackList: envBlackList
@@ -49,7 +67,10 @@ export class BootstrapApp {
49
67
 
50
68
  await bootstrap.use(bootstrapsRegistry.register()).start();
51
69
  } catch (error) {
70
+ console.log(error);
52
71
  logger.error(`${appConfig.appName} starup error:`, error);
53
72
  }
73
+
74
+ return args;
54
75
  }
55
76
  }
@@ -33,7 +33,7 @@ export class BootstrapsRegistry {
33
33
  IOC(I18nKeyErrorPlugin)
34
34
  ];
35
35
 
36
- if (this.appConfig.env !== 'production') {
36
+ if (!this.appConfig.isProduction) {
37
37
  bootstrapList.push(printBootstrap);
38
38
  }
39
39
 
@@ -16,8 +16,6 @@ import { DialogHandler } from '@/base/cases/DialogHandler';
16
16
  import { loggerStyles } from '@config/common';
17
17
  import { AppConfig } from '@/base/cases/AppConfig';
18
18
 
19
- const isProduction = import.meta.env.VITE_USER_NODE_ENV === 'production';
20
-
21
19
  export const appConfig = new AppConfig();
22
20
 
23
21
  export const dialogHandler = new DialogHandler();
@@ -27,7 +25,7 @@ export const dialogHandler = new DialogHandler();
27
25
  */
28
26
  export const logger = new Logger({
29
27
  handlers: new ConsoleHandler(new ColorFormatter(loggerStyles)),
30
- silent: isProduction,
28
+ silent: appConfig.isProduction,
31
29
  level: 'debug'
32
30
  });
33
31
 
@@ -16,7 +16,8 @@ import { themeConfig } from '@config/theme';
16
16
  import { localStorage, logger } from '../globals';
17
17
  import { I18nService } from '@/base/services/I18nService';
18
18
  import { RouteService } from '@/base/services/RouteService';
19
- import { baseRoutes } from '@config/app.router';
19
+ import { baseRoutes, baseNoLocaleRoutes } from '@config/app.router';
20
+ import { useLocaleRoutes } from '@config/common';
20
21
  import { UserService } from '@/base/services/UserService';
21
22
  import { IOCRegister } from '../IOC';
22
23
  import { IOCIdentifier } from '@config/IOCIdentifier';
@@ -62,8 +63,9 @@ export const RegisterCommon: IOCRegister = {
62
63
  container.bind(
63
64
  RouteService,
64
65
  new RouteService({
65
- routes: baseRoutes,
66
- logger
66
+ routes: useLocaleRoutes ? baseRoutes : baseNoLocaleRoutes,
67
+ logger,
68
+ hasLocalRoutes: useLocaleRoutes
67
69
  })
68
70
  );
69
71
 
@@ -4,8 +4,13 @@ import { StrictMode } from 'react';
4
4
  import { createRoot } from 'react-dom/client';
5
5
  import App from './App.tsx';
6
6
  import { BootstrapApp } from './core/bootstraps/BootstrapApp';
7
+ import { IOC } from './core/IOC.ts';
7
8
 
8
- BootstrapApp.main();
9
+ BootstrapApp.main({
10
+ root: window,
11
+ bootHref: window.location.href,
12
+ IOC: IOC
13
+ });
9
14
 
10
15
  createRoot(document.getElementById('root')!).render(
11
16
  <StrictMode>
@@ -8,7 +8,6 @@ export default function NotFound({ route }: { route?: string }) {
8
8
  <div className="flex flex-col justify-center min-h-screen py-6 bg-background sm:py-12">
9
9
  <div className="relative py-3 mx-auto sm:max-w-xl">
10
10
  <h1 className="text-text text-2xl font-bold text-center">
11
- 404 -
12
11
  {route ? `${t(NOT_FOUND_COMPONENT)}: ${route}` : t(PAGE_404_TITLE)}
13
12
  </h1>
14
13
  </div>
@@ -7,7 +7,7 @@ export default function NotFound500() {
7
7
  <div className="flex flex-col justify-center min-h-screen py-6 bg-background sm:py-12">
8
8
  <div className="relative py-3 mx-auto sm:max-w-xl">
9
9
  <h1 className="text-text text-2xl font-bold text-center">
10
- 500 - {t(PAGE_500_TITLE)}
10
+ {t(PAGE_500_TITLE)}
11
11
  </h1>
12
12
  </div>
13
13
  </div>
@@ -1,3 +1,5 @@
1
+ import { RouteService } from '@/base/services/RouteService';
2
+ import { IOC } from '@/core/IOC';
1
3
  import { useEffect } from 'react';
2
4
  import { useNavigate } from 'react-router-dom';
3
5
 
@@ -6,7 +8,7 @@ const RedirectToDefault = () => {
6
8
 
7
9
  useEffect(() => {
8
10
  // Redirect to the default language path
9
- navigate('/en', { replace: true });
11
+ IOC(RouteService).redirectToDefault(navigate);
10
12
  }, [navigate]);
11
13
 
12
14
  return null;
@@ -11,7 +11,10 @@ export default function BaseHeader({
11
11
  showLogoutButton?: boolean;
12
12
  }) {
13
13
  return (
14
- <header className="h-14 bg-secondary border-b border-border sticky top-0 z-50">
14
+ <header
15
+ data-testid="base-header"
16
+ className="h-14 bg-secondary border-b border-border sticky top-0 z-50"
17
+ >
15
18
  <div className="flex items-center justify-between h-full px-4 mx-auto max-w-7xl">
16
19
  <div className="flex items-center">
17
20
  <LocaleLink
@@ -19,11 +22,15 @@ export default function BaseHeader({
19
22
  className="flex items-center hover:opacity-80 transition-opacity"
20
23
  >
21
24
  <img
25
+ data-testid="base-header-logo"
22
26
  src={IOC(PublicAssetsPath).getPath('/logo.svg')}
23
27
  alt="logo"
24
28
  className="h-8 w-auto"
25
29
  />
26
- <span className="ml-2 text-lg font-semibold text-text">
30
+ <span
31
+ data-testid="base-header-app-name"
32
+ className="ml-2 text-lg font-semibold text-text"
33
+ >
27
34
  {IOC('AppConfig').appName}
28
35
  </span>
29
36
  </LocaleLink>
@@ -1,5 +1,6 @@
1
1
  import { LinkProps, Link as RouterLink, To, useParams } from 'react-router-dom';
2
2
  import { ReactNode } from 'react';
3
+ import { useLocaleRoutes } from '@config/common';
3
4
 
4
5
  interface LocaleLinkProps extends Omit<LinkProps, 'href' | 'to'> {
5
6
  href: string | To;
@@ -21,19 +22,20 @@ const LocaleLink: React.FC<LocaleLinkProps> = ({
21
22
  locale = locale || lng;
22
23
 
23
24
  const isDefaultLocale = locale === defaultLocale;
25
+ const shouldAddLocale = useLocaleRoutes && !isDefaultLocale;
24
26
 
25
27
  let localizedHref: string | To;
26
28
  if (typeof href === 'string') {
27
- localizedHref = isDefaultLocale ? href : `/${locale}${href}`;
29
+ localizedHref = shouldAddLocale ? `/${locale}${href}` : href;
28
30
  } else {
29
31
  localizedHref = {
30
32
  ...href,
31
- pathname: isDefaultLocale ? href.pathname : `/${locale}${href.pathname}`
33
+ pathname: shouldAddLocale ? `/${locale}${href.pathname}` : href.pathname
32
34
  };
33
35
  }
34
36
 
35
37
  return (
36
- <RouterLink {...props} to={localizedHref}>
38
+ <RouterLink data-testid="locale-link" {...props} to={localizedHref}>
37
39
  {children}
38
40
  </RouterLink>
39
41
  );
@@ -1,4 +1,6 @@
1
- import { I18nService, I18nServiceLocale } from '@/base/services/I18nService';
1
+ import { I18nServiceLocale } from '@/base/services/I18nService';
2
+ import { RouteService } from '@/base/services/RouteService';
3
+ import { IOC } from '@/core/IOC';
2
4
  import { useEffect } from 'react';
3
5
  import { useNavigate } from 'react-router-dom';
4
6
  import { useParams } from 'react-router-dom';
@@ -15,10 +17,6 @@ export function useI18nGuard() {
15
17
  const navigate = useNavigate();
16
18
 
17
19
  useEffect(() => {
18
- if (!lng) {
19
- navigate('/404', { replace: true });
20
- } else if (!I18nService.isValidLanguage(lng)) {
21
- navigate('/404', { replace: true });
22
- }
20
+ IOC(RouteService).i18nGuard(lng as I18nServiceLocale, navigate);
23
21
  }, [lng, navigate]);
24
22
  }
@@ -2,7 +2,8 @@
2
2
  "files": [],
3
3
  "references": [
4
4
  { "path": "./tsconfig.app.json" },
5
- { "path": "./tsconfig.node.json" }
5
+ { "path": "./tsconfig.node.json" },
6
+ { "path": "./tsconfig.test.json" }
6
7
  ],
7
8
  "compilerOptions": {
8
9
  "experimentalDecorators": true
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "./tsconfig.app.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "types": ["vitest/globals", "vite/client"],
6
+ "paths": {
7
+ "@/*": ["./src/*"],
8
+ "@config/*": ["./config/*"]
9
+ }
10
+ },
11
+ "include": ["__tests__/**/*"],
12
+ "exclude": ["node_modules", "dist"]
13
+ }
@@ -8,7 +8,7 @@ import {
8
8
  } from './config/common';
9
9
  import { name, version } from './package.json';
10
10
  import tsconfigPaths from 'vite-tsconfig-paths';
11
- import envConfig from '@qlover/corekit-bridge/vite-env-config';
11
+ import envConfig from '@qlover/corekit-bridge/build/vite-env-config';
12
12
  import ts2Locales from '@brain-toolkit/ts2locales/vite';
13
13
  import i18nConfig from './config/i18n';
14
14
  import tailwindcss from '@tailwindcss/vite';
@@ -120,8 +120,9 @@ export default defineConfig({
120
120
  port: Number(process.env.VITE_SERVER_PORT || 3200)
121
121
  },
122
122
  test: {
123
+ watch: false,
123
124
  environment: 'jsdom',
124
125
  globals: true,
125
- setupFiles: ['./src/setupTests.ts']
126
+ setupFiles: ['./__tests__/setup/index.ts']
126
127
  }
127
128
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qlover/create-app",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Create a new app with a single command",
5
5
  "private": false,
6
6
  "type": "module",