@to-kn/koa-locales 2.1.0 → 2.1.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@to-kn/koa-locales",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "koa locales, i18n solution for koa",
5
5
  "keywords": [
6
6
  "koa-locales",
@@ -22,13 +22,13 @@
22
22
  "author": "to-kn <t.knipping@tks.eu> (https://tks.eu)",
23
23
  "type": "module",
24
24
  "exports": {
25
- "import": "./dist/esm/index.js",
26
- "require": "./dist/cjs/index.cjs",
27
- "types": "./dist/types/index.d.ts"
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/cjs/index.js",
27
+ "types": "./dist/index.d.ts"
28
28
  },
29
- "main": "./dist/cjs/index.cjs",
30
- "module": "./dist/esm/index.js",
31
- "types": "./dist/types/index.d.ts",
29
+ "main": "./dist/cjs/index.js",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
32
  "files": [
33
33
  "dist/"
34
34
  ],
package/dist/esm/index.js DELETED
@@ -1,271 +0,0 @@
1
- import fs from "node:fs";
2
- import { createRequire } from "node:module";
3
- import path from "node:path";
4
- import util from "node:util";
5
- import Debug from "debug";
6
- import { ms } from "humanize-ms";
7
- import ini from "ini";
8
- import yaml from "js-yaml";
9
- const DEFAULT_OPTIONS = {
10
- defaultLocale: "en-US",
11
- queryField: "locale",
12
- cookieField: "locale",
13
- localeAlias: {},
14
- writeCookie: true,
15
- cookieMaxAge: "1y",
16
- dir: undefined,
17
- dirs: [path.join(process.cwd(), "locales")],
18
- functionName: "__",
19
- };
20
- function locales(app, options = {}) {
21
- options = Object.assign({}, DEFAULT_OPTIONS, options);
22
- const defaultLocale = formatLocale(options.defaultLocale || "en-US");
23
- const queryField = options.queryField || "locale";
24
- const cookieField = options.cookieField || "locale";
25
- const cookieDomain = options.cookieDomain;
26
- const localeAlias = options.localeAlias || {};
27
- const writeCookie = options.writeCookie !== false;
28
- const cookieMaxAge = ms(options.cookieMaxAge || "1y");
29
- const localeDir = options.dir;
30
- const localeDirs = options.dirs || [path.join(process.cwd(), "locales")];
31
- const functionName = options.functionName || "__";
32
- const resources = {};
33
- /**
34
- * @Deprecated Use options.dirs instead.
35
- */
36
- if (localeDir && !localeDirs.includes(localeDir)) {
37
- localeDirs.push(localeDir);
38
- }
39
- appendDebugLog("Starting resource loading");
40
- // Loop through all directories, merging resources for the same locale
41
- // Later directories override earlier ones
42
- for (let i = 0; i < localeDirs.length; i++) {
43
- const dir = localeDirs[i];
44
- if (!fs.existsSync(dir)) {
45
- continue;
46
- }
47
- const names = fs.readdirSync(dir);
48
- for (let j = 0; j < names.length; j++) {
49
- const name = names[j];
50
- const filepath = path.join(dir, name);
51
- // support en_US.js => en-US.js
52
- const locale = formatLocale(name.split(".")[0]);
53
- let resource = {};
54
- if (name.endsWith(".js")) {
55
- const require = createRequire(import.meta.url);
56
- const mod = require(filepath);
57
- resource = flattening((mod.default || mod));
58
- appendDebugLog(`Loaded JS resource for locale '${locale}' from: ${filepath}`, resource);
59
- }
60
- else if (name.endsWith(".json")) {
61
- // @ts-ignore
62
- resource = flattening(require(filepath));
63
- appendDebugLog(`Loaded JSON resource for locale '${locale}' from: ${filepath}`, resource);
64
- }
65
- else if (name.endsWith(".properties")) {
66
- resource = ini.parse(fs.readFileSync(filepath, "utf8"));
67
- appendDebugLog(`Loaded PROPERTIES resource for locale '${locale}' from: ${filepath}`, resource);
68
- }
69
- else if (name.endsWith(".yml") || name.endsWith(".yaml")) {
70
- resource = flattening(yaml.load(fs.readFileSync(filepath, "utf8")));
71
- appendDebugLog(`Loaded YAML resource for locale '${locale}' from: ${filepath}`, resource);
72
- }
73
- // Always merge, but let later dirs override earlier ones
74
- resources[locale] = { ...resources[locale], ...resource };
75
- appendDebugLog(`Merged resource for locale '${locale}'`, resources[locale]);
76
- }
77
- }
78
- appendDebugLog("Finished resource loading");
79
- const debug = Debug("koa-locales");
80
- const debugSilly = Debug("koa-locales:silly");
81
- debug("Init locales with %j, got %j resources", options, Object.keys(resources));
82
- if (typeof app[functionName] !==
83
- "undefined") {
84
- console.warn('[koa-locales] will override exists "%s" function on app', functionName);
85
- }
86
- function gettext(locale, key, ...args) {
87
- if (!key || key === "")
88
- return "";
89
- const resource = resources[locale] || {};
90
- let text = resource[key];
91
- if (typeof text !== "string") {
92
- text = key;
93
- }
94
- debugSilly("%s: %j => %j", locale, key, text);
95
- if (args.length === 0) {
96
- // __(locale, key)
97
- return text;
98
- }
99
- if (args.length === 1) {
100
- const value = args[0];
101
- if (isObject(value)) {
102
- return formatWithObject(text, value);
103
- }
104
- if (Array.isArray(value)) {
105
- return formatWithArray(text, value);
106
- }
107
- return util.format(text, value);
108
- }
109
- // __(locale, key, value1, ...)
110
- return util.format(text, ...args);
111
- }
112
- // Attach to app and context using proper Koa extension
113
- app[functionName] = gettext;
114
- app.context[functionName] =
115
- function (key, ...args) {
116
- const ctx = this;
117
- const locale = ctx.__getLocale ? ctx.__getLocale() : "";
118
- return gettext(locale, key, ...args);
119
- };
120
- app.context.__getLocale =
121
- function () {
122
- const ctx = this;
123
- if (typeof ctx.__locale === "string" && ctx.__locale) {
124
- return ctx.__locale;
125
- }
126
- const cookieLocale = ctx.cookies.get(cookieField, { signed: false });
127
- let locale = ctx.query[queryField];
128
- let localeOrigin = "query";
129
- if (!locale) {
130
- locale = cookieLocale;
131
- localeOrigin = "cookie";
132
- }
133
- if (!locale) {
134
- let languages = ctx.acceptsLanguages();
135
- if (languages) {
136
- if (Array.isArray(languages)) {
137
- if (languages[0] === "*") {
138
- languages = languages.slice(1);
139
- }
140
- if (languages.length > 0) {
141
- for (let i = 0; i < languages.length; i++) {
142
- const lang = formatLocale(String(languages[i]));
143
- if (resources[lang] || localeAlias[lang]) {
144
- locale = lang;
145
- localeOrigin = "header";
146
- break;
147
- }
148
- }
149
- }
150
- }
151
- else if (typeof languages === "string") {
152
- locale = languages;
153
- localeOrigin = "header";
154
- }
155
- }
156
- if (!locale) {
157
- locale = defaultLocale;
158
- localeOrigin = "default";
159
- }
160
- }
161
- if (locale && typeof locale !== "string") {
162
- locale = String(locale);
163
- }
164
- if (locale && locale in localeAlias) {
165
- const originalLocale = locale;
166
- locale = localeAlias[locale];
167
- debugSilly("Used alias, received %s but using %s", originalLocale, locale);
168
- }
169
- locale = formatLocale(locale || defaultLocale);
170
- if (!resources[locale]) {
171
- debugSilly("Locale %s is not supported. Using default (%s)", locale, defaultLocale);
172
- locale = defaultLocale;
173
- }
174
- if (writeCookie && cookieLocale !== locale && !ctx.headerSent) {
175
- updateCookie(ctx, locale);
176
- }
177
- debug("Locale: %s from %s", locale, localeOrigin);
178
- debugSilly("Locale: %s from %s", locale, localeOrigin);
179
- ctx.__locale = locale;
180
- ctx.__localeOrigin = localeOrigin;
181
- return String(locale);
182
- };
183
- app.context.__getLocaleOrigin =
184
- function () {
185
- const ctx = this;
186
- if (typeof ctx.__localeOrigin === "string" && ctx.__localeOrigin)
187
- return ctx.__localeOrigin;
188
- ctx.__getLocale?.();
189
- return String(ctx.__localeOrigin ?? "");
190
- };
191
- app.context.__setLocale =
192
- function (locale) {
193
- const ctx = this;
194
- ctx.__locale = locale;
195
- ctx.__localeOrigin = "set";
196
- updateCookie(ctx, locale);
197
- };
198
- function updateCookie(ctx, locale) {
199
- const cookieOptions = {
200
- httpOnly: false,
201
- maxAge: cookieMaxAge,
202
- signed: false,
203
- domain: cookieDomain,
204
- overwrite: true,
205
- };
206
- ctx.cookies.set(cookieField, locale, cookieOptions);
207
- debugSilly("Saved cookie with locale %s", locale);
208
- }
209
- }
210
- function isObject(obj) {
211
- return Object.prototype.toString.call(obj) === "[object Object]";
212
- }
213
- const ARRAY_INDEX_RE = /\{(\d+)\}/g;
214
- function formatWithArray(text, values) {
215
- return text.replace(ARRAY_INDEX_RE, (orignal, matched) => {
216
- const index = parseInt(matched);
217
- if (index < values.length) {
218
- return String(values[index]);
219
- }
220
- // not match index, return orignal text
221
- return orignal;
222
- });
223
- }
224
- const Object_INDEX_RE = /\{(.+?)\}/g;
225
- function formatWithObject(text, values) {
226
- return text.replace(Object_INDEX_RE, (orignal, matched) => {
227
- const value = values[matched];
228
- if (value !== undefined && value !== null) {
229
- return String(value);
230
- }
231
- // not match index, return orignal text
232
- return orignal;
233
- });
234
- }
235
- function formatLocale(locale) {
236
- if (!locale)
237
- return "";
238
- return locale.replace(/_/g, "-").toLowerCase();
239
- }
240
- function flattening(data) {
241
- const result = {};
242
- function deepFlat(data, keys) {
243
- Object.keys(data).forEach((key) => {
244
- const value = data[key];
245
- const k = keys ? `${keys}.${key}` : key;
246
- if (isObject(value)) {
247
- deepFlat(value, k);
248
- }
249
- else {
250
- result[k] = String(value);
251
- }
252
- });
253
- }
254
- deepFlat(data, "");
255
- return result;
256
- }
257
- function appendDebugLog(message, obj) {
258
- const logPath = path.resolve(process.cwd(), "resource-debug.log");
259
- let line = `[DEBUG] ${message}`;
260
- if (obj !== undefined) {
261
- try {
262
- line += ` ${JSON.stringify(obj)}`;
263
- }
264
- catch {
265
- line += ` ${String(obj)}`;
266
- }
267
- }
268
- fs.appendFileSync(logPath, `${line}
269
- `);
270
- }
271
- export default locales;
@@ -1,320 +0,0 @@
1
- import Debug from "debug";
2
- import fs from "fs";
3
- import { ms } from "humanize-ms";
4
- import ini from "ini";
5
- import yaml from "js-yaml";
6
- import { createRequire } from "module";
7
- import assign from "object-assign";
8
- import path from "path";
9
- import util from "util";
10
-
11
- const DEFAULT_OPTIONS = {
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: "__",
21
- };
22
- function locales(app, options = {}) {
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
- appendDebugLog("Starting resource loading");
42
- throw new Error("Resource loader invoked");
43
- // Loop through all directories, merging resources for the same locale
44
- // Later directories override earlier ones
45
- for (let i = 0; i < localeDirs.length; i++) {
46
- const dir = localeDirs[i];
47
- if (!fs.existsSync(dir)) {
48
- continue;
49
- }
50
- const names = fs.readdirSync(dir);
51
- for (let j = 0; j < names.length; j++) {
52
- const name = names[j];
53
- const filepath = path.join(dir, name);
54
- // support en_US.js => en-US.js
55
- const locale = formatLocale(name.split(".")[0]);
56
- let resource = {};
57
- if (name.endsWith(".js")) {
58
- const require = createRequire(import.meta.url);
59
- const mod = require(filepath);
60
- resource = flattening(mod.default || mod);
61
- appendDebugLog(
62
- `Loaded JS resource for locale '${locale}' from: ${filepath}`,
63
- resource,
64
- );
65
- } else if (name.endsWith(".json")) {
66
- // @ts-ignore
67
- resource = flattening(require(filepath));
68
- appendDebugLog(
69
- `Loaded JSON resource for locale '${locale}' from: ${filepath}`,
70
- resource,
71
- );
72
- } else if (name.endsWith(".properties")) {
73
- resource = ini.parse(fs.readFileSync(filepath, "utf8"));
74
- appendDebugLog(
75
- `Loaded PROPERTIES resource for locale '${locale}' from: ${filepath}`,
76
- resource,
77
- );
78
- } else if (name.endsWith(".yml") || name.endsWith(".yaml")) {
79
- resource = flattening(yaml.load(fs.readFileSync(filepath, "utf8")));
80
- appendDebugLog(
81
- `Loaded YAML resource for locale '${locale}' from: ${filepath}`,
82
- resource,
83
- );
84
- }
85
- // Always merge, but let later dirs override earlier ones
86
- resources[locale] = { ...resources[locale], ...resource };
87
- appendDebugLog(
88
- `Merged resource for locale '${locale}'`,
89
- resources[locale],
90
- );
91
- }
92
- }
93
- appendDebugLog("Finished resource loading");
94
- const debug = Debug("koa-locales");
95
- const debugSilly = Debug("koa-locales:silly");
96
- debug(
97
- "Init locales with %j, got %j resources",
98
- options,
99
- Object.keys(resources),
100
- );
101
- if (typeof app[functionName] !== "undefined") {
102
- console.warn(
103
- '[koa-locales] will override exists "%s" function on app',
104
- functionName,
105
- );
106
- }
107
- function gettext(locale, key, value) {
108
- if (arguments.length === 0 || arguments.length === 1) {
109
- // __()
110
- // --('en')
111
- return "";
112
- }
113
- const resource = resources[locale] || {};
114
- let text = resource[key];
115
- if (text === undefined) {
116
- text = key;
117
- }
118
- debugSilly("%s: %j => %j", locale, key, text);
119
- if (!text) {
120
- return "";
121
- }
122
- if (arguments.length === 2) {
123
- // __(locale, key)
124
- return text;
125
- }
126
- if (arguments.length === 3) {
127
- if (isObject(value)) {
128
- // __(locale, key, object)
129
- // __('zh', '{a} {b} {b} {a}', {a: 'foo', b: 'bar'})
130
- // =>
131
- // foo bar bar foo
132
- return formatWithObject(text, value);
133
- }
134
- if (Array.isArray(value)) {
135
- // __(locale, key, array)
136
- // __('zh', '{0} {1} {1} {0}', ['foo', 'bar'])
137
- // =>
138
- // foo bar bar foo
139
- return formatWithArray(text, value);
140
- }
141
- // __(locale, key, value)
142
- return util.format(text, value);
143
- }
144
- // __(locale, key, value1, ...)
145
- const args = new Array(arguments.length - 1);
146
- args[0] = text;
147
- for (let i = 2; i < arguments.length; i++) {
148
- args[i - 1] = arguments[i];
149
- }
150
- return util.format.apply(util, args);
151
- }
152
- app[functionName] = gettext;
153
- app.context[functionName] = function (key, value) {
154
- if (arguments.length === 0) {
155
- // __()
156
- return "";
157
- }
158
- const locale = this.__getLocale();
159
- if (arguments.length === 1) {
160
- return gettext(locale, key);
161
- }
162
- if (arguments.length === 2) {
163
- return gettext(locale, key, value);
164
- }
165
- const args = new Array(arguments.length + 1);
166
- args[0] = locale;
167
- for (let i = 0; i < arguments.length; i++) {
168
- args[i + 1] = arguments[i];
169
- }
170
- // @ts-expect-error: dynamic argument forwarding
171
- return gettext(...args);
172
- };
173
- app.context.__getLocale = function () {
174
- if (this.__locale) {
175
- return this.__locale;
176
- }
177
- const cookieLocale = this.cookies.get(cookieField, { signed: false });
178
- // 1. Query
179
- let locale = this.query[queryField];
180
- let localeOrigin = "query";
181
- // 2. Cookie
182
- if (!locale) {
183
- locale = cookieLocale;
184
- localeOrigin = "cookie";
185
- }
186
- // 3. Header
187
- if (!locale) {
188
- let languages = this.acceptsLanguages();
189
- if (languages) {
190
- if (Array.isArray(languages)) {
191
- if (languages[0] === "*") {
192
- languages = languages.slice(1);
193
- }
194
- if (languages.length > 0) {
195
- for (let i = 0; i < languages.length; i++) {
196
- const lang = formatLocale(languages[i]);
197
- if (resources[lang] || localeAlias[lang]) {
198
- locale = lang;
199
- localeOrigin = "header";
200
- break;
201
- }
202
- }
203
- }
204
- } else {
205
- locale = languages;
206
- localeOrigin = "header";
207
- }
208
- }
209
- if (!locale) {
210
- locale = defaultLocale;
211
- localeOrigin = "default";
212
- }
213
- }
214
- if (locale && locale in localeAlias) {
215
- const originalLocale = locale;
216
- locale = localeAlias[locale];
217
- debugSilly(
218
- "Used alias, received %s but using %s",
219
- originalLocale,
220
- locale,
221
- );
222
- }
223
- locale = formatLocale(locale || defaultLocale);
224
- if (!resources[locale]) {
225
- debugSilly(
226
- "Locale %s is not supported. Using default (%s)",
227
- locale,
228
- defaultLocale,
229
- );
230
- locale = defaultLocale;
231
- }
232
- if (writeCookie && cookieLocale !== locale && !this.headerSent) {
233
- updateCookie(this, locale);
234
- }
235
- debug("Locale: %s from %s", locale, localeOrigin);
236
- debugSilly("Locale: %s from %s", locale, localeOrigin);
237
- this.__locale = locale;
238
- this.__localeOrigin = localeOrigin;
239
- return locale;
240
- };
241
- app.context.__getLocaleOrigin = function () {
242
- if (this.__localeOrigin) return this.__localeOrigin;
243
- this.__getLocale();
244
- return this.__localeOrigin;
245
- };
246
- app.context.__setLocale = function (locale) {
247
- this.__locale = locale;
248
- this.__localeOrigin = "set";
249
- updateCookie(this, locale);
250
- };
251
- function updateCookie(ctx, locale) {
252
- const cookieOptions = {
253
- httpOnly: false,
254
- maxAge: cookieMaxAge,
255
- signed: false,
256
- domain: cookieDomain,
257
- overwrite: true,
258
- };
259
- ctx.cookies.set(cookieField, locale, cookieOptions);
260
- debugSilly("Saved cookie with locale %s", locale);
261
- }
262
- }
263
- function isObject(obj) {
264
- return Object.prototype.toString.call(obj) === "[object Object]";
265
- }
266
- const ARRAY_INDEX_RE = /\{(\d+)\}/g;
267
- function formatWithArray(text, values) {
268
- return text.replace(ARRAY_INDEX_RE, (orignal, matched) => {
269
- const index = parseInt(matched);
270
- if (index < values.length) {
271
- return values[index];
272
- }
273
- // not match index, return orignal text
274
- return orignal;
275
- });
276
- }
277
- const Object_INDEX_RE = /\{(.+?)\}/g;
278
- function formatWithObject(text, values) {
279
- return text.replace(Object_INDEX_RE, (orignal, matched) => {
280
- const value = values[matched];
281
- if (value) {
282
- return value;
283
- }
284
- // not match index, return orignal text
285
- return orignal;
286
- });
287
- }
288
- function formatLocale(locale) {
289
- if (!locale) return "";
290
- return locale.replace(/_/g, "-").toLowerCase();
291
- }
292
- function flattening(data) {
293
- const result = {};
294
- function deepFlat(data, keys) {
295
- Object.keys(data).forEach((key) => {
296
- const value = data[key];
297
- const k = keys ? keys + "." + key : key;
298
- if (isObject(value)) {
299
- deepFlat(value, k);
300
- } else {
301
- result[k] = String(value);
302
- }
303
- });
304
- }
305
- deepFlat(data, "");
306
- return result;
307
- }
308
- function appendDebugLog(message, obj) {
309
- const logPath = path.resolve(process.cwd(), "resource-debug.log");
310
- let line = `[DEBUG] ${message}`;
311
- if (obj !== undefined) {
312
- try {
313
- line += " " + JSON.stringify(obj);
314
- } catch {
315
- line += " " + String(obj);
316
- }
317
- }
318
- fs.appendFileSync(logPath, line + "\n");
319
- }
320
- export default locales;
@@ -1,580 +0,0 @@
1
- import assert from "assert";
2
- import Koa from "koa";
3
- import mm from "mm";
4
- import request from "supertest";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- // @ts-expect-error: legacy test expects any type for locales import
7
- import locales from "..";
8
-
9
- describe("koa-locales.test.js", () => {
10
- afterEach(mm.restore);
11
- describe("default options", () => {
12
- const app = createApp();
13
- it("should use default locale: en-US", async () => {
14
- await request(app.callback())
15
- .get("/")
16
- .expect({
17
- email: "Email",
18
- hello: "Hello fengmk2, how are you today?",
19
- message: "Hello fengmk2, how are you today? How was your 18.",
20
- empty: "",
21
- notexists_key: "key not exists",
22
- empty_string: "",
23
- empty_value: "emptyValue",
24
- novalue: "key %s ok",
25
- arguments3: "1 2 3",
26
- arguments4: "1 2 3 4",
27
- arguments5: "1 2 3 4 5",
28
- arguments6: "1 2 3 4 5. 6",
29
- values: "foo bar foo bar {2} {100}",
30
- object: "foo bar foo bar {z}",
31
- gender: "model.user.fields.gender",
32
- name: "model.user.fields.name",
33
- })
34
- .expect("Set-Cookie", /^locale=en-us; path=\/+; expires=[^;]+ GMT$/)
35
- .expect(200);
36
- });
37
- it("should not set locale cookie after header sent", async () => {
38
- await request(app.callback())
39
- .get("/headerSent")
40
- .expect("foo")
41
- .expect(200);
42
- });
43
- });
44
- describe("options.cookieDomain", () => {
45
- const app = createApp({
46
- cookieDomain: ".foo.com",
47
- });
48
- it("should use default locale: en-US", async () => {
49
- await request(app.callback())
50
- .get("/")
51
- .expect({
52
- email: "Email",
53
- hello: "Hello fengmk2, how are you today?",
54
- message: "Hello fengmk2, how are you today? How was your 18.",
55
- empty: "",
56
- notexists_key: "key not exists",
57
- empty_string: "",
58
- empty_value: "emptyValue",
59
- novalue: "key %s ok",
60
- arguments3: "1 2 3",
61
- arguments4: "1 2 3 4",
62
- arguments5: "1 2 3 4 5",
63
- arguments6: "1 2 3 4 5. 6",
64
- values: "foo bar foo bar {2} {100}",
65
- object: "foo bar foo bar {z}",
66
- gender: "model.user.fields.gender",
67
- name: "model.user.fields.name",
68
- })
69
- .expect(
70
- "Set-Cookie",
71
- /^locale=en-us; path=\/; expires=[^;]+; domain=.foo.com$/,
72
- )
73
- .expect(200);
74
- });
75
- });
76
- describe("custom options", () => {
77
- const app = createApp({
78
- dirs: [__dirname + "/locales", __dirname + "/other-locales"],
79
- });
80
- const cookieFieldMapApp = createApp({
81
- dirs: [__dirname + "/locales", __dirname + "/other-locales"],
82
- localeAlias: {
83
- en: "en-US",
84
- "de-de": "de",
85
- },
86
- });
87
- const appNotWriteCookie = createApp({
88
- dirs: [__dirname + "/locales", __dirname + "/other-locales"],
89
- writeCookie: false,
90
- });
91
- it("should use default locale: en-US", async () => {
92
- await request(app.callback())
93
- .get("/")
94
- .expect({
95
- email: "Email",
96
- hello: "Hello fengmk2, how are you today?",
97
- message: "Hello fengmk2, how are you today? How was your 18.",
98
- empty: "",
99
- notexists_key: "key not exists",
100
- empty_string: "",
101
- empty_value: "emptyValue",
102
- novalue: "key %s ok",
103
- arguments3: "1 2 3",
104
- arguments4: "1 2 3 4",
105
- arguments5: "1 2 3 4 5",
106
- arguments6: "1 2 3 4 5. 6",
107
- values: "foo bar foo bar {2} {100}",
108
- object: "foo bar foo bar {z}",
109
- gender: "model.user.fields.gender",
110
- name: "model.user.fields.name",
111
- })
112
- .expect("Set-Cookie", /^locale=en-us; path=\/; expires=\w+/)
113
- .expect(200);
114
- });
115
- it("should gettext work on app.__(locale, key, value)", async () => {
116
- await request(app.callback())
117
- .get("/app_locale_zh")
118
- .expect({
119
- email: "邮箱1",
120
- })
121
- .expect(200);
122
- });
123
- describe("query.locale", () => {
124
- it("should use query locale: zh-CN", async () => {
125
- await request(app.callback())
126
- .get("/?locale=zh-CN")
127
- .expect({
128
- email: "邮箱1",
129
- hello: "fengmk2,今天过得如何?",
130
- message: "Hello fengmk2, how are you today? How was your 18.",
131
- empty: "",
132
- notexists_key: "key not exists",
133
- empty_string: "",
134
- empty_value: "",
135
- novalue: "key %s ok",
136
- arguments3: "1 2 3",
137
- arguments4: "1 2 3 4",
138
- arguments5: "1 2 3 4 5",
139
- arguments6: "1 2 3 4 5. 6",
140
- values: "foo bar foo bar {2} {100}",
141
- object: "foo bar foo bar {z}",
142
- gender: "性别",
143
- name: "姓名",
144
- })
145
- .expect("Set-Cookie", /^locale=zh-cn; path=\/; expires=\w+/)
146
- .expect(200);
147
- });
148
- it("should use query locale: de on *.properties format", async () => {
149
- await request(app.callback())
150
- .get("/?locale=de")
151
- .expect({
152
- email: "Emailde",
153
- hello: "Hallo fengmk2, wie geht es dir heute?",
154
- message: "Hallo fengmk2, wie geht es dir heute? Wie war dein 18.",
155
- empty: "",
156
- notexists_key: "key not exists",
157
- empty_string: "",
158
- empty_value: "emptyValue",
159
- novalue: "key %s ok",
160
- arguments3: "1 2 3",
161
- arguments4: "1 2 3 4",
162
- arguments5: "1 2 3 4 5",
163
- arguments6: "1 2 3 4 5. 6",
164
- values: "foo bar foo bar {2} {100}",
165
- object: "foo bar foo bar {z}",
166
- gender: "model.user.fields.gender",
167
- name: "model.user.fields.name",
168
- })
169
- .expect("Set-Cookie", /^locale=de; path=\/; expires=\w+/)
170
- .expect(200);
171
- });
172
- it("should use query locale and change cookie locale", async () => {
173
- await request(app.callback())
174
- .get("/?locale=zh-CN")
175
- .set("cookie", "locale=zh-TW")
176
- .expect({
177
- email: "邮箱1",
178
- hello: "fengmk2,今天过得如何?",
179
- message: "Hello fengmk2, how are you today? How was your 18.",
180
- empty: "",
181
- notexists_key: "key not exists",
182
- empty_string: "",
183
- empty_value: "",
184
- novalue: "key %s ok",
185
- arguments3: "1 2 3",
186
- arguments4: "1 2 3 4",
187
- arguments5: "1 2 3 4 5",
188
- arguments6: "1 2 3 4 5. 6",
189
- values: "foo bar foo bar {2} {100}",
190
- object: "foo bar foo bar {z}",
191
- gender: "性别",
192
- name: "姓名",
193
- })
194
- .expect("Set-Cookie", /^locale=zh-cn; path=\/; expires=\w+/)
195
- .expect(200);
196
- });
197
- it("should ignore invalid locale value", async () => {
198
- await request(app.callback())
199
- .get("/?locale=xss")
200
- .expect({
201
- email: "Email",
202
- hello: "Hello fengmk2, how are you today?",
203
- message: "Hello fengmk2, how are you today? How was your 18.",
204
- empty: "",
205
- notexists_key: "key not exists",
206
- empty_string: "",
207
- empty_value: "emptyValue",
208
- novalue: "key %s ok",
209
- arguments3: "1 2 3",
210
- arguments4: "1 2 3 4",
211
- arguments5: "1 2 3 4 5",
212
- arguments6: "1 2 3 4 5. 6",
213
- values: "foo bar foo bar {2} {100}",
214
- object: "foo bar foo bar {z}",
215
- gender: "model.user.fields.gender",
216
- name: "model.user.fields.name",
217
- })
218
- .expect("Set-Cookie", /^locale=en-us; path=\/; expires=\w+/)
219
- .expect(200);
220
- });
221
- it("should use localeAlias", async () => {
222
- await request(cookieFieldMapApp.callback())
223
- .get("/?locale=de-de")
224
- .expect({
225
- email: "Emailde",
226
- hello: "Hallo fengmk2, wie geht es dir heute?",
227
- message: "Hallo fengmk2, wie geht es dir heute? Wie war dein 18.",
228
- empty: "",
229
- notexists_key: "key not exists",
230
- empty_string: "",
231
- empty_value: "emptyValue",
232
- novalue: "key %s ok",
233
- arguments3: "1 2 3",
234
- arguments4: "1 2 3 4",
235
- arguments5: "1 2 3 4 5",
236
- arguments6: "1 2 3 4 5. 6",
237
- values: "foo bar foo bar {2} {100}",
238
- object: "foo bar foo bar {z}",
239
- gender: "model.user.fields.gender",
240
- name: "model.user.fields.name",
241
- })
242
- .expect("Set-Cookie", /^locale=de; path=\/; expires=\w+/)
243
- .expect(200);
244
- });
245
- it("should use query locale and response without set-cookie", async () => {
246
- await request(appNotWriteCookie.callback())
247
- .get("/?locale=zh-CN")
248
- .expect({
249
- email: "邮箱1",
250
- hello: "fengmk2,今天过得如何?",
251
- message: "Hello fengmk2, how are you today? How was your 18.",
252
- empty: "",
253
- notexists_key: "key not exists",
254
- empty_string: "",
255
- empty_value: "",
256
- novalue: "key %s ok",
257
- arguments3: "1 2 3",
258
- arguments4: "1 2 3 4",
259
- arguments5: "1 2 3 4 5",
260
- arguments6: "1 2 3 4 5. 6",
261
- values: "foo bar foo bar {2} {100}",
262
- object: "foo bar foo bar {z}",
263
- gender: "性别",
264
- name: "姓名",
265
- })
266
- .expect((res) => {
267
- if (res.headers["set-cookie"] || res.headers["Set-Cookie"]) {
268
- throw new Error("should not write cookie");
269
- }
270
- })
271
- .expect(200);
272
- });
273
- });
274
- describe("cookie.locale", () => {
275
- it("should use cookie locale: zh-CN", async () => {
276
- await request(app.callback())
277
- .get("/?locale=")
278
- .set("cookie", "locale=zh-cn")
279
- .expect({
280
- email: "邮箱1",
281
- hello: "fengmk2,今天过得如何?",
282
- message: "Hello fengmk2, how are you today? How was your 18.",
283
- empty: "",
284
- notexists_key: "key not exists",
285
- empty_string: "",
286
- empty_value: "",
287
- novalue: "key %s ok",
288
- arguments3: "1 2 3",
289
- arguments4: "1 2 3 4",
290
- arguments5: "1 2 3 4 5",
291
- arguments6: "1 2 3 4 5. 6",
292
- values: "foo bar foo bar {2} {100}",
293
- object: "foo bar foo bar {z}",
294
- gender: "性别",
295
- name: "姓名",
296
- })
297
- .expect((res) => {
298
- assert(!res.headers["set-cookie"]);
299
- })
300
- .expect(200);
301
- });
302
- });
303
- describe("Accept-Language", () => {
304
- it("should use Accept-Language: zh-CN", async () => {
305
- await request(app.callback())
306
- .get("/?locale=")
307
- .set("Accept-Language", "zh-CN")
308
- .expect({
309
- email: "邮箱1",
310
- hello: "fengmk2,今天过得如何?",
311
- message: "Hello fengmk2, how are you today? How was your 18.",
312
- empty: "",
313
- notexists_key: "key not exists",
314
- empty_string: "",
315
- empty_value: "",
316
- novalue: "key %s ok",
317
- arguments3: "1 2 3",
318
- arguments4: "1 2 3 4",
319
- arguments5: "1 2 3 4 5",
320
- arguments6: "1 2 3 4 5. 6",
321
- values: "foo bar foo bar {2} {100}",
322
- object: "foo bar foo bar {z}",
323
- gender: "性别",
324
- name: "姓名",
325
- })
326
- .expect("Set-Cookie", /^locale=zh-cn; path=\/; expires=\w+/)
327
- .expect(200);
328
- });
329
- it('should work with "Accept-Language: " header', async () => {
330
- await request(app.callback())
331
- .get("/?locale=")
332
- .set("Accept-Language", "")
333
- .expect({
334
- email: "Email",
335
- hello: "Hello fengmk2, how are you today?",
336
- message: "Hello fengmk2, how are you today? How was your 18.",
337
- empty: "",
338
- notexists_key: "key not exists",
339
- empty_string: "",
340
- empty_value: "emptyValue",
341
- novalue: "key %s ok",
342
- arguments3: "1 2 3",
343
- arguments4: "1 2 3 4",
344
- arguments5: "1 2 3 4 5",
345
- arguments6: "1 2 3 4 5. 6",
346
- values: "foo bar foo bar {2} {100}",
347
- object: "foo bar foo bar {z}",
348
- gender: "model.user.fields.gender",
349
- name: "model.user.fields.name",
350
- })
351
- .expect("Set-Cookie", /^locale=en-us; path=\/; expires=\w+/)
352
- .expect(200);
353
- });
354
- it('should work with "Accept-Language: en"', async () => {
355
- await request(app.callback())
356
- .get("/")
357
- .set("Accept-Language", "en")
358
- .expect({
359
- email: "Email",
360
- hello: "Hello fengmk2, how are you today?",
361
- message: "Hello fengmk2, how are you today? How was your 18.",
362
- empty: "",
363
- notexists_key: "key not exists",
364
- empty_string: "",
365
- empty_value: "emptyValue",
366
- novalue: "key %s ok",
367
- arguments3: "1 2 3",
368
- arguments4: "1 2 3 4",
369
- arguments5: "1 2 3 4 5",
370
- arguments6: "1 2 3 4 5. 6",
371
- values: "foo bar foo bar {2} {100}",
372
- object: "foo bar foo bar {z}",
373
- gender: "model.user.fields.gender",
374
- name: "model.user.fields.name",
375
- })
376
- .expect("Set-Cookie", /^locale=en-us; path=\/; expires=\w+/)
377
- .expect(200);
378
- });
379
- it('should work with "Accept-Language: de-de" by localeAlias', async () => {
380
- await request(cookieFieldMapApp.callback())
381
- .get("/")
382
- .set("Accept-Language", "ja,de-de;q=0.8")
383
- .expect({
384
- email: "Emailde",
385
- hello: "Hallo fengmk2, wie geht es dir heute?",
386
- message: "Hallo fengmk2, wie geht es dir heute? Wie war dein 18.",
387
- empty: "",
388
- notexists_key: "key not exists",
389
- empty_string: "",
390
- empty_value: "emptyValue",
391
- novalue: "key %s ok",
392
- arguments3: "1 2 3",
393
- arguments4: "1 2 3 4",
394
- arguments5: "1 2 3 4 5",
395
- arguments6: "1 2 3 4 5. 6",
396
- values: "foo bar foo bar {2} {100}",
397
- object: "foo bar foo bar {z}",
398
- gender: "model.user.fields.gender",
399
- name: "model.user.fields.name",
400
- })
401
- .expect("Set-Cookie", /^locale=de; path=\/; expires=\w+/)
402
- .expect(200);
403
- });
404
- it("should mock acceptsLanguages return string", async () => {
405
- mm(app.request, "acceptsLanguages", () => "zh-TW");
406
- await request(app.callback())
407
- .get("/?locale=")
408
- .expect({
409
- email: "郵箱",
410
- hello: "fengmk2,今天過得如何?",
411
- message: "Hello fengmk2, how are you today? How was your 18.",
412
- empty: "",
413
- notexists_key: "key not exists",
414
- empty_string: "",
415
- empty_value: "",
416
- novalue: "key %s ok",
417
- arguments3: "1 2 3",
418
- arguments4: "1 2 3 4",
419
- arguments5: "1 2 3 4 5",
420
- arguments6: "1 2 3 4 5. 6",
421
- values: "foo bar foo bar {2} {100}",
422
- object: "foo bar foo bar {z}",
423
- gender: "性別",
424
- name: "姓名",
425
- })
426
- .expect("Set-Cookie", /^locale=zh-tw; path=\/; expires=\w+/)
427
- .expect(200);
428
- });
429
- it("should mock acceptsLanguages return string", async () => {
430
- mm(app.request, "acceptsLanguages", () => "fr");
431
- await request(app.callback())
432
- .get("/?locale=fr")
433
- .set("Accept-Language", "fr;q=0.8, fr, fr")
434
- .expect({
435
- email: "le email",
436
- hello: "fengmk2, Comment allez-vous",
437
- message: "Hello fengmk2, how are you today? How was your 18.",
438
- empty: "",
439
- notexists_key: "key not exists",
440
- empty_string: "",
441
- empty_value: "",
442
- novalue: "key %s ok",
443
- arguments3: "1 2 3",
444
- arguments4: "1 2 3 4",
445
- arguments5: "1 2 3 4 5",
446
- arguments6: "1 2 3 4 5. 6",
447
- values: "foo bar foo bar {2} {100}",
448
- object: "foo bar foo bar {z}",
449
- gender: "le sexe",
450
- name: "prénom",
451
- })
452
- .expect("Set-Cookie", /^locale=fr; path=\/; expires=\w+/)
453
- .expect(200);
454
- });
455
- it("should mock acceptsLanguages return null", async () => {
456
- mm(app.request, "acceptsLanguages", () => null);
457
- await request(app.callback())
458
- .get("/?locale=")
459
- .expect({
460
- email: "Email",
461
- hello: "Hello fengmk2, how are you today?",
462
- message: "Hello fengmk2, how are you today? How was your 18.",
463
- empty: "",
464
- notexists_key: "key not exists",
465
- empty_string: "",
466
- empty_value: "emptyValue",
467
- novalue: "key %s ok",
468
- arguments3: "1 2 3",
469
- arguments4: "1 2 3 4",
470
- arguments5: "1 2 3 4 5",
471
- arguments6: "1 2 3 4 5. 6",
472
- values: "foo bar foo bar {2} {100}",
473
- object: "foo bar foo bar {z}",
474
- gender: "model.user.fields.gender",
475
- name: "model.user.fields.name",
476
- })
477
- .expect("Set-Cookie", /^locale=en-us; path=\/; expires=\w+/)
478
- .expect(200);
479
- });
480
- });
481
- describe("__getLocale and __getLocaleOrigin", () => {
482
- it("should __getLocale and __getLocaleOrigin from cookie", async () => {
483
- const res = await request(app.callback())
484
- .get("/origin")
485
- .set("cookie", "locale=de");
486
- expect(res.body).toEqual({ locale: "de", localeOrigin: "cookie" });
487
- });
488
- it("should __getLocale and __getLocaleOrigin from query", async () => {
489
- const res = await request(app.callback()).get("/origin?locale=de");
490
- expect(res.body).toEqual({ locale: "de", localeOrigin: "query" });
491
- });
492
- it("should __getLocale and __getLocaleOrigin from header", async () => {
493
- const res = await request(app.callback())
494
- .get("/origin")
495
- .set("Accept-Language", "zh-cn");
496
- expect(res.body).toEqual({ locale: "zh-cn", localeOrigin: "header" });
497
- });
498
- it("should __getLocale and __getLocaleOrigin from default", async () => {
499
- const res = await request(app.callback()).get("/origin");
500
- expect(res.body).toEqual({ locale: "en-us", localeOrigin: "default" });
501
- });
502
- });
503
- describe("__setLocale", () => {
504
- it("should set locale and cookie", async () => {
505
- const res = await request(app.callback())
506
- .get("/set")
507
- .set("cookie", "locale=de");
508
- expect(res.body).toEqual({ locale: "zh-hk", localeOrigin: "set" });
509
- expect(res.headers["set-cookie"]).toBeTruthy();
510
- });
511
- });
512
- });
513
- });
514
- // @ts-ignore: legacy test expects any type for options
515
- function createApp(options) {
516
- const app = new Koa();
517
- locales(app, options);
518
- const fname = (options && options.functionName) || "__";
519
- app.use(async (ctx, next) => {
520
- if (ctx.url === "/app_locale_zh") {
521
- ctx.body = {
522
- // @ts-ignore: legacy test expects any type for Application index
523
- email: ctx.app[fname]("zh-cn", "Email"),
524
- };
525
- return;
526
- }
527
- if (ctx.path === "/origin") {
528
- ctx.body = {
529
- locale: ctx.__getLocale(),
530
- localeOrigin: ctx.__getLocaleOrigin(),
531
- };
532
- return;
533
- }
534
- if (ctx.path === "/set") {
535
- ctx.__getLocale();
536
- ctx.__setLocale("zh-tw");
537
- ctx.__setLocale("zh-hk");
538
- ctx.body = {
539
- locale: ctx.__getLocale(),
540
- localeOrigin: ctx.__getLocaleOrigin(),
541
- };
542
- return;
543
- }
544
- if (ctx.url === "/headerSent") {
545
- ctx.body = "foo";
546
- // @ts-ignore: legacy test expects any type for Application index
547
- setTimeout(() => {
548
- ctx.app[fname]("Email");
549
- }, 50);
550
- return;
551
- }
552
- ctx.body = {
553
- email: ctx[fname]("Email"),
554
- name: ctx[fname]("model.user.fields.name"),
555
- gender: ctx[fname]("model.user.fields.gender"),
556
- hello: ctx[fname]("Hello %s, how are you today?", "fengmk2"),
557
- message: ctx[fname](
558
- "Hello %s, how are you today? How was your %s.",
559
- "fengmk2",
560
- 18,
561
- ),
562
- empty: ctx[fname](),
563
- notexists_key: ctx[fname]("key not exists"),
564
- empty_string: ctx[fname](""),
565
- empty_value: ctx[fname]("emptyValue"),
566
- novalue: ctx[fname]("key %s ok"),
567
- arguments3: ctx[fname]("%s %s %s", 1, 2, 3),
568
- arguments4: ctx[fname]("%s %s %s %s", 1, 2, 3, 4),
569
- arguments5: ctx[fname]("%s %s %s %s %s", 1, 2, 3, 4, 5),
570
- arguments6: ctx[fname]("%s %s %s %s %s.", 1, 2, 3, 4, 5, 6),
571
- values: ctx[fname]("{0} {1} {0} {1} {2} {100}", ["foo", "bar"]),
572
- object: ctx[fname]("{foo} {bar} {foo} {bar} {z}", {
573
- foo: "foo",
574
- bar: "bar",
575
- }),
576
- };
577
- if (next) await next();
578
- });
579
- return app;
580
- }
@@ -1,28 +0,0 @@
1
- import type Koa from "koa";
2
- 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;
28
- export type { LocalesOptions };
@@ -1,27 +0,0 @@
1
- import type Koa from "koa";
2
- 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: any[]) => 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;
@@ -1 +0,0 @@
1
- export {};