@qlover/create-app 0.1.10 → 0.1.12

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 (59) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/package.json +1 -1
  3. package/templates/react-app/.env +23 -2
  4. package/templates/react-app/config/common.ts +3 -0
  5. package/templates/react-app/lib/bootstrap/Bootstrap.ts +36 -0
  6. package/templates/react-app/lib/bootstrap/BootstrapExecutorPlugin.ts +20 -0
  7. package/templates/react-app/lib/bootstrap/IOCContainerInterface.ts +33 -0
  8. package/templates/react-app/lib/bootstrap/IOCManagerInterface.ts +12 -0
  9. package/templates/react-app/lib/bootstrap/index.ts +7 -0
  10. package/templates/react-app/lib/bootstrap/plugins/InjectEnv.ts +61 -0
  11. package/templates/react-app/lib/bootstrap/plugins/InjectGlobal.ts +36 -0
  12. package/templates/react-app/lib/bootstrap/plugins/InjectIOC.ts +24 -0
  13. package/templates/react-app/lib/env-config/injectPkgConfig.ts +11 -0
  14. package/templates/react-app/lib/openAiApi/OpenAIClient.ts +1 -1
  15. package/templates/react-app/lib/router-loader/Page.ts +64 -0
  16. package/templates/react-app/lib/router-loader/RouterLoader.ts +90 -0
  17. package/templates/react-app/package.json +3 -1
  18. package/templates/react-app/src/App.tsx +27 -6
  19. package/templates/react-app/src/base/consts/IOCIdentifier.ts +16 -0
  20. package/templates/react-app/src/base/port/IOCFunctionInterface.ts +39 -0
  21. package/templates/react-app/src/base/port/InversifyIocInterface.ts +9 -0
  22. package/templates/react-app/src/base/port/StorageTokenInterface.ts +22 -0
  23. package/templates/react-app/src/base/types/Page.ts +5 -17
  24. package/templates/react-app/src/base/types/global.d.ts +2 -1
  25. package/templates/react-app/src/components/RouterRenderComponent.tsx +16 -0
  26. package/templates/react-app/src/components/ThemeSwitcher.tsx +1 -1
  27. package/templates/react-app/src/core/AppConfig.ts +27 -0
  28. package/templates/react-app/src/core/AppIOCContainer.ts +30 -0
  29. package/templates/react-app/src/core/IOC.ts +48 -0
  30. package/templates/react-app/src/core/bootstrap.ts +60 -15
  31. package/templates/react-app/src/core/globals.ts +1 -1
  32. package/templates/react-app/src/core/registers/RegisterApi.ts +59 -0
  33. package/templates/react-app/src/core/registers/RegisterCommon.ts +21 -0
  34. package/templates/react-app/src/core/{feIOC → registers}/RegisterControllers.ts +16 -11
  35. package/templates/react-app/src/core/registers/RegisterGlobals.ts +20 -0
  36. package/templates/react-app/src/core/registers/index.ts +17 -0
  37. package/templates/react-app/src/main.tsx +4 -3
  38. package/templates/react-app/src/pages/auth/Layout.tsx +1 -1
  39. package/templates/react-app/src/pages/auth/Login.tsx +4 -4
  40. package/templates/react-app/src/pages/base/Executor.tsx +1 -1
  41. package/templates/react-app/src/pages/base/JSONStorage.tsx +1 -1
  42. package/templates/react-app/src/pages/base/Request.tsx +1 -1
  43. package/templates/react-app/src/services/{i18n/index.ts → I18nService.ts} +25 -17
  44. package/templates/react-app/src/services/processer/ProcesserService.ts +2 -2
  45. package/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +7 -1
  46. package/templates/react-app/src/uikit/controllers/RouterController.ts +13 -16
  47. package/templates/react-app/src/uikit/hooks/useLanguageGuard.ts +1 -1
  48. package/templates/react-app/src/uikit/providers/ProcessProvider.tsx +2 -4
  49. package/templates/react-app/src/uikit/utils/RequestLogger.ts +4 -1
  50. package/templates/react-app/tsconfig.json +2 -1
  51. package/templates/react-app/tsconfig.node.json +6 -2
  52. package/templates/react-app/vite.config.ts +14 -0
  53. package/templates/react-app/config/app.common.ts +0 -52
  54. package/templates/react-app/src/base/port/IOCInterface.ts +0 -53
  55. package/templates/react-app/src/core/feIOC/FeIOC.ts +0 -32
  56. package/templates/react-app/src/core/feIOC/RegisterApi.ts +0 -41
  57. package/templates/react-app/src/core/feIOC/RegisterCommon.ts +0 -20
  58. package/templates/react-app/src/core/index.ts +0 -31
  59. package/templates/react-app/src/pages/index.tsx +0 -113
@@ -0,0 +1,16 @@
1
+ import { Suspense } from 'react';
2
+ import { Loading } from './Loading';
3
+ import { RouterLoaderRender } from '@lib/router-loader/RouterLoader';
4
+ import BaseRouteProvider from '@/uikit/providers/BaseRouteProvider';
5
+
6
+ export const RouterRenderComponent: RouterLoaderRender = (route) => {
7
+ const Component = route.element();
8
+
9
+ return (
10
+ <Suspense fallback={<Loading fullscreen />}>
11
+ <BaseRouteProvider {...route.meta}>
12
+ <Component />
13
+ </BaseRouteProvider>
14
+ </Suspense>
15
+ );
16
+ };
@@ -1,4 +1,4 @@
1
- import { IOC } from '@/core';
1
+ import { IOC } from '@/core/IOC';
2
2
  import { useController } from '@lib/fe-react-controller';
3
3
  import { ThemeController } from '@lib/fe-react-theme/ThemeController';
4
4
  import { useTranslation } from 'react-i18next';
@@ -0,0 +1,27 @@
1
+ import type { EnvConfigInterface } from '@lib/bootstrap';
2
+
3
+ class AppConfig implements EnvConfigInterface {
4
+ readonly appName = '';
5
+ readonly appVersion = '';
6
+ /**
7
+ * vite mode
8
+ */
9
+ readonly env = import.meta.env.VITE_USER_NODE_ENV;
10
+
11
+ readonly userTokenStorageKey = 'fe_user_token';
12
+ readonly openAiModels = [
13
+ 'gpt-4o-mini',
14
+ 'gpt-3.5-turbo',
15
+ 'gpt-3.5-turbo-2',
16
+ 'gpt-4',
17
+ 'gpt-4-32k'
18
+ ];
19
+ readonly openAiBaseUrl = '';
20
+ readonly openAiToken = '';
21
+ readonly openAiTokenPrefix = '';
22
+ readonly openAiRequireToken = true;
23
+ readonly loginUser = '';
24
+ readonly loginPassword = '';
25
+ }
26
+
27
+ export default new AppConfig();
@@ -0,0 +1,30 @@
1
+ import type { InversifyRegisterInterface } from '@/base/port/InversifyIocInterface';
2
+ import type { IOCContainerInterface } from '@lib/bootstrap';
3
+ import { ServiceIdentifier, Container } from 'inversify';
4
+
5
+ export class AppIOCContainer implements IOCContainerInterface {
6
+ private container: Container;
7
+
8
+ constructor() {
9
+ this.container = new Container({
10
+ // allow `@injectable` decorator, auto bind injectable classes
11
+ autobind: true,
12
+ // use singleton scope
13
+ defaultScope: 'Singleton'
14
+ });
15
+ }
16
+
17
+ configure(registers?: InversifyRegisterInterface[]): void {
18
+ if (registers) {
19
+ registers.forEach((register) => register.register(this.container, this));
20
+ }
21
+ }
22
+
23
+ bind<T>(key: ServiceIdentifier<T>, value: T): void {
24
+ this.container.bind<T>(key).toConstantValue(value);
25
+ }
26
+
27
+ get<T>(key: string): T {
28
+ return this.container.get<T>(key);
29
+ }
30
+ }
@@ -0,0 +1,48 @@
1
+ // ! dont't import tsx, only ts file
2
+ import type { IOCContainerInterface } from '@lib/bootstrap';
3
+ import type {
4
+ IOCIdentifierMap,
5
+ IOCFunctionInterface
6
+ } from '@/base/port/IOCFunctionInterface';
7
+ import type { ServiceIdentifier } from 'inversify';
8
+
9
+ let implemention: IOCContainerInterface | null;
10
+
11
+ function ioc<T>(serviceIdentifier: ServiceIdentifier<T>): T;
12
+ function ioc<K extends keyof IOCIdentifierMap>(
13
+ serviceIdentifier: K
14
+ ): IOCIdentifierMap[K];
15
+ function ioc<T, K extends keyof IOCIdentifierMap>(
16
+ serviceIdentifier: ServiceIdentifier<T> | K
17
+ ): T | IOCIdentifierMap[K] {
18
+ if (!implemention) {
19
+ throw new Error('IOC is not implemented');
20
+ }
21
+ return implemention.get(serviceIdentifier);
22
+ }
23
+
24
+ /**
25
+ * IOC manager function.
26
+ *
27
+ * eg.
28
+ * ```ts
29
+ * class A {}
30
+ * const a = IOC(A);
31
+ * ```
32
+ *
33
+ * or
34
+ * ```ts
35
+ * const a = IOC<A>();
36
+ * ```
37
+ *
38
+ * or use get(),
39
+ */
40
+ export const IOC: IOCFunctionInterface = Object.assign(ioc, {
41
+ get implemention() {
42
+ return implemention;
43
+ },
44
+ implement: (container: IOCContainerInterface) => {
45
+ implemention = container;
46
+ },
47
+ get: ioc
48
+ });
@@ -1,21 +1,66 @@
1
- import * as feGlobals from '@/core/globals';
2
- import { IOC } from '.';
3
- import { IOCInterface } from '@/base/port/IOCInterface';
4
- import { I18nService } from '@/services/i18n';
1
+ import {
2
+ Bootstrap,
3
+ BootstrapExecutorPlugin,
4
+ InjectEnv,
5
+ InjectIOC,
6
+ InjectGlobal
7
+ } from '@lib/bootstrap';
8
+ import { AppIOCContainer } from '@/core/AppIOCContainer';
9
+ import AppConfig from '@/core/AppConfig';
10
+ import { envPrefix, browserGlobalsName } from '@config/common';
11
+ import { IOC } from './IOC';
12
+ import * as globals from '@/core/globals';
13
+ import { I18nService } from '@/services/I18nService';
14
+ import { registerList } from './registers';
5
15
 
6
- export class Bootstrap {
7
- constructor(private IOCContainer: IOCInterface) {}
16
+ /**
17
+ * Bootstrap
18
+ *
19
+ * 1. inject env config to AppConfig
20
+ * 2. inject IOC to Application
21
+ * 3. inject globals to window
22
+ *
23
+ */
24
+ export default function startup(root: typeof globalThis) {
25
+ const window =
26
+ typeof root !== 'undefined' && root instanceof Window ? root : undefined;
8
27
 
9
- start(): void {
10
- // set global feGlobals
11
- if (typeof window !== 'undefined') {
12
- window.feGlobals = Object.freeze(Object.assign({}, feGlobals));
13
- }
28
+ if (!window) {
29
+ throw new Error('Not Found Window');
30
+ }
31
+
32
+ const bootstrap = new Bootstrap(new AppIOCContainer());
14
33
 
15
- // startup IOC
16
- IOC.implement(this.IOCContainer);
34
+ /**
35
+ * bootstrap start list
36
+ *
37
+ * - inject env config to AppConfig
38
+ * - inject IOC to Application
39
+ * - inject globals to window
40
+ * - inject i18n service to Application
41
+ */
42
+ const bootstrapList: BootstrapExecutorPlugin[] = [
43
+ new InjectEnv(AppConfig, import.meta.env, envPrefix),
44
+ new InjectIOC(IOC, registerList),
45
+ new InjectGlobal(globals, browserGlobalsName),
46
+ new I18nService(window.location.pathname)
47
+ ];
48
+
49
+ if (AppConfig.env !== 'production') {
50
+ bootstrapList.push({
51
+ pluginName: 'InjectDevTools',
52
+ onBefore() {
53
+ console.log(AppConfig);
54
+ },
55
+ onError({ error }) {
56
+ console.error(`${AppConfig.appName} starup error:`, error);
57
+ }
58
+ });
59
+ }
17
60
 
18
- // startup i18n
19
- I18nService.init();
61
+ try {
62
+ bootstrap.use(bootstrapList).start(root);
63
+ } catch (error) {
64
+ console.error(`${AppConfig.appName} starup error:`, error);
20
65
  }
21
66
  }
@@ -6,7 +6,7 @@ import {
6
6
  SyncStorage
7
7
  } from '@qlover/fe-utils';
8
8
 
9
- export const env = import.meta.env.VITE_NODE_ENV;
9
+ export const env = import.meta.env.VITE_USER_NODE_ENV;
10
10
  export const isProduction = env === 'production';
11
11
 
12
12
  /**
@@ -0,0 +1,59 @@
1
+ import type {
2
+ InversifyRegisterInterface,
3
+ InversifyRegisterContainer
4
+ } from '@/base/port/InversifyIocInterface';
5
+
6
+ import { RequestLogger } from '@/uikit/utils/RequestLogger';
7
+ import { localJsonStorage } from '../globals';
8
+ import { FetchAbortPlugin, FetchURLPlugin } from '@qlover/fe-utils';
9
+ import { FeApiMockPlugin } from '@/base/apis/feApi';
10
+ import { OpenAIClient } from '@lib/openAiApi';
11
+ import { FeApi } from '@/base/apis/feApi';
12
+ import { RequestCommonPlugin } from '@lib/request-common-plugin';
13
+ import mockDataJson from '@config/feapi.mock.json';
14
+ import AppConfig from '@/core/AppConfig';
15
+
16
+ export class RegisterApi implements InversifyRegisterInterface {
17
+ register(container: InversifyRegisterContainer): void {
18
+ const openAiApi = new OpenAIClient({
19
+ baseURL: AppConfig.openAiBaseUrl,
20
+ models: AppConfig.openAiModels,
21
+ commonPluginConfig: {
22
+ tokenPrefix: AppConfig.openAiTokenPrefix,
23
+ token: AppConfig.openAiToken,
24
+ defaultHeaders: {
25
+ 'Content-Type': 'application/json'
26
+ },
27
+ defaultRequestData: {
28
+ model: AppConfig.openAiModels[0],
29
+ stream: true
30
+ },
31
+ requiredToken: true,
32
+ requestDataSerializer: (data) => JSON.stringify(data)
33
+ }
34
+ }).usePlugin(container.get(RequestLogger));
35
+
36
+ const abortPlugin = container.get(FetchAbortPlugin);
37
+ const feApi = new FeApi({
38
+ abortPlugin,
39
+ config: {
40
+ responseType: 'json',
41
+ baseURL: 'https://api.example.com/'
42
+ }
43
+ })
44
+ .usePlugin(new FetchURLPlugin())
45
+ .usePlugin(
46
+ new RequestCommonPlugin({
47
+ tokenPrefix: AppConfig.openAiTokenPrefix,
48
+ requiredToken: true,
49
+ token: () => localJsonStorage.getItem('fe_user_token')
50
+ })
51
+ )
52
+ .usePlugin(new FeApiMockPlugin(mockDataJson))
53
+ .usePlugin(container.get(RequestLogger))
54
+ .usePlugin(abortPlugin);
55
+
56
+ container.bind(OpenAIClient).toConstantValue(openAiApi);
57
+ container.bind(FeApi).toConstantValue(feApi);
58
+ }
59
+ }
@@ -0,0 +1,21 @@
1
+ import type {
2
+ InversifyRegisterInterface,
3
+ InversifyRegisterContainer
4
+ } from '@/base/port/InversifyIocInterface';
5
+
6
+ import { FetchAbortPlugin, JSONStorage } from '@qlover/fe-utils';
7
+ import { UserToken } from '@/base/cases/UserToken';
8
+ import AppConfig from '@/core/AppConfig';
9
+
10
+ export class RegisterCommon implements InversifyRegisterInterface {
11
+ register(container: InversifyRegisterContainer): void {
12
+ const feApiAbort = new FetchAbortPlugin();
13
+ const userToken = new UserToken({
14
+ storageKey: AppConfig.userTokenStorageKey,
15
+ storage: container.get(JSONStorage)
16
+ });
17
+
18
+ container.bind(FetchAbortPlugin).toConstantValue(feApiAbort);
19
+ container.bind(UserToken).toConstantValue(userToken);
20
+ }
21
+ }
@@ -1,4 +1,8 @@
1
- import { IOCInterface, IOCRegisterInterface } from '@/base/port/IOCInterface';
1
+ import type {
2
+ InversifyRegisterInterface,
3
+ InversifyRegisterContainer
4
+ } from '@/base/port/InversifyIocInterface';
5
+
2
6
  import { localJsonStorage, logger } from '../globals';
3
7
  import { RouterController } from '@/uikit/controllers/RouterController';
4
8
  import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
@@ -10,13 +14,12 @@ import { ThemeController } from '@lib/fe-react-theme/ThemeController';
10
14
  import { RouteConfig } from '@/base/types/Page';
11
15
  import { OpenAIClient } from '@lib/openAiApi';
12
16
  import { FeApi } from '@/base/apis/feApi';
13
-
14
17
  import appRouterConfig from '@config/app.router.json';
15
18
  import themeConfigJson from '@config/theme.json';
16
19
  import { UserToken } from '@/base/cases/UserToken';
17
20
 
18
- export class RegisterControllers implements IOCRegisterInterface {
19
- register(container: IOCInterface): void {
21
+ export class RegisterControllers implements InversifyRegisterInterface {
22
+ register(container: InversifyRegisterContainer): void {
20
23
  const routerController = new RouterController({
21
24
  config: appRouterConfig.base as RouteConfig,
22
25
  logger
@@ -42,12 +45,14 @@ export class RegisterControllers implements IOCRegisterInterface {
42
45
  logger
43
46
  }).usePlugin(userController);
44
47
 
45
- container.bind(RouterController, routerController);
46
- container.bind(JSONStorageController, jsonStorageController);
47
- container.bind(RequestController, requestController);
48
- container.bind(ExecutorController, executorController);
49
- container.bind(UserController, userController);
50
- container.bind(ThemeController, themeController);
51
- container.bind(ProcesserService, pageProcesser);
48
+ container.bind(RouterController).toConstantValue(routerController);
49
+ container
50
+ .bind(JSONStorageController)
51
+ .toConstantValue(jsonStorageController);
52
+ container.bind(RequestController).toConstantValue(requestController);
53
+ container.bind(ExecutorController).toConstantValue(executorController);
54
+ container.bind(UserController).toConstantValue(userController);
55
+ container.bind(ThemeController).toConstantValue(themeController);
56
+ container.bind(ProcesserService).toConstantValue(pageProcesser);
52
57
  }
53
58
  }
@@ -0,0 +1,20 @@
1
+ import type {
2
+ InversifyRegisterInterface,
3
+ InversifyRegisterContainer
4
+ } from '@/base/port/InversifyIocInterface';
5
+ import { JSON, localJsonStorage, logger } from '../globals';
6
+ import { JSONSerializer, JSONStorage, Logger } from '@qlover/fe-utils';
7
+ import { IOCIdentifier } from '@/base/consts/IOCIdentifier';
8
+
9
+ export class RegisterGlobals implements InversifyRegisterInterface {
10
+ register(container: InversifyRegisterContainer): void {
11
+ container.bind(JSONSerializer).toConstantValue(JSON);
12
+ container.bind(IOCIdentifier.JSON).toConstantValue(JSON);
13
+
14
+ container.bind(Logger).toConstantValue(logger);
15
+ container.bind(IOCIdentifier.Logger).toConstantValue(logger);
16
+
17
+ container.bind(JSONStorage).toConstantValue(localJsonStorage);
18
+ container.bind(IOCIdentifier.JSONStorage).toConstantValue(localJsonStorage);
19
+ }
20
+ }
@@ -0,0 +1,17 @@
1
+ import type { InversifyRegisterInterface } from '@/base/port/InversifyIocInterface';
2
+ import { RegisterGlobals } from './RegisterGlobals';
3
+ import { RegisterCommon } from './RegisterCommon';
4
+ import { RegisterApi } from './RegisterApi';
5
+ import { RegisterControllers } from './RegisterControllers';
6
+
7
+ /**
8
+ * Register List
9
+ *
10
+ * Register List is used to register dependencies in bootstrap
11
+ */
12
+ export const registerList: InversifyRegisterInterface[] = [
13
+ new RegisterGlobals(),
14
+ new RegisterCommon(),
15
+ new RegisterApi(),
16
+ new RegisterControllers()
17
+ ];
@@ -1,10 +1,11 @@
1
+ // !only this file use `window`, `document` ...global variables
2
+ import 'reflect-metadata';
1
3
  import { StrictMode } from 'react';
2
4
  import { createRoot } from 'react-dom/client';
3
5
  import App from './App.tsx';
4
- import { Bootstrap } from './core/bootstrap';
5
- import { FeIOC } from './core/feIOC/FeIOC';
6
+ import startup from './core/bootstrap';
6
7
 
7
- new Bootstrap(new FeIOC()).start();
8
+ startup(window);
8
9
 
9
10
  createRoot(document.getElementById('root')!).render(
10
11
  <StrictMode>
@@ -1,4 +1,4 @@
1
- import { IOC } from '@/core';
1
+ import { IOC } from '@/core/IOC';
2
2
  import { UserController } from '@/uikit/controllers/UserController';
3
3
  import { useController } from '@lib/fe-react-controller';
4
4
  import { Navigate, Outlet } from 'react-router-dom';
@@ -1,16 +1,16 @@
1
1
  import { useState } from 'react';
2
2
  import { useController } from '@lib/fe-react-controller';
3
- import { IOC } from '@/core';
3
+ import { IOC } from '@/core/IOC';
4
4
  import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
5
- import { defaultLoginInfo } from '@config/app.common';
6
5
  import { RouterController } from '@/uikit/controllers/RouterController';
7
6
  import { UserController } from '@/uikit/controllers/UserController';
7
+ import AppConfig from '@/core/AppConfig';
8
8
 
9
9
  export default function Login() {
10
10
  const { t } = useBaseRoutePage();
11
11
  const controller = useController(IOC(UserController));
12
- const [email, setEmail] = useState(defaultLoginInfo.name);
13
- const [password, setPassword] = useState(defaultLoginInfo.password);
12
+ const [email, setEmail] = useState(AppConfig.loginUser);
13
+ const [password, setPassword] = useState(AppConfig.loginPassword);
14
14
  const [loading, setLoading] = useState(false);
15
15
 
16
16
  const handleLogin = async () => {
@@ -1,4 +1,4 @@
1
- import { IOC } from '@/core';
1
+ import { IOC } from '@/core/IOC';
2
2
  import { useControllerState } from '@lib/fe-react-controller';
3
3
  import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
4
4
  import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
@@ -1,4 +1,4 @@
1
- import { IOC } from '@/core';
1
+ import { IOC } from '@/core/IOC';
2
2
  import { useController, useControllerState } from '@lib/fe-react-controller';
3
3
  import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
4
4
  import template from 'lodash/template';
@@ -1,4 +1,4 @@
1
- import { IOC } from '@/core';
1
+ import { IOC } from '@/core/IOC';
2
2
  import { useControllerState } from '@lib/fe-react-controller';
3
3
  import { useBaseRoutePage } from '@/uikit/contexts/BaseRouteContext';
4
4
  import { JSONStorageController } from '@/uikit/controllers/JSONStorageController';
@@ -4,24 +4,16 @@ import LanguageDetector from 'i18next-browser-languagedetector';
4
4
  import HttpApi from 'i18next-http-backend';
5
5
  import merge from 'lodash/merge';
6
6
  import { i18nConfig, I18nServiceLocale } from '@config/i18n';
7
+ import type { BootstrapExecutorPlugin } from '@lib/bootstrap';
7
8
 
8
- // custom language detector
9
- const pathLanguageDetector = {
10
- name: 'pathLanguageDetector',
11
- lookup() {
12
- const path = window.location.pathname.split('/');
13
- const language = path[1];
14
- return I18nService.isValidLanguage(language)
15
- ? language
16
- : i18nConfig.fallbackLng;
17
- },
18
- cacheUserLanguage() {
19
- // no cache, because we get language from URL
20
- }
21
- };
9
+ const { supportedLngs, fallbackLng } = i18nConfig;
10
+
11
+ export class I18nService implements BootstrapExecutorPlugin {
12
+ readonly pluginName = 'I18nService';
13
+
14
+ constructor(private pathname: string) {}
22
15
 
23
- export class I18nService {
24
- static init(): void {
16
+ onBefore(): void {
25
17
  i18n
26
18
  .use(HttpApi)
27
19
  .use(LanguageDetector)
@@ -36,15 +28,31 @@ export class I18nService {
36
28
  );
37
29
 
38
30
  // add custom detector
31
+ // custom language detector
32
+ const pathLanguageDetector = {
33
+ name: 'pathLanguageDetector',
34
+ lookup: () => {
35
+ const path = this.pathname.split('/');
36
+ const language = path[1];
37
+ return I18nService.isValidLanguage(language) ? language : fallbackLng;
38
+ },
39
+ cacheUserLanguage() {
40
+ // no cache, because we get language from URL
41
+ }
42
+ };
39
43
  i18n.services.languageDetector.addDetector(pathLanguageDetector);
40
44
  }
41
45
 
46
+ static getCurrentLanguage(): I18nServiceLocale {
47
+ return i18n.language as I18nServiceLocale;
48
+ }
49
+
42
50
  /**
43
51
  * check if the language is supported
44
52
  * @param language - language to check
45
53
  * @returns true if the language is supported, false otherwise
46
54
  */
47
55
  static isValidLanguage(language: string): language is I18nServiceLocale {
48
- return i18nConfig.supportedLngs.includes(language as I18nServiceLocale);
56
+ return supportedLngs.includes(language as I18nServiceLocale);
49
57
  }
50
58
  }
@@ -22,8 +22,8 @@ export class ProcesserService {
22
22
  }
23
23
 
24
24
  init(): Promise<unknown> {
25
- return this.executor.exec(this.handler).catch(() => {
26
- this.options.logger.error('PageProcesser init failed');
25
+ return this.executor.exec(this.handler).catch((err) => {
26
+ this.options.logger.error('PageProcesser init failed', err);
27
27
  });
28
28
  }
29
29
  }
@@ -3,8 +3,14 @@ import { useContext, useMemo } from 'react';
3
3
  import { BasePageProvider } from '@/base/types/Page';
4
4
  import { RouteMeta } from '@/base/types/Page';
5
5
  import { createContext } from 'react';
6
- import { defaultBaseRoutemeta } from '@config/app.common';
7
6
  import merge from 'lodash/merge';
7
+ import { i18nConfig } from '@config/i18n';
8
+
9
+ const defaultBaseRoutemeta = {
10
+ localNamespace: i18nConfig.defaultNS,
11
+ title: '',
12
+ icon: ''
13
+ };
8
14
 
9
15
  export const BaseRoutePageContext = createContext<RouteMeta>({});
10
16
 
@@ -1,12 +1,11 @@
1
- import { I18nService } from '@/services/i18n';
2
- import { RouteConfig, RouteType } from '@/base/types/Page';
1
+ import { I18nService } from '@/services/I18nService';
3
2
  import { UIDependenciesInterface } from '@/base/port/UIDependenciesInterface';
4
- import { i18nConfig } from '@config/i18n';
5
3
  import { Logger } from '@qlover/fe-utils';
6
4
  import { NavigateFunction, NavigateOptions } from 'react-router-dom';
5
+ import { RouteConfigValue } from '@lib/router-loader/RouterLoader';
6
+ import { RouteConfig } from '@/base/types/Page';
7
7
 
8
8
  export type RouterControllerDependencies = {
9
- location: globalThis.Location;
10
9
  navigate: NavigateFunction;
11
10
  };
12
11
 
@@ -16,7 +15,7 @@ export type RouterControllerOptions = {
16
15
  };
17
16
 
18
17
  export type RouterControllerState = {
19
- routes: RouteType[];
18
+ routes: RouteConfigValue[];
20
19
  };
21
20
 
22
21
  export class RouterController
@@ -49,6 +48,11 @@ export class RouterController
49
48
  return navigate;
50
49
  }
51
50
 
51
+ static composePath(path: string): string {
52
+ const targetLang = I18nService.getCurrentLanguage();
53
+ return `/${targetLang}${path}`;
54
+ }
55
+
52
56
  /**
53
57
  * @override
54
58
  */
@@ -59,24 +63,17 @@ export class RouterController
59
63
  ) as RouterControllerDependencies;
60
64
  }
61
65
 
62
- getRoutes(): RouteType[] {
66
+ getRoutes(): RouteConfigValue[] {
63
67
  return this.state.routes;
64
68
  }
65
69
 
66
- changeRoutes(routes: RouteType[]): void {
70
+ changeRoutes(routes: RouteConfigValue[]): void {
67
71
  this.state.routes = routes;
68
72
  }
69
73
 
70
74
  goto(path: string, options?: NavigateOptions): void {
71
- console.trace('path');
72
-
73
- const tryLng = path.split('/')[0];
74
- if (!I18nService.isValidLanguage(tryLng)) {
75
- path = i18nConfig.fallbackLng + path.replace(tryLng, '');
76
- }
77
-
78
- this.navigate?.(path, options);
79
-
75
+ path = RouterController.composePath(path);
76
+ this.logger.debug('Goto path => ', path);
80
77
  this.navigate?.(path, options);
81
78
  }
82
79
 
@@ -1,4 +1,4 @@
1
- import { I18nService } from '@/services/i18n';
1
+ import { I18nService } from '@/services/I18nService';
2
2
  import { I18nServiceLocale } from '@config/i18n';
3
3
  import { useEffect } from 'react';
4
4
  import { useNavigate } from 'react-router-dom';
@@ -1,5 +1,5 @@
1
1
  import { createContext, useEffect } from 'react';
2
- import { IOC } from '@/core';
2
+ import { IOC } from '@/core/IOC';
3
3
  import { useLanguageGuard } from '@/uikit/hooks/useLanguageGuard';
4
4
  import { useStrictEffect } from '@/uikit/hooks/useStrictEffect';
5
5
  import { ProcesserService } from '@/services/processer/ProcesserService';
@@ -26,9 +26,7 @@ export function ProcessProvider({ children }: { children: React.ReactNode }) {
26
26
  }, []);
27
27
 
28
28
  useEffect(() => {
29
- IOC(RouterController).setDependencies({
30
- navigate
31
- });
29
+ IOC(RouterController).setDependencies({ navigate });
32
30
  }, [navigate]);
33
31
 
34
32
  if (!success) {