@dune2/tools 0.7.3 → 1.0.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/package.json +9 -17
- package/src/index.ts +2 -0
- package/src/niceModal/createModal.tsx +2 -2
- package/src/niceModal/index.tsx +1 -1
- package/src/niceModal/niceModalStore.tsx +31 -29
- package/src/numbro/index.ts +7 -9
- package/src/numbro/shared.ts +0 -5
- package/src/rq/RequestBuilder.react-server.ts +157 -0
- package/src/rq/createApi.ts +1 -1
- package/src/rq/index.react-server.ts +1 -0
- package/src/shared/Overwrite.ts +1 -0
- package/src/storage/index.ts +6 -3
- package/src/i18n/EventEmitterType.ts +0 -26
- package/src/i18n/I18nProvider.tsx +0 -27
- package/src/i18n/compile.ts +0 -96
- package/src/i18n/duneI18n.ts +0 -222
- package/src/i18n/index.react-server.tsx +0 -38
- package/src/i18n/index.ts +0 -22
- package/src/i18n/msg.ts +0 -45
- package/src/i18n/readme.md +0 -52
- package/src/i18n/shared.tsx +0 -35
- package/src/i18n/t.ts +0 -69
- package/src/i18n/useLocale.ts +0 -19
- package/src/i18n/useT.ts +0 -10
- package/src/valtio/index.ts +0 -54
- package/src/valtio/shared.ts +0 -12
- package/src/valtio/withAutoSet.ts +0 -60
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dune2/tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n"
|
|
@@ -14,14 +14,12 @@
|
|
|
14
14
|
"author": "",
|
|
15
15
|
"exports": {
|
|
16
16
|
"./factory/*": "./src/factory/*.tsx",
|
|
17
|
-
"./i18n": {
|
|
18
|
-
"react-server": "./src/i18n/index.react-server.tsx",
|
|
19
|
-
"import": "./src/i18n/index.ts",
|
|
20
|
-
"require": "./src/i18n/index.ts"
|
|
21
|
-
},
|
|
22
|
-
"./i18n/*": "./src/i18n/*",
|
|
23
17
|
"./numbro": "./src/numbro/index.ts",
|
|
24
|
-
"./rq":
|
|
18
|
+
"./rq": {
|
|
19
|
+
"react-server": "./src/rq/index.react-server.ts",
|
|
20
|
+
"import": "./src/rq/index.ts",
|
|
21
|
+
"require": "./src/rq/index.ts"
|
|
22
|
+
},
|
|
25
23
|
"./rq/*": "./src/rq/*.ts",
|
|
26
24
|
"./shared": "./src/shared/index.ts",
|
|
27
25
|
"./shared/*": "./src/shared/*.ts",
|
|
@@ -29,21 +27,16 @@
|
|
|
29
27
|
"./storage/*": "./src/storage/*.ts",
|
|
30
28
|
"./logger": "./src/logger/index.ts",
|
|
31
29
|
"./logger/*": "./src/logger/*.ts",
|
|
32
|
-
"./valtio": "./src/valtio/index.ts",
|
|
33
|
-
"./valtio/*": "./src/valtio/*.ts",
|
|
34
30
|
"./niceModal": "./src/niceModal/index.tsx",
|
|
35
31
|
"./store": "./src/store/index.ts",
|
|
36
|
-
"./package.json": "./package.json"
|
|
32
|
+
"./package.json": "./package.json",
|
|
33
|
+
".": "./src/index.ts"
|
|
37
34
|
},
|
|
38
35
|
"files": [
|
|
39
36
|
"src"
|
|
40
37
|
],
|
|
41
38
|
"dependencies": {
|
|
42
39
|
"@ebay/nice-modal-react": "^1.2.13",
|
|
43
|
-
"@lingui/core": "^4.7.1",
|
|
44
|
-
"@lingui/detect-locale": "^4.7.1",
|
|
45
|
-
"@lingui/react": "^4.7.1",
|
|
46
|
-
"@messageformat/parser": "^5.1.0",
|
|
47
40
|
"bignumber.js": "^9.1.2",
|
|
48
41
|
"js-cookie": "^3.0.5",
|
|
49
42
|
"lodash": "^4",
|
|
@@ -55,8 +48,7 @@
|
|
|
55
48
|
"@types/lodash": "^4.17.0",
|
|
56
49
|
"axios": "^1.6.8",
|
|
57
50
|
"react": "^19",
|
|
58
|
-
"valtio": "^2"
|
|
59
|
-
"zx": "^7.0.8"
|
|
51
|
+
"valtio": "^2"
|
|
60
52
|
},
|
|
61
53
|
"publishConfig": {
|
|
62
54
|
"access": "public"
|
package/src/index.ts
ADDED
|
@@ -12,7 +12,7 @@ export const createModal = <P extends object, Res = unknown>(
|
|
|
12
12
|
NiceModal.register(modalId, customModal);
|
|
13
13
|
return Object.assign(customModal, {
|
|
14
14
|
async show(props?: P): Promise<Res> {
|
|
15
|
-
await niceModalStore.initModal(modalId);
|
|
15
|
+
await niceModalStore.actions.initModal(modalId);
|
|
16
16
|
return NiceModal.show(modalId, props);
|
|
17
17
|
},
|
|
18
18
|
hide() {
|
|
@@ -25,7 +25,7 @@ export const createModal = <P extends object, Res = unknown>(
|
|
|
25
25
|
* 获取当前弹框是否打开
|
|
26
26
|
*/
|
|
27
27
|
getVisible() {
|
|
28
|
-
return !!niceModalStore.modals[modalId]?.visible;
|
|
28
|
+
return !!niceModalStore.getState().modals[modalId]?.visible;
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
31
|
};
|
package/src/niceModal/index.tsx
CHANGED
|
@@ -25,7 +25,7 @@ export function NiceModalProvider(props: PropsWithChildren) {
|
|
|
25
25
|
const { modals } = niceModalStore.useSnapshot();
|
|
26
26
|
|
|
27
27
|
return (
|
|
28
|
-
<Provider dispatch={niceModalStore.dispatch} modals={modals}>
|
|
28
|
+
<Provider dispatch={niceModalStore.actions.dispatch} modals={modals}>
|
|
29
29
|
{props.children}
|
|
30
30
|
</Provider>
|
|
31
31
|
);
|
|
@@ -3,36 +3,38 @@ import {
|
|
|
3
3
|
type NiceModalAction,
|
|
4
4
|
type NiceModalStore,
|
|
5
5
|
} from "@ebay/nice-modal-react";
|
|
6
|
-
import {
|
|
6
|
+
import { createStore } from "../store";
|
|
7
7
|
|
|
8
|
-
export const niceModalStore =
|
|
9
|
-
|
|
8
|
+
export const niceModalStore = createStore({
|
|
9
|
+
name: "niceModalStore",
|
|
10
|
+
state: {
|
|
10
11
|
modals: {} as NiceModalStore,
|
|
11
|
-
dispatch(action: NiceModalAction) {
|
|
12
|
-
niceModalStore.modals = reducer(niceModalStore.modals, action);
|
|
13
|
-
},
|
|
14
|
-
/**
|
|
15
|
-
* 为了让 modal 第一次打开的时候过渡动画存在
|
|
16
|
-
* 目前主要是兼容 mantine ui,它们使用的是 css 动画
|
|
17
|
-
* 要确保 dom 存在
|
|
18
|
-
*/
|
|
19
|
-
async initModal(id: string) {
|
|
20
|
-
if (niceModalStore.modals[id]) {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
niceModalStore.modals = {
|
|
24
|
-
...niceModalStore.modals,
|
|
25
|
-
[id]: {
|
|
26
|
-
id,
|
|
27
|
-
visible: false,
|
|
28
|
-
delayVisible: false,
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
// 确保下一帧调用,让 过渡动画有效果
|
|
32
|
-
return Promise.resolve();
|
|
33
|
-
},
|
|
34
12
|
},
|
|
35
|
-
{
|
|
36
|
-
|
|
13
|
+
actionsCreator: (state) => {
|
|
14
|
+
return {
|
|
15
|
+
dispatch(action: NiceModalAction) {
|
|
16
|
+
state.modals = reducer(state.modals, action);
|
|
17
|
+
},
|
|
18
|
+
/**
|
|
19
|
+
* 为了让 modal 第一次打开的时候过渡动画存在
|
|
20
|
+
* 目前主要是兼容 mantine ui,它们使用的是 css 动画
|
|
21
|
+
* 要确保 dom 存在
|
|
22
|
+
*/
|
|
23
|
+
async initModal(id: string) {
|
|
24
|
+
if (state.modals[id]) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
state.modals = {
|
|
28
|
+
...state.modals,
|
|
29
|
+
[id]: {
|
|
30
|
+
id,
|
|
31
|
+
visible: false,
|
|
32
|
+
delayVisible: false,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
// 确保下一帧调用,让 过渡动画有效果
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
},
|
|
38
|
+
};
|
|
37
39
|
},
|
|
38
|
-
);
|
|
40
|
+
});
|
package/src/numbro/index.ts
CHANGED
|
@@ -194,7 +194,6 @@ export class Numbro {
|
|
|
194
194
|
};
|
|
195
195
|
let {
|
|
196
196
|
position = "prefix",
|
|
197
|
-
currencySymbol,
|
|
198
197
|
symbol,
|
|
199
198
|
spaceSeparated,
|
|
200
199
|
|
|
@@ -203,10 +202,6 @@ export class Numbro {
|
|
|
203
202
|
// 是否强制显示正负号
|
|
204
203
|
const sign = this.getPrefixSign(rest.forceSign);
|
|
205
204
|
|
|
206
|
-
// TODO: 兼容之前的逻辑 后面会移除
|
|
207
|
-
if (typeof currencySymbol !== "undefined") {
|
|
208
|
-
symbol = currencySymbol;
|
|
209
|
-
}
|
|
210
205
|
let space = spaceSeparated ? " " : "";
|
|
211
206
|
// 在 currency format 中,需要使用绝对值来格式化
|
|
212
207
|
// 方便后续添加 正负号
|
|
@@ -278,10 +273,13 @@ export class Numbro {
|
|
|
278
273
|
}
|
|
279
274
|
|
|
280
275
|
if (shouldDeleteEndZero) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
276
|
+
const isDecimal = outputFormat.includes(".");
|
|
277
|
+
if (isDecimal) {
|
|
278
|
+
// 移除尾数 0
|
|
279
|
+
// outputFormat = outputFormat.replace(/\.?0+$/, "");
|
|
280
|
+
const reg = new RegExp(`\\.?0+${suffix}$`);
|
|
281
|
+
outputFormat = outputFormat.replace(reg, suffix);
|
|
282
|
+
}
|
|
285
283
|
}
|
|
286
284
|
return outputFormat;
|
|
287
285
|
}
|
package/src/numbro/shared.ts
CHANGED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
|
|
3
|
+
import { queryClient } from "./defaultQueryClient";
|
|
4
|
+
import type { Basic, RequestBuilderOptions, RequestConfig } from "./options";
|
|
5
|
+
|
|
6
|
+
export class RequestBuilder<Req = any, Res = any> {
|
|
7
|
+
constructor(public options: RequestBuilderOptions<Req, Res>) {
|
|
8
|
+
this.defaultQueryFn = this.defaultQueryFn.bind(this);
|
|
9
|
+
this.request = this.request.bind(this);
|
|
10
|
+
this.requestWithConfig = this.requestWithConfig.bind(this);
|
|
11
|
+
this.options.method ??= "get";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#region default requestFn
|
|
15
|
+
static requestFn: Basic["requestFn"] | null = null;
|
|
16
|
+
static setRequestFn(requestFn: Basic["requestFn"] | null) {
|
|
17
|
+
RequestBuilder.requestFn = requestFn;
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
|
|
21
|
+
// 这是默认的 Query Client 实例
|
|
22
|
+
static queryClient: QueryClient | null = queryClient;
|
|
23
|
+
static setQueryClient(queryClient: QueryClient | null) {
|
|
24
|
+
RequestBuilder.queryClient = queryClient;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 确保 queryClient 的存在
|
|
29
|
+
* 会依次从以下地方获取
|
|
30
|
+
* - options
|
|
31
|
+
* - 当前实例 options
|
|
32
|
+
* - RequestBuilder.queryClient
|
|
33
|
+
*/
|
|
34
|
+
ensureQueryClient(options?: { queryClient?: QueryClient }) {
|
|
35
|
+
const queryClient =
|
|
36
|
+
options?.queryClient ??
|
|
37
|
+
this.options.queryClient ??
|
|
38
|
+
RequestBuilder.queryClient;
|
|
39
|
+
if (!queryClient) {
|
|
40
|
+
throw new Error("queryClient is not defined");
|
|
41
|
+
}
|
|
42
|
+
return queryClient;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 包装好的请求函数
|
|
47
|
+
* useQuery、useMutation 内部会调用这个
|
|
48
|
+
* 另外也可以直接调用这个函数来发送请求
|
|
49
|
+
* @param params 请求参数 默认会根据请求方法来放到 url 上或者 body 里
|
|
50
|
+
* @param config axios 的配置,一般不需要传,内部用
|
|
51
|
+
*/
|
|
52
|
+
request<P extends Req, T = Res>(params?: P, config?: RequestConfig) {
|
|
53
|
+
const method = this.options.method!;
|
|
54
|
+
let data;
|
|
55
|
+
// 根据请求方法来放到 url 上或者 body 里
|
|
56
|
+
if (!["get", "head", "options"].includes(method)) {
|
|
57
|
+
data = params;
|
|
58
|
+
params = undefined;
|
|
59
|
+
}
|
|
60
|
+
return this.requestWithConfig<T>({
|
|
61
|
+
meta: this.options.meta,
|
|
62
|
+
...config,
|
|
63
|
+
data,
|
|
64
|
+
params,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 常规情况下使用 request 方法就可以了
|
|
70
|
+
* 特殊情况,如:url 上有 query 参数,又需要传 body 参数
|
|
71
|
+
*/
|
|
72
|
+
requestWithConfig<T = Res>(config: RequestConfig) {
|
|
73
|
+
const method = this.options.method!;
|
|
74
|
+
let { url } = this.options;
|
|
75
|
+
// 优先使用传入的 requestFn
|
|
76
|
+
// 其次使用实例化时候的 requestFn
|
|
77
|
+
let requestFn =
|
|
78
|
+
config.requestFn ?? this.options.requestFn ?? RequestBuilder.requestFn;
|
|
79
|
+
if (!requestFn) {
|
|
80
|
+
throw new Error("request function is not defined");
|
|
81
|
+
}
|
|
82
|
+
this.options.urlPathParams?.forEach((param) => {
|
|
83
|
+
let t = "";
|
|
84
|
+
//#region config.params || config.data 在 queryHash 之后不变的话,会保持同一个引用,这里需要做个浅拷贝,将引用打破
|
|
85
|
+
if (config.params?.[param]) {
|
|
86
|
+
config.params = { ...config.params };
|
|
87
|
+
t = config.params[param];
|
|
88
|
+
delete config.params[param];
|
|
89
|
+
} else if (config.data?.[param]) {
|
|
90
|
+
config.data = { ...config.data };
|
|
91
|
+
t = config.data[param];
|
|
92
|
+
delete config.data[param];
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
url = url.replace(`{${param}}`, t);
|
|
96
|
+
});
|
|
97
|
+
return requestFn<T>({
|
|
98
|
+
url,
|
|
99
|
+
method,
|
|
100
|
+
...config,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#region query
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 获取 queryKey
|
|
108
|
+
* 通常配置 react-query 的 queryKey
|
|
109
|
+
*/
|
|
110
|
+
getQueryKey(params?: Req) {
|
|
111
|
+
if (typeof params === "undefined") {
|
|
112
|
+
return [this.options.url, this.options.method];
|
|
113
|
+
}
|
|
114
|
+
return [this.options.url, this.options.method!, params] as const;
|
|
115
|
+
}
|
|
116
|
+
defaultQueryFn = throwErrorInRSC;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 对 useQuery 的封装
|
|
120
|
+
* 获取数据的时候可以直接调用这个
|
|
121
|
+
* @see https://tanstack.com/query/v4/docs/guides/queries
|
|
122
|
+
*/
|
|
123
|
+
useQuery = throwErrorInRSC;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 用来预请求接口
|
|
127
|
+
* @see https://tanstack.com/query/v4/docs/guides/prefetching
|
|
128
|
+
*/
|
|
129
|
+
prefetchQuery = throwErrorInRSC;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 用来请求接口
|
|
133
|
+
* @see https://tanstack.com/query/v4/docs/react/reference/QueryClient#queryclientfetchquery
|
|
134
|
+
*/
|
|
135
|
+
fetchQuery = throwErrorInRSC;
|
|
136
|
+
invalidateQuery = throwErrorInRSC;
|
|
137
|
+
refetchQueries = throwErrorInRSC;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* https://tanstack.com/query/v5/docs/react/reference/QueryClient#queryclientgetquerydata
|
|
141
|
+
*/
|
|
142
|
+
getQueryData = throwErrorInRSC;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* https://tanstack.com/query/v5/docs/react/reference/QueryClient#queryclientsetquerydata
|
|
146
|
+
*/
|
|
147
|
+
setQueryData = throwErrorInRSC;
|
|
148
|
+
ensureQueryData = throwErrorInRSC;
|
|
149
|
+
useInfiniteQuery = throwErrorInRSC;
|
|
150
|
+
|
|
151
|
+
getMutationFn = throwErrorInRSC;
|
|
152
|
+
useMutation = throwErrorInRSC;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const throwErrorInRSC = () => {
|
|
156
|
+
throw new Error("This method is not supported in RSC");
|
|
157
|
+
};
|
package/src/rq/createApi.ts
CHANGED
|
@@ -29,7 +29,7 @@ export function createApi<Req, Res>(opts: Options<Req, Res>) {
|
|
|
29
29
|
const { requestFn, ...rest } = opts;
|
|
30
30
|
const api = new RequestBuilder<Req, Res>({
|
|
31
31
|
url: opts.queryKey,
|
|
32
|
-
// 给定 get 则 在 requestFn 中可以通过 params 获取到参数,否则是 data
|
|
32
|
+
// 给定 get 则 在 requestFn 中可以通过 params 获取到参数,否则是 data 字段。这是为了对齐 axios 的规则
|
|
33
33
|
method: "get",
|
|
34
34
|
requestFn: (config) => {
|
|
35
35
|
return requestFn(config.params) as never;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./RequestBuilder.react-server";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Overwrite<T, U> = Omit<T, keyof U> & U;
|
package/src/storage/index.ts
CHANGED
|
@@ -103,11 +103,14 @@ class StorageHelper<V = any> {
|
|
|
103
103
|
useValue() {
|
|
104
104
|
return useSyncExternalStore(
|
|
105
105
|
this.subscribe,
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
106
|
+
this.getSnapshot,
|
|
107
|
+
this.getServerSnapshot,
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
|
-
private
|
|
110
|
+
private getSnapshot = this.get.bind(this);
|
|
111
|
+
private getServerSnapshot = () => {
|
|
112
|
+
return this.defaultValue;
|
|
113
|
+
};
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
/**
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 这个文件重新定义 lingui 的 EventEmitter 类型
|
|
3
|
-
* 为了 添加更多的事件
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface EventEmitter {
|
|
7
|
-
on<E extends keyof Events>(event: E, listener: Events[E]): () => void;
|
|
8
|
-
removeListener<E extends keyof Events>(event: E, listener: Events[E]): void;
|
|
9
|
-
emit<E extends keyof Events>(event: E, ...args: Parameters<Events[E]>): void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Events {
|
|
13
|
-
change: () => void;
|
|
14
|
-
missing: (event: MissingMessageEvent) => void;
|
|
15
|
-
/**
|
|
16
|
-
* 与 change 事件不同的是:
|
|
17
|
-
* change 事件 不管是语言切换,还是语言包更新,都会触发
|
|
18
|
-
* 而 localeChange 只有语言切换时才会触发
|
|
19
|
-
*/
|
|
20
|
-
localeChange: (locale: string) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
type MissingMessageEvent = {
|
|
24
|
-
locale: string;
|
|
25
|
-
id: string;
|
|
26
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { I18nProvider as I18nProviderRaw } from "@lingui/react";
|
|
2
|
-
import type { PropsWithChildren } from "react";
|
|
3
|
-
import { useEffect } from "react";
|
|
4
|
-
import { i18n } from "./duneI18n";
|
|
5
|
-
|
|
6
|
-
interface I18nProviderPropsCustom {
|
|
7
|
-
enableDetectLocale?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 这里封装好了 传递给 Lingui 的 i18n 实例
|
|
12
|
-
* 以及在组件挂载时自动开启语言检测
|
|
13
|
-
*/
|
|
14
|
-
export const I18nProvider = (
|
|
15
|
-
props: PropsWithChildren<I18nProviderPropsCustom>,
|
|
16
|
-
) => {
|
|
17
|
-
const { enableDetectLocale = true } = props;
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (enableDetectLocale) {
|
|
20
|
-
i18n.activate(i18n.detectLocale());
|
|
21
|
-
}
|
|
22
|
-
}, [enableDetectLocale]);
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<I18nProviderRaw i18n={i18n.baseI18n}>{props.children}</I18nProviderRaw>
|
|
26
|
-
);
|
|
27
|
-
};
|
package/src/i18n/compile.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type { Content, Token } from "@messageformat/parser";
|
|
2
|
-
import { parse } from "@messageformat/parser";
|
|
3
|
-
|
|
4
|
-
export type CompiledIcuChoices = Record<string, CompiledMessage> & {
|
|
5
|
-
offset: number | undefined;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type CompiledMessageToken =
|
|
9
|
-
| string
|
|
10
|
-
| [name: string, type?: string, format?: null | string | CompiledIcuChoices];
|
|
11
|
-
|
|
12
|
-
export type CompiledMessage = string | CompiledMessageToken[];
|
|
13
|
-
|
|
14
|
-
type MapTextFn = (value: string) => string;
|
|
15
|
-
|
|
16
|
-
function processTokens(tokens: Token[], mapText: MapTextFn): CompiledMessage {
|
|
17
|
-
if (!tokens.filter((token) => token.type !== "content").length) {
|
|
18
|
-
return tokens.map((token) => mapText((token as Content).value)).join("");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return tokens.map<CompiledMessageToken>((token) => {
|
|
22
|
-
if (token.type === "content") {
|
|
23
|
-
return mapText(token.value);
|
|
24
|
-
|
|
25
|
-
// # in plural case
|
|
26
|
-
} else if (token.type === "octothorpe") {
|
|
27
|
-
return "#";
|
|
28
|
-
|
|
29
|
-
// simple argument
|
|
30
|
-
} else if (token.type === "argument") {
|
|
31
|
-
return [token.arg];
|
|
32
|
-
|
|
33
|
-
// argument with custom format (date, number)
|
|
34
|
-
} else if (token.type === "function") {
|
|
35
|
-
const _param = token?.param?.[0] as Content;
|
|
36
|
-
|
|
37
|
-
if (_param) {
|
|
38
|
-
return [token.arg, token.key, _param.value.trim()];
|
|
39
|
-
} else {
|
|
40
|
-
return [token.arg, token.key];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const offset = token.pluralOffset;
|
|
45
|
-
|
|
46
|
-
// complex argument with cases
|
|
47
|
-
const formatProps: Record<string, CompiledMessage> = {};
|
|
48
|
-
token.cases.forEach((item) => {
|
|
49
|
-
formatProps[item.key.replace(/^=(.)+/, "$1")] = processTokens(
|
|
50
|
-
item.tokens,
|
|
51
|
-
mapText,
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return [
|
|
56
|
-
token.arg,
|
|
57
|
-
token.type,
|
|
58
|
-
{
|
|
59
|
-
offset,
|
|
60
|
-
...formatProps,
|
|
61
|
-
} as CompiledIcuChoices,
|
|
62
|
-
];
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function compileMessage(
|
|
67
|
-
message: string,
|
|
68
|
-
mapText: MapTextFn = (v) => v,
|
|
69
|
-
): CompiledMessage {
|
|
70
|
-
try {
|
|
71
|
-
return processTokens(parse(message), mapText);
|
|
72
|
-
} catch (e) {
|
|
73
|
-
console.error(`${(e as Error).message} \n\nMessage: ${message}`);
|
|
74
|
-
return message;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 编译消息函数
|
|
80
|
-
* 使用建议:
|
|
81
|
-
* 1. 在 rsc 中直接使用,会减少 client 端的打包大小
|
|
82
|
-
* 2. 在 spa 中直接使用,会增加 client 端的打包大小
|
|
83
|
-
*/
|
|
84
|
-
export function compileMessages(msgs: Record<string, string>) {
|
|
85
|
-
// 创建一个空对象用于存储编译后的消息
|
|
86
|
-
const compiled: Record<string, any> = {};
|
|
87
|
-
|
|
88
|
-
// 遍历输入的消息对象的键
|
|
89
|
-
Object.keys(msgs).forEach((k) => {
|
|
90
|
-
// 将编译后的消息存入 compiled 对象
|
|
91
|
-
compiled[k] = compileMessage(msgs[k] || k);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// 返回编译后的消息对象
|
|
95
|
-
return compiled;
|
|
96
|
-
}
|
package/src/i18n/duneI18n.ts
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { setupI18n } from "@lingui/core";
|
|
2
|
-
import {
|
|
3
|
-
fromNavigator as fromNavigatorBase,
|
|
4
|
-
fromPath,
|
|
5
|
-
fromStorage,
|
|
6
|
-
fromUrl,
|
|
7
|
-
} from "@lingui/detect-locale";
|
|
8
|
-
import type { EventEmitter } from "./EventEmitterType";
|
|
9
|
-
|
|
10
|
-
import { LocalesEnum } from "./enums";
|
|
11
|
-
import type { Config } from "./shared";
|
|
12
|
-
|
|
13
|
-
const isServer = typeof window === "undefined";
|
|
14
|
-
|
|
15
|
-
const defaultConfig: Config = {
|
|
16
|
-
defaultLocale: LocalesEnum.en,
|
|
17
|
-
detectFromPath: false,
|
|
18
|
-
storageKey: "dune-lang",
|
|
19
|
-
queryKey: "lang",
|
|
20
|
-
supportedLocales: [],
|
|
21
|
-
debug: false,
|
|
22
|
-
navigatorMapper: [
|
|
23
|
-
["zh-", LocalesEnum.zh],
|
|
24
|
-
["id-", LocalesEnum.id],
|
|
25
|
-
["en-", LocalesEnum.en],
|
|
26
|
-
["lt-", LocalesEnum.lt],
|
|
27
|
-
["ru-", LocalesEnum.ru],
|
|
28
|
-
["ja-", LocalesEnum.ja],
|
|
29
|
-
["tr-", LocalesEnum.tr],
|
|
30
|
-
],
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// 这个 i18n 是对 lingui/core 的封装
|
|
34
|
-
export class DuneI18n {
|
|
35
|
-
baseI18n = setupI18n();
|
|
36
|
-
|
|
37
|
-
initT() {
|
|
38
|
-
return this.baseI18n.t.bind(this.baseI18n);
|
|
39
|
-
}
|
|
40
|
-
t = this.initT();
|
|
41
|
-
on = this.baseI18n.on.bind(this.baseI18n) as EventEmitter["on"];
|
|
42
|
-
emit = this.baseI18n.emit.bind(this.baseI18n) as EventEmitter["emit"];
|
|
43
|
-
|
|
44
|
-
get locale() {
|
|
45
|
-
return this.baseI18n.locale;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
log(...args: any[]) {
|
|
49
|
-
if (this.config.debug) {
|
|
50
|
-
console.log(`[dune-i18n]: `, ...args);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
//#region 一些配置
|
|
55
|
-
private config: Config = defaultConfig;
|
|
56
|
-
updateConfig(config: Partial<Config>) {
|
|
57
|
-
this.log("updateConfig ", config);
|
|
58
|
-
this.config = { ...this.config, ...config };
|
|
59
|
-
this.log("updateConfig result ", this.config);
|
|
60
|
-
}
|
|
61
|
-
// 主要给测试用例使用
|
|
62
|
-
resetConfig() {
|
|
63
|
-
this.config = defaultConfig;
|
|
64
|
-
this.log("reset to defaultConfig ", this.config);
|
|
65
|
-
}
|
|
66
|
-
//#endregion
|
|
67
|
-
|
|
68
|
-
//#region 支持的语言
|
|
69
|
-
// 用户可以额外设置,同时也会从已加载的语言包中获取
|
|
70
|
-
getSupportedLocales() {
|
|
71
|
-
const loadedLocales = Object.keys(this.messageLoader);
|
|
72
|
-
const r = this.config.supportedLocales.concat(loadedLocales);
|
|
73
|
-
this.log("getSupportedLocales ", r);
|
|
74
|
-
return r;
|
|
75
|
-
}
|
|
76
|
-
isSupportedLocale(locale: string) {
|
|
77
|
-
const r = this.getSupportedLocales().includes(locale);
|
|
78
|
-
this.log("isSupportedLocale ", locale, r);
|
|
79
|
-
return r;
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
|
|
83
|
-
//#region 推导语言
|
|
84
|
-
detectLocale() {
|
|
85
|
-
const { detectFromPath, storageKey, queryKey } = this.config;
|
|
86
|
-
// 判断 传过来的语言 是否可用
|
|
87
|
-
let defaultLocale = this.config.defaultLocale;
|
|
88
|
-
|
|
89
|
-
if (!isServer) {
|
|
90
|
-
const fromNavigator = () => {
|
|
91
|
-
const browserLocale = fromNavigatorBase();
|
|
92
|
-
const mappers = this.config.navigatorMapper;
|
|
93
|
-
for (let i = 0; i < mappers.length; i++) {
|
|
94
|
-
const [prefix, locale] = mappers[i];
|
|
95
|
-
if (browserLocale.startsWith(prefix)) {
|
|
96
|
-
return locale;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
// fix: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
|
|
102
|
-
const safeFromStorage = () => {
|
|
103
|
-
try {
|
|
104
|
-
return fromStorage(storageKey);
|
|
105
|
-
} catch (e) {
|
|
106
|
-
console.error("fromStorage error", e);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
let arr = [
|
|
111
|
-
detectFromPath && fromPath(0, location),
|
|
112
|
-
fromUrl(queryKey),
|
|
113
|
-
safeFromStorage(),
|
|
114
|
-
fromNavigator(),
|
|
115
|
-
];
|
|
116
|
-
for (let i = 0; i < arr.length; i++) {
|
|
117
|
-
const locale = arr[i];
|
|
118
|
-
if (locale && this.isSupportedLocale(locale)) {
|
|
119
|
-
defaultLocale = locale as LocalesEnum;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
this.log("detectLocale ", defaultLocale);
|
|
125
|
-
return defaultLocale;
|
|
126
|
-
}
|
|
127
|
-
//#endregion
|
|
128
|
-
activate = async (
|
|
129
|
-
locale:
|
|
130
|
-
| string
|
|
131
|
-
| {
|
|
132
|
-
/**
|
|
133
|
-
* 是否同步到 localStorage
|
|
134
|
-
* 在 ssg 第一次激活选中语言时,需要设置为 false,否则导致水合告警
|
|
135
|
-
* @default true
|
|
136
|
-
*/
|
|
137
|
-
syncToStorage?: boolean;
|
|
138
|
-
locale: string;
|
|
139
|
-
},
|
|
140
|
-
) => {
|
|
141
|
-
const opts = typeof locale !== "object" ? { locale } : locale;
|
|
142
|
-
this.log("activate ", opts);
|
|
143
|
-
|
|
144
|
-
const combinedLocale = opts.locale;
|
|
145
|
-
const syncToStorage = opts.syncToStorage ?? true;
|
|
146
|
-
|
|
147
|
-
// try load message
|
|
148
|
-
// 需要兼容 同步和异步加载的语言
|
|
149
|
-
// 原因是 同步的语言包不能再 下一个事件循环后再去激活语言
|
|
150
|
-
// 不然会导致 ssr 时 无法同步获取到语言
|
|
151
|
-
const loadPromise = this.tryLoadMessage(combinedLocale);
|
|
152
|
-
if (typeof loadPromise?.then === "function") {
|
|
153
|
-
await loadPromise;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
this.baseI18n.activate(combinedLocale);
|
|
157
|
-
this.emit("localeChange", combinedLocale);
|
|
158
|
-
// 默认需要 同步到 localStorage
|
|
159
|
-
if (syncToStorage) {
|
|
160
|
-
//https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document/
|
|
161
|
-
//fix: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
|
|
162
|
-
try {
|
|
163
|
-
localStorage.setItem(this.config.storageKey, combinedLocale);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.error("set localStorage error", error);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
//#region 注册语言包,并不一定会加载
|
|
171
|
-
private messageLoader: Record<string, MsgLoader> = {};
|
|
172
|
-
get messageLoadResult() {
|
|
173
|
-
//@ts-expect-error 内部属性
|
|
174
|
-
return this.baseI18n._messages;
|
|
175
|
-
}
|
|
176
|
-
register(locale: LocalesEnum, message: MsgLoader) {
|
|
177
|
-
this.messageLoader[locale] = message;
|
|
178
|
-
this.log("register ", locale, message);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 外部可以直接调用,加载语言包
|
|
183
|
-
* 这是一个同步方法
|
|
184
|
-
*/
|
|
185
|
-
loadMessage(locale: string, message: BaseMsg) {
|
|
186
|
-
this.baseI18n.load(locale, message);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 这里不能变成 async 方法,因为在 ssg 时,需要同步加载语言包
|
|
190
|
-
private tryLoadMessage(
|
|
191
|
-
locale: string,
|
|
192
|
-
loader = this.messageLoader[locale],
|
|
193
|
-
): Promise<void> | void {
|
|
194
|
-
if (!loader) {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
// case: i18n.register(LocalesEnum.zh, {});
|
|
198
|
-
if (typeof loader === "object") {
|
|
199
|
-
this.loadMessage(locale, loader);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
// case: i18n.register(LocalesEnum.zh, () => import("./zh.json"));
|
|
203
|
-
return loader().then((res) => {
|
|
204
|
-
this.tryLoadMessage(locale, res);
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
//#endregion
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export type BaseMsg = Record<string, any>;
|
|
212
|
-
|
|
213
|
-
export type MsgLoader =
|
|
214
|
-
| Record<string, unknown>
|
|
215
|
-
| (() => Promise<Record<string, unknown>>);
|
|
216
|
-
|
|
217
|
-
export const i18n = new DuneI18n();
|
|
218
|
-
|
|
219
|
-
if (typeof window !== "undefined") {
|
|
220
|
-
//@ts-ignore 暴露给浏览器插件使用
|
|
221
|
-
window["__d_i18n"] = i18n;
|
|
222
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// this is rsc exports
|
|
2
|
-
// will import in rsc by next.js automatically
|
|
3
|
-
// see https://github.com/vercel/next.js/blob/944159a6d44b30194b45c5297be303dd20aee904/packages/next/webpack.config.js#L233
|
|
4
|
-
|
|
5
|
-
import type { TransProps } from "@lingui/react";
|
|
6
|
-
import { i18n } from "./duneI18n";
|
|
7
|
-
|
|
8
|
-
import { TransNoContext } from "@lingui/react/server";
|
|
9
|
-
import { t } from "./t";
|
|
10
|
-
export * from "./enums";
|
|
11
|
-
export { msg } from "./msg";
|
|
12
|
-
export { i18n, t };
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* this use in rsc
|
|
16
|
-
* @example
|
|
17
|
-
* ```tsx
|
|
18
|
-
* <Trans>Refresh inbox</Trans>;
|
|
19
|
-
* <Trans>Attachment {name} saved.</Trans>;
|
|
20
|
-
* <Trans>Attachment {props.name ?? defaultName} saved.</Trans>;
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export function Trans(props: TransProps) {
|
|
24
|
-
return (
|
|
25
|
-
<TransNoContext
|
|
26
|
-
{...props}
|
|
27
|
-
lingui={{
|
|
28
|
-
i18n: i18n.baseI18n,
|
|
29
|
-
}}
|
|
30
|
-
/>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* this is use in rsc
|
|
35
|
-
*/
|
|
36
|
-
export function useT() {
|
|
37
|
-
return t;
|
|
38
|
-
}
|
package/src/i18n/index.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { Trans as TransRaw, type TransProps } from "@lingui/react";
|
|
3
|
-
import { type FC } from "react";
|
|
4
|
-
|
|
5
|
-
export type { I18nContext, I18nProviderProps } from "@lingui/react";
|
|
6
|
-
export { I18nProvider } from "./I18nProvider";
|
|
7
|
-
export { i18n } from "./duneI18n";
|
|
8
|
-
export { LocalesEnum } from "./enums";
|
|
9
|
-
export { msg } from "./msg";
|
|
10
|
-
export { t } from "./t";
|
|
11
|
-
export { useLocale } from "./useLocale";
|
|
12
|
-
export { useT } from "./useT";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @example
|
|
16
|
-
* ```tsx
|
|
17
|
-
* <Trans>Refresh inbox</Trans>;
|
|
18
|
-
* <Trans>Attachment {name} saved.</Trans>;
|
|
19
|
-
* <Trans>Attachment {props.name ?? defaultName} saved.</Trans>;
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export const Trans = TransRaw as FC<Partial<TransProps>>;
|
package/src/i18n/msg.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
//#region msg fn
|
|
2
|
-
import type { MessageDescriptor } from "@lingui/core";
|
|
3
|
-
|
|
4
|
-
interface MsgFn {
|
|
5
|
-
(descriptor: MessageDescriptor): string;
|
|
6
|
-
(id: string): string;
|
|
7
|
-
(literals: TemplateStringsArray): string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @see https://lingui.dev/tutorials/react-patterns#lazy-translations
|
|
12
|
-
* 标记需要提取的 i18n 字符串(用于提取翻译),主要给编译器用的
|
|
13
|
-
* 一般用在以下场景:
|
|
14
|
-
* 在组件外声明 options 需要翻译,组件外没有 t 函数
|
|
15
|
-
* @example
|
|
16
|
-
* ```jsx
|
|
17
|
-
* const favoriteColors = [msg`Red`, msg`Orange`, msg`Yellow`, msg`Green`];
|
|
18
|
-
* export default function ColorList() {
|
|
19
|
-
* const t = useT()
|
|
20
|
-
* return (
|
|
21
|
-
* <ul>
|
|
22
|
-
* {favoriteColors.map((color) => (
|
|
23
|
-
* <li>
|
|
24
|
-
* {t.ignoreExtract(color)}
|
|
25
|
-
* </li>
|
|
26
|
-
* ))}
|
|
27
|
-
* </ul>
|
|
28
|
-
* );
|
|
29
|
-
* }
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export const msg: MsgFn = (...args: any[]) => {
|
|
33
|
-
if (process.env.NODE_ENV !== "development") {
|
|
34
|
-
if (args.length > 1) {
|
|
35
|
-
throw new Error("msg函数只能接受字符串常量");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
let first = args[0];
|
|
39
|
-
if (first.raw) {
|
|
40
|
-
return first.raw[0];
|
|
41
|
-
}
|
|
42
|
-
return args[0];
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
//#endregion
|
package/src/i18n/readme.md
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
## @dune2/i18n
|
|
2
|
-
|
|
3
|
-
基于 [lingui](https://lingui.js.org/ref/macro.html) 包装的 i18n 工具。
|
|
4
|
-
|
|
5
|
-
由于`next.js 12`默认使用 `swc`编译,所以`lingui`的`babel`插件有些过时,这里提供了一系列基于`swc`的工具来对齐`lingui`部分功能。
|
|
6
|
-
|
|
7
|
-
- 支持`lingui`的`macro`插件中的`t`/`Trans`
|
|
8
|
-
- 支持提取翻译
|
|
9
|
-
|
|
10
|
-
### features
|
|
11
|
-
|
|
12
|
-
**需要启用`@dune2/swc-plugin`来编译**
|
|
13
|
-
|
|
14
|
-
- 提供`t`函数,用于翻译
|
|
15
|
-
|
|
16
|
-
```js
|
|
17
|
-
const name = "dune";
|
|
18
|
-
|
|
19
|
-
// 启用 `@dune2/swc-plugin` 可以使用以下方式
|
|
20
|
-
t`hello ${name}`; // hello dune
|
|
21
|
-
// 编译成,也可以直接写成下面的代码
|
|
22
|
-
t("hello {name}", { name }); // hello dune
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
- 提供`Trans`组件
|
|
26
|
-
|
|
27
|
-
```jsx
|
|
28
|
-
<Trans>Attachment {name} saved.</Trans>
|
|
29
|
-
// 编译成
|
|
30
|
-
<Trans id='Attachment {name} saved.' values={{name}}></Trans>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
```jsx
|
|
34
|
-
<Trans>Attachment {props.name ?? defaultName} saved.</Trans>;
|
|
35
|
-
// 编译成
|
|
36
|
-
<Trans
|
|
37
|
-
id="Attachment {0} saved."
|
|
38
|
-
values={{ 0: props.name ?? defaultName }}
|
|
39
|
-
></Trans>;
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
```jsx
|
|
43
|
-
<Trans>
|
|
44
|
-
Read the <a href="/docs">docs</a>.
|
|
45
|
-
</Trans>;
|
|
46
|
-
// 编译成
|
|
47
|
-
<Trans id="Read the <0>docs</0>." components={{ 0: <a href="/docs" /> }} />;
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### 配置`next.config.js`
|
|
51
|
-
|
|
52
|
-
使用 `@dune2/next-plugin`来预设相关配置
|
package/src/i18n/shared.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { LocalesEnum } from "./enums";
|
|
2
|
-
|
|
3
|
-
export interface Config {
|
|
4
|
-
// 推导失败后的默认语言
|
|
5
|
-
defaultLocale: LocalesEnum;
|
|
6
|
-
/**
|
|
7
|
-
* 存储的 key
|
|
8
|
-
* @default dune-lang
|
|
9
|
-
*/
|
|
10
|
-
storageKey: string;
|
|
11
|
-
/**
|
|
12
|
-
* 是否开启 debug 模式
|
|
13
|
-
*/
|
|
14
|
-
debug: boolean;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* url 参数
|
|
18
|
-
* @default lang
|
|
19
|
-
*/
|
|
20
|
-
queryKey: string;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 是否从 url 中获取语言 默认为 false
|
|
24
|
-
* 在后管系统中,一般不需要
|
|
25
|
-
*/
|
|
26
|
-
detectFromPath: boolean;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 用户可以额外设置,同时也会从已加载的语言包中获取
|
|
30
|
-
*/
|
|
31
|
-
supportedLocales: string[];
|
|
32
|
-
|
|
33
|
-
// [ 浏览器语言 , dune 语言 ]
|
|
34
|
-
navigatorMapper: [string, string][];
|
|
35
|
-
}
|
package/src/i18n/t.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { type MessageDescriptor, type MessageOptions } from "@lingui/core";
|
|
2
|
-
import { i18n } from "./duneI18n";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Translates a template string using the global I18n instance
|
|
6
|
-
* @example
|
|
7
|
-
* ```
|
|
8
|
-
* const message = t`Hello ${name}`;
|
|
9
|
-
* ```
|
|
10
|
-
*/
|
|
11
|
-
export let t = initT();
|
|
12
|
-
interface TFunction {
|
|
13
|
-
(
|
|
14
|
-
id: string,
|
|
15
|
-
values?: Record<string, unknown>,
|
|
16
|
-
options?: MessageOptions,
|
|
17
|
-
): string;
|
|
18
|
-
(descriptor: MessageDescriptor): string;
|
|
19
|
-
(literals: TemplateStringsArray, ...placeholders: any[]): string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 使用这个方法将被 cli 不会提取出来翻译
|
|
23
|
-
* 一般用在 key 是后端返回的动态情况
|
|
24
|
-
* 但是这种情况下需要开发者手动将翻译填入翻译文件中
|
|
25
|
-
*/
|
|
26
|
-
ignoreExtract: TFunction;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* 翻译后端错误信息
|
|
30
|
-
*
|
|
31
|
-
* 传入错误码,会补齐 `error_` 前缀
|
|
32
|
-
* 传入对象,会使用对象的 code 字段去翻译,如果没有对应的文案,则使用 message 字段返回
|
|
33
|
-
*/
|
|
34
|
-
displayError: (
|
|
35
|
-
arg:
|
|
36
|
-
| number
|
|
37
|
-
| string
|
|
38
|
-
| {
|
|
39
|
-
code?: string | number;
|
|
40
|
-
message?: string | number;
|
|
41
|
-
// 后端某些错误信息需要替换变量,这个字段用来传入变量
|
|
42
|
-
errorVars?: Record<string, string>;
|
|
43
|
-
},
|
|
44
|
-
) => string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function initT() {
|
|
48
|
-
let t = i18n.initT() as unknown as TFunction;
|
|
49
|
-
t.ignoreExtract = t;
|
|
50
|
-
t.displayError = (arg) => {
|
|
51
|
-
let normalizedArg = typeof arg === "object" ? arg : { code: arg };
|
|
52
|
-
// 修复外部可能 传入 undefined 或 null 的情况
|
|
53
|
-
if (!normalizedArg || !normalizedArg.code) {
|
|
54
|
-
return "";
|
|
55
|
-
}
|
|
56
|
-
let key = `error_${normalizedArg.code}`;
|
|
57
|
-
const translated = t(key, normalizedArg.errorVars);
|
|
58
|
-
// 如果没有对应的文案,则使用 message 字段返回
|
|
59
|
-
if (translated === key && !!normalizedArg.message) {
|
|
60
|
-
return normalizedArg.message + "";
|
|
61
|
-
}
|
|
62
|
-
return translated;
|
|
63
|
-
};
|
|
64
|
-
return t;
|
|
65
|
-
}
|
|
66
|
-
// 修复 更换语言的时候 t 没有重新生成,导致某些写在 useMemo 的 t 调用没有更新语言
|
|
67
|
-
i18n.on("change", () => {
|
|
68
|
-
t = initT();
|
|
69
|
-
});
|
package/src/i18n/useLocale.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { useLingui } from "@lingui/react";
|
|
2
|
-
import { i18n as globalI18n } from "./duneI18n";
|
|
3
|
-
import { LocalesEnum } from "./enums";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 获取当前的 语言 相关信息
|
|
7
|
-
*/
|
|
8
|
-
export function useLocale() {
|
|
9
|
-
const { i18n } = useLingui();
|
|
10
|
-
|
|
11
|
-
return {
|
|
12
|
-
locale: i18n.locale,
|
|
13
|
-
isZH: i18n.locale === LocalesEnum.zh,
|
|
14
|
-
isID: i18n.locale === LocalesEnum.id,
|
|
15
|
-
isEn: i18n.locale === LocalesEnum.en,
|
|
16
|
-
isLt: i18n.locale === LocalesEnum.lt,
|
|
17
|
-
activate: globalI18n.activate,
|
|
18
|
-
};
|
|
19
|
-
}
|
package/src/i18n/useT.ts
DELETED
package/src/valtio/index.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { snapshot, useSnapshot, proxy as valtioProxy } from "valtio";
|
|
2
|
-
|
|
3
|
-
import { devtools } from "valtio/utils";
|
|
4
|
-
import type { EnhancedStore } from "./shared";
|
|
5
|
-
|
|
6
|
-
export { snapshot, subscribe } from "valtio";
|
|
7
|
-
export { proxyMap, proxySet, subscribeKey, watch } from "valtio/utils";
|
|
8
|
-
export { withAutoSet } from "./withAutoSet";
|
|
9
|
-
|
|
10
|
-
const stores: any = {};
|
|
11
|
-
|
|
12
|
-
if (typeof window !== "undefined") {
|
|
13
|
-
Object.defineProperty(window, "__stores", {
|
|
14
|
-
get() {
|
|
15
|
-
const r = snapshot(valtioProxy(stores));
|
|
16
|
-
return { ...r, __raw: stores };
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @deprecated use createStore instead
|
|
23
|
-
*/
|
|
24
|
-
export function proxy<T extends object>(
|
|
25
|
-
initialObject: T,
|
|
26
|
-
opts: Options,
|
|
27
|
-
): EnhancedStore<T> {
|
|
28
|
-
const store = valtioProxy(initialObject) as EnhancedStore<T>;
|
|
29
|
-
|
|
30
|
-
if (typeof window !== "undefined" && opts.devtools === true) {
|
|
31
|
-
devtools(store, {
|
|
32
|
-
name: opts.name,
|
|
33
|
-
enabled: opts.devtools,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
stores[opts.name] = store;
|
|
37
|
-
|
|
38
|
-
store.useSnapshot = function useStoreSnapshot(options) {
|
|
39
|
-
return useSnapshot(options?.faker ? emptyStore : store, options) as T;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
return store;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface Options {
|
|
46
|
-
name: string;
|
|
47
|
-
/**
|
|
48
|
-
* false to disable devtools
|
|
49
|
-
* in development mode devtools are enabled by default
|
|
50
|
-
*/
|
|
51
|
-
devtools?: boolean;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const emptyStore = valtioProxy({});
|
package/src/valtio/shared.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type EnhancedStore<T extends object> = T & {
|
|
2
|
-
useSnapshot(options?: {
|
|
3
|
-
sync?: boolean;
|
|
4
|
-
/**
|
|
5
|
-
* 性能优化,某些 store 变化很频繁
|
|
6
|
-
* 但是其中的值可能在 部分场景才用到
|
|
7
|
-
* 通过设置 faker 为 true,可以避免不必要的渲染
|
|
8
|
-
* faker = true , 时候将返回一个不变的 store,这是假的 store
|
|
9
|
-
*/
|
|
10
|
-
faker?: boolean;
|
|
11
|
-
}): T;
|
|
12
|
-
};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import _ from "lodash";
|
|
2
|
-
|
|
3
|
-
//# region 增强 set 方法
|
|
4
|
-
type EnhancedWithSet<T extends object> = T & {
|
|
5
|
-
[k in NotFunctionKeys<T> as SetFnName<k> extends keyof T
|
|
6
|
-
? never
|
|
7
|
-
: SetFnName<k>]: (v: T[k]) => void;
|
|
8
|
-
};
|
|
9
|
-
type SetFnName<T> = T extends string ? `set${Capitalize<T>}` : never;
|
|
10
|
-
type NotFunctionKeys<T extends object> = keyof {
|
|
11
|
-
[k in keyof T as T[k] extends Function ? never : k]: T[k];
|
|
12
|
-
};
|
|
13
|
-
//# endregion
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* 为传进来的 store 增加相关属性的 set 方法
|
|
17
|
-
* eg:
|
|
18
|
-
* const store = { a: 1, b: 2 };
|
|
19
|
-
* const enhancedStore = addSetMethod(store);
|
|
20
|
-
* output:
|
|
21
|
-
* enhancedStore = { a: 1, b: 2, setA: (v) => { enhancedStore.a = v; }, setB: (v) => { enhancedStore.b = v; } };
|
|
22
|
-
*/
|
|
23
|
-
export function withAutoSet<T extends object>(store: T) {
|
|
24
|
-
type Result = T & EnhancedWithSet<T>;
|
|
25
|
-
|
|
26
|
-
const enhancedStore = store as Result;
|
|
27
|
-
|
|
28
|
-
_.forEach(enhancedStore, (value, key) => {
|
|
29
|
-
if (typeof value === "function") {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const setFnName = `set${_.upperFirst(key)}` as keyof Result;
|
|
33
|
-
|
|
34
|
-
if (_.has(store, setFnName)) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
// @ts-expect-error need to be fixed
|
|
38
|
-
enhancedStore[setFnName] = (v) => {
|
|
39
|
-
// @ts-expect-error need to be fixed
|
|
40
|
-
enhancedStore[key] = v;
|
|
41
|
-
};
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return enhancedStore;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
//type AA33 = NotFunctionKeys<A>;
|
|
48
|
-
//type A = { a: string; b: number; setA: () => void };
|
|
49
|
-
//type AA2 = Omit<A, SetFnName<keyof A>>;
|
|
50
|
-
//type AA3 = {
|
|
51
|
-
// [k in keyof AA2 as SetFnName<k> extends keyof A ? never : SetFnName<k>]: (
|
|
52
|
-
// v: AA2[k],
|
|
53
|
-
// ) => void;
|
|
54
|
-
//} & A;
|
|
55
|
-
//type StringKeys<T> = Extract<keyof T, string>;
|
|
56
|
-
//type Debug<T> = {
|
|
57
|
-
// [k in keyof T]: T[k];
|
|
58
|
-
//};
|
|
59
|
-
//type A22 = Debug<AA3>;
|
|
60
|
-
//type A222 = Debug<EnhancedWithSet<A>>;
|