@to-kn/koa-locales 2.0.0 → 2.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/dist/index.js +260 -234
- package/package.json +3 -2
- package/dist/index.d.ts +0 -27
package/dist/index.js
CHANGED
@@ -1,250 +1,276 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
const Debug = require("debug");
|
2
|
+
const debug = Debug("koa-locales");
|
3
|
+
const debugSilly = Debug("koa-locales:silly");
|
4
|
+
const ini = require("ini");
|
5
|
+
const util = require("util");
|
6
|
+
const fs = require("fs");
|
7
|
+
const path = require("path");
|
8
|
+
const ms = require("humanize-ms").default || require("humanize-ms");
|
9
|
+
const assign = require("object-assign");
|
10
|
+
const yaml = require("js-yaml");
|
9
11
|
const DEFAULT_OPTIONS = {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
defaultLocale: "en-US",
|
13
|
+
queryField: "locale",
|
14
|
+
cookieField: "locale",
|
15
|
+
localeAlias: {},
|
16
|
+
writeCookie: true,
|
17
|
+
cookieMaxAge: "1y",
|
18
|
+
dir: undefined,
|
19
|
+
dirs: [path.join(process.cwd(), "locales")],
|
20
|
+
functionName: "__",
|
19
21
|
};
|
20
22
|
function locales(app, options = {}) {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
23
|
+
options = assign({}, DEFAULT_OPTIONS, options);
|
24
|
+
const defaultLocale = formatLocale(options.defaultLocale || "en-US");
|
25
|
+
const queryField = options.queryField || "locale";
|
26
|
+
const cookieField = options.cookieField || "locale";
|
27
|
+
const cookieDomain = options.cookieDomain;
|
28
|
+
const localeAlias = options.localeAlias || {};
|
29
|
+
const writeCookie = options.writeCookie !== false;
|
30
|
+
const cookieMaxAge = ms(options.cookieMaxAge || "1y");
|
31
|
+
const localeDir = options.dir;
|
32
|
+
const localeDirs = options.dirs || [path.join(process.cwd(), "locales")];
|
33
|
+
const functionName = options.functionName || "__";
|
34
|
+
const resources = {};
|
35
|
+
/**
|
36
|
+
* @Deprecated Use options.dirs instead.
|
37
|
+
*/
|
38
|
+
if (localeDir && !localeDirs.includes(localeDir)) {
|
39
|
+
localeDirs.push(localeDir);
|
40
|
+
}
|
41
|
+
for (let i = 0; i < localeDirs.length; i++) {
|
42
|
+
const dir = localeDirs[i];
|
43
|
+
if (!fs.existsSync(dir)) {
|
44
|
+
continue;
|
45
|
+
}
|
46
|
+
const names = fs.readdirSync(dir);
|
47
|
+
for (let j = 0; j < names.length; j++) {
|
48
|
+
const name = names[j];
|
49
|
+
const filepath = path.join(dir, name);
|
50
|
+
// support en_US.js => en-US.js
|
51
|
+
const locale = formatLocale(name.split(".")[0]);
|
52
|
+
let resource = {};
|
53
|
+
if (name.endsWith(".js") || name.endsWith(".json")) {
|
54
|
+
resource = flattening(require(filepath));
|
55
|
+
} else if (name.endsWith(".properties")) {
|
56
|
+
resource = ini.parse(fs.readFileSync(filepath, "utf8"));
|
57
|
+
} else if (name.endsWith(".yml") || name.endsWith(".yaml")) {
|
58
|
+
resource = flattening(yaml.safeLoad(fs.readFileSync(filepath, "utf8")));
|
59
|
+
}
|
60
|
+
resources[locale] = resources[locale] || {};
|
61
|
+
assign(resources[locale], resource);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
debug(
|
65
|
+
"Init locales with %j, got %j resources",
|
66
|
+
options,
|
67
|
+
Object.keys(resources),
|
68
|
+
);
|
69
|
+
if (typeof app[functionName] !== "undefined") {
|
70
|
+
console.warn(
|
71
|
+
'[koa-locales] will override exists "%s" function on app',
|
72
|
+
functionName,
|
73
|
+
);
|
74
|
+
}
|
75
|
+
function gettext(locale, key, value) {
|
76
|
+
if (arguments.length === 0 || arguments.length === 1) {
|
77
|
+
// __()
|
78
|
+
// --('en')
|
79
|
+
return "";
|
80
|
+
}
|
81
|
+
const resource = resources[locale] || {};
|
82
|
+
let text = resource[key];
|
83
|
+
if (text === undefined) {
|
84
|
+
text = key;
|
85
|
+
}
|
86
|
+
debugSilly("%s: %j => %j", locale, key, text);
|
87
|
+
if (!text) {
|
88
|
+
return "";
|
89
|
+
}
|
90
|
+
if (arguments.length === 2) {
|
91
|
+
// __(locale, key)
|
92
|
+
return text;
|
93
|
+
}
|
94
|
+
if (arguments.length === 3) {
|
95
|
+
if (isObject(value)) {
|
96
|
+
// __(locale, key, object)
|
97
|
+
// __('zh', '{a} {b} {b} {a}', {a: 'foo', b: 'bar'})
|
98
|
+
// =>
|
99
|
+
// foo bar bar foo
|
100
|
+
return formatWithObject(text, value);
|
101
|
+
}
|
102
|
+
if (Array.isArray(value)) {
|
103
|
+
// __(locale, key, array)
|
104
|
+
// __('zh', '{0} {1} {1} {0}', ['foo', 'bar'])
|
105
|
+
// =>
|
106
|
+
// foo bar bar foo
|
107
|
+
return formatWithArray(text, value);
|
108
|
+
}
|
109
|
+
// __(locale, key, value)
|
110
|
+
return util.format(text, value);
|
111
|
+
}
|
112
|
+
// __(locale, key, value1, ...)
|
113
|
+
const args = new Array(arguments.length - 1);
|
114
|
+
args[0] = text;
|
115
|
+
for (let i = 2; i < arguments.length; i++) {
|
116
|
+
args[i - 1] = arguments[i];
|
117
|
+
}
|
118
|
+
return util.format.apply(util, args);
|
119
|
+
}
|
120
|
+
app[functionName] = gettext;
|
121
|
+
app.context[functionName] = function (key, value) {
|
122
|
+
if (arguments.length === 0) {
|
123
|
+
// __()
|
124
|
+
return "";
|
125
|
+
}
|
126
|
+
const locale = this.__getLocale();
|
127
|
+
if (arguments.length === 1) {
|
128
|
+
return gettext(locale, key);
|
129
|
+
}
|
130
|
+
if (arguments.length === 2) {
|
131
|
+
return gettext(locale, key, value);
|
132
|
+
}
|
133
|
+
const args = new Array(arguments.length + 1);
|
134
|
+
args[0] = locale;
|
135
|
+
for (let i = 0; i < arguments.length; i++) {
|
136
|
+
args[i + 1] = arguments[i];
|
137
|
+
}
|
138
|
+
// @ts-expect-error: dynamic argument forwarding
|
139
|
+
return gettext(...args);
|
140
|
+
};
|
141
|
+
app.context.__getLocale = function () {
|
142
|
+
if (this.__locale) {
|
143
|
+
return this.__locale;
|
144
|
+
}
|
145
|
+
const cookieLocale = this.cookies.get(cookieField, { signed: false });
|
146
|
+
// 1. Query
|
147
|
+
let locale = this.query[queryField];
|
148
|
+
let localeOrigin = "query";
|
149
|
+
// 2. Cookie
|
150
|
+
if (!locale) {
|
151
|
+
locale = cookieLocale;
|
152
|
+
localeOrigin = "cookie";
|
153
|
+
}
|
154
|
+
// 3. Header
|
155
|
+
if (!locale) {
|
156
|
+
let languages = this.acceptsLanguages();
|
157
|
+
if (languages) {
|
158
|
+
if (Array.isArray(languages)) {
|
159
|
+
if (languages[0] === "*") {
|
160
|
+
languages = languages.slice(1);
|
161
|
+
}
|
162
|
+
if (languages.length > 0) {
|
163
|
+
for (let i = 0; i < languages.length; i++) {
|
164
|
+
const lang = formatLocale(languages[i]);
|
165
|
+
if (resources[lang] || localeAlias[lang]) {
|
166
|
+
locale = lang;
|
167
|
+
localeOrigin = "header";
|
168
|
+
break;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
} else {
|
173
|
+
locale = languages;
|
174
|
+
localeOrigin = "header";
|
175
|
+
}
|
176
|
+
}
|
177
|
+
if (!locale) {
|
178
|
+
locale = defaultLocale;
|
179
|
+
localeOrigin = "default";
|
180
|
+
}
|
181
|
+
}
|
182
|
+
if (locale && locale in localeAlias) {
|
183
|
+
const originalLocale = locale;
|
184
|
+
locale = localeAlias[locale];
|
185
|
+
debugSilly(
|
186
|
+
"Used alias, received %s but using %s",
|
187
|
+
originalLocale,
|
188
|
+
locale,
|
189
|
+
);
|
190
|
+
}
|
191
|
+
locale = formatLocale(locale || defaultLocale);
|
192
|
+
if (!resources[locale]) {
|
193
|
+
debugSilly(
|
194
|
+
"Locale %s is not supported. Using default (%s)",
|
195
|
+
locale,
|
196
|
+
defaultLocale,
|
197
|
+
);
|
198
|
+
locale = defaultLocale;
|
199
|
+
}
|
200
|
+
if (writeCookie && cookieLocale !== locale && !this.headerSent) {
|
201
|
+
updateCookie(this, locale);
|
202
|
+
}
|
203
|
+
debug("Locale: %s from %s", locale, localeOrigin);
|
204
|
+
debugSilly("Locale: %s from %s", locale, localeOrigin);
|
205
|
+
this.__locale = locale;
|
206
|
+
this.__localeOrigin = localeOrigin;
|
207
|
+
return locale;
|
208
|
+
};
|
209
|
+
app.context.__getLocaleOrigin = function () {
|
210
|
+
if (this.__localeOrigin) return this.__localeOrigin;
|
211
|
+
this.__getLocale();
|
212
|
+
return this.__localeOrigin;
|
213
|
+
};
|
214
|
+
app.context.__setLocale = function (locale) {
|
215
|
+
this.__locale = locale;
|
216
|
+
this.__localeOrigin = "set";
|
217
|
+
updateCookie(this, locale);
|
218
|
+
};
|
219
|
+
function updateCookie(ctx, locale) {
|
220
|
+
const cookieOptions = {
|
221
|
+
httpOnly: false,
|
222
|
+
maxAge: cookieMaxAge,
|
223
|
+
signed: false,
|
224
|
+
domain: cookieDomain,
|
225
|
+
overwrite: true,
|
226
|
+
};
|
227
|
+
ctx.cookies.set(cookieField, locale, cookieOptions);
|
228
|
+
debugSilly("Saved cookie with locale %s", locale);
|
229
|
+
}
|
202
230
|
}
|
203
231
|
function isObject(obj) {
|
204
|
-
|
232
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
205
233
|
}
|
206
234
|
const ARRAY_INDEX_RE = /\{(\d+)\}/g;
|
207
235
|
function formatWithArray(text, values) {
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
236
|
+
return text.replace(ARRAY_INDEX_RE, (orignal, matched) => {
|
237
|
+
const index = parseInt(matched);
|
238
|
+
if (index < values.length) {
|
239
|
+
return values[index];
|
240
|
+
}
|
241
|
+
// not match index, return orignal text
|
242
|
+
return orignal;
|
243
|
+
});
|
216
244
|
}
|
217
245
|
const Object_INDEX_RE = /\{(.+?)\}/g;
|
218
246
|
function formatWithObject(text, values) {
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
247
|
+
return text.replace(Object_INDEX_RE, (orignal, matched) => {
|
248
|
+
const value = values[matched];
|
249
|
+
if (value) {
|
250
|
+
return value;
|
251
|
+
}
|
252
|
+
// not match index, return orignal text
|
253
|
+
return orignal;
|
254
|
+
});
|
227
255
|
}
|
228
256
|
function formatLocale(locale) {
|
229
|
-
|
230
|
-
|
231
|
-
return locale.replace(/_/g, "-").toLowerCase();
|
257
|
+
if (!locale) return "";
|
258
|
+
return locale.replace(/_/g, "-").toLowerCase();
|
232
259
|
}
|
233
260
|
function flattening(data) {
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
return result;
|
261
|
+
const result = {};
|
262
|
+
function deepFlat(data, keys) {
|
263
|
+
Object.keys(data).forEach((key) => {
|
264
|
+
const value = data[key];
|
265
|
+
const k = keys ? keys + "." + key : key;
|
266
|
+
if (isObject(value)) {
|
267
|
+
deepFlat(value, k);
|
268
|
+
} else {
|
269
|
+
result[k] = String(value);
|
270
|
+
}
|
271
|
+
});
|
272
|
+
}
|
273
|
+
deepFlat(data, "");
|
274
|
+
return result;
|
249
275
|
}
|
250
|
-
|
276
|
+
module.exports = locales;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@to-kn/koa-locales",
|
3
|
-
"version": "2.0.
|
3
|
+
"version": "2.0.1",
|
4
4
|
"description": "koa locales, i18n solution for koa",
|
5
5
|
"keywords": [
|
6
6
|
"koa-locales",
|
@@ -23,7 +23,8 @@
|
|
23
23
|
"type": "module",
|
24
24
|
"exports": {
|
25
25
|
"import": "./dist/esm/index.js",
|
26
|
-
"require": "./dist/cjs/index.cjs"
|
26
|
+
"require": "./dist/cjs/index.cjs",
|
27
|
+
"types": "./dist/types/index.d.ts"
|
27
28
|
},
|
28
29
|
"main": "./dist/esm/index.js",
|
29
30
|
"module": "./dist/esm/index.js",
|
package/dist/index.d.ts
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
import type Koa from "koa";
|
2
|
-
export interface LocalesOptions {
|
3
|
-
defaultLocale?: string;
|
4
|
-
queryField?: string;
|
5
|
-
cookieField?: string;
|
6
|
-
cookieDomain?: string;
|
7
|
-
localeAlias?: Record<string, string>;
|
8
|
-
writeCookie?: boolean;
|
9
|
-
cookieMaxAge?: string | number;
|
10
|
-
dir?: string;
|
11
|
-
dirs?: string[];
|
12
|
-
functionName?: string;
|
13
|
-
}
|
14
|
-
type GettextFunction = (locale: string, key: string, ...args: unknown[]) => string;
|
15
|
-
declare module "koa" {
|
16
|
-
interface Context {
|
17
|
-
__: GettextFunction;
|
18
|
-
__getLocale(): string;
|
19
|
-
__setLocale(locale: string): void;
|
20
|
-
__getLocaleOrigin(): string;
|
21
|
-
}
|
22
|
-
interface Application {
|
23
|
-
__: GettextFunction;
|
24
|
-
}
|
25
|
-
}
|
26
|
-
declare function locales(app: Koa, options?: LocalesOptions): void;
|
27
|
-
export default locales;
|