@qlover/create-app 0.9.0 → 0.10.1
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.
- package/CHANGELOG.md +19 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +9 -10
- package/dist/templates/next-app/eslint.config.mjs +5 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +17 -3
- package/dist/templates/next-app/next.config.ts +1 -1
- package/dist/templates/next-app/package.json +2 -3
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +2 -1
- package/dist/templates/next-app/src/base/cases/DialogHandler.ts +1 -2
- package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +4 -5
- package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +44 -29
- package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +1 -2
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +22 -10
- package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +17 -9
- package/dist/templates/next-app/src/base/services/ResourceService.ts +3 -4
- package/dist/templates/next-app/src/base/services/UserService.ts +37 -13
- package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +8 -7
- package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +15 -26
- package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +4 -3
- package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +4 -3
- package/dist/templates/next-app/src/core/globals.ts +2 -1
- package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +4 -3
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +5 -6
- package/dist/templates/next-app/src/i18n/request.ts +2 -2
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +1 -3
- package/dist/templates/next-app/src/uikit/components/LocaleLink.tsx +1 -2
- package/dist/templates/react-app/__tests__/__mocks__/{MockAppConfit.ts → MockAppConfig.ts} +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +10 -17
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +27 -8
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +110 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +3 -2
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +13 -0
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +3 -1
- package/dist/templates/react-app/config/IOCIdentifier.ts +9 -6
- package/dist/templates/react-app/config/common.ts +38 -0
- package/dist/templates/react-app/config/feapi.mock.json +5 -12
- package/dist/templates/react-app/eslint.config.mjs +6 -5
- package/dist/templates/react-app/package.json +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +22 -13
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +3 -3
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +17 -12
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +19 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +2 -4
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +15 -9
- package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +55 -0
- package/dist/templates/react-app/src/base/services/I18nService.ts +1 -0
- package/dist/templates/react-app/src/base/services/UserBootstrap.ts +43 -0
- package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +16 -0
- package/dist/templates/react-app/src/base/services/UserService.ts +51 -80
- package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +8 -3
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +6 -6
- package/dist/templates/react-app/src/core/bootstraps/SaveAppInfo.ts +28 -0
- package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +24 -18
- package/dist/templates/react-app/src/core/globals.ts +10 -11
- package/dist/templates/react-app/src/main.tsx +1 -1
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +4 -4
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +1 -1
- package/dist/templates/react-app/src/pages/base/Layout.tsx +3 -3
- package/dist/templates/react-app/src/uikit/components/BaseLayoutProvider.tsx +44 -0
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +1 -3
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +9 -0
- package/dist/templates/react-app/src/uikit/hooks/{useI18nGuard.ts → useRouterI18nGuard.ts} +7 -4
- package/dist/templates/react-app/tsconfig.app.json +1 -1
- package/package.json +3 -3
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +0 -102
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +0 -61
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +0 -57
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +0 -28
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +0 -16
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
import { inject, injectable } from 'inversify';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type {
|
|
3
|
+
AppUserApiInterface,
|
|
4
|
+
UserApiLoginTransaction,
|
|
5
|
+
UserApiLogoutTransaction,
|
|
6
|
+
UserApiRegisterTransaction
|
|
7
|
+
} from '@/base/port/AppUserApiInterface';
|
|
4
8
|
import { AppApiRequester } from './AppApiRequester';
|
|
5
|
-
import type { AppApiConfig
|
|
9
|
+
import type { AppApiConfig } from './AppApiRequester';
|
|
6
10
|
import type { RequestTransaction } from '@qlover/fe-corekit';
|
|
7
11
|
|
|
8
|
-
export type UserApiLoginTransaction = AppApiTransaction<
|
|
9
|
-
{ email: string; password: string },
|
|
10
|
-
{
|
|
11
|
-
token: string;
|
|
12
|
-
}
|
|
13
|
-
>;
|
|
14
|
-
|
|
15
|
-
export type UserApiRegisterTransaction = AppApiTransaction<
|
|
16
|
-
{
|
|
17
|
-
email: string;
|
|
18
|
-
password: string;
|
|
19
|
-
},
|
|
20
|
-
AppApiTransaction['response']['data']
|
|
21
|
-
>;
|
|
22
|
-
|
|
23
12
|
/**
|
|
24
13
|
* UserApi
|
|
25
14
|
*
|
|
@@ -36,7 +25,7 @@ export class AppUserApi implements AppUserApiInterface {
|
|
|
36
25
|
|
|
37
26
|
async login(
|
|
38
27
|
params: UserApiLoginTransaction['data']
|
|
39
|
-
): Promise<
|
|
28
|
+
): Promise<UserApiLoginTransaction['response']> {
|
|
40
29
|
const response = await this.client.request<UserApiLoginTransaction>({
|
|
41
30
|
url: '/user/login',
|
|
42
31
|
method: 'POST',
|
|
@@ -44,12 +33,12 @@ export class AppUserApi implements AppUserApiInterface {
|
|
|
44
33
|
encryptProps: 'password'
|
|
45
34
|
});
|
|
46
35
|
|
|
47
|
-
return response
|
|
36
|
+
return response;
|
|
48
37
|
}
|
|
49
38
|
|
|
50
39
|
async register(
|
|
51
40
|
params: UserApiRegisterTransaction['data']
|
|
52
|
-
): Promise<
|
|
41
|
+
): Promise<UserApiRegisterTransaction['response']> {
|
|
53
42
|
const response = await this.client.request<UserApiRegisterTransaction>({
|
|
54
43
|
url: '/user/register',
|
|
55
44
|
method: 'POST',
|
|
@@ -57,15 +46,15 @@ export class AppUserApi implements AppUserApiInterface {
|
|
|
57
46
|
encryptProps: 'password'
|
|
58
47
|
});
|
|
59
48
|
|
|
60
|
-
return response
|
|
49
|
+
return response;
|
|
61
50
|
}
|
|
62
51
|
|
|
63
|
-
async logout(
|
|
64
|
-
|
|
52
|
+
async logout(
|
|
53
|
+
_params?: unknown
|
|
54
|
+
): Promise<UserApiLogoutTransaction['response']> {
|
|
55
|
+
return await this.client.request<UserApiLogoutTransaction>({
|
|
65
56
|
url: '/user/logout',
|
|
66
57
|
method: 'POST'
|
|
67
58
|
});
|
|
68
|
-
|
|
69
|
-
return response.data as AppApiResult<unknown>;
|
|
70
59
|
}
|
|
71
60
|
}
|
|
@@ -9,9 +9,10 @@ import type { IOCIdentifierMap } from '@config/IOCIdentifier';
|
|
|
9
9
|
import { ClientIOCRegister } from './ClientIOCRegister';
|
|
10
10
|
import { appConfig } from '../globals';
|
|
11
11
|
|
|
12
|
-
export class ClientIOC
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export class ClientIOC implements IOCInterface<
|
|
13
|
+
IOCIdentifierMap,
|
|
14
|
+
IOCContainerInterface
|
|
15
|
+
> {
|
|
15
16
|
protected ioc: IOCFunctionInterface<
|
|
16
17
|
IOCIdentifierMap,
|
|
17
18
|
IOCContainerInterface
|
|
@@ -10,9 +10,10 @@ import type {
|
|
|
10
10
|
IOCRegisterInterface
|
|
11
11
|
} from '@qlover/corekit-bridge';
|
|
12
12
|
|
|
13
|
-
export class ClientIOCRegister
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
export class ClientIOCRegister implements IOCRegisterInterface<
|
|
14
|
+
IOCContainerInterface,
|
|
15
|
+
IocRegisterOptions
|
|
16
|
+
> {
|
|
16
17
|
constructor(protected options: IocRegisterOptions) {}
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
|
|
3
3
|
// ! global variables, don't import any dependencies and don't have side effects
|
|
4
|
-
import { ColorFormatter
|
|
4
|
+
import { ColorFormatter } from '@qlover/corekit-bridge';
|
|
5
5
|
import { JSONSerializer } from '@qlover/fe-corekit';
|
|
6
|
+
import { Logger, ConsoleHandler } from '@qlover/logger';
|
|
6
7
|
import { AppConfig } from '@/base/cases/AppConfig';
|
|
7
8
|
import { DialogHandler } from '@/base/cases/DialogHandler';
|
|
8
9
|
import { loggerStyles } from '@config/common';
|
|
@@ -9,9 +9,10 @@ import type { IOCInterface } from '@/base/port/IOCInterface';
|
|
|
9
9
|
import type { IOCIdentifierMapServer } from '@config/IOCIdentifier';
|
|
10
10
|
import { ServerIOCRegister } from './ServerIOCRegister';
|
|
11
11
|
|
|
12
|
-
export class ServerIOC
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export class ServerIOC implements IOCInterface<
|
|
13
|
+
IOCIdentifierMapServer,
|
|
14
|
+
IOCContainerInterface
|
|
15
|
+
> {
|
|
15
16
|
static instance: ServerIOC | null = null;
|
|
16
17
|
|
|
17
18
|
protected ioc: IOCFunctionInterface<
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ConsoleHandler,
|
|
3
|
-
Logger,
|
|
4
|
-
TimestampFormatter,
|
|
5
2
|
type IOCContainerInterface,
|
|
6
3
|
type IOCManagerInterface,
|
|
7
4
|
type IOCRegisterInterface
|
|
8
5
|
} from '@qlover/corekit-bridge';
|
|
6
|
+
import { Logger, ConsoleHandler, TimestampFormatter } from '@qlover/logger';
|
|
9
7
|
import type { IocRegisterOptions } from '@/base/port/IOCInterface';
|
|
10
8
|
import { SupabaseBridge } from '@/server/sqlBridges/SupabaseBridge';
|
|
11
9
|
import { IOCIdentifier as I } from '@config/IOCIdentifier';
|
|
12
10
|
|
|
13
|
-
export class ServerIOCRegister
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
export class ServerIOCRegister implements IOCRegisterInterface<
|
|
12
|
+
IOCContainerInterface,
|
|
13
|
+
IocRegisterOptions
|
|
14
|
+
> {
|
|
16
15
|
constructor(protected options: IocRegisterOptions) {}
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -27,7 +27,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
27
27
|
onError: (error) => {
|
|
28
28
|
if (error.message.includes('MISSING_MESSAGE')) {
|
|
29
29
|
console.warn(`[i18n] Missing translation: ${error.message}`);
|
|
30
|
-
return error.
|
|
30
|
+
return error.message; // 返回 key 作为 fallback 文本
|
|
31
31
|
}
|
|
32
32
|
throw error; // 其他错误仍然抛出
|
|
33
33
|
}
|
|
@@ -42,7 +42,7 @@ export default getRequestConfig(async ({ requestLocale }) => {
|
|
|
42
42
|
onError: (error) => {
|
|
43
43
|
if (error.message.includes('MISSING_MESSAGE')) {
|
|
44
44
|
console.warn(`[i18n] Missing translation: ${error.message}`);
|
|
45
|
-
return error.
|
|
45
|
+
return error.message; // 返回 key 作为 fallback 文本
|
|
46
46
|
}
|
|
47
47
|
throw error; // 其他错误仍然抛出
|
|
48
48
|
}
|
|
@@ -7,9 +7,7 @@ import type { CrentialTokenInterface } from './port/CrentialTokenInterface';
|
|
|
7
7
|
export type UserCredentialTokenValue = Pick<UserSchema, 'id' | 'email'>;
|
|
8
8
|
|
|
9
9
|
@injectable()
|
|
10
|
-
export class UserCredentialToken
|
|
11
|
-
implements CrentialTokenInterface<UserCredentialTokenValue>
|
|
12
|
-
{
|
|
10
|
+
export class UserCredentialToken implements CrentialTokenInterface<UserCredentialTokenValue> {
|
|
13
11
|
protected jwtSecret: string;
|
|
14
12
|
protected jwtExpiresIn: string;
|
|
15
13
|
|
|
@@ -5,8 +5,7 @@ import type { LinkProps } from 'next/link';
|
|
|
5
5
|
import type { ReactNode } from 'react';
|
|
6
6
|
|
|
7
7
|
interface LocaleLinkProps
|
|
8
|
-
extends Omit<LinkProps, 'href'>,
|
|
9
|
-
React.HTMLAttributes<HTMLAnchorElement> {
|
|
8
|
+
extends Omit<LinkProps, 'href'>, React.HTMLAttributes<HTMLAnchorElement> {
|
|
10
9
|
href:
|
|
11
10
|
| string
|
|
12
11
|
| {
|
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Initial URL path for the router
|
|
7
|
-
* @default ['/en/']
|
|
8
|
-
*/
|
|
9
|
-
routerInitialEntries?: string[];
|
|
10
|
-
/**
|
|
11
|
-
* Initial index of the entries array
|
|
12
|
-
* @default 0
|
|
13
|
-
*/
|
|
14
|
-
routerInitialIndex?: number;
|
|
15
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
TestBootstrapsProvider,
|
|
3
|
+
type TestBootstrapsProviderProps
|
|
4
|
+
} from './TestBootstrapsProvider';
|
|
16
5
|
|
|
17
6
|
/**
|
|
18
7
|
* TestApp - Complete test wrapper with IOC and Router
|
|
@@ -31,13 +20,17 @@ interface TestAppProps {
|
|
|
31
20
|
export function TestApp({
|
|
32
21
|
children,
|
|
33
22
|
routerInitialEntries,
|
|
34
|
-
routerInitialIndex
|
|
35
|
-
|
|
23
|
+
routerInitialIndex,
|
|
24
|
+
bootHref,
|
|
25
|
+
appConfig
|
|
26
|
+
}: TestBootstrapsProviderProps) {
|
|
36
27
|
return (
|
|
37
28
|
<TestBootstrapsProvider
|
|
38
29
|
data-testid="TestApp"
|
|
39
30
|
routerInitialEntries={routerInitialEntries}
|
|
40
31
|
routerInitialIndex={routerInitialIndex}
|
|
32
|
+
bootHref={bootHref}
|
|
33
|
+
appConfig={appConfig}
|
|
41
34
|
>
|
|
42
35
|
{children}
|
|
43
36
|
</TestBootstrapsProvider>
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
import { appConfig as globalsAppConfig } from '@/core/globals';
|
|
1
2
|
import { IOCContext } from '@/uikit/contexts/IOCContext';
|
|
2
3
|
import { TestRouter } from './TestRouter';
|
|
3
4
|
import { testIOC } from '../testIOC/TestIOC';
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
children,
|
|
7
|
-
routerInitialEntries,
|
|
8
|
-
routerInitialIndex
|
|
9
|
-
}: {
|
|
5
|
+
import type { EnvConfigInterface } from '@qlover/corekit-bridge';
|
|
6
|
+
export interface TestBootstrapsProviderProps {
|
|
10
7
|
children: React.ReactNode;
|
|
11
8
|
/**
|
|
12
9
|
* Initial URL path for the router
|
|
@@ -18,8 +15,30 @@ export function TestBootstrapsProvider({
|
|
|
18
15
|
* @default 0
|
|
19
16
|
*/
|
|
20
17
|
routerInitialIndex?: number;
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The boot href
|
|
21
|
+
*
|
|
22
|
+
* @default `https://localhost.test:3000/en/`
|
|
23
|
+
*/
|
|
24
|
+
bootHref?: string;
|
|
25
|
+
|
|
26
|
+
appConfig?: EnvConfigInterface;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultBootHref = 'https://localhost.test:3000/en/';
|
|
30
|
+
|
|
31
|
+
export function TestBootstrapsProvider({
|
|
32
|
+
children,
|
|
33
|
+
routerInitialEntries,
|
|
34
|
+
routerInitialIndex,
|
|
35
|
+
bootHref,
|
|
36
|
+
appConfig
|
|
37
|
+
}: TestBootstrapsProviderProps) {
|
|
38
|
+
const IOC = testIOC.create({
|
|
39
|
+
pathname: bootHref ?? defaultBootHref,
|
|
40
|
+
appConfig: appConfig ?? globalsAppConfig
|
|
41
|
+
});
|
|
23
42
|
|
|
24
43
|
return (
|
|
25
44
|
<IOCContext.Provider data-testid="TestBootstrapsProvider" value={IOC}>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock i18next-http-backend for testing
|
|
3
|
+
*
|
|
4
|
+
* This mock prevents network requests while still allowing i18next to work properly.
|
|
5
|
+
* It loads translation data directly from JSON files, enabling translation testing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import enCommon from '../../public/locales/en/common.json';
|
|
9
|
+
import zhCommon from '../../public/locales/zh/common.json';
|
|
10
|
+
import type { i18n as I18nType } from 'i18next';
|
|
11
|
+
|
|
12
|
+
// Translation resources loaded from JSON files
|
|
13
|
+
const resources: Record<string, Record<string, Record<string, string>>> = {
|
|
14
|
+
en: {
|
|
15
|
+
common: enCommon as Record<string, string>
|
|
16
|
+
},
|
|
17
|
+
zh: {
|
|
18
|
+
common: zhCommon as Record<string, string>
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mock HttpApi backend class for i18next
|
|
24
|
+
* Implements the backend interface to avoid network requests
|
|
25
|
+
*/
|
|
26
|
+
export class MockHttpBackend {
|
|
27
|
+
type = 'backend';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the backend
|
|
31
|
+
* This is called by i18next when .use() is called
|
|
32
|
+
*/
|
|
33
|
+
init(
|
|
34
|
+
_services: unknown,
|
|
35
|
+
_backendOptions: unknown,
|
|
36
|
+
_i18nextOptions: unknown,
|
|
37
|
+
i18nextInstance: I18nType
|
|
38
|
+
): void {
|
|
39
|
+
// Preload resources directly into i18next
|
|
40
|
+
if (
|
|
41
|
+
i18nextInstance &&
|
|
42
|
+
typeof i18nextInstance.addResourceBundle === 'function'
|
|
43
|
+
) {
|
|
44
|
+
// Add resources for all languages and namespaces
|
|
45
|
+
Object.keys(resources).forEach((lng) => {
|
|
46
|
+
Object.keys(resources[lng]).forEach((ns) => {
|
|
47
|
+
i18nextInstance.addResourceBundle(
|
|
48
|
+
lng,
|
|
49
|
+
ns,
|
|
50
|
+
resources[lng][ns],
|
|
51
|
+
true,
|
|
52
|
+
true
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read translation data for a language and namespace
|
|
61
|
+
* This method is called by i18next to load translations
|
|
62
|
+
*/
|
|
63
|
+
read(
|
|
64
|
+
language: string,
|
|
65
|
+
namespace: string,
|
|
66
|
+
callback: (error: Error | null, data?: Record<string, string>) => void
|
|
67
|
+
): void {
|
|
68
|
+
try {
|
|
69
|
+
const data = resources[language]?.[namespace];
|
|
70
|
+
if (data) {
|
|
71
|
+
// Return immediately with the data
|
|
72
|
+
callback(null, data);
|
|
73
|
+
} else {
|
|
74
|
+
callback(new Error(`Translation not found: ${language}/${namespace}`));
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
callback(error as Error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load URL - called by i18next-http-backend to load from URL
|
|
83
|
+
* We intercept this and return data from memory instead
|
|
84
|
+
*/
|
|
85
|
+
loadUrl(
|
|
86
|
+
_url: string,
|
|
87
|
+
callback: (error: Error | null, data?: Record<string, string>) => void
|
|
88
|
+
): void {
|
|
89
|
+
// Extract language and namespace from URL if possible
|
|
90
|
+
// Format: /locales/{{lng}}/{{ns}}.json
|
|
91
|
+
const match = _url.match(/locales\/([^/]+)\/([^/]+)\.json/);
|
|
92
|
+
if (match) {
|
|
93
|
+
const [, lng, ns] = match;
|
|
94
|
+
const data = resources[lng]?.[ns];
|
|
95
|
+
if (data) {
|
|
96
|
+
callback(null, data);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Fallback: return empty object
|
|
101
|
+
callback(null, {});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create - not used in our mock
|
|
106
|
+
*/
|
|
107
|
+
create(): void {
|
|
108
|
+
// No-op
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { baseNoLocaleRoutes, baseRoutes } from '@config/app.router';
|
|
2
|
-
import { useLocaleRoutes } from '@config/common';
|
|
2
|
+
import { routerPrefix, useLocaleRoutes } from '@config/common';
|
|
3
3
|
import { I } from '@config/IOCIdentifier';
|
|
4
4
|
import { themeConfig } from '@config/theme';
|
|
5
5
|
import { ThemeService } from '@qlover/corekit-bridge';
|
|
@@ -54,7 +54,8 @@ export class TestIOCRegister
|
|
|
54
54
|
{
|
|
55
55
|
routes: useLocaleRoutes ? baseRoutes : baseNoLocaleRoutes,
|
|
56
56
|
logger: ioc.get(I.Logger),
|
|
57
|
-
hasLocalRoutes: useLocaleRoutes
|
|
57
|
+
hasLocalRoutes: useLocaleRoutes,
|
|
58
|
+
routerPrefix: routerPrefix
|
|
58
59
|
}
|
|
59
60
|
)
|
|
60
61
|
);
|
|
@@ -49,3 +49,16 @@ global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
|
|
49
49
|
|
|
50
50
|
// Mock globals
|
|
51
51
|
vi.mock('@/core/globals', () => createMockGlobals());
|
|
52
|
+
|
|
53
|
+
// Mock i18next-http-backend to avoid network requests in tests
|
|
54
|
+
// This prevents timeout issues when I18nService tries to load language files
|
|
55
|
+
// The mock loads translations directly from JSON files, enabling translation testing
|
|
56
|
+
vi.mock('i18next-http-backend', async () => {
|
|
57
|
+
const { MockHttpBackend } = await import('@__mocks__/i18nextHttpBackend');
|
|
58
|
+
return { default: MockHttpBackend };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Mock i18next-browser-languagedetector to avoid browser API calls
|
|
62
|
+
vi.mock('i18next-browser-languagedetector', () => ({
|
|
63
|
+
default: {}
|
|
64
|
+
}));
|
|
@@ -205,7 +205,8 @@ describe('I18nService', () => {
|
|
|
205
205
|
const result = service.t(key);
|
|
206
206
|
expect(result).toBe('translated_test.key');
|
|
207
207
|
expect(i18n.t).toHaveBeenCalledWith(key, {
|
|
208
|
-
lng: 'en'
|
|
208
|
+
lng: 'en',
|
|
209
|
+
nsSeparator: false
|
|
209
210
|
});
|
|
210
211
|
});
|
|
211
212
|
|
|
@@ -216,6 +217,7 @@ describe('I18nService', () => {
|
|
|
216
217
|
expect(result).toBe('translated_test.key');
|
|
217
218
|
expect(i18n.t).toHaveBeenCalledWith(key, {
|
|
218
219
|
lng: 'en',
|
|
220
|
+
nsSeparator: false,
|
|
219
221
|
...params
|
|
220
222
|
});
|
|
221
223
|
});
|
|
@@ -6,8 +6,6 @@ import type { ExecutorPageBridgeInterface } from '@/base/port/ExecutorPageBridge
|
|
|
6
6
|
import type { JSONStoragePageBridgeInterface } from '@/base/port/JSONStoragePageBridgeInterface';
|
|
7
7
|
import type { RequestPageBridgeInterface } from '@/base/port/RequestPageBridgeInterface';
|
|
8
8
|
import type { I18nService } from '@/base/services/I18nService';
|
|
9
|
-
import type { MessageService } from '@/base/services/MessageService';
|
|
10
|
-
import type { ProcesserExecutor } from '@/base/services/ProcesserExecutor';
|
|
11
9
|
import type { RouteService } from '@/base/services/RouteService';
|
|
12
10
|
import type { UserService } from '@/base/services/UserService';
|
|
13
11
|
import type * as CorekitBridge from '@qlover/corekit-bridge';
|
|
@@ -30,10 +28,17 @@ export const IOCIdentifier = Object.freeze({
|
|
|
30
28
|
AntdStaticApiInterface: 'AntdStaticApiInterface',
|
|
31
29
|
RequestCatcherInterface: 'RequestCatcherInterface',
|
|
32
30
|
I18nServiceInterface: 'I18nServiceInterface',
|
|
33
|
-
ProcesserExecutorInterface: 'ProcesserExecutorInterface',
|
|
34
31
|
RouteServiceInterface: 'RouteServiceInterface',
|
|
32
|
+
/**
|
|
33
|
+
* User service interface
|
|
34
|
+
*
|
|
35
|
+
* This interface is implemented by the corekit-bridge UserServiceInterface interface, used to manage user information and authentication.
|
|
36
|
+
*
|
|
37
|
+
* @example ```
|
|
38
|
+
* const userService = useIOC(IOCIdentifier.UserServiceInterface);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
35
41
|
UserServiceInterface: 'UserServiceInterface',
|
|
36
|
-
MessageServiceInterface: 'MessageServiceInterface',
|
|
37
42
|
I18nKeyErrorPlugin: 'I18nKeyErrorPlugin',
|
|
38
43
|
FeApiCommonPlugin: 'FeApiCommonPlugin',
|
|
39
44
|
ApiMockPlugin: 'ApiMockPlugin',
|
|
@@ -73,10 +78,8 @@ export interface IOCIdentifierMap {
|
|
|
73
78
|
[IOCIdentifier.AntdStaticApiInterface]: DialogHandler;
|
|
74
79
|
[IOCIdentifier.RequestCatcherInterface]: RequestStatusCatcher;
|
|
75
80
|
[IOCIdentifier.I18nServiceInterface]: I18nService;
|
|
76
|
-
[IOCIdentifier.ProcesserExecutorInterface]: ProcesserExecutor;
|
|
77
81
|
[IOCIdentifier.RouteServiceInterface]: RouteService;
|
|
78
82
|
[IOCIdentifier.UserServiceInterface]: UserService;
|
|
79
|
-
[IOCIdentifier.MessageServiceInterface]: MessageService;
|
|
80
83
|
[IOCIdentifier.I18nKeyErrorPlugin]: I18nKeyErrorPlugin;
|
|
81
84
|
[IOCIdentifier.FeApiCommonPlugin]: CorekitBridge.RequestCommonPlugin;
|
|
82
85
|
[IOCIdentifier.ApiMockPlugin]: CorekitBridge.ApiMockPlugin;
|
|
@@ -35,6 +35,8 @@ export const loggerStyles = {
|
|
|
35
35
|
*
|
|
36
36
|
* - 需要以 / 开头
|
|
37
37
|
* - 但是不能只有 /
|
|
38
|
+
*
|
|
39
|
+
* **TODO: 未来可能需要修改为支持 vercel 环境使用前缀**
|
|
38
40
|
*/
|
|
39
41
|
export const routerPrefix = '/router-root';
|
|
40
42
|
|
|
@@ -45,3 +47,39 @@ export const routerPrefix = '/router-root';
|
|
|
45
47
|
* - false: 不使用本地化路由,直接使用路径 (例如: /home)
|
|
46
48
|
*/
|
|
47
49
|
export const useLocaleRoutes = true;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 注入到浏览器全局变量中需要忽略的变量
|
|
53
|
+
*
|
|
54
|
+
* 应用 `@/core/globals.ts` 中的变量
|
|
55
|
+
*
|
|
56
|
+
* 可能 appConfig 有敏感信息,需要忽略
|
|
57
|
+
*
|
|
58
|
+
* - 可以直接忽略整个 appConfig 对象, 例如: 'appConfig'
|
|
59
|
+
* - 也可以忽略单个属性, 例如: 'appConfig.openAiTokenPrefix', 'appConfig.openAiToken'
|
|
60
|
+
*
|
|
61
|
+
* @example 忽略 appConfig 对象
|
|
62
|
+
* ```typescript
|
|
63
|
+
* export const omitInjectedGlobals = [
|
|
64
|
+
* 'appConfig'
|
|
65
|
+
* ];
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example 忽略 appConfig 中的 openAiTokenPrefix 和 openAiToken 属性
|
|
69
|
+
* ```typescript
|
|
70
|
+
* export const omitInjectedGlobals = [
|
|
71
|
+
* 'appConfig.openAiTokenPrefix',
|
|
72
|
+
* 'appConfig.openAiToken'
|
|
73
|
+
* ];
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export const omitInjectedGlobals = [
|
|
77
|
+
'appConfig.openAiTokenPrefix',
|
|
78
|
+
'appConfig.openAiToken',
|
|
79
|
+
'appConfig.loginPassword',
|
|
80
|
+
'appConfig.loginUser',
|
|
81
|
+
'appConfig.aiApiTokenPrefix',
|
|
82
|
+
'appConfig.openAiBaseUrl',
|
|
83
|
+
'appConfig.aiApiBaseUrl',
|
|
84
|
+
'appConfig.aiApiToken'
|
|
85
|
+
];
|
|
@@ -3,29 +3,19 @@
|
|
|
3
3
|
"mock": true,
|
|
4
4
|
"noUrl": true
|
|
5
5
|
},
|
|
6
|
-
|
|
7
|
-
"name": "John Doe",
|
|
8
|
-
"email": "john.doe@example.com",
|
|
9
|
-
"picture": "https://randomuser.me/api/portraits/men/1.jpg"
|
|
10
|
-
},
|
|
6
|
+
|
|
11
7
|
"GET /api/userinfo": {
|
|
12
8
|
"name": "John Doe",
|
|
13
9
|
"email": "john.doe@example.com",
|
|
14
10
|
"picture": "https://randomuser.me/api/portraits/men/1.jpg"
|
|
15
11
|
},
|
|
16
|
-
"POST https://feapi.example.com/api/login": {
|
|
17
|
-
"token": "asdfasdf123123asdfasdf"
|
|
18
|
-
},
|
|
19
12
|
"POST /api/login": {
|
|
20
13
|
"token": "/api/login-token-adfasdfasdf"
|
|
21
14
|
},
|
|
22
|
-
"POST https://feapi.example.com/api/register": {
|
|
23
|
-
"token": "asdfasdf123123asdfasdf"
|
|
24
|
-
},
|
|
25
15
|
"POST /api/register": {
|
|
26
16
|
"token": "asdfasdf123123asdfasdf"
|
|
27
17
|
},
|
|
28
|
-
"POST
|
|
18
|
+
"POST /chat/completions": {
|
|
29
19
|
"id": "chatcmpl-1234567890",
|
|
30
20
|
"object": "chat.completion",
|
|
31
21
|
"created": 1721702400,
|
|
@@ -37,5 +27,8 @@
|
|
|
37
27
|
}
|
|
38
28
|
}
|
|
39
29
|
]
|
|
30
|
+
},
|
|
31
|
+
"POST /api/logout": {
|
|
32
|
+
"redirect": "/login"
|
|
40
33
|
}
|
|
41
34
|
}
|
|
@@ -7,10 +7,7 @@ import unusedImports from 'eslint-plugin-unused-imports';
|
|
|
7
7
|
import qloverEslint from '@qlover/eslint-plugin';
|
|
8
8
|
import tseslint from 'typescript-eslint';
|
|
9
9
|
import globals from 'globals';
|
|
10
|
-
import {
|
|
11
|
-
disableGlobals,
|
|
12
|
-
restrictSpecificGlobals
|
|
13
|
-
} from './makes/eslint-utils.mjs';
|
|
10
|
+
import { restrictSpecificGlobals } from './makes/eslint-utils.mjs';
|
|
14
11
|
|
|
15
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
16
13
|
const __dirname = dirname(__filename);
|
|
@@ -186,10 +183,14 @@ const eslintConfig = [
|
|
|
186
183
|
'prettier/prettier': [
|
|
187
184
|
'error',
|
|
188
185
|
{
|
|
189
|
-
semi: true,
|
|
190
186
|
singleQuote: true,
|
|
191
187
|
trailingComma: 'none',
|
|
192
188
|
endOfLine: 'lf'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
// 仅用于单独部署时对 eslint prettier 插件自动查找 prettierrc 时报错
|
|
192
|
+
// 注意: vscode 等编辑器会失效, 作为单独项目开发时可以去掉
|
|
193
|
+
usePrettierrc: false
|
|
193
194
|
}
|
|
194
195
|
],
|
|
195
196
|
// 默认禁用 export default
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dev:force": "vite --mode localhost --force",
|
|
33
33
|
"dev:staging": "vite --mode staging",
|
|
34
34
|
"dev:prod": "vite --mode production",
|
|
35
|
-
"build": "
|
|
35
|
+
"build": "vite build",
|
|
36
36
|
"build:staging": "npm run lint && vite build --mode staging",
|
|
37
37
|
"build:prod": "npm run lint && vite build --mode production",
|
|
38
38
|
"build:analyze": "vite build --mode production && start dist/stats.html",
|