@eggjs/i18n 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/dist/commonjs/app/extend/application.d.ts +8 -0
  4. package/dist/commonjs/app/extend/application.js +80 -0
  5. package/dist/commonjs/app/extend/context.d.ts +72 -0
  6. package/dist/commonjs/app/extend/context.js +177 -0
  7. package/dist/commonjs/app.d.ts +56 -0
  8. package/dist/commonjs/app.js +95 -0
  9. package/dist/commonjs/config/config.default.d.ts +5 -0
  10. package/dist/commonjs/config/config.default.js +16 -0
  11. package/dist/commonjs/index.d.ts +1 -0
  12. package/dist/commonjs/index.js +4 -0
  13. package/dist/commonjs/locales.d.ts +3 -0
  14. package/dist/commonjs/locales.js +71 -0
  15. package/dist/commonjs/package.json +3 -0
  16. package/dist/commonjs/types.d.ts +74 -0
  17. package/dist/commonjs/types.js +3 -0
  18. package/dist/commonjs/utils.d.ts +2 -0
  19. package/dist/commonjs/utils.js +12 -0
  20. package/dist/esm/app/extend/application.d.ts +8 -0
  21. package/dist/esm/app/extend/application.js +76 -0
  22. package/dist/esm/app/extend/context.d.ts +72 -0
  23. package/dist/esm/app/extend/context.js +174 -0
  24. package/dist/esm/app.d.ts +56 -0
  25. package/dist/esm/app.js +89 -0
  26. package/dist/esm/config/config.default.d.ts +5 -0
  27. package/dist/esm/config/config.default.js +14 -0
  28. package/dist/esm/index.d.ts +1 -0
  29. package/dist/esm/index.js +2 -0
  30. package/dist/esm/locales.d.ts +3 -0
  31. package/dist/esm/locales.js +65 -0
  32. package/dist/esm/package.json +3 -0
  33. package/dist/esm/types.d.ts +74 -0
  34. package/dist/esm/types.js +2 -0
  35. package/dist/esm/utils.d.ts +2 -0
  36. package/dist/esm/utils.js +8 -0
  37. package/dist/package.json +4 -0
  38. package/package.json +96 -0
  39. package/src/app/extend/application.ts +91 -0
  40. package/src/app/extend/context.ts +192 -0
  41. package/src/app.ts +99 -0
  42. package/src/config/config.default.ts +15 -0
  43. package/src/index.ts +1 -0
  44. package/src/locales.ts +73 -0
  45. package/src/types.ts +80 -0
  46. package/src/typings/index.d.ts +4 -0
  47. package/src/utils.ts +8 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Alibaba Group Holding Limited and other contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # @eggjs/i18n
2
+
3
+ [![NPM version][npm-image]][npm-url]
4
+ [![Node.js CI](https://github.com/eggjs/i18n/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/i18n/actions/workflows/nodejs.yml)
5
+ [![Test coverage][codecov-image]][codecov-url]
6
+ [![Known Vulnerabilities][snyk-image]][snyk-url]
7
+ [![npm download][download-image]][download-url]
8
+ [![Node.js Version](https://img.shields.io/node/v/@eggjs/i18n.svg?style=flat)](https://nodejs.org/en/download/)
9
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com)
10
+
11
+ [npm-image]: https://img.shields.io/npm/v/@eggjs/i18n.svg?style=flat-square
12
+ [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
+ [snyk-image]: https://snyk.io/test/npm/@eggjs/i18n/badge.svg?style=flat-square
16
+ [snyk-url]: https://snyk.io/test/npm/@eggjs/i18n
17
+ [download-image]: https://img.shields.io/npm/dm/@eggjs/i18n.svg?style=flat-square
18
+ [download-url]: https://npmjs.org/package/@eggjs/i18n
19
+
20
+ 可以为你的应用提供多语言的特性
21
+
22
+ ## 功能
23
+
24
+ - 支持多种语言独立配置,统一存放在 config/locale/\*.js 下( 兼容 `config/locales/*.js` );
25
+ - 提供 Middleware 为 View 提供 `\_\_`, `gettext` 函数获取多语言文案;
26
+ - 基于 URL 参数 `locale` 修改语言显示,同时会记录到 Cookie,下次请求会用 Cookie 里面的语言方案。
27
+
28
+ ## 配置
29
+
30
+ 默认处于关闭状态,你需要在 `config/plugin.ts` 开启它:
31
+
32
+ ```ts
33
+ // config/plugin.ts
34
+ export default {
35
+ i18n: {
36
+ enable: true,
37
+ package: '@eggjs/i18n',
38
+ },
39
+ };
40
+ ```
41
+
42
+ 你可以修改 `config/config.default.ts` 来设定 i18n 的配置项:
43
+
44
+ ```ts
45
+ // config/config.default.ts
46
+ export default {
47
+ i18n: {
48
+ // 默认语言,默认 "en_US"
49
+ defaultLocale: 'zh-CN',
50
+ // URL 参数,默认 "locale"
51
+ queryField: 'locale',
52
+ // Cookie 记录的 key, 默认:"locale"
53
+ cookieField: 'locale',
54
+ // Cookie 的 domain 配置,默认为空,代表当前域名有效
55
+ cookieDomain: '',
56
+ // Cookie 默认 `1y` 一年后过期, 如果设置为 Number,则单位为 ms
57
+ cookieMaxAge: '1y',
58
+ },
59
+ };
60
+ ```
61
+
62
+ 其实大部分时候,你只需要修改一下 `defaultLocale` 设定默认的语言。
63
+
64
+ ## 编写你的 I18n 多语言文件
65
+
66
+ ```ts
67
+ // config/locale/zh-CN.ts
68
+ export default {
69
+ "Email": "邮箱",
70
+ "Welcome back, %s!": "欢迎回来,%s!",
71
+ "Hello %s, how are you today?": "你好 %s, 今天过得咋样?",
72
+ };
73
+ ```
74
+
75
+ ```ts
76
+ // config/locale/en-US.ts
77
+ export default {
78
+ "Email": "Email",
79
+ };
80
+ ```
81
+
82
+ 或者也可以用 JSON 格式的文件:
83
+
84
+ ```json
85
+ // config/locale/zh-CN.json
86
+ {
87
+ "email": "邮箱",
88
+ "login": "帐号",
89
+ "createdAt": "注册时间"
90
+ }
91
+ ```
92
+
93
+ ## 使用 I18n 函数获取语言文本
94
+
95
+ I18n 为你提供 `__` (Alias: `gettext`) 函数,让你可以轻松获得 locale 文件夹下面的多语言文本。
96
+
97
+ > NOTE: __ 是两个下划线哦!
98
+
99
+ - `ctx.__ = function (key, value[, value2, ...])`: 类似 util.format 接口
100
+ - `ctx.__ = function (key, values)`: 支持数组下标占位符方式,如
101
+
102
+ ```ts
103
+ ctx.__('{0} {0} {1} {1}'), ['foo', 'bar']);
104
+ ctx.gettext('{0} {0} {1} {1}'), ['foo', 'bar']);
105
+
106
+ =>
107
+ foo foo bar bar
108
+ ```
109
+
110
+ ### Controllers 下的使用示例
111
+
112
+ ```ts
113
+ export default ctx => {
114
+ ctx.body = {
115
+ message: ctx.__('Welcome back, %s!', ctx.user.name)
116
+ // 或者使用 gettext,如果觉得 __ 不好看的话
117
+ // message: this.gettext('Welcome back, %s!', ctx.user.name)
118
+ user: ctx.user,
119
+ };
120
+ };
121
+ ```
122
+
123
+ ### View 文件下的使用示例
124
+
125
+ ```html
126
+ <li>{{ __('Email') }}: {{ user.email }}</li>
127
+ <li>
128
+ {{ __('Hello %s, how are you today?', user.name) }}
129
+ </li>
130
+ <li>
131
+ {{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}
132
+ </li>
133
+ ```
134
+
135
+ ### 修改应用的默认语言
136
+
137
+ 你可以用下面几种方式修改应用的当前语言(修改或会记录到 Cookie),下次请求直接用设定好的语言。
138
+
139
+ 优先级从上到下:
140
+
141
+ - query: `/?locale=en-US`
142
+ - cookie: `locale=zh-TW`
143
+ - header: `Accept-Language: zh-CN,zh;q=0.5`
144
+
145
+ ## Questions & Suggestions
146
+
147
+ Please open an issue [here](https://github.com/eggjs/egg/issues).
148
+
149
+ ## License
150
+
151
+ [MIT](LICENSE)
152
+
153
+ ## Contributors
154
+
155
+ [![Contributors](https://contrib.rocks/image?repo=eggjs/i18n)](https://github.com/eggjs/i18n/graphs/contributors)
156
+
157
+ Made with [contributors-img](https://contrib.rocks).
@@ -0,0 +1,8 @@
1
+ import { EggCore } from '@eggjs/core';
2
+ export declare const I18N_RESOURCES: unique symbol;
3
+ export default class I18nApplication extends EggCore {
4
+ [I18N_RESOURCES]: Record<string, Record<string, string>>;
5
+ isSupportLocale(locale: string): boolean;
6
+ gettext(locale: string, key: string, value?: any, ...args: any[]): string;
7
+ __(locale: string, key: string, value?: any, ...args: any[]): string;
8
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.I18N_RESOURCES = void 0;
4
+ const node_util_1 = require("node:util");
5
+ const core_1 = require("@eggjs/core");
6
+ const utils_js_1 = require("../../utils.js");
7
+ const debug = (0, node_util_1.debuglog)('@eggjs/i18n/app/extend/application');
8
+ exports.I18N_RESOURCES = Symbol('Application i18n resources');
9
+ class I18nApplication extends core_1.EggCore {
10
+ isSupportLocale(locale) {
11
+ return !!this[exports.I18N_RESOURCES][locale];
12
+ }
13
+ gettext(locale, key, value, ...args) {
14
+ if (!locale || !key) {
15
+ // __()
16
+ // __('en')
17
+ return '';
18
+ }
19
+ const resource = this[exports.I18N_RESOURCES][locale] || {};
20
+ let text = resource[key];
21
+ if (text === undefined) {
22
+ text = key;
23
+ }
24
+ debug('%s: %j => %j', locale, key, text);
25
+ if (!text) {
26
+ return '';
27
+ }
28
+ if (value === undefined) {
29
+ // __(locale, key)
30
+ return text;
31
+ }
32
+ if (args.length === 0) {
33
+ if ((0, utils_js_1.isObject)(value)) {
34
+ // __(locale, key, object)
35
+ // __('zh', '{a} {b} {b} {a}', {a: 'foo', b: 'bar'})
36
+ // =>
37
+ // foo bar bar foo
38
+ return formatWithObject(text, value);
39
+ }
40
+ if (Array.isArray(value)) {
41
+ // __(locale, key, array)
42
+ // __('zh', '{0} {1} {1} {0}', ['foo', 'bar'])
43
+ // =>
44
+ // foo bar bar foo
45
+ return formatWithArray(text, value);
46
+ }
47
+ // __(locale, key, value)
48
+ return (0, node_util_1.format)(text, value);
49
+ }
50
+ // __(locale, key, value1, ...)
51
+ return (0, node_util_1.format)(text, value, ...args);
52
+ }
53
+ __(locale, key, value, ...args) {
54
+ return this.gettext(locale, key, value, ...args);
55
+ }
56
+ }
57
+ exports.default = I18nApplication;
58
+ const ARRAY_INDEX_RE = /\{(\d+)\}/g;
59
+ function formatWithArray(text, values) {
60
+ return text.replace(ARRAY_INDEX_RE, (original, matched) => {
61
+ const index = parseInt(matched);
62
+ if (index < values.length) {
63
+ return values[index];
64
+ }
65
+ // not match index, return original text
66
+ return original;
67
+ });
68
+ }
69
+ const Object_INDEX_RE = /\{(.+?)\}/g;
70
+ function formatWithObject(text, values) {
71
+ return text.replace(Object_INDEX_RE, (original, matched) => {
72
+ const value = values[matched];
73
+ if (value) {
74
+ return value;
75
+ }
76
+ // not match index, return original text
77
+ return original;
78
+ });
79
+ }
80
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbGljYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYXBwL2V4dGVuZC9hcHBsaWNhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx5Q0FBNkM7QUFDN0Msc0NBQXNDO0FBQ3RDLDZDQUEwQztBQUUxQyxNQUFNLEtBQUssR0FBRyxJQUFBLG9CQUFRLEVBQUMsb0NBQW9DLENBQUMsQ0FBQztBQUVoRCxRQUFBLGNBQWMsR0FBRyxNQUFNLENBQUMsNEJBQTRCLENBQUMsQ0FBQztBQUVuRSxNQUFxQixlQUFnQixTQUFRLGNBQU87SUFHbEQsZUFBZSxDQUFDLE1BQWM7UUFDNUIsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLHNCQUFjLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN4QyxDQUFDO0lBRUQsT0FBTyxDQUFDLE1BQWMsRUFBRSxHQUFXLEVBQUUsS0FBVyxFQUFFLEdBQUcsSUFBVztRQUM5RCxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDcEIsT0FBTztZQUNQLFdBQVc7WUFDWCxPQUFPLEVBQUUsQ0FBQztRQUNaLENBQUM7UUFFRCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsc0JBQWMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUVwRCxJQUFJLElBQUksR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDekIsSUFBSSxJQUFJLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDdkIsSUFBSSxHQUFHLEdBQUcsQ0FBQztRQUNiLENBQUM7UUFFRCxLQUFLLENBQUMsY0FBYyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDekMsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1YsT0FBTyxFQUFFLENBQUM7UUFDWixDQUFDO1FBRUQsSUFBSSxLQUFLLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDeEIsa0JBQWtCO1lBQ2xCLE9BQU8sSUFBSSxDQUFDO1FBQ2QsQ0FBQztRQUNELElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN0QixJQUFJLElBQUEsbUJBQVEsRUFBQyxLQUFLLENBQUMsRUFBRSxDQUFDO2dCQUNwQiwwQkFBMEI7Z0JBQzFCLG9EQUFvRDtnQkFDcEQsS0FBSztnQkFDTCxrQkFBa0I7Z0JBQ2xCLE9BQU8sZ0JBQWdCLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ3ZDLENBQUM7WUFFRCxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDekIseUJBQXlCO2dCQUN6Qiw4Q0FBOEM7Z0JBQzlDLEtBQUs7Z0JBQ0wsa0JBQWtCO2dCQUNsQixPQUFPLGVBQWUsQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDdEMsQ0FBQztZQUVELHlCQUF5QjtZQUN6QixPQUFPLElBQUEsa0JBQU0sRUFBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFDN0IsQ0FBQztRQUVELCtCQUErQjtRQUMvQixPQUFPLElBQUEsa0JBQU0sRUFBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVELEVBQUUsQ0FBQyxNQUFjLEVBQUUsR0FBVyxFQUFFLEtBQVcsRUFBRSxHQUFHLElBQVc7UUFDekQsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDbkQsQ0FBQztDQUNGO0FBMURELGtDQTBEQztBQUVELE1BQU0sY0FBYyxHQUFHLFlBQVksQ0FBQztBQUNwQyxTQUFTLGVBQWUsQ0FBQyxJQUFZLEVBQUUsTUFBYTtJQUNsRCxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsY0FBYyxFQUFFLENBQUMsUUFBUSxFQUFFLE9BQU8sRUFBRSxFQUFFO1FBQ3hELE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNoQyxJQUFJLEtBQUssR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDMUIsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDdkIsQ0FBQztRQUNELHdDQUF3QztRQUN4QyxPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxNQUFNLGVBQWUsR0FBRyxZQUFZLENBQUM7QUFDckMsU0FBUyxnQkFBZ0IsQ0FBQyxJQUFZLEVBQUUsTUFBMkI7SUFDakUsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRSxDQUFDLFFBQVEsRUFBRSxPQUFPLEVBQUUsRUFBRTtRQUN6RCxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDOUIsSUFBSSxLQUFLLEVBQUUsQ0FBQztZQUNWLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztRQUNELHdDQUF3QztRQUN4QyxPQUFPLFFBQVEsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMifQ==
@@ -0,0 +1,72 @@
1
+ import { Context } from '@eggjs/core';
2
+ export default class I18nContext extends Context {
3
+ /**
4
+ * get current request locale
5
+ * @member Context#locale
6
+ * @return {String} lower case locale string, e.g.: 'zh-cn', 'en-us'
7
+ */
8
+ get locale(): string;
9
+ set locale(l: string);
10
+ /**
11
+ * `ctx.__` 的别名。
12
+ * @see {@link Context#__}
13
+ * @function Context#gettext
14
+ */
15
+ gettext(key: string, value?: any, ...args: any[]): string;
16
+ /**
17
+ * 如果开启了 I18n 多语言功能,那么会出现此 API,通过它可以获取到当前请求对应的本地化数据。
18
+ *
19
+ * 详细使用说明,请查看 {@link I18n}
20
+ * - `ctx.__ = function (key, value[, value2, ...])`: 类似 `util.format` 接口
21
+ * - `ctx.__ = function (key, values)`: 支持数组下标占位符方式,如
22
+ * - `__` 的别名是 `gettext(key, value)`
23
+ *
24
+ * > NOTE: __ 是两个下划线哦!
25
+ * @function Context#__
26
+ * @example
27
+ * ```js
28
+ * ctx.__('{0} {0} {1} {1}'), ['foo', 'bar'])
29
+ * ctx.gettext('{0} {0} {1} {1}'), ['foo', 'bar'])
30
+ * =>
31
+ * foo foo bar bar
32
+ * ```
33
+ * ##### Controller 下的使用示例
34
+ *
35
+ * ```js
36
+ * module.exports = function* () {
37
+ * this.body = {
38
+ * message: this.__('Welcome back, %s!', this.user.name),
39
+ * // 或者使用 gettext,如果觉得 __ 不好看的话
40
+ * // message: this.gettext('Welcome back, %s!', this.user.name),
41
+ * user: this.user,
42
+ * };
43
+ * };
44
+ * ```
45
+ *
46
+ * ##### View 文件下的使用示例
47
+ *
48
+ * ```html
49
+ * <li>{{ __('Email') }}: {{ user.email }}</li>
50
+ * <li>
51
+ * {{ __('Hello %s, how are you today?', user.name) }}
52
+ * </li>
53
+ * <li>
54
+ * {{ __('{0} {0} {1} {1}'), ['foo', 'bar']) }}
55
+ * </li>
56
+ * ```
57
+ *
58
+ * ##### locale 参数获取途径
59
+ *
60
+ * 优先级从上到下:
61
+ *
62
+ * - query: `/?locale=en-US`
63
+ * - cookie: `locale=zh-TW`
64
+ * - header: `Accept-Language: zh-CN,zh;q=0.5`
65
+ */
66
+ __(key: string, value?: any, ...args: any[]): string;
67
+ __locale: string;
68
+ __getLocale(): string;
69
+ __localeOrigin: string;
70
+ __getLocaleOrigin(): string;
71
+ __setLocale(locale: string): void;
72
+ }
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_util_1 = require("node:util");
4
+ const core_1 = require("@eggjs/core");
5
+ const utils_js_1 = require("../../utils.js");
6
+ const debug = (0, node_util_1.debuglog)('@eggjs/i18n/app/extend/context');
7
+ class I18nContext extends core_1.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
+ // 1. query: /?locale=en-US
81
+ // 2. cookie: locale=zh-TW
82
+ // 3. header: Accept-Language: zh-CN,zh;q=0.5
83
+ __getLocale() {
84
+ if (this.__locale) {
85
+ return this.__locale;
86
+ }
87
+ const { localeAlias, defaultLocale, cookieField, queryField, writeCookie } = this.app.config.i18n;
88
+ const cookieLocale = this.cookies.get(cookieField, { signed: false });
89
+ // 1. Query
90
+ let locale = this.query[queryField];
91
+ let localeOrigin = 'query';
92
+ // 2. Cookie
93
+ if (!locale) {
94
+ locale = cookieLocale;
95
+ localeOrigin = 'cookie';
96
+ }
97
+ // 3. Header
98
+ if (!locale) {
99
+ // Accept-Language: zh-CN,zh;q=0.5
100
+ // Accept-Language: zh-CN
101
+ let languages = this.acceptsLanguages();
102
+ if (languages) {
103
+ if (Array.isArray(languages)) {
104
+ if (languages[0] === '*') {
105
+ languages = languages.slice(1);
106
+ }
107
+ if (languages.length > 0) {
108
+ for (const l of languages) {
109
+ const lang = (0, utils_js_1.formatLocale)(l);
110
+ if (this.app.isSupportLocale(lang) || localeAlias[lang]) {
111
+ locale = lang;
112
+ localeOrigin = 'header';
113
+ break;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ else {
119
+ locale = languages;
120
+ localeOrigin = 'header';
121
+ }
122
+ }
123
+ // all missing, set it to defaultLocale
124
+ if (!locale) {
125
+ locale = defaultLocale;
126
+ localeOrigin = 'default';
127
+ }
128
+ }
129
+ // cookie alias
130
+ if (locale in localeAlias) {
131
+ const originalLocale = locale;
132
+ locale = localeAlias[locale];
133
+ debug('Used alias, received %s but using %s', originalLocale, locale);
134
+ }
135
+ locale = (0, utils_js_1.formatLocale)(locale);
136
+ // validate locale
137
+ if (!this.app.isSupportLocale(locale)) {
138
+ debug('Locale %s is not supported. Using default (%s)', locale, defaultLocale);
139
+ locale = defaultLocale;
140
+ }
141
+ // if header not send, set the locale cookie
142
+ if (writeCookie && cookieLocale !== locale && !this.headerSent) {
143
+ updateCookie(this, locale);
144
+ }
145
+ debug('Locale: %s from %s', locale, localeOrigin);
146
+ this.__locale = locale;
147
+ this.__localeOrigin = localeOrigin;
148
+ return locale;
149
+ }
150
+ __getLocaleOrigin() {
151
+ if (this.__localeOrigin) {
152
+ return this.__localeOrigin;
153
+ }
154
+ this.__getLocale();
155
+ return this.__localeOrigin;
156
+ }
157
+ __setLocale(locale) {
158
+ this.__locale = locale;
159
+ this.__localeOrigin = 'set';
160
+ updateCookie(this, locale);
161
+ }
162
+ }
163
+ exports.default = I18nContext;
164
+ function updateCookie(ctx, locale) {
165
+ const { cookieMaxAge, cookieField, cookieDomain } = ctx.app.config.i18n;
166
+ const cookieOptions = {
167
+ // make sure browser javascript can read the cookie
168
+ httpOnly: false,
169
+ maxAge: cookieMaxAge,
170
+ signed: false,
171
+ domain: cookieDomain,
172
+ overwrite: true,
173
+ };
174
+ ctx.cookies.set(cookieField, locale, cookieOptions);
175
+ debug('Saved cookie with locale %s, options: %j', locale, cookieOptions);
176
+ }
177
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9hcHAvZXh0ZW5kL2NvbnRleHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSx5Q0FBcUM7QUFDckMsc0NBQXNDO0FBQ3RDLDZDQUE4QztBQUU5QyxNQUFNLEtBQUssR0FBRyxJQUFBLG9CQUFRLEVBQUMsZ0NBQWdDLENBQUMsQ0FBQztBQUV6RCxNQUFxQixXQUFZLFNBQVEsY0FBTztJQUM5Qzs7OztPQUlHO0lBQ0gsSUFBSSxNQUFNO1FBQ1IsT0FBTyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDNUIsQ0FBQztJQUVELElBQUksTUFBTSxDQUFDLENBQVM7UUFDbEIsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN0QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILE9BQU8sQ0FBQyxHQUFXLEVBQUUsS0FBVyxFQUFFLEdBQUcsSUFBVztRQUM5QyxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQzVELENBQUM7SUFFRDs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztPQWlERztJQUNILEVBQUUsQ0FBQyxHQUFXLEVBQUUsS0FBVyxFQUFFLEdBQUcsSUFBVztRQUN6QyxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0lBQzNDLENBQUM7SUFHRCwyQkFBMkI7SUFDM0IsMEJBQTBCO0lBQzFCLDZDQUE2QztJQUM3QyxXQUFXO1FBQ1QsSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDbEIsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO1FBQ3ZCLENBQUM7UUFFRCxNQUFNLEVBQUUsV0FBVyxF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
@@ -0,0 +1,56 @@
1
+ import type { ILifecycleBoot } from '@eggjs/core';
2
+ import type I18nApplication from './app/extend/application.js';
3
+ /**
4
+ * I18n 国际化
5
+ *
6
+ * 通过设置 Plugin 配置 `i18n: true`,开启多语言支持。
7
+ *
8
+ * #### 语言文件存储路径
9
+ *
10
+ * 统一存放在 `config/locale/*.js` 下( 兼容`config/locales/*.js` ),如包含英文,简体中文,繁体中文的语言文件:
11
+ *
12
+ * ```
13
+ * - config/locale/
14
+ * - en-US.js
15
+ * - zh-CN.js
16
+ * - zh-TW.js
17
+ * ```
18
+ * @class I18n
19
+ * @param {App} app Application object.
20
+ * @example
21
+ *
22
+ * #### I18n 文件内容
23
+ *
24
+ * ```js
25
+ * // config/locale/zh-CN.js
26
+ * module.exports = {
27
+ * "Email": "邮箱",
28
+ * "Welcome back, %s!": "欢迎回来, %s!",
29
+ * "Hello %s, how are you today?": "你好 %s, 今天过得咋样?",
30
+ * };
31
+ * ```
32
+ *
33
+ * ```js
34
+ * // config/locale/en-US.js
35
+ * module.exports = {
36
+ * "Email": "Email",
37
+ * };
38
+ * ```
39
+ * 或者也可以用 JSON 格式的文件:
40
+ *
41
+ * ```js
42
+ * // config/locale/zh-CN.json
43
+ * {
44
+ * "email": "邮箱",
45
+ * "login": "帐号",
46
+ * "createdAt": "注册时间"
47
+ * }
48
+ * ```
49
+ */
50
+ export default class I18n implements ILifecycleBoot {
51
+ private readonly app;
52
+ constructor(app: I18nApplication & {
53
+ locals: Record<string, any>;
54
+ });
55
+ didLoad(): Promise<void>;
56
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_path_1 = __importDefault(require("node:path"));
7
+ const node_util_1 = require("node:util");
8
+ const locales_js_1 = require("./locales.js");
9
+ const utility_1 = require("utility");
10
+ const humanize_ms_1 = require("humanize-ms");
11
+ const utils_js_1 = require("./utils.js");
12
+ const debug = (0, node_util_1.debuglog)('@eggjs/i18n/app');
13
+ /**
14
+ * I18n 国际化
15
+ *
16
+ * 通过设置 Plugin 配置 `i18n: true`,开启多语言支持。
17
+ *
18
+ * #### 语言文件存储路径
19
+ *
20
+ * 统一存放在 `config/locale/*.js` 下( 兼容`config/locales/*.js` ),如包含英文,简体中文,繁体中文的语言文件:
21
+ *
22
+ * ```
23
+ * - config/locale/
24
+ * - en-US.js
25
+ * - zh-CN.js
26
+ * - zh-TW.js
27
+ * ```
28
+ * @class I18n
29
+ * @param {App} app Application object.
30
+ * @example
31
+ *
32
+ * #### I18n 文件内容
33
+ *
34
+ * ```js
35
+ * // config/locale/zh-CN.js
36
+ * module.exports = {
37
+ * "Email": "邮箱",
38
+ * "Welcome back, %s!": "欢迎回来, %s!",
39
+ * "Hello %s, how are you today?": "你好 %s, 今天过得咋样?",
40
+ * };
41
+ * ```
42
+ *
43
+ * ```js
44
+ * // config/locale/en-US.js
45
+ * module.exports = {
46
+ * "Email": "Email",
47
+ * };
48
+ * ```
49
+ * 或者也可以用 JSON 格式的文件:
50
+ *
51
+ * ```js
52
+ * // config/locale/zh-CN.json
53
+ * {
54
+ * "email": "邮箱",
55
+ * "login": "帐号",
56
+ * "createdAt": "注册时间"
57
+ * }
58
+ * ```
59
+ */
60
+ class I18n {
61
+ app;
62
+ constructor(app) {
63
+ this.app = app;
64
+ }
65
+ async didLoad() {
66
+ const i18nConfig = this.app.config.i18n;
67
+ i18nConfig.defaultLocale = (0, utils_js_1.formatLocale)(i18nConfig.defaultLocale);
68
+ i18nConfig.cookieMaxAge = (0, humanize_ms_1.ms)(i18nConfig.cookieMaxAge);
69
+ i18nConfig.dirs = Array.isArray(i18nConfig.dirs) ? i18nConfig.dirs : [];
70
+ // 按 egg > 插件 > 框架 > 应用的顺序遍历 config/locale(config/locales) 目录,加载所有配置文件
71
+ for (const unit of this.app.loader.getLoadUnits()) {
72
+ let localePath = node_path_1.default.join(unit.path, 'config/locale');
73
+ /**
74
+ * 优先选择 `config/locale` 目录下的多语言文件,不存在时再选择 `config/locales` 目录
75
+ * 避免 2 个目录同时存在时可能导致的冲突
76
+ */
77
+ if (!(await (0, utility_1.exists)(localePath))) {
78
+ localePath = node_path_1.default.join(unit.path, 'config/locales');
79
+ }
80
+ i18nConfig.dirs.push(localePath);
81
+ }
82
+ debug('app.config.i18n.dirs:', i18nConfig.dirs);
83
+ await (0, locales_js_1.loadLocaleResources)(this.app, i18nConfig);
84
+ const app = this.app;
85
+ function gettextInContext(key, ...args) {
86
+ const ctx = app.ctxStorage.getStore();
87
+ return ctx.gettext(key, ...args);
88
+ }
89
+ // 在 view 中使用 `__(key, value, ...args)`
90
+ this.app.locals.gettext = gettextInContext;
91
+ this.app.locals.__ = gettextInContext;
92
+ }
93
+ }
94
+ exports.default = I18n;
95
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDBEQUE2QjtBQUM3Qix5Q0FBcUM7QUFDckMsNkNBQW1EO0FBQ25ELHFDQUFpQztBQUNqQyw2Q0FBaUM7QUFHakMseUNBQTBDO0FBRTFDLE1BQU0sS0FBSyxHQUFHLElBQUEsb0JBQVEsRUFBQyxpQkFBaUIsQ0FBQyxDQUFDO0FBRTFDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBOENHO0FBRUgsTUFBcUIsSUFBSTtJQUNOLEdBQUcsQ0FBQztJQUVyQixZQUFZLEdBQXNEO1FBQ2hFLElBQUksQ0FBQyxHQUFHLEdBQUcsR0FBRyxDQUFDO0lBQ2pCLENBQUM7SUFFRCxLQUFLLENBQUMsT0FBTztRQUNYLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQztRQUN4QyxVQUFVLENBQUMsYUFBYSxHQUFHLElBQUEsdUJBQVksRUFBQyxVQUFVLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDbEUsVUFBVSxDQUFDLFlBQVksR0FBRyxJQUFBLGdCQUFFLEVBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBRXRELFVBQVUsQ0FBQyxJQUFJLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztRQUN4RSxzRUFBc0U7UUFDdEUsS0FBSyxNQUFNLElBQUksSUFBSSxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFDO1lBQ2xELElBQUksVUFBVSxHQUFHLG1CQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsZUFBZSxDQUFDLENBQUM7WUFDdkQ7OztlQUdHO1lBQ0gsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFBLGdCQUFNLEVBQUMsVUFBVSxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNoQyxVQUFVLEdBQUcsbUJBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO1lBQ3RELENBQUM7WUFDRCxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNuQyxDQUFDO1FBRUQsS0FBSyxDQUFDLHVCQUF1QixFQUFFLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVoRCxNQUFNLElBQUEsZ0NBQW1CLEVBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUVoRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDO1FBQ3JCLFNBQVMsZ0JBQWdCLENBQUMsR0FBVyxFQUFFLEdBQUcsSUFBVztZQUNuRCxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRyxDQUFDO1lBQ3ZDLE9BQU8sR0FBRyxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztRQUNuQyxDQUFDO1FBQ0QsdUNBQXVDO1FBQ3ZDLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztRQUMzQyxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxFQUFFLEdBQUcsZ0JBQWdCLENBQUM7SUFDeEMsQ0FBQztDQUNGO0FBdkNELHVCQXVDQyJ9