intl-template 0.0.5 → 1.0.0-alpha.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.
package/README.md CHANGED
@@ -0,0 +1,116 @@
1
+ # intel-template
2
+
3
+ A tiny i18n/l10n TTL(Tagged Template Literals) function
4
+
5
+ ## Table of Contents
6
+
7
+ - [intel-template](#intel-template)
8
+ - [Table of Contents](#table-of-contents)
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [Use browser specifies locale](#use-browser-specifies-locale)
12
+ - [Use with React](#use-with-react)
13
+ - [Specify slot order](#specify-slot-order)
14
+ - [Nested](#nested)
15
+ - [Function slot](#function-slot)
16
+ - [Call as function](#call-as-function)
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install intl-template
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```
27
+ import translation from "intl-template"
28
+
29
+ translation.templates["es-ES"] = {
30
+ "hello {}": "hola {}"
31
+ }
32
+
33
+ const l10n = translation.translate.bind(null, "es-ES")
34
+
35
+ const name = "willow";
36
+
37
+ console.log(l10n`hello ${name}`)
38
+ // => hola willow
39
+ ```
40
+
41
+ ### Use browser specifies locale
42
+ ```
43
+ import translation, { l10n } from "intl-template"
44
+
45
+ translation.templates["es-ES"] = {
46
+ "hello {}": "hola {}"
47
+ }
48
+
49
+ // l10n = translation.translate.bind(null, navigator,language)
50
+
51
+ const name = "willow";
52
+
53
+ console.log(l10n`hello ${name}`)
54
+ // => hola willow
55
+ ```
56
+
57
+ ### Use with React
58
+
59
+ ```javascript
60
+
61
+ function SomeComponent({ name }) {
62
+ return (
63
+ <div>
64
+ {l10n`hello ${<b>{name}</b>}`}
65
+ </div>
66
+ )
67
+ }
68
+ ```
69
+
70
+ ### Specify slot order
71
+
72
+ ```
73
+ translation.templates["de-DE"] = {
74
+ "hello {} and {}": "hallo {1} und {0}"
75
+ }
76
+
77
+ const l10n = translation.translate.bind(null, "de-DE")
78
+
79
+ const name1 = "willow"
80
+ const name2 = "jack"
81
+
82
+ console.log(l10n`hello ${name1} and ${name2}`)
83
+ // => holla jack und willow
84
+ ```
85
+
86
+ ### Nested
87
+
88
+ ```javascript
89
+ translation.templates["de-DE"] = {
90
+ "bill": "schmidt",
91
+ "hello {}": "hallo {1}"
92
+ }
93
+
94
+ const l10n = translation.translate.bind(null, "de-DE")
95
+
96
+ l10n`hello ${l10n`bill`}` // => hallo schmidt
97
+ ```
98
+
99
+ ### Function slot
100
+
101
+ ```javascript
102
+ translation.templates["de-DE"] = {
103
+ "bill": "schmidt",
104
+ "hello {}": "hallo {1}"
105
+ }
106
+
107
+ const l10n = translation.translate.bind(null, "de-DE")
108
+
109
+ l10n`hello ${(locale) => 123}` // => hallo 123
110
+ ```
111
+
112
+ ### Call as function
113
+
114
+ ```javascript
115
+ l10n("hello {}", name)
116
+ ```
package/dist/intl.d.ts CHANGED
@@ -1,3 +1,37 @@
1
- export function setTransition(locale: any, transition: any): {};
2
- export function translate(locale: any, strings: any, ...parts: any[]): any;
3
- export const l10n: (strings?: any, ...parts: any[]) => any;
1
+ /**
2
+ * Parses a template string and extracts the template parts and order of slots.
3
+ * @param {string} templateString - The template string to parse.
4
+ * @returns {{ template: string[], order: number[] }}
5
+ */
6
+ export function parseTemplate(templateString: string): {
7
+ template: string[];
8
+ order: number[];
9
+ };
10
+ export class Runes extends Array<any> {
11
+ constructor(arrayLength?: number | undefined);
12
+ constructor(arrayLength: number);
13
+ constructor(...items: any[]);
14
+ }
15
+ /**
16
+ * Represents a Translation object that handles string translation based on locale and templates.
17
+ */
18
+ export class Translation {
19
+ /**
20
+ * Templates object that stores the translation templates for each locale.
21
+ * @type {Proxy}
22
+ */
23
+ templates: ProxyConstructor;
24
+ /**
25
+ * Translates a string based on the provided locale and strings.
26
+ *
27
+ * @param {string} locale - The locale to use for translation.
28
+ * @param {string | string[]} strings - The string or array of strings to be translated.
29
+ * @param {...any} parts - The dynamic parts to be inserted into the translated string.
30
+ * @returns {Runes} - The translated string with dynamic parts inserted.
31
+ * @throws {Error} - If the length of the template parts does not match the length of the template.
32
+ */
33
+ translate: (locale: string, strings: string | string[], ...parts: any[]) => Runes;
34
+ }
35
+ export default translation;
36
+ export const l10n: (strings: string | string[], ...parts: any[]) => Runes;
37
+ declare const translation: Translation;
package/dist/intl.js CHANGED
@@ -1,73 +1,138 @@
1
+ var __extends = (this && this.__extends) || (function () {
2
+ var extendStatics = function (d, b) {
3
+ extendStatics = Object.setPrototypeOf ||
4
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
6
+ return extendStatics(d, b);
7
+ };
8
+ return function (d, b) {
9
+ if (typeof b !== "function" && b !== null)
10
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
11
+ extendStatics(d, b);
12
+ function __() { this.constructor = d; }
13
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
14
+ };
15
+ })();
1
16
  var _a;
2
- var TRANSLATIONS = {};
3
- export function setTransition(locale, transition) {
4
- return TRANSLATIONS[locale] = Object.entries(transition).reduce(function (obj, _a) {
5
- var key = _a[0], value = _a[1];
6
- var order = [];
7
- var template = [];
8
- var parts = value.split(/({\d*})/);
9
- parts.forEach(function (part, index) {
17
+ var Runes = /** @class */ (function (_super) {
18
+ __extends(Runes, _super);
19
+ function Runes() {
20
+ return _super !== null && _super.apply(this, arguments) || this;
21
+ }
22
+ Runes.prototype.toString = function () {
23
+ return this.join("");
24
+ };
25
+ return Runes;
26
+ }(Array));
27
+ export { Runes };
28
+ /**
29
+ * Parses a template string and extracts the template parts and order of slots.
30
+ * @param {string} templateString - The template string to parse.
31
+ * @returns {{ template: string[], order: number[] }}
32
+ */
33
+ export function parseTemplate(templateString) {
34
+ var order = [];
35
+ var template = [];
36
+ var parts = templateString.split(/({\d*})/);
37
+ parts.forEach(function (part) {
38
+ if (part.match(/^{\d*}$/)) {
10
39
  if (part === '{}') {
11
- if (index === 0 || index === parts.length - 1) {
12
- template.push("");
13
- }
14
40
  order.push(order.length);
15
- return;
16
41
  }
17
- if (part.match(/^{\d+}$/)) {
18
- if (index === 0 || index === parts.length - 1) {
19
- template.push("");
20
- }
42
+ else {
43
+ // slot with order, e.g. `{2}abc{1}def{0}`
21
44
  order.push(parseInt(part.slice(1, -1), 10));
22
- return;
23
45
  }
24
- template.push(part);
25
- });
26
- obj[key] = { template: template, order: order };
27
- return obj;
28
- }, {});
29
- }
30
- export function translate(locale, strings) {
31
- var _a, _b;
32
- var parts = [];
33
- for (var _i = 2; _i < arguments.length; _i++) {
34
- parts[_i - 2] = arguments[_i];
35
- }
36
- var key = strings.join("{}");
37
- var translation = TRANSLATIONS === null || TRANSLATIONS === void 0 ? void 0 : TRANSLATIONS[locale];
38
- var _c = (translation === null || translation === void 0 ? void 0 : translation[key]) || {}, template = _c.template, order = _c.order;
39
- if (!template) {
40
- if (((_b = (_a = import.meta) === null || _a === void 0 ? void 0 : _a.env) === null || _b === void 0 ? void 0 : _b.MODE) === "development") {
41
- console.warn("not match translate key, ".concat(key), { translation: translation, locale: locale, strings: strings, parts: parts });
42
46
  }
43
- template = strings.slice();
44
- order = parts.map(function (_, i) { return i; });
45
- }
46
- if (parts.length > 0 && template.length - 1 !== parts.length) {
47
- throw new Error("translate template parts length not match. locale: ".concat(locale, ", key: ").concat(key));
48
- }
49
- var ret = template.reduce(function (ret, template, idx) {
50
- ret.push(template);
51
- var orderIdx = order[idx];
52
- if (orderIdx >= 0) {
53
- var part = parts[orderIdx];
54
- if (typeof part == "function") {
55
- ret.push(part(locale, translation));
56
- }
57
- else {
58
- ret.push(part);
59
- }
47
+ else {
48
+ template.push(part);
60
49
  }
61
- return ret;
62
- }, []);
63
- var text = ret.join("");
64
- Object.defineProperty(ret, "toString", {
65
- enumerable: false,
66
- writable: false,
67
- configurable: false,
68
- value: function () { return text; }
69
50
  });
70
- return ret;
51
+ return { template: template, order: order };
71
52
  }
72
- export var l10n = translate.bind(null, (_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator) === null || _a === void 0 ? void 0 : _a.language);
53
+ /**
54
+ * Represents a Translation object that handles string translation based on locale and templates.
55
+ */
56
+ var Translation = /** @class */ (function () {
57
+ function Translation() {
58
+ var _this = this;
59
+ /**
60
+ * Templates object that stores the translation templates for each locale.
61
+ * @type {Proxy}
62
+ */
63
+ this.templates = new Proxy({}, {
64
+ get: function (templates, locale) {
65
+ return new Proxy(templates[locale] || {}, {
66
+ set: function (region, key, value) {
67
+ if (typeof value !== "string") {
68
+ throw new Error("Template must be a string.");
69
+ }
70
+ region[key] = parseTemplate(value);
71
+ return true;
72
+ }
73
+ });
74
+ },
75
+ set: function (templates, locale, regionTemplates) {
76
+ templates[locale] = Object.entries(regionTemplates).reduce(function (region, _a) {
77
+ var key = _a[0], value = _a[1];
78
+ region[key] = parseTemplate(value);
79
+ return region;
80
+ }, {});
81
+ return true;
82
+ }
83
+ });
84
+ /**
85
+ * Translates a string based on the provided locale and strings.
86
+ *
87
+ * @param {string} locale - The locale to use for translation.
88
+ * @param {string | string[]} strings - The string or array of strings to be translated.
89
+ * @param {...any} parts - The dynamic parts to be inserted into the translated string.
90
+ * @returns {Runes} - The translated string with dynamic parts inserted.
91
+ * @throws {Error} - If the length of the template parts does not match the length of the template.
92
+ */
93
+ this.translate = function (locale, strings) {
94
+ var _a, _b, _c;
95
+ var parts = [];
96
+ for (var _i = 2; _i < arguments.length; _i++) {
97
+ parts[_i - 2] = arguments[_i];
98
+ }
99
+ if (typeof strings === "string") {
100
+ strings = strings.split("{}");
101
+ }
102
+ var key = strings.join("{}");
103
+ var translation = (_a = _this.templates) === null || _a === void 0 ? void 0 : _a[locale];
104
+ var _d = (translation === null || translation === void 0 ? void 0 : translation[key]) || {}, template = _d.template, order = _d.order;
105
+ if (!template) {
106
+ if (((_c = (_b = import.meta) === null || _b === void 0 ? void 0 : _b.env) === null || _c === void 0 ? void 0 : _c.MODE) === "development") {
107
+ console.warn("not match translate key, ".concat(key), { translation: translation, locale: locale, strings: strings, parts: parts });
108
+ }
109
+ template = strings.slice();
110
+ order = parts.map(function (_, i) { return i; });
111
+ }
112
+ if (parts.length !== template.length - 1) {
113
+ throw new Error("translate template parts length does not match. locale: ".concat(locale, ", key: ").concat(key));
114
+ }
115
+ var runes = template.reduce(function (runes, template, idx) {
116
+ runes.push(template);
117
+ var orderIdx = order[idx];
118
+ if (orderIdx >= 0) {
119
+ var part = parts[orderIdx];
120
+ if (typeof part === "function") {
121
+ runes.push(part(locale));
122
+ }
123
+ else {
124
+ runes.push(part);
125
+ }
126
+ }
127
+ return runes;
128
+ }, new Runes());
129
+ return runes;
130
+ };
131
+ }
132
+ return Translation;
133
+ }());
134
+ export { Translation };
135
+ var translation = new Translation();
136
+ export default translation;
137
+ export var l10n = translation.translate.bind(null, (_a = globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator) === null || _a === void 0 ? void 0 : _a.language);
73
138
  //# sourceMappingURL=intl.js.map
package/dist/intl.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"intl.js","sourceRoot":"","sources":["../src/intl.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"intl.js","sourceRoot":"","sources":["../src/intl.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;IAA2B,yBAAK;IAAhC;;IAIA,CAAC;IAHA,wBAAQ,GAAR;QACC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACrB,CAAC;IACF,YAAC;AAAD,CAAC,AAJD,CAA2B,KAAK,GAI/B;;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,cAAc;IAC3C,IAAM,KAAK,GAAG,EAAE,CAAC;IACjB,IAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,IAAM,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;IAC7C,KAAK,CAAC,OAAO,CAAC,UAAC,IAAI;QAClB,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBACnB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;YACzB,CAAC;iBAAM,CAAC;gBACP,0CAA0C;gBAC1C,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;YAC5C,CAAC;QACF,CAAC;aAAM,CAAC;YACP,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;IACF,CAAC,CAAC,CAAA;IAEF,OAAO,EAAE,QAAQ,UAAA,EAAE,KAAK,OAAA,EAAE,CAAA;AAC3B,CAAC;AAED;;GAEG;AACH;IAAA;QAAA,iBA6EC;QA5EA;;;WAGG;QACH,cAAS,GAAG,IAAI,KAAK,CAAC,EAAE,EAAE;YACzB,GAAG,YAAC,SAAS,EAAE,MAAM;gBACpB,OAAO,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE;oBACzC,GAAG,YAAC,MAAM,EAAE,GAAG,EAAE,KAAK;wBACrB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;4BAC/B,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAA;wBAC9C,CAAC;wBAED,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;wBAElC,OAAO,IAAI,CAAA;oBACZ,CAAC;iBACD,CAAC,CAAA;YACH,CAAC;YACD,GAAG,YAAC,SAAS,EAAE,MAAM,EAAE,eAAe;gBACrC,SAAS,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,UAAC,MAAM,EAAE,EAAY;wBAAX,GAAG,QAAA,EAAE,KAAK,QAAA;oBAC9E,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAA;oBAElC,OAAO,MAAM,CAAA;gBACd,CAAC,EAAE,EAAE,CAAC,CAAA;gBAEN,OAAO,IAAI,CAAA;YACZ,CAAC;SACD,CAAC,CAAA;QAEF;;;;;;;;WAQG;QACH,cAAS,GAAG,UAAC,MAAM,EAAE,OAAO;;YAAE,eAAQ;iBAAR,UAAQ,EAAR,qBAAQ,EAAR,IAAQ;gBAAR,8BAAQ;;YACrC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;gBACjC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAC9B,CAAC;YAED,IAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAC9B,IAAM,WAAW,GAAG,MAAA,KAAI,CAAC,SAAS,0CAAG,MAAM,CAAC,CAAA;YACxC,IAAA,KAAsB,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAG,GAAG,CAAC,KAAI,EAAE,EAA5C,QAAQ,cAAA,EAAE,KAAK,WAA6B,CAAA;YAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACf,IAAI,CAAA,MAAA,MAAA,MAAM,CAAC,IAAI,0CAAE,GAAG,0CAAE,IAAI,MAAK,aAAa,EAAE,CAAC;oBAC9C,OAAO,CAAC,IAAI,CAAC,mCAA4B,GAAG,CAAE,EAAE,EAAE,WAAW,aAAA,EAAE,MAAM,QAAA,EAAE,OAAO,SAAA,EAAE,KAAK,OAAA,EAAE,CAAC,CAAA;gBACzF,CAAC;gBACD,QAAQ,GAAG,OAAO,CAAC,KAAK,EAAE,CAAA;gBAC1B,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,UAAC,CAAC,EAAE,CAAC,IAAK,OAAA,CAAC,EAAD,CAAC,CAAC,CAAA;YAC/B,CAAC;YAED,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,kEAA2D,MAAM,oBAAU,GAAG,CAAE,CAAC,CAAA;YAClG,CAAC;YAED,IAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,UAAC,KAAK,EAAE,QAAQ,EAAE,GAAG;gBAClD,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBAEpB,IAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;gBAC3B,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;oBACnB,IAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAA;oBAC5B,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;wBAChC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAA;oBACzB,CAAC;yBAAM,CAAC;wBACP,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;oBACjB,CAAC;gBACF,CAAC;gBAED,OAAO,KAAK,CAAA;YACb,CAAC,EAAE,IAAI,KAAK,EAAE,CAAC,CAAA;YAEf,OAAO,KAAK,CAAA;QACb,CAAC,CAAA;IACF,CAAC;IAAD,kBAAC;AAAD,CAAC,AA7ED,IA6EC;;AAED,IAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAA;AAErC,eAAe,WAAW,CAAA;AAE1B,MAAM,CAAC,IAAM,IAAI,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,SAAS,0CAAE,QAAQ,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,22 +1,23 @@
1
1
  {
2
- "name": "intl-template",
3
- "version": "0.0.5",
4
- "description": "l10n tagged template literals",
5
- "type": "module",
6
- "main": "dist/intl.js",
7
- "module": "src/intl.js",
8
- "scripts": {
9
- "build": "tsc",
10
- "test": "node --test"
11
- },
12
- "keywords": [
13
- "i18n",
14
- "l10n",
15
- "tagged template literals"
16
- ],
17
- "author": "p10y",
18
- "license": "ISC",
19
- "devDependencies": {
20
- "typescript": "^5.0.0"
21
- }
2
+ "name": "intl-template",
3
+ "version": "1.0.0-alpha.0",
4
+ "description": "l10n tagged template literals",
5
+ "type": "module",
6
+ "main": "dist/intl.js",
7
+ "module": "src/intl.js",
8
+ "repository": "github:performonkey/intl-template",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "node --test"
12
+ },
13
+ "keywords": [
14
+ "i18n",
15
+ "l10n",
16
+ "tagged template literals"
17
+ ],
18
+ "author": "p10y",
19
+ "license": "ISC",
20
+ "devDependencies": {
21
+ "typescript": "^5.0.0"
22
+ }
22
23
  }
package/src/intl.js CHANGED
@@ -1,74 +1,118 @@
1
- const TRANSLATIONS = {}
2
-
3
- export function setTransition(locale, transition) {
4
- return TRANSLATIONS[locale] = Object.entries(transition).reduce((obj, [key, value]) => {
5
- const order = [];
6
- const template = [];
7
- const parts = value.split(/({\d*})/)
8
- parts.forEach((part, index) => {
1
+ export class Runes extends Array {
2
+ toString() {
3
+ return this.join("")
4
+ }
5
+ }
6
+
7
+ /**
8
+ * Parses a template string and extracts the template parts and order of slots.
9
+ * @param {string} templateString - The template string to parse.
10
+ * @returns {{ template: string[], order: number[] }}
11
+ */
12
+ export function parseTemplate(templateString) {
13
+ const order = [];
14
+ const template = [];
15
+ const parts = templateString.split(/({\d*})/)
16
+ parts.forEach((part) => {
17
+ if (part.match(/^{\d*}$/)) {
9
18
  if (part === '{}') {
10
- if (index === 0 || index === parts.length - 1) {
11
- template.push("")
12
- }
13
19
  order.push(order.length)
14
- return
15
- }
16
- if (part.match(/^{\d+}$/)) {
17
- if (index === 0 || index === parts.length - 1) {
18
- template.push("")
19
- }
20
+ } else {
21
+ // slot with order, e.g. `{2}abc{1}def{0}`
20
22
  order.push(parseInt(part.slice(1, -1), 10))
21
- return
22
23
  }
23
-
24
+ } else {
24
25
  template.push(part)
25
- })
26
-
27
- obj[key] = { template, order }
26
+ }
27
+ })
28
28
 
29
- return obj
30
- }, {})
29
+ return { template, order }
31
30
  }
32
31
 
33
- export function translate(locale, strings, ...parts) {
34
- const key = strings.join("{}")
35
- const translation = TRANSLATIONS?.[locale]
36
- let { template, order } = translation?.[key] || {}
37
- if (!template) {
38
- if (import.meta?.env?.MODE === "development") {
39
- console.warn(`not match translate key, ${key}`, { translation, locale, strings, parts })
32
+ /**
33
+ * Represents a Translation object that handles string translation based on locale and templates.
34
+ */
35
+ export class Translation {
36
+ /**
37
+ * Templates object that stores the translation templates for each locale.
38
+ * @type {Proxy}
39
+ */
40
+ templates = new Proxy({}, {
41
+ get(templates, locale) {
42
+ return new Proxy(templates[locale] || {}, {
43
+ set(region, key, value) {
44
+ if (typeof value !== "string") {
45
+ throw new Error("Template must be a string.")
46
+ }
47
+
48
+ region[key] = parseTemplate(value)
49
+
50
+ return true
51
+ }
52
+ })
53
+ },
54
+ set(templates, locale, regionTemplates) {
55
+ templates[locale] = Object.entries(regionTemplates).reduce((region, [key, value]) => {
56
+ region[key] = parseTemplate(value)
57
+
58
+ return region
59
+ }, {})
60
+
61
+ return true
40
62
  }
41
- template = strings.slice()
42
- order = parts.map((_, i) => i)
43
- }
63
+ })
44
64
 
45
- if (parts.length > 0 && template.length - 1 !== parts.length) {
46
- throw new Error(`translate template parts length not match. locale: ${locale}, key: ${key}`)
47
- }
65
+ /**
66
+ * Translates a string based on the provided locale and strings.
67
+ *
68
+ * @param {string} locale - The locale to use for translation.
69
+ * @param {string | string[]} strings - The string or array of strings to be translated.
70
+ * @param {...any} parts - The dynamic parts to be inserted into the translated string.
71
+ * @returns {Runes} - The translated string with dynamic parts inserted.
72
+ * @throws {Error} - If the length of the template parts does not match the length of the template.
73
+ */
74
+ translate = (locale, strings, ...parts) => {
75
+ if (typeof strings === "string") {
76
+ strings = strings.split("{}")
77
+ }
48
78
 
49
- const ret = template.reduce((ret, template, idx) => {
50
- ret.push(template)
51
- const orderIdx = order[idx]
52
- if (orderIdx >= 0) {
53
- const part = parts[orderIdx]
54
- if (typeof part == "function") {
55
- ret.push(part(locale, translation))
56
- } else {
57
- ret.push(part)
79
+ const key = strings.join("{}")
80
+ const translation = this.templates?.[locale]
81
+ let { template, order } = translation?.[key] || {}
82
+ if (!template) {
83
+ if (import.meta?.env?.MODE === "development") {
84
+ console.warn(`not match translate key, ${key}`, { translation, locale, strings, parts })
58
85
  }
86
+ template = strings.slice()
87
+ order = parts.map((_, i) => i)
88
+ }
89
+
90
+ if (parts.length !== template.length - 1) {
91
+ throw new Error(`translate template parts length does not match. locale: ${locale}, key: ${key}`)
59
92
  }
60
- return ret
61
- }, [])
62
-
63
- const text = ret.join("")
64
- Object.defineProperty(ret, "toString", {
65
- enumerable: false,
66
- writable: false,
67
- configurable: false,
68
- value: () => text
69
- })
70
93
 
71
- return ret
94
+ const runes = template.reduce((runes, template, idx) => {
95
+ runes.push(template)
96
+
97
+ const orderIdx = order[idx]
98
+ if (orderIdx >= 0) {
99
+ const part = parts[orderIdx]
100
+ if (typeof part === "function") {
101
+ runes.push(part(locale))
102
+ } else {
103
+ runes.push(part)
104
+ }
105
+ }
106
+
107
+ return runes
108
+ }, new Runes())
109
+
110
+ return runes
111
+ }
72
112
  }
73
113
 
74
- export const l10n = translate.bind(null, globalThis?.navigator?.language)
114
+ const translation = new Translation()
115
+
116
+ export default translation
117
+
118
+ export const l10n = translation.translate.bind(null, globalThis?.navigator?.language);
package/src/intl.test.js CHANGED
@@ -1,46 +1,110 @@
1
- import assert from 'node:assert';
2
- import test from 'node:test';
3
- import { translate, setTransition } from './intl.js';
1
+ import assert from "node:assert"
2
+ import test from "node:test"
3
+ import { Translation, Runes } from "./intl.js"
4
4
 
5
+ test("template", async (t) => {
6
+ await t.test("template slot parse", () => {
7
+ const translation = new Translation()
8
+ const locale = "zh-CN"
9
+ translation.templates[locale] = {
10
+ "{}matched{}": "{}{}丁戊卯",
11
+ }
5
12
 
6
- test('apply template', (t) => {
7
- setTransition('zh-CN', {
8
- 'abc {} def {}': '甲乙丙 {} {} 丁戊卯',
13
+ Object.assign(translation.templates[locale], {
14
+ "assign{}": "123{}"
15
+ })
16
+
17
+ assert.deepEqual(
18
+ translation.templates[locale],
19
+ {
20
+ "{}matched{}": {
21
+ template: ["", "", "丁戊卯"],
22
+ order: [0, 1]
23
+ },
24
+ "assign{}": {
25
+ template: ["123", ""],
26
+ order: [0]
27
+ }
28
+ },
29
+ )
9
30
  })
10
- const l10n = translate.bind(null, 'zh-CN')
11
- assert.deepEqual(l10n`abc ${123} def ${345}`, ['甲乙丙 ', 123, ' ', 345, ' 丁戊卯'])
12
- });
13
31
 
14
- test('change slot order', (t) => {
15
- setTransition('zh-CN', {
16
- 'abc {} def {}': '甲乙丙 {1} {0} 丁戊卯',
32
+ await t.test("template slot order parse", () => {
33
+ const translation = new Translation()
34
+ const locale = "zh-CN"
35
+ const key = "{}matched{}"
36
+ translation.templates[locale] = {
37
+ [key]: "{1}{0}丁戊卯",
38
+ }
39
+
40
+ assert.deepEqual(
41
+ translation.templates[locale][key],
42
+ {
43
+ template: ["", "", "丁戊卯"],
44
+ order: [1, 0]
45
+ }
46
+ )
17
47
  })
18
- const l10n = translate.bind(null, 'zh-CN')
19
- assert.equal(l10n`abc ${123} def ${345}`.toString(), '甲乙丙 345 123 丁戊卯')
20
- });
48
+ })
21
49
 
22
- test('slot function', (t) => {
23
- setTransition('zh-CN', {
24
- '{} def {}': '{1} {0} 丁戊卯',
50
+ test("translation.translate", async (t) => {
51
+ await t.test("apply template", () => {
52
+ const translation = new Translation()
53
+ translation.templates["zh-CN"] = {
54
+ "abc {} def {}": "甲乙丙 {} {} 丁戊卯",
55
+ }
56
+ const l10n = translation.translate.bind(null, "zh-CN")
57
+ assert.deepEqual(l10n`abc ${123} def ${345}`, ["甲乙丙 ", 123, " ", 345, " 丁戊卯"])
25
58
  })
26
- const l10n = translate.bind(null, 'zh-CN')
27
- assert.equal(l10n`${locale => locale} def ${345}`.toString(), '345 zh-CN 丁戊卯')
28
- });
29
-
30
- test('slot count match', (t) => {
31
- setTransition('zh-CN', {
32
- '{} def {}': '{} 丁戊卯',
33
- '{}matched{}': '{}{}丁戊卯',
59
+
60
+ await t.test("change slot order", () => {
61
+ const translation = new Translation()
62
+ translation.templates["zh-CN"] = {
63
+ "abc {} def {}": "甲乙丙 {1} {0} 丁戊卯",
64
+ }
65
+ const l10n = translation.translate.bind(null, "zh-CN")
66
+ assert.equal(l10n`abc ${123} def ${345}`.toString(), "甲乙丙 345 123 丁戊卯")
34
67
  })
35
- const l10n = translate.bind(null, 'zh-CN')
36
- try {
37
- l10n`${locale => locale} def ${345}`
38
- } catch (err) {
39
- assert.equal(
40
- err.message,
41
- `translate template parts length not match. locale: zh-CN, key: {} def {}`
42
- )
43
- }
44
68
 
45
- assert.equal(l10n`${locale => locale}matched${345}`.toString(), `zh-CN345丁戊卯`)
46
- });
69
+ await t.test("slot function", () => {
70
+ const translation = new Translation()
71
+ translation.templates["zh-CN"] = {
72
+ "{} def {}": "{1} {0} 丁戊卯",
73
+ }
74
+ const l10n = translation.translate.bind(null, "zh-CN")
75
+ assert.equal(l10n`${locale => locale} def ${345}`.toString(), "345 zh-CN 丁戊卯")
76
+ })
77
+
78
+ await t.test("slot count match", () => {
79
+ const translation = new Translation()
80
+ translation.templates["zh-CN"] = {
81
+ "{} def {}": "{} 丁戊卯",
82
+ "{}matched{}": "{}{}丁戊卯",
83
+ }
84
+ const l10n = translation.translate.bind(null, "zh-CN")
85
+ try {
86
+ l10n`${locale => locale} def ${345}`
87
+ } catch (err) {
88
+ assert.equal(
89
+ err.message,
90
+ `translate template parts length does not match. locale: zh-CN, key: {} def {}`
91
+ )
92
+ }
93
+
94
+ assert.equal(l10n`${locale => locale}matched${345}`.toString(), `zh-CN345丁戊卯`)
95
+ })
96
+
97
+ await t.test("call as function", () => {
98
+ const translation = new Translation()
99
+ translation.templates["zh-CN"] = {}
100
+ const l10n = translation.translate.bind(null, "zh-CN")
101
+ assert.equal(l10n("{} def {} {}", 1, "a", "b").toString(), '1 def a b')
102
+ })
103
+ })
104
+
105
+ test("Runes", async (t) => {
106
+ assert.equal(
107
+ new Runes('a', ' b ', 1, 2).toString(),
108
+ 'a b 12'
109
+ )
110
+ })
package/tsconfig.json CHANGED
@@ -15,9 +15,5 @@
15
15
  "strict": true,
16
16
  "skipLibCheck": true
17
17
  },
18
- "exclude": [
19
- "dist",
20
- "*.test.js",
21
- "jest.config.js"
22
- ]
18
+ "exclude": ["dist", "src/*.test.js", "jest.config.js"]
23
19
  }
@@ -1 +0,0 @@
1
- export {};
package/dist/intl.test.js DELETED
@@ -1,44 +0,0 @@
1
- var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
2
- if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
3
- return cooked;
4
- };
5
- import assert from 'node:assert';
6
- import test from 'node:test';
7
- import { translate, setTransition } from './intl.js';
8
- test('apply template', function (t) {
9
- setTransition('zh-CN', {
10
- 'abc {} def {}': '甲乙丙 {} {} 丁戊卯',
11
- });
12
- var l10n = translate.bind(null, 'zh-CN');
13
- assert.deepEqual(l10n(templateObject_1 || (templateObject_1 = __makeTemplateObject(["abc ", " def ", ""], ["abc ", " def ", ""])), 123, 345), ['甲乙丙 ', 123, ' ', 345, ' 丁戊卯']);
14
- });
15
- test('change slot order', function (t) {
16
- setTransition('zh-CN', {
17
- 'abc {} def {}': '甲乙丙 {1} {0} 丁戊卯',
18
- });
19
- var l10n = translate.bind(null, 'zh-CN');
20
- assert.equal(l10n(templateObject_2 || (templateObject_2 = __makeTemplateObject(["abc ", " def ", ""], ["abc ", " def ", ""])), 123, 345).toString(), '甲乙丙 345 123 丁戊卯');
21
- });
22
- test('slot function', function (t) {
23
- setTransition('zh-CN', {
24
- '{} def {}': '{1} {0} 丁戊卯',
25
- });
26
- var l10n = translate.bind(null, 'zh-CN');
27
- assert.equal(l10n(templateObject_3 || (templateObject_3 = __makeTemplateObject(["", " def ", ""], ["", " def ", ""])), function (locale) { return locale; }, 345).toString(), '345 zh-CN 丁戊卯');
28
- });
29
- test('slot count match', function (t) {
30
- setTransition('zh-CN', {
31
- '{} def {}': '{} 丁戊卯',
32
- '{}matched{}': '{}{}丁戊卯',
33
- });
34
- var l10n = translate.bind(null, 'zh-CN');
35
- try {
36
- l10n(templateObject_4 || (templateObject_4 = __makeTemplateObject(["", " def ", ""], ["", " def ", ""])), function (locale) { return locale; }, 345);
37
- }
38
- catch (err) {
39
- assert.equal(err.message, "translate template parts length not match. locale: zh-CN, key: {} def {}");
40
- }
41
- assert.equal(l10n(templateObject_5 || (templateObject_5 = __makeTemplateObject(["", "matched", ""], ["", "matched", ""])), function (locale) { return locale; }, 345).toString(), "zh-CN345\u4E01\u620A\u536F");
42
- });
43
- var templateObject_1, templateObject_2, templateObject_3, templateObject_4, templateObject_5;
44
- //# sourceMappingURL=intl.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"intl.test.js","sourceRoot":"","sources":["../src/intl.test.js"],"names":[],"mappings":";;;;AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGrD,IAAI,CAAC,gBAAgB,EAAE,UAAC,CAAC;IACxB,aAAa,CAAC,OAAO,EAAE;QACtB,eAAe,EAAE,eAAe;KAChC,CAAC,CAAA;IACF,IAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1C,MAAM,CAAC,SAAS,CAAC,IAAI,sFAAA,MAAO,EAAG,OAAQ,EAAG,EAAE,KAAhB,GAAG,EAAQ,GAAG,GAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;AAC/E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mBAAmB,EAAE,UAAC,CAAC;IAC3B,aAAa,CAAC,OAAO,EAAE;QACtB,eAAe,EAAE,iBAAiB;KAClC,CAAC,CAAA;IACF,IAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,sFAAA,MAAO,EAAG,OAAQ,EAAG,EAAE,KAAhB,GAAG,EAAQ,GAAG,EAAG,QAAQ,EAAE,EAAE,iBAAiB,CAAC,CAAA;AACxE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,eAAe,EAAE,UAAC,CAAC;IACvB,aAAa,CAAC,OAAO,EAAE;QACtB,WAAW,EAAE,aAAa;KAC1B,CAAC,CAAA;IACF,IAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,kFAAA,EAAG,EAAgB,OAAQ,EAAG,EAAE,KAA7B,UAAA,MAAM,IAAI,OAAA,MAAM,EAAN,CAAM,EAAQ,GAAG,EAAG,QAAQ,EAAE,EAAE,eAAe,CAAC,CAAA;AAC/E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kBAAkB,EAAE,UAAC,CAAC;IAC1B,aAAa,CAAC,OAAO,EAAE;QACtB,WAAW,EAAE,QAAQ;QACrB,aAAa,EAAE,SAAS;KACxB,CAAC,CAAA;IACF,IAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1C,IAAI,CAAC;QACJ,IAAI,kFAAA,EAAG,EAAgB,OAAQ,EAAG,EAAE,KAA7B,UAAA,MAAM,IAAI,OAAA,MAAM,EAAN,CAAM,EAAQ,GAAG,EAAE;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CACX,GAAG,CAAC,OAAO,EACX,0EAA0E,CAC1E,CAAA;IACF,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,IAAI,oFAAA,EAAG,EAAgB,SAAU,EAAG,EAAE,KAA/B,UAAA,MAAM,IAAI,OAAA,MAAM,EAAN,CAAM,EAAU,GAAG,EAAG,QAAQ,EAAE,EAAE,4BAAa,CAAC,CAAA;AAC/E,CAAC,CAAC,CAAC"}