@eggjs/i18n 3.0.1 → 4.0.0-beta.17
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/README.md +10 -18
- package/dist/app/extend/application.d.ts +12 -0
- package/dist/app/extend/application.js +48 -0
- package/dist/app/extend/context.d.ts +76 -0
- package/dist/app/extend/context.js +152 -0
- package/dist/{commonjs/app.d.ts → app.d.ts} +10 -8
- package/dist/app.js +96 -0
- package/{src/types.ts → dist/config/config.default.d.ts} +7 -33
- package/dist/config/config.default.js +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -0
- package/dist/locales.d.ts +7 -0
- package/dist/locales.js +53 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +10 -0
- package/package.json +54 -73
- package/dist/commonjs/app/extend/application.d.ts +0 -8
- package/dist/commonjs/app/extend/application.js +0 -80
- package/dist/commonjs/app/extend/context.d.ts +0 -72
- package/dist/commonjs/app/extend/context.js +0 -177
- package/dist/commonjs/app.js +0 -103
- package/dist/commonjs/config/config.default.d.ts +0 -5
- package/dist/commonjs/config/config.default.js +0 -16
- package/dist/commonjs/index.d.ts +0 -1
- package/dist/commonjs/index.js +0 -4
- package/dist/commonjs/locales.d.ts +0 -3
- package/dist/commonjs/locales.js +0 -71
- package/dist/commonjs/package.json +0 -3
- package/dist/commonjs/types.d.ts +0 -74
- package/dist/commonjs/types.js +0 -3
- package/dist/commonjs/utils.d.ts +0 -2
- package/dist/commonjs/utils.js +0 -12
- package/dist/esm/app/extend/application.d.ts +0 -8
- package/dist/esm/app/extend/application.js +0 -76
- package/dist/esm/app/extend/context.d.ts +0 -72
- package/dist/esm/app/extend/context.js +0 -174
- package/dist/esm/app.d.ts +0 -56
- package/dist/esm/app.js +0 -97
- package/dist/esm/config/config.default.d.ts +0 -5
- package/dist/esm/config/config.default.js +0 -14
- package/dist/esm/index.d.ts +0 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/locales.d.ts +0 -3
- package/dist/esm/locales.js +0 -65
- package/dist/esm/package.json +0 -3
- package/dist/esm/types.d.ts +0 -74
- package/dist/esm/types.js +0 -2
- package/dist/esm/utils.d.ts +0 -2
- package/dist/esm/utils.js +0 -8
- package/dist/package.json +0 -4
- package/src/app/extend/application.ts +0 -91
- package/src/app/extend/context.ts +0 -192
- package/src/app.ts +0 -107
- package/src/config/config.default.ts +0 -15
- package/src/index.ts +0 -1
- package/src/locales.ts +0 -73
- package/src/typings/index.d.ts +0 -4
- package/src/utils.ts +0 -8
package/README.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @eggjs/i18n
|
|
2
2
|
|
|
3
3
|
[![NPM version][npm-image]][npm-url]
|
|
4
|
-
[](https://github.com/eggjs/i18n/actions/workflows/nodejs.yml)
|
|
5
|
-
[![Test coverage][codecov-image]][codecov-url]
|
|
6
4
|
[![Known Vulnerabilities][snyk-image]][snyk-url]
|
|
7
5
|
[![npm download][download-image]][download-url]
|
|
8
6
|
[](https://nodejs.org/en/download/)
|
|
@@ -10,8 +8,6 @@
|
|
|
10
8
|
|
|
11
9
|
[npm-image]: https://img.shields.io/npm/v/@eggjs/i18n.svg?style=flat-square
|
|
12
10
|
[npm-url]: https://npmjs.org/package/@eggjs/i18n
|
|
13
|
-
[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/i18n.svg?style=flat-square
|
|
14
|
-
[codecov-url]: https://codecov.io/github/eggjs/i18n?branch=master
|
|
15
11
|
[snyk-image]: https://snyk.io/test/npm/@eggjs/i18n/badge.svg?style=flat-square
|
|
16
12
|
[snyk-url]: https://snyk.io/test/npm/@eggjs/i18n
|
|
17
13
|
[download-image]: https://img.shields.io/npm/dm/@eggjs/i18n.svg?style=flat-square
|
|
@@ -66,16 +62,16 @@ export default {
|
|
|
66
62
|
```ts
|
|
67
63
|
// config/locale/zh-CN.ts
|
|
68
64
|
export default {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
Email: '邮箱',
|
|
66
|
+
'Welcome back, %s!': '欢迎回来,%s!',
|
|
67
|
+
'Hello %s, how are you today?': '你好 %s, 今天过得咋样?',
|
|
72
68
|
};
|
|
73
69
|
```
|
|
74
70
|
|
|
75
71
|
```ts
|
|
76
72
|
// config/locale/en-US.ts
|
|
77
73
|
export default {
|
|
78
|
-
|
|
74
|
+
Email: 'Email',
|
|
79
75
|
};
|
|
80
76
|
```
|
|
81
77
|
|
|
@@ -94,14 +90,14 @@ export default {
|
|
|
94
90
|
|
|
95
91
|
I18n 为你提供 `__` (Alias: `gettext`) 函数,让你可以轻松获得 locale 文件夹下面的多语言文本。
|
|
96
92
|
|
|
97
|
-
> NOTE:
|
|
93
|
+
> NOTE: \_\_ 是两个下划线哦!
|
|
98
94
|
|
|
99
95
|
- `ctx.__ = function (key, value[, value2, ...])`: 类似 util.format 接口
|
|
100
96
|
- `ctx.__ = function (key, values)`: 支持数组下标占位符方式,如
|
|
101
97
|
|
|
102
98
|
```ts
|
|
103
|
-
ctx.__('{0} {0} {1} {1}'
|
|
104
|
-
ctx.gettext('{0} {0} {1} {1}'
|
|
99
|
+
ctx.__('{0} {0} {1} {1}', ['foo', 'bar']);
|
|
100
|
+
ctx.gettext('{0} {0} {1} {1}', ['foo', 'bar']);
|
|
105
101
|
|
|
106
102
|
=>
|
|
107
103
|
foo foo bar bar
|
|
@@ -124,12 +120,8 @@ export default ctx => {
|
|
|
124
120
|
|
|
125
121
|
```html
|
|
126
122
|
<li>{{ __('Email') }}: {{ user.email }}</li>
|
|
127
|
-
<li>
|
|
128
|
-
|
|
129
|
-
</li>
|
|
130
|
-
<li>
|
|
131
|
-
{{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}
|
|
132
|
-
</li>
|
|
123
|
+
<li>{{ __('Hello %s, how are you today?', user.name) }}</li>
|
|
124
|
+
<li>{{ __('{0} {0} {1} {1}', ['foo', 'bar']) }}</li>
|
|
133
125
|
```
|
|
134
126
|
|
|
135
127
|
### 修改应用的默认语言
|
|
@@ -152,6 +144,6 @@ Please open an issue [here](https://github.com/eggjs/egg/issues).
|
|
|
152
144
|
|
|
153
145
|
## Contributors
|
|
154
146
|
|
|
155
|
-
[](https://github.com/eggjs/egg/graphs/contributors)
|
|
156
148
|
|
|
157
149
|
Made with [contributors-img](https://contrib.rocks).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Application } from "egg";
|
|
2
|
+
|
|
3
|
+
//#region src/app/extend/application.d.ts
|
|
4
|
+
declare const I18N_RESOURCES: unique symbol;
|
|
5
|
+
declare class I18nApplication extends Application {
|
|
6
|
+
[I18N_RESOURCES]: Record<string, Record<string, string>>;
|
|
7
|
+
isSupportLocale(locale: string): boolean;
|
|
8
|
+
gettext(locale: string, key: string, value?: any, ...args: any[]): string;
|
|
9
|
+
__(locale: string, key: string, value?: any, ...args: any[]): string;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { I18N_RESOURCES, I18nApplication as default };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isObject } from "../../utils.js";
|
|
2
|
+
import { debuglog, format } from "node:util";
|
|
3
|
+
import { Application } from "egg";
|
|
4
|
+
|
|
5
|
+
//#region src/app/extend/application.ts
|
|
6
|
+
const debug = debuglog("egg/i18n/app/extend/application");
|
|
7
|
+
const I18N_RESOURCES = Symbol("Application i18n resources");
|
|
8
|
+
var I18nApplication = class extends Application {
|
|
9
|
+
isSupportLocale(locale) {
|
|
10
|
+
return !!this[I18N_RESOURCES][locale];
|
|
11
|
+
}
|
|
12
|
+
gettext(locale, key, value, ...args) {
|
|
13
|
+
if (!locale || !key) return "";
|
|
14
|
+
let text = (this[I18N_RESOURCES][locale] || {})[key];
|
|
15
|
+
if (text === void 0) text = key;
|
|
16
|
+
debug("%s: %j => %j", locale, key, text);
|
|
17
|
+
if (!text) return "";
|
|
18
|
+
if (value === void 0) return text;
|
|
19
|
+
if (args.length === 0) {
|
|
20
|
+
if (isObject(value)) return formatWithObject(text, value);
|
|
21
|
+
if (Array.isArray(value)) return formatWithArray(text, value);
|
|
22
|
+
return format(text, value);
|
|
23
|
+
}
|
|
24
|
+
return format(text, value, ...args);
|
|
25
|
+
}
|
|
26
|
+
__(locale, key, value, ...args) {
|
|
27
|
+
return this.gettext(locale, key, value, ...args);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const ARRAY_INDEX_RE = /\{(\d+)\}/g;
|
|
31
|
+
function formatWithArray(text, values) {
|
|
32
|
+
return text.replace(ARRAY_INDEX_RE, (original, matched) => {
|
|
33
|
+
const index = parseInt(matched);
|
|
34
|
+
if (index < values.length) return values[index];
|
|
35
|
+
return original;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const Object_INDEX_RE = /\{(.+?)\}/g;
|
|
39
|
+
function formatWithObject(text, values) {
|
|
40
|
+
return text.replace(Object_INDEX_RE, (original, matched) => {
|
|
41
|
+
const value = values[matched];
|
|
42
|
+
if (value) return value;
|
|
43
|
+
return original;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { I18N_RESOURCES, I18nApplication as default };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Context } from "egg";
|
|
2
|
+
|
|
3
|
+
//#region src/app/extend/context.d.ts
|
|
4
|
+
declare class I18nContext extends Context {
|
|
5
|
+
/**
|
|
6
|
+
* get current request locale
|
|
7
|
+
* @member Context#locale
|
|
8
|
+
* @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'
|
|
9
|
+
*/
|
|
10
|
+
get locale(): string;
|
|
11
|
+
set locale(l: string);
|
|
12
|
+
/**
|
|
13
|
+
* `ctx.__` 的别名。
|
|
14
|
+
* @see {@link Context#__}
|
|
15
|
+
* @function Context#gettext
|
|
16
|
+
*/
|
|
17
|
+
gettext(key: string, value?: any, ...args: any[]): string;
|
|
18
|
+
/**
|
|
19
|
+
* 如果开启了 I18n 多语言功能,那么会出现此 API,通过它可以获取到当前请求对应的本地化数据。
|
|
20
|
+
*
|
|
21
|
+
* 详细使用说明,请查看 {@link I18n}
|
|
22
|
+
* - `ctx.__ = function (key, value[, value2, ...])`: 类似 `util.format` 接口
|
|
23
|
+
* - `ctx.__ = function (key, values)`: 支持数组下标占位符方式,如
|
|
24
|
+
* - `__` 的别名是 `gettext(key, value)`
|
|
25
|
+
*
|
|
26
|
+
* > NOTE: __ 是两个下划线哦!
|
|
27
|
+
* @function Context#__
|
|
28
|
+
* @example
|
|
29
|
+
* ```js
|
|
30
|
+
* ctx.__('{0} {0} {1} {1}'), ['foo', 'bar'])
|
|
31
|
+
* ctx.gettext('{0} {0} {1} {1}'), ['foo', 'bar'])
|
|
32
|
+
* =>
|
|
33
|
+
* foo foo bar bar
|
|
34
|
+
* ```
|
|
35
|
+
* ##### Controller 下的使用示例
|
|
36
|
+
*
|
|
37
|
+
* ```js
|
|
38
|
+
* module.exports = function* () {
|
|
39
|
+
* this.body = {
|
|
40
|
+
* message: this.__('Welcome back, %s!', this.user.name),
|
|
41
|
+
* // 或者使用 gettext,如果觉得 __ 不好看的话
|
|
42
|
+
* // message: this.gettext('Welcome back, %s!', this.user.name),
|
|
43
|
+
* user: this.user,
|
|
44
|
+
* };
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* ##### View 文件下的使用示例
|
|
49
|
+
*
|
|
50
|
+
* ```html
|
|
51
|
+
* <li>{{ __('Email') }}: {{ user.email }}</li>
|
|
52
|
+
* <li>
|
|
53
|
+
* {{ __('Hello %s, how are you today?', user.name) }}
|
|
54
|
+
* </li>
|
|
55
|
+
* <li>
|
|
56
|
+
* {{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}
|
|
57
|
+
* </li>
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* ##### locale 参数获取途径
|
|
61
|
+
*
|
|
62
|
+
* 优先级从上到下:
|
|
63
|
+
*
|
|
64
|
+
* - query: `/?locale=en-US`
|
|
65
|
+
* - cookie: `locale=zh-TW`
|
|
66
|
+
* - header: `Accept-Language: zh-CN,zh;q=0.5`
|
|
67
|
+
*/
|
|
68
|
+
__(key: string, value?: any, ...args: any[]): string;
|
|
69
|
+
__locale: string;
|
|
70
|
+
__getLocale(): string;
|
|
71
|
+
__localeOrigin: string;
|
|
72
|
+
__getLocaleOrigin(): string;
|
|
73
|
+
__setLocale(locale: string): void;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { I18nContext as default };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { formatLocale } from "../../utils.js";
|
|
2
|
+
import { debuglog } from "node:util";
|
|
3
|
+
import { Context } from "egg";
|
|
4
|
+
|
|
5
|
+
//#region src/app/extend/context.ts
|
|
6
|
+
const debug = debuglog("egg/i18n/app/extend/context");
|
|
7
|
+
var I18nContext = class extends Context {
|
|
8
|
+
/**
|
|
9
|
+
* get current request locale
|
|
10
|
+
* @member Context#locale
|
|
11
|
+
* @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'
|
|
12
|
+
*/
|
|
13
|
+
get locale() {
|
|
14
|
+
return this.__getLocale();
|
|
15
|
+
}
|
|
16
|
+
set locale(l) {
|
|
17
|
+
this.__setLocale(l);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* `ctx.__` 的别名。
|
|
21
|
+
* @see {@link Context#__}
|
|
22
|
+
* @function Context#gettext
|
|
23
|
+
*/
|
|
24
|
+
gettext(key, value, ...args) {
|
|
25
|
+
return this.app.gettext(this.locale, key, value, ...args);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 如果开启了 I18n 多语言功能,那么会出现此 API,通过它可以获取到当前请求对应的本地化数据。
|
|
29
|
+
*
|
|
30
|
+
* 详细使用说明,请查看 {@link I18n}
|
|
31
|
+
* - `ctx.__ = function (key, value[, value2, ...])`: 类似 `util.format` 接口
|
|
32
|
+
* - `ctx.__ = function (key, values)`: 支持数组下标占位符方式,如
|
|
33
|
+
* - `__` 的别名是 `gettext(key, value)`
|
|
34
|
+
*
|
|
35
|
+
* > NOTE: __ 是两个下划线哦!
|
|
36
|
+
* @function Context#__
|
|
37
|
+
* @example
|
|
38
|
+
* ```js
|
|
39
|
+
* ctx.__('{0} {0} {1} {1}'), ['foo', 'bar'])
|
|
40
|
+
* ctx.gettext('{0} {0} {1} {1}'), ['foo', 'bar'])
|
|
41
|
+
* =>
|
|
42
|
+
* foo foo bar bar
|
|
43
|
+
* ```
|
|
44
|
+
* ##### Controller 下的使用示例
|
|
45
|
+
*
|
|
46
|
+
* ```js
|
|
47
|
+
* module.exports = function* () {
|
|
48
|
+
* this.body = {
|
|
49
|
+
* message: this.__('Welcome back, %s!', this.user.name),
|
|
50
|
+
* // 或者使用 gettext,如果觉得 __ 不好看的话
|
|
51
|
+
* // message: this.gettext('Welcome back, %s!', this.user.name),
|
|
52
|
+
* user: this.user,
|
|
53
|
+
* };
|
|
54
|
+
* };
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* ##### View 文件下的使用示例
|
|
58
|
+
*
|
|
59
|
+
* ```html
|
|
60
|
+
* <li>{{ __('Email') }}: {{ user.email }}</li>
|
|
61
|
+
* <li>
|
|
62
|
+
* {{ __('Hello %s, how are you today?', user.name) }}
|
|
63
|
+
* </li>
|
|
64
|
+
* <li>
|
|
65
|
+
* {{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}
|
|
66
|
+
* </li>
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* ##### locale 参数获取途径
|
|
70
|
+
*
|
|
71
|
+
* 优先级从上到下:
|
|
72
|
+
*
|
|
73
|
+
* - query: `/?locale=en-US`
|
|
74
|
+
* - cookie: `locale=zh-TW`
|
|
75
|
+
* - header: `Accept-Language: zh-CN,zh;q=0.5`
|
|
76
|
+
*/
|
|
77
|
+
__(key, value, ...args) {
|
|
78
|
+
return this.gettext(key, value, ...args);
|
|
79
|
+
}
|
|
80
|
+
__getLocale() {
|
|
81
|
+
if (this.__locale) return this.__locale;
|
|
82
|
+
const { localeAlias, defaultLocale, cookieField, queryField, writeCookie } = this.app.config.i18n;
|
|
83
|
+
const cookieLocale = this.cookies.get(cookieField, { signed: false });
|
|
84
|
+
let locale = this.query[queryField];
|
|
85
|
+
let localeOrigin = "query";
|
|
86
|
+
if (!locale && cookieLocale) {
|
|
87
|
+
locale = cookieLocale;
|
|
88
|
+
localeOrigin = "cookie";
|
|
89
|
+
}
|
|
90
|
+
if (!locale) {
|
|
91
|
+
let languages = this.acceptsLanguages();
|
|
92
|
+
if (languages) if (Array.isArray(languages)) {
|
|
93
|
+
if (languages[0] === "*") languages = languages.slice(1);
|
|
94
|
+
if (languages.length > 0) for (const l of languages) {
|
|
95
|
+
const lang = formatLocale(l);
|
|
96
|
+
if (this.app.isSupportLocale(lang) || localeAlias[lang]) {
|
|
97
|
+
locale = lang;
|
|
98
|
+
localeOrigin = "header";
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
locale = languages;
|
|
104
|
+
localeOrigin = "header";
|
|
105
|
+
}
|
|
106
|
+
if (!locale) {
|
|
107
|
+
locale = defaultLocale;
|
|
108
|
+
localeOrigin = "default";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (locale in localeAlias) {
|
|
112
|
+
const originalLocale = locale;
|
|
113
|
+
locale = localeAlias[locale];
|
|
114
|
+
debug("Used alias, received %s but using %s", originalLocale, locale);
|
|
115
|
+
}
|
|
116
|
+
locale = formatLocale(locale);
|
|
117
|
+
if (!this.app.isSupportLocale(locale)) {
|
|
118
|
+
debug("Locale %s is not supported. Using default (%s)", locale, defaultLocale);
|
|
119
|
+
locale = defaultLocale;
|
|
120
|
+
}
|
|
121
|
+
if (writeCookie && cookieLocale !== locale && !this.headerSent) updateCookie(this, locale);
|
|
122
|
+
debug("Locale: %s from %s", locale, localeOrigin);
|
|
123
|
+
this.__locale = locale;
|
|
124
|
+
this.__localeOrigin = localeOrigin;
|
|
125
|
+
return locale;
|
|
126
|
+
}
|
|
127
|
+
__getLocaleOrigin() {
|
|
128
|
+
if (this.__localeOrigin) return this.__localeOrigin;
|
|
129
|
+
this.__getLocale();
|
|
130
|
+
return this.__localeOrigin;
|
|
131
|
+
}
|
|
132
|
+
__setLocale(locale) {
|
|
133
|
+
this.__locale = locale;
|
|
134
|
+
this.__localeOrigin = "set";
|
|
135
|
+
if (this.app.config.i18n.writeCookie && !this.headerSent) updateCookie(this, locale);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
function updateCookie(ctx, locale) {
|
|
139
|
+
const { cookieMaxAge, cookieField, cookieDomain } = ctx.app.config.i18n;
|
|
140
|
+
const cookieOptions = {
|
|
141
|
+
httpOnly: false,
|
|
142
|
+
maxAge: cookieMaxAge,
|
|
143
|
+
signed: false,
|
|
144
|
+
domain: cookieDomain,
|
|
145
|
+
overwrite: true
|
|
146
|
+
};
|
|
147
|
+
ctx.cookies.set(cookieField, locale, cookieOptions);
|
|
148
|
+
debug("Saved cookie with locale %s, options: %j", locale, cookieOptions);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
//#endregion
|
|
152
|
+
export { I18nContext as default };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import { Application, ILifecycleBoot } from "egg";
|
|
2
|
+
|
|
3
|
+
//#region src/app.d.ts
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* I18n 国际化
|
|
5
7
|
*
|
|
@@ -47,10 +49,10 @@ import type I18nApplication from './app/extend/application.js';
|
|
|
47
49
|
* }
|
|
48
50
|
* ```
|
|
49
51
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
55
|
-
didLoad(): Promise<void>;
|
|
52
|
+
declare class I18n implements ILifecycleBoot {
|
|
53
|
+
private readonly app;
|
|
54
|
+
constructor(app: Application);
|
|
55
|
+
didLoad(): Promise<void>;
|
|
56
56
|
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { I18n as default };
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { formatLocale } from "./utils.js";
|
|
2
|
+
import { loadLocaleResources } from "./locales.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { debuglog } from "node:util";
|
|
5
|
+
import { exists } from "utility";
|
|
6
|
+
import { ms } from "humanize-ms";
|
|
7
|
+
|
|
8
|
+
//#region src/app.ts
|
|
9
|
+
const debug = debuglog("egg/i18n/app");
|
|
10
|
+
/**
|
|
11
|
+
* I18n 国际化
|
|
12
|
+
*
|
|
13
|
+
* 通过设置 Plugin 配置 `i18n: true`,开启多语言支持。
|
|
14
|
+
*
|
|
15
|
+
* #### 语言文件存储路径
|
|
16
|
+
*
|
|
17
|
+
* 统一存放在 `config/locale/*.js` 下( 兼容`config/locales/*.js` ),如包含英文,简体中文,繁体中文的语言文件:
|
|
18
|
+
*
|
|
19
|
+
* ```
|
|
20
|
+
* - config/locale/
|
|
21
|
+
* - en-US.js
|
|
22
|
+
* - zh-CN.js
|
|
23
|
+
* - zh-TW.js
|
|
24
|
+
* ```
|
|
25
|
+
* @class I18n
|
|
26
|
+
* @param {App} app Application object.
|
|
27
|
+
* @example
|
|
28
|
+
*
|
|
29
|
+
* #### I18n 文件内容
|
|
30
|
+
*
|
|
31
|
+
* ```js
|
|
32
|
+
* // config/locale/zh-CN.js
|
|
33
|
+
* module.exports = {
|
|
34
|
+
* "Email": "邮箱",
|
|
35
|
+
* "Welcome back, %s!": "欢迎回来, %s!",
|
|
36
|
+
* "Hello %s, how are you today?": "你好 %s, 今天过得咋样?",
|
|
37
|
+
* };
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* ```js
|
|
41
|
+
* // config/locale/en-US.js
|
|
42
|
+
* module.exports = {
|
|
43
|
+
* "Email": "Email",
|
|
44
|
+
* };
|
|
45
|
+
* ```
|
|
46
|
+
* 或者也可以用 JSON 格式的文件:
|
|
47
|
+
*
|
|
48
|
+
* ```js
|
|
49
|
+
* // config/locale/zh-CN.json
|
|
50
|
+
* {
|
|
51
|
+
* "email": "邮箱",
|
|
52
|
+
* "login": "帐号",
|
|
53
|
+
* "createdAt": "注册时间"
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
var I18n = class {
|
|
58
|
+
app;
|
|
59
|
+
constructor(app) {
|
|
60
|
+
this.app = app;
|
|
61
|
+
}
|
|
62
|
+
async didLoad() {
|
|
63
|
+
const i18nConfig = this.app.config.i18n;
|
|
64
|
+
i18nConfig.defaultLocale = formatLocale(i18nConfig.defaultLocale);
|
|
65
|
+
i18nConfig.cookieMaxAge = ms(i18nConfig.cookieMaxAge);
|
|
66
|
+
i18nConfig.dirs = Array.isArray(i18nConfig.dirs) ? i18nConfig.dirs : [];
|
|
67
|
+
for (const unit of this.app.loader.getLoadUnits()) {
|
|
68
|
+
let localePath = path.join(unit.path, "config/locale");
|
|
69
|
+
/**
|
|
70
|
+
* 优先选择 `config/locale` 目录下的多语言文件,不存在时再选择 `config/locales` 目录
|
|
71
|
+
* 避免 2 个目录同时存在时可能导致的冲突
|
|
72
|
+
*/
|
|
73
|
+
if (!await exists(localePath)) localePath = path.join(unit.path, "config/locales");
|
|
74
|
+
i18nConfig.dirs.push(localePath);
|
|
75
|
+
}
|
|
76
|
+
debug("app.config.i18n.dirs:", i18nConfig.dirs);
|
|
77
|
+
await loadLocaleResources(this.app, i18nConfig);
|
|
78
|
+
const app = this.app;
|
|
79
|
+
function gettextInContext(key, ...args) {
|
|
80
|
+
return app.ctxStorage.getStore().gettext(key, ...args);
|
|
81
|
+
}
|
|
82
|
+
Object.defineProperties(app.locals, {
|
|
83
|
+
__: {
|
|
84
|
+
value: gettextInContext,
|
|
85
|
+
enumerable: true
|
|
86
|
+
},
|
|
87
|
+
gettext: {
|
|
88
|
+
value: gettextInContext,
|
|
89
|
+
enumerable: true
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
export { I18n as default };
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* @member Config#i18n
|
|
4
|
-
*/
|
|
5
|
-
export interface I18nConfig {
|
|
1
|
+
//#region src/config/config.default.d.ts
|
|
2
|
+
interface I18nConfig {
|
|
6
3
|
/**
|
|
7
4
|
* 默认语言是美式英语,毕竟支持多语言,基本都是以英语为母板
|
|
8
5
|
* 默认值是 `en_US`
|
|
@@ -50,31 +47,8 @@ export interface I18nConfig {
|
|
|
50
47
|
*/
|
|
51
48
|
writeCookie: boolean;
|
|
52
49
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface Context {
|
|
61
|
-
/**
|
|
62
|
-
* get and set current request locale
|
|
63
|
-
* @member Context#locale
|
|
64
|
-
* @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'
|
|
65
|
-
*/
|
|
66
|
-
locale: string;
|
|
67
|
-
|
|
68
|
-
gettext(key: string, value?: any, ...args: any[]): string;
|
|
69
|
-
__(key: string, value?: any, ...args: any[]): string;
|
|
70
|
-
|
|
71
|
-
__getLocale(): string;
|
|
72
|
-
__setLocale(l: string): void;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface EggCore {
|
|
76
|
-
isSupportLocale(locale: string): boolean;
|
|
77
|
-
gettext(locale: string, key: string, value?: any, ...args: any[]): string;
|
|
78
|
-
__(locale: string, key: string, value?: any, ...args: any[]): string;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
50
|
+
declare const _default: {
|
|
51
|
+
i18n: I18nConfig;
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { I18nConfig, _default as default };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/config/config.default.ts
|
|
2
|
+
var config_default_default = { i18n: {
|
|
3
|
+
defaultLocale: "en_US",
|
|
4
|
+
dirs: [],
|
|
5
|
+
queryField: "locale",
|
|
6
|
+
cookieField: "locale",
|
|
7
|
+
cookieDomain: "",
|
|
8
|
+
cookieMaxAge: "1y",
|
|
9
|
+
localeAlias: {},
|
|
10
|
+
writeCookie: true,
|
|
11
|
+
dir: void 0
|
|
12
|
+
} };
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { config_default_default as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.js
ADDED
package/dist/locales.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { formatLocale, isObject } from "./utils.js";
|
|
2
|
+
import { I18N_RESOURCES } from "./app/extend/application.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { debuglog } from "node:util";
|
|
5
|
+
import { exists, readJSON } from "utility";
|
|
6
|
+
import fs from "node:fs/promises";
|
|
7
|
+
import ini from "ini";
|
|
8
|
+
import yaml from "js-yaml";
|
|
9
|
+
import { importModule } from "@eggjs/utils";
|
|
10
|
+
|
|
11
|
+
//#region src/locales.ts
|
|
12
|
+
const debug = debuglog("egg/i18n/locales");
|
|
13
|
+
async function loadLocaleResources(app, options) {
|
|
14
|
+
const localeDirs = options.dirs;
|
|
15
|
+
const resources = {};
|
|
16
|
+
if (options.dir && !localeDirs.includes(options.dir)) {
|
|
17
|
+
app.deprecate("[@eggjs/i18n] `config.i18n.dir` is deprecated, please use `config.i18n.dirs` instead");
|
|
18
|
+
localeDirs.push(options.dir);
|
|
19
|
+
}
|
|
20
|
+
for (const dir of localeDirs) {
|
|
21
|
+
if (!await exists(dir)) continue;
|
|
22
|
+
const names = await fs.readdir(dir);
|
|
23
|
+
for (const name of names) {
|
|
24
|
+
const filepath = path.join(dir, name);
|
|
25
|
+
const locale = formatLocale(name.split(".")[0]);
|
|
26
|
+
let resource = {};
|
|
27
|
+
if (name.endsWith(".js") || name.endsWith(".ts")) resource = flattening(await importModule(filepath, { importDefaultOnly: true }));
|
|
28
|
+
else if (name.endsWith(".json")) resource = flattening(await readJSON(filepath));
|
|
29
|
+
else if (name.endsWith(".properties")) resource = ini.parse(await fs.readFile(filepath, "utf8"));
|
|
30
|
+
else if (name.endsWith(".yml") || name.endsWith(".yaml")) resource = flattening(yaml.load(await fs.readFile(filepath, "utf8")));
|
|
31
|
+
resources[locale] = resources[locale] || {};
|
|
32
|
+
Object.assign(resources[locale], resource);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
debug("Init locales with %j, got %j resources", options, Object.keys(resources));
|
|
36
|
+
app[I18N_RESOURCES] = resources;
|
|
37
|
+
}
|
|
38
|
+
function flattening(data) {
|
|
39
|
+
const result = {};
|
|
40
|
+
function deepFlat(data$1, prefix) {
|
|
41
|
+
for (const key in data$1) {
|
|
42
|
+
const value = data$1[key];
|
|
43
|
+
const k = prefix ? prefix + "." + key : key;
|
|
44
|
+
if (isObject(value)) deepFlat(value, k);
|
|
45
|
+
else result[k] = String(value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
deepFlat(data, "");
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { loadLocaleResources };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { I18nConfig } from "./config/config.default.js";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
declare module 'egg' {
|
|
5
|
+
interface EggAppConfig {
|
|
6
|
+
/**
|
|
7
|
+
* I18n options
|
|
8
|
+
* @member Config#i18n
|
|
9
|
+
*/
|
|
10
|
+
i18n: I18nConfig;
|
|
11
|
+
}
|
|
12
|
+
interface Application {
|
|
13
|
+
isSupportLocale(locale: string): boolean;
|
|
14
|
+
gettext(locale: string, key: string, value?: any, ...args: any[]): string;
|
|
15
|
+
__(locale: string, key: string, value?: any, ...args: any[]): string;
|
|
16
|
+
}
|
|
17
|
+
interface Context {
|
|
18
|
+
/**
|
|
19
|
+
* get and set current request locale
|
|
20
|
+
* @member Context#locale
|
|
21
|
+
* @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'
|
|
22
|
+
*/
|
|
23
|
+
locale: string;
|
|
24
|
+
gettext(key: string, value?: any, ...args: any[]): string;
|
|
25
|
+
__(key: string, value?: any, ...args: any[]): string;
|
|
26
|
+
__getLocale(): string;
|
|
27
|
+
__setLocale(l: string): void;
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/utils.ts
|
|
2
|
+
function isObject(obj) {
|
|
3
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
4
|
+
}
|
|
5
|
+
function formatLocale(locale) {
|
|
6
|
+
return locale.replaceAll("_", "-").toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
export { formatLocale, isObject };
|