@zerolith/i18n 0.1.0-alpha.5 → 0.1.0-alpha.6
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/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -7
- package/dist/cli.js.map +1 -1
- package/dist/compile.d.ts +6 -1
- package/dist/compile.d.ts.map +1 -1
- package/dist/compile.js +146 -68
- package/dist/compile.js.map +1 -1
- package/dist/index.d.ts +14 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +70 -7
- package/src/compile.ts +164 -69
- package/src/index.ts +43 -16
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAsGA,2EAA2E;AAC3E,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8EnE"}
|
package/dist/cli.js
CHANGED
|
@@ -9,6 +9,36 @@ import { basename, join } from "node:path";
|
|
|
9
9
|
import process from "node:process";
|
|
10
10
|
import { compileIndex, compileMessages, messageNames } from "./compile.js";
|
|
11
11
|
const USAGE = "usage: zerolith-i18n <folder> [--out <folder>] [--js] [--strict]\n";
|
|
12
|
+
/** The locale a catalog's file name gives, refusing a name that is not a BCP 47 language tag, such as `en_US.json`. */
|
|
13
|
+
function localeOf(file) {
|
|
14
|
+
const locale = basename(file, ".json");
|
|
15
|
+
try {
|
|
16
|
+
Intl.getCanonicalLocales(locale);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error(`${locale} is not a language tag; name the file as en.json or pt-BR.json`);
|
|
20
|
+
}
|
|
21
|
+
return locale;
|
|
22
|
+
}
|
|
23
|
+
/** A catalog's messages, refusing a file that is not a JSON object of strings. */
|
|
24
|
+
function catalogOf(text) {
|
|
25
|
+
const parsed = JSON.parse(text);
|
|
26
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
27
|
+
throw new Error("a catalog is a JSON object of messages");
|
|
28
|
+
}
|
|
29
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
30
|
+
if (typeof value !== "string") {
|
|
31
|
+
throw new Error(`the message "${key}" is not a string`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
/** Reports why a catalog cannot be compiled, naming its file, and gives the exit code; nothing has been written. */
|
|
37
|
+
function failure(file, error) {
|
|
38
|
+
const message = error instanceof Error ? error.message.replace(/^zerolith\/i18n: /, "") : String(error);
|
|
39
|
+
process.stderr.write(`zerolith-i18n: ${file}: ${message}\n`);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
12
42
|
function parse(argv) {
|
|
13
43
|
const parsed = { folder: undefined, out: undefined, javascript: false, strict: false };
|
|
14
44
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -59,10 +89,21 @@ export async function main(argv) {
|
|
|
59
89
|
return 1;
|
|
60
90
|
}
|
|
61
91
|
const out = parsed.out ?? parsed.folder;
|
|
62
|
-
await mkdir(out, { recursive: true });
|
|
63
92
|
const language = parsed.javascript ? "js" : "ts";
|
|
64
93
|
const folder = parsed.folder;
|
|
65
|
-
const
|
|
94
|
+
const read = await Promise.all(files.map(async (file) => {
|
|
95
|
+
try {
|
|
96
|
+
return { file, locale: localeOf(file), catalog: catalogOf(await readFile(join(folder, file), "utf8")) };
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return { file, error };
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
const unreadable = read.find((entry) => "error" in entry);
|
|
103
|
+
if (unreadable !== undefined) {
|
|
104
|
+
return failure(unreadable.file, unreadable.error);
|
|
105
|
+
}
|
|
106
|
+
const catalogs = new Map(read.map((entry) => [entry.locale ?? "", entry.catalog ?? {}]));
|
|
66
107
|
const all = new Set();
|
|
67
108
|
for (const catalog of catalogs.values()) {
|
|
68
109
|
for (const name of messageNames(catalog)) {
|
|
@@ -70,7 +111,7 @@ export async function main(argv) {
|
|
|
70
111
|
}
|
|
71
112
|
}
|
|
72
113
|
let missing = 0;
|
|
73
|
-
const
|
|
114
|
+
const modules = new Map();
|
|
74
115
|
for (const [locale, catalog] of catalogs) {
|
|
75
116
|
const present = new Set(messageNames(catalog));
|
|
76
117
|
for (const name of all) {
|
|
@@ -79,11 +120,16 @@ export async function main(argv) {
|
|
|
79
120
|
process.stderr.write(`zerolith-i18n: ${locale} lacks ${name}\n`);
|
|
80
121
|
}
|
|
81
122
|
}
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
try {
|
|
124
|
+
modules.set(`${locale}.${language}`, compileMessages(locale, catalog, { language, source: `${locale}.json` }).code);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return failure(`${locale}.json`, error);
|
|
128
|
+
}
|
|
84
129
|
}
|
|
85
|
-
|
|
86
|
-
await
|
|
130
|
+
modules.set(`index.${language}`, compileIndex([...catalogs.keys()], { language }));
|
|
131
|
+
await mkdir(out, { recursive: true });
|
|
132
|
+
await Promise.all([...modules].map(([name, code]) => writeFile(join(out, name), code)));
|
|
87
133
|
process.stdout.write(`zerolith-i18n: ${catalogs.size} locale(s), ${all.size} message(s), written to ${out}\n`);
|
|
88
134
|
return missing > 0 && parsed.strict ? 1 : 0;
|
|
89
135
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAgB,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEzF,MAAM,KAAK,GAAG,oEAAoE,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;GAIG;AACH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAgB,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEzF,MAAM,KAAK,GAAG,oEAAoE,CAAC;AAEnF,uHAAuH;AACvH,SAAS,QAAQ,CAAC,IAAY;IAE1B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACvC,IACA,CAAC;QACG,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IACD,MACA,CAAC;QACG,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,gEAAgE,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,kFAAkF;AAClF,SAAS,SAAS,CAAC,IAAY;IAE3B,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAC1E,CAAC;QACG,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EACjD,CAAC;QACG,IAAI,OAAO,KAAK,KAAK,QAAQ,EAC7B,CAAC;YACG,MAAM,IAAI,KAAK,CAAC,gBAAgB,GAAG,mBAAmB,CAAC,CAAC;QAC5D,CAAC;IACL,CAAC;IACD,OAAO,MAAiB,CAAC;AAC7B,CAAC;AAED,oHAAoH;AACpH,SAAS,OAAO,CAAC,IAAY,EAAE,KAAc;IAEzC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC;IAC7D,OAAO,CAAC,CAAC;AACb,CAAC;AAUD,SAAS,KAAK,CAAC,IAAuB;IAElC,MAAM,MAAM,GAAc,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAClG,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,EAChD,CAAC;QACG,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,QAAQ,KAAK,OAAO,EACxB,CAAC;YACG,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,KAAK,KAAK,SAAS,EACvB,CAAC;gBACG,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;YAC3C,CAAC;YACD,MAAM,CAAC,GAAG,GAAG,KAAK,CAAC;YACnB,KAAK,EAAE,CAAC;QACZ,CAAC;aACI,IAAI,QAAQ,KAAK,MAAM,EAC5B,CAAC;YACG,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC;QAC7B,CAAC;aACI,IAAI,QAAQ,KAAK,UAAU,EAChC,CAAC;YACG,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;QACzB,CAAC;aACI,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAClC,CAAC;YACG,MAAM,IAAI,KAAK,CAAC,kBAAkB,QAAQ,EAAE,CAAC,CAAC;QAClD,CAAC;aACI,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EACpC,CAAC;YACG,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAC;QAC7B,CAAC;aAED,CAAC;YACG,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,EAAE,CAAC,CAAC;QACvD,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,IAAuB;IAE9C,IAAI,MAAiB,CAAC;IACtB,IACA,CAAC;QACG,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,KAAK,EACZ,CAAC;QACG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;QAC3G,OAAO,CAAC,CAAC;IACb,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAC/B,CAAC;QACG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,CAAC,CAAC;IACb,CAAC;IACD,MAAM,KAAK,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACjG,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EACtB,CAAC;QACG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;QAC/E,OAAO,CAAC,CAAC;IACb,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAEpD,IACA,CAAC;YACG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC;QAC5G,CAAC;QACD,OAAO,KAAK,EACZ,CAAC;YACG,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QAC3B,CAAC;IACL,CAAC,CAAC,CAAC,CAAC;IACJ,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,CAAC,CAAC;IAC1D,IAAI,UAAU,KAAK,SAAS,EAC5B,CAAC;QACG,OAAO,OAAO,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IACtD,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAkB,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1G,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,EACvC,CAAC;QACG,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,OAAO,CAAC,EACxC,CAAC;YACG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;IACL,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,QAAQ,EACxC,CAAC;QACG,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;QAC/C,KAAK,MAAM,IAAI,IAAI,GAAG,EACtB,CAAC;YACG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EACtB,CAAC;gBACG,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,MAAM,UAAU,IAAI,IAAI,CAAC,CAAC;YACrE,CAAC;QACL,CAAC;QACD,IACA,CAAC;YACG,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,QAAQ,EAAE,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACxH,CAAC;QACD,OAAO,KAAK,EACZ,CAAC;YACG,OAAO,OAAO,CAAC,GAAG,MAAM,OAAO,EAAE,KAAK,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,SAAS,QAAQ,EAAE,EAAE,YAAY,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACnF,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACxF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,kBAAkB,QAAQ,CAAC,IAAI,eAAe,GAAG,CAAC,IAAI,2BAA2B,GAAG,IAAI,CAAC,CAAC;IAC/G,OAAO,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,IAAI,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EACxF,CAAC;IACG,OAAO,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC"}
|
package/dist/compile.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface CompileOptions {
|
|
|
20
20
|
}
|
|
21
21
|
/** The result of compiling one catalog. */
|
|
22
22
|
export interface CompiledCatalog {
|
|
23
|
+
/** The generated module's source, TypeScript or JavaScript as the `language` option chose; the `zerolith-i18n` CLI writes it to `<locale>.ts` or `<locale>.js`. */
|
|
23
24
|
code: string;
|
|
24
25
|
/** The message keys as function names, in source order. */
|
|
25
26
|
names: string[];
|
|
@@ -28,7 +29,11 @@ export interface CompiledCatalog {
|
|
|
28
29
|
* Compiles one locale's catalog into a module: `locale`, one exported
|
|
29
30
|
* function per message named from its key, and the `Intl` helpers they
|
|
30
31
|
* share. Each function takes an `args` object typed from the message's
|
|
31
|
-
* placeholders, or nothing when the message has none.
|
|
32
|
+
* placeholders, or nothing when the message has none. A formatter whose
|
|
33
|
+
* options are all literals is a module-level binding created on first use
|
|
34
|
+
* and shared by every message that formats the same way; options that read
|
|
35
|
+
* an argument find their formatter in a cache keyed by the options. Only
|
|
36
|
+
* the helpers the messages use are written.
|
|
32
37
|
*/
|
|
33
38
|
export declare function compileMessages(locale: string, catalog: Catalog, options?: CompileOptions): CompiledCatalog;
|
|
34
39
|
/** The keys of a catalog as function names, for comparing locales. */
|
package/dist/compile.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wDAAwD;AACxD,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAE3B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAE5B,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../src/compile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wDAAwD;AACxD,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAEvD,qCAAqC;AACrC,MAAM,WAAW,cAAc;IAE3B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAE5B,mKAAmK;IACnK,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;AAy4BD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,cAAmB,GAAG,eAAe,CA+D/G;AAED,sEAAsE;AACtE,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAGvD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAsB7F"}
|
package/dist/compile.js
CHANGED
|
@@ -279,6 +279,14 @@ const NUMBER_OPTIONS = {
|
|
|
279
279
|
currencySign: "currencySign",
|
|
280
280
|
numberingSystem: "numberingSystem"
|
|
281
281
|
};
|
|
282
|
+
/** Names a module cannot bind; a message named so is exported under its name from a binding of its own. */
|
|
283
|
+
const RESERVED = new Set(["arguments", "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", "eval", "export", "extends", "false", "finally", "for", "function", "if", "implements", "import", "in", "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield"]);
|
|
284
|
+
/** An argument or local name JavaScript can write as it is; MessageFormat also allows `-` and `.`, as in `$first-name`. */
|
|
285
|
+
const PLAIN_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
286
|
+
/** A property name as code: as it is when plain, quoted otherwise. */
|
|
287
|
+
function property(name) {
|
|
288
|
+
return PLAIN_NAME.test(name) ? name : JSON.stringify(name);
|
|
289
|
+
}
|
|
282
290
|
function identifier(key) {
|
|
283
291
|
const words = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((word) => word !== "");
|
|
284
292
|
const name = words.map((word, index) => (index === 0 ? word.charAt(0).toLowerCase() + word.slice(1) : word.charAt(0).toUpperCase() + word.slice(1))).join("");
|
|
@@ -290,14 +298,30 @@ class Emitter {
|
|
|
290
298
|
prelude = [];
|
|
291
299
|
key;
|
|
292
300
|
message;
|
|
293
|
-
|
|
301
|
+
formatters;
|
|
302
|
+
used;
|
|
303
|
+
reads = 0;
|
|
304
|
+
constructor(key, message, formatters, used) {
|
|
294
305
|
this.key = key;
|
|
295
306
|
this.message = message;
|
|
307
|
+
this.formatters = formatters;
|
|
308
|
+
this.used = used;
|
|
309
|
+
}
|
|
310
|
+
/** The module-level binding of a formatter whose options are known at compile time, created on first use and shared by every message that creates it the same way. */
|
|
311
|
+
shared(create) {
|
|
312
|
+
this.used.add("once");
|
|
313
|
+
let binding = this.formatters.get(create);
|
|
314
|
+
if (binding === undefined) {
|
|
315
|
+
binding = `$f${this.formatters.size}`;
|
|
316
|
+
this.formatters.set(create, binding);
|
|
317
|
+
}
|
|
318
|
+
return binding;
|
|
296
319
|
}
|
|
297
320
|
fail(text) {
|
|
298
321
|
throw new Error(`zerolith/i18n: message "${this.key}": ${text}`);
|
|
299
322
|
}
|
|
300
323
|
argument(name, type) {
|
|
324
|
+
this.reads++;
|
|
301
325
|
const local = this.locals.get(name);
|
|
302
326
|
if (local !== undefined) {
|
|
303
327
|
return local;
|
|
@@ -309,7 +333,7 @@ class Emitter {
|
|
|
309
333
|
else if (existing !== type && type !== "string") {
|
|
310
334
|
this.fail(`$${name} is used both as ${existing} and as ${type}`);
|
|
311
335
|
}
|
|
312
|
-
return `args.${name}`;
|
|
336
|
+
return PLAIN_NAME.test(name) ? `args.${name}` : `args[${JSON.stringify(name)}]`;
|
|
313
337
|
}
|
|
314
338
|
optionValue(value) {
|
|
315
339
|
if (value === undefined) {
|
|
@@ -347,7 +371,7 @@ class Emitter {
|
|
|
347
371
|
entries.push(`minimumFractionDigits: ${value.value}`, `maximumFractionDigits: ${value.value}`);
|
|
348
372
|
}
|
|
349
373
|
}
|
|
350
|
-
return `{ ${entries.join(", ")} }`;
|
|
374
|
+
return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
|
|
351
375
|
}
|
|
352
376
|
dateOptions(expression) {
|
|
353
377
|
const entries = [];
|
|
@@ -376,7 +400,7 @@ class Emitter {
|
|
|
376
400
|
if (style !== undefined) {
|
|
377
401
|
entries.push(`timeZoneName: ${JSON.stringify(style)}`);
|
|
378
402
|
}
|
|
379
|
-
return `{ ${entries.join(", ")} }`;
|
|
403
|
+
return entries.length === 0 ? "{}" : `{ ${entries.join(", ")} }`;
|
|
380
404
|
}
|
|
381
405
|
/** The JavaScript for an expression's numeric value, before formatting. */
|
|
382
406
|
numeric(expression) {
|
|
@@ -401,11 +425,11 @@ class Emitter {
|
|
|
401
425
|
}
|
|
402
426
|
return value;
|
|
403
427
|
}
|
|
404
|
-
/** Resolves a plain `{$x}` to its declaration when one exists. */
|
|
405
|
-
resolve(expression) {
|
|
428
|
+
/** Resolves a plain `{$x}` to its `.input` declaration when one exists, or with `locals` to its `.local` one too, so a selector on a local selects as its expression does. */
|
|
429
|
+
resolve(expression, locals = false) {
|
|
406
430
|
if (expression.fn === undefined && expression.operand?.kind === "variable") {
|
|
407
431
|
const declared = this.message.declarations.get(expression.operand.name);
|
|
408
|
-
if (declared !== undefined && !declared.local) {
|
|
432
|
+
if (declared !== undefined && (locals || !declared.local)) {
|
|
409
433
|
return declared.expression;
|
|
410
434
|
}
|
|
411
435
|
}
|
|
@@ -423,7 +447,14 @@ class Emitter {
|
|
|
423
447
|
return operand.kind === "literal" ? JSON.stringify(operand.value) : `String(${this.argument(operand.name, "string")})`;
|
|
424
448
|
}
|
|
425
449
|
if (NUMBER_FUNCTIONS.has(fn)) {
|
|
426
|
-
|
|
450
|
+
const value = this.numeric(expression);
|
|
451
|
+
const reads = this.reads;
|
|
452
|
+
const options = this.numberOptions(expression);
|
|
453
|
+
if (this.reads === reads) {
|
|
454
|
+
return `${this.shared(`new Intl.NumberFormat(locale, ${options})`)}().format(${value})`;
|
|
455
|
+
}
|
|
456
|
+
this.used.add("number");
|
|
457
|
+
return `$number(${value}, ${options})`;
|
|
427
458
|
}
|
|
428
459
|
if (DATE_FUNCTIONS.has(fn)) {
|
|
429
460
|
const operand = expression.operand;
|
|
@@ -431,13 +462,13 @@ class Emitter {
|
|
|
431
462
|
this.fail(`:${fn} needs an operand`);
|
|
432
463
|
}
|
|
433
464
|
const value = operand.kind === "literal" ? `new Date(${JSON.stringify(operand.value)})` : this.argument(operand.name, "date");
|
|
434
|
-
return `
|
|
465
|
+
return `${this.shared(`new Intl.DateTimeFormat(locale, ${this.dateOptions(expression)})`)}().format(${value})`;
|
|
435
466
|
}
|
|
436
467
|
this.fail(`unknown function :${fn}`);
|
|
437
468
|
}
|
|
438
469
|
/** The JavaScript for a selector: its comparable value and the categories it matches. */
|
|
439
470
|
selector(raw) {
|
|
440
|
-
const expression = this.resolve(raw);
|
|
471
|
+
const expression = this.resolve(raw, true);
|
|
441
472
|
const fn = expression.fn ?? "string";
|
|
442
473
|
if (NUMBER_FUNCTIONS.has(fn)) {
|
|
443
474
|
const select = expression.options["select"];
|
|
@@ -446,7 +477,14 @@ class Emitter {
|
|
|
446
477
|
if (mode === "exact") {
|
|
447
478
|
return `{ value: String(${value}), categories: [] }`;
|
|
448
479
|
}
|
|
449
|
-
|
|
480
|
+
const type = JSON.stringify(mode === "ordinal" ? "ordinal" : "cardinal");
|
|
481
|
+
const reads = this.reads;
|
|
482
|
+
const options = this.numberOptions(expression);
|
|
483
|
+
if (this.reads === reads) {
|
|
484
|
+
return `{ value: String(${value}), categories: [${this.shared(`new Intl.PluralRules(locale, { ...new Intl.NumberFormat(locale, ${options}).resolvedOptions(), type: ${type} })`)}().select(${value})] }`;
|
|
485
|
+
}
|
|
486
|
+
this.used.add("plural");
|
|
487
|
+
return `{ value: String(${value}), categories: [$plural(${value}, ${type}, ${options})] }`;
|
|
450
488
|
}
|
|
451
489
|
if (fn === "string") {
|
|
452
490
|
const operand = expression.operand;
|
|
@@ -468,8 +506,9 @@ class Emitter {
|
|
|
468
506
|
for (const [name, declaration] of this.message.declarations) {
|
|
469
507
|
if (declaration.local) {
|
|
470
508
|
const value = this.format(declaration.expression);
|
|
471
|
-
|
|
472
|
-
this.
|
|
509
|
+
const binding = PLAIN_NAME.test(name) ? `local_${name}` : `local_${this.locals.size}`;
|
|
510
|
+
this.locals.set(name, binding);
|
|
511
|
+
this.prelude.push(`const ${binding} = ${value};`);
|
|
473
512
|
}
|
|
474
513
|
}
|
|
475
514
|
if (this.message.pattern !== undefined) {
|
|
@@ -477,34 +516,72 @@ class Emitter {
|
|
|
477
516
|
}
|
|
478
517
|
const selectors = this.message.selectors.map((selector) => this.selector(selector));
|
|
479
518
|
const variants = this.message.variants.map((variant) => `{ keys: ${JSON.stringify(variant.keys)}, pattern: () => ${this.pattern(variant.pattern)} }`);
|
|
480
|
-
|
|
519
|
+
this.used.add("select");
|
|
520
|
+
return `$select([${selectors.join(", ")}], [${variants.join(", ")}])`;
|
|
481
521
|
}
|
|
482
522
|
}
|
|
483
|
-
|
|
484
|
-
|
|
523
|
+
/** The helpers in JavaScript and in TypeScript, in the order a module writes them. */
|
|
524
|
+
const HELPERS = {
|
|
525
|
+
cache: {
|
|
526
|
+
js: `const $formats = new Map();
|
|
527
|
+
function $cached(kind, options, create)
|
|
485
528
|
{
|
|
486
529
|
const key = kind + JSON.stringify(options);
|
|
487
|
-
let instance = formats.get(key);
|
|
530
|
+
let instance = $formats.get(key);
|
|
488
531
|
if (instance === undefined)
|
|
489
532
|
{
|
|
490
533
|
instance = create();
|
|
491
|
-
formats.set(key, instance);
|
|
534
|
+
$formats.set(key, instance);
|
|
492
535
|
}
|
|
493
536
|
return instance;
|
|
494
|
-
}
|
|
495
|
-
|
|
537
|
+
}`,
|
|
538
|
+
ts: `const $formats: Map<string, unknown> = new Map();
|
|
539
|
+
function $cached<T>(kind: string, options: object, create: () => T): T
|
|
496
540
|
{
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
541
|
+
const key = kind + JSON.stringify(options);
|
|
542
|
+
let instance = $formats.get(key) as T | undefined;
|
|
543
|
+
if (instance === undefined)
|
|
544
|
+
{
|
|
545
|
+
instance = create();
|
|
546
|
+
$formats.set(key, instance);
|
|
547
|
+
}
|
|
548
|
+
return instance;
|
|
549
|
+
}`
|
|
550
|
+
},
|
|
551
|
+
once: {
|
|
552
|
+
js: `function $once(create)
|
|
500
553
|
{
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
554
|
+
let instance;
|
|
555
|
+
return () => (instance ??= create());
|
|
556
|
+
}`,
|
|
557
|
+
ts: `function $once<T>(create: () => T): () => T
|
|
504
558
|
{
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
559
|
+
let instance: T | undefined;
|
|
560
|
+
return () => (instance ??= create());
|
|
561
|
+
}`
|
|
562
|
+
},
|
|
563
|
+
number: {
|
|
564
|
+
js: `function $number(value, options)
|
|
565
|
+
{
|
|
566
|
+
return $cached("n", options, () => new Intl.NumberFormat(locale, options)).format(value);
|
|
567
|
+
}`,
|
|
568
|
+
ts: `function $number(value: number, options: Intl.NumberFormatOptions): string
|
|
569
|
+
{
|
|
570
|
+
return $cached("n", options, () => new Intl.NumberFormat(locale, options)).format(value);
|
|
571
|
+
}`
|
|
572
|
+
},
|
|
573
|
+
plural: {
|
|
574
|
+
js: `function $plural(value, type, options)
|
|
575
|
+
{
|
|
576
|
+
return $cached("p" + type, options, () => new Intl.PluralRules(locale, { ...$cached("n", options, () => new Intl.NumberFormat(locale, options)).resolvedOptions(), type })).select(value);
|
|
577
|
+
}`,
|
|
578
|
+
ts: `function $plural(value: number, type: "cardinal" | "ordinal", options: Intl.NumberFormatOptions): string
|
|
579
|
+
{
|
|
580
|
+
return $cached("p" + type, options, () => new Intl.PluralRules(locale, { ...$cached("n", options, () => new Intl.NumberFormat(locale, options)).resolvedOptions(), type })).select(value);
|
|
581
|
+
}`
|
|
582
|
+
},
|
|
583
|
+
select: {
|
|
584
|
+
js: `function $select(selectors, variants)
|
|
508
585
|
{
|
|
509
586
|
let best;
|
|
510
587
|
let bestRank;
|
|
@@ -537,7 +614,7 @@ function select(selectors, variants)
|
|
|
537
614
|
{
|
|
538
615
|
continue;
|
|
539
616
|
}
|
|
540
|
-
if (matches && better(rank, bestRank))
|
|
617
|
+
if (matches && $better(rank, bestRank))
|
|
541
618
|
{
|
|
542
619
|
best = variant;
|
|
543
620
|
bestRank = rank;
|
|
@@ -545,7 +622,7 @@ function select(selectors, variants)
|
|
|
545
622
|
}
|
|
546
623
|
return best === undefined ? "" : best.pattern();
|
|
547
624
|
}
|
|
548
|
-
function better(rank, current)
|
|
625
|
+
function $better(rank, current)
|
|
549
626
|
{
|
|
550
627
|
if (current === undefined)
|
|
551
628
|
{
|
|
@@ -560,8 +637,8 @@ function better(rank, current)
|
|
|
560
637
|
}
|
|
561
638
|
}
|
|
562
639
|
return false;
|
|
563
|
-
}
|
|
564
|
-
|
|
640
|
+
}`,
|
|
641
|
+
ts: `interface Selector
|
|
565
642
|
{
|
|
566
643
|
value: string;
|
|
567
644
|
categories: string[];
|
|
@@ -571,31 +648,7 @@ interface Variant
|
|
|
571
648
|
keys: string[];
|
|
572
649
|
pattern: () => string;
|
|
573
650
|
}
|
|
574
|
-
|
|
575
|
-
function cached<T>(kind: string, options: object, create: () => T): T
|
|
576
|
-
{
|
|
577
|
-
const key = kind + JSON.stringify(options);
|
|
578
|
-
let instance = formats.get(key) as T | undefined;
|
|
579
|
-
if (instance === undefined)
|
|
580
|
-
{
|
|
581
|
-
instance = create();
|
|
582
|
-
formats.set(key, instance);
|
|
583
|
-
}
|
|
584
|
-
return instance;
|
|
585
|
-
}
|
|
586
|
-
function number(value: number, options: Intl.NumberFormatOptions): string
|
|
587
|
-
{
|
|
588
|
-
return cached("n", options, () => new Intl.NumberFormat(locale, options)).format(value);
|
|
589
|
-
}
|
|
590
|
-
function date(value: Date | number, options: Intl.DateTimeFormatOptions): string
|
|
591
|
-
{
|
|
592
|
-
return cached("d", options, () => new Intl.DateTimeFormat(locale, options)).format(value);
|
|
593
|
-
}
|
|
594
|
-
function plural(value: number, type: "cardinal" | "ordinal"): string
|
|
595
|
-
{
|
|
596
|
-
return cached("p", { type }, () => new Intl.PluralRules(locale, { type })).select(value);
|
|
597
|
-
}
|
|
598
|
-
function better(rank: number[], current: number[] | undefined): boolean
|
|
651
|
+
function $better(rank: number[], current: number[] | undefined): boolean
|
|
599
652
|
{
|
|
600
653
|
if (current === undefined)
|
|
601
654
|
{
|
|
@@ -611,7 +664,7 @@ function better(rank: number[], current: number[] | undefined): boolean
|
|
|
611
664
|
}
|
|
612
665
|
return false;
|
|
613
666
|
}
|
|
614
|
-
function select(selectors: Selector[], variants: Variant[]): string
|
|
667
|
+
function $select(selectors: Selector[], variants: Variant[]): string
|
|
615
668
|
{
|
|
616
669
|
let best: Variant | undefined;
|
|
617
670
|
let bestRank: number[] | undefined;
|
|
@@ -640,14 +693,24 @@ function select(selectors: Selector[], variants: Variant[]): string
|
|
|
640
693
|
break;
|
|
641
694
|
}
|
|
642
695
|
}
|
|
643
|
-
if (matches && better(rank, bestRank))
|
|
696
|
+
if (matches && $better(rank, bestRank))
|
|
644
697
|
{
|
|
645
698
|
best = variant;
|
|
646
699
|
bestRank = rank;
|
|
647
700
|
}
|
|
648
701
|
}
|
|
649
702
|
return best === undefined ? "" : best.pattern();
|
|
650
|
-
}
|
|
703
|
+
}`
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
/** The helpers a module's messages use, with the cache that `$number` and `$plural` share. */
|
|
707
|
+
function helpersFor(used, javascript) {
|
|
708
|
+
const needed = new Set(used);
|
|
709
|
+
if (needed.has("number") || needed.has("plural")) {
|
|
710
|
+
needed.add("cache");
|
|
711
|
+
}
|
|
712
|
+
return Object.keys(HELPERS).filter((name) => needed.has(name)).map((name) => HELPERS[name][javascript ? "js" : "ts"]).join("\n");
|
|
713
|
+
}
|
|
651
714
|
function typeOf(type) {
|
|
652
715
|
return type === "number" ? "number" : type === "date" ? "Date | number" : "string | number";
|
|
653
716
|
}
|
|
@@ -655,7 +718,11 @@ function typeOf(type) {
|
|
|
655
718
|
* Compiles one locale's catalog into a module: `locale`, one exported
|
|
656
719
|
* function per message named from its key, and the `Intl` helpers they
|
|
657
720
|
* share. Each function takes an `args` object typed from the message's
|
|
658
|
-
* placeholders, or nothing when the message has none.
|
|
721
|
+
* placeholders, or nothing when the message has none. A formatter whose
|
|
722
|
+
* options are all literals is a module-level binding created on first use
|
|
723
|
+
* and shared by every message that formats the same way; options that read
|
|
724
|
+
* an argument find their formatter in a cache keyed by the options. Only
|
|
725
|
+
* the helpers the messages use are written.
|
|
659
726
|
*/
|
|
660
727
|
export function compileMessages(locale, catalog, options = {}) {
|
|
661
728
|
const javascript = options.language === "js";
|
|
@@ -666,8 +733,9 @@ export function compileMessages(locale, catalog, options = {}) {
|
|
|
666
733
|
}
|
|
667
734
|
out.push(`export const locale${javascript ? "" : ": string"} = ${JSON.stringify(locale)};`);
|
|
668
735
|
out.push("");
|
|
669
|
-
|
|
670
|
-
|
|
736
|
+
const head = out.length;
|
|
737
|
+
const formatters = new Map();
|
|
738
|
+
const used = new Set();
|
|
671
739
|
const names = [];
|
|
672
740
|
const seen = new Set();
|
|
673
741
|
for (const [key, source] of Object.entries(catalog)) {
|
|
@@ -675,21 +743,25 @@ export function compileMessages(locale, catalog, options = {}) {
|
|
|
675
743
|
if (seen.has(name)) {
|
|
676
744
|
throw new Error(`zerolith/i18n: keys "${key}" and another one both become ${name}`);
|
|
677
745
|
}
|
|
746
|
+
if (name === "locale") {
|
|
747
|
+
throw new Error(`zerolith/i18n: key "${key}" becomes locale, which every compiled module exports; rename the key`);
|
|
748
|
+
}
|
|
749
|
+
const binding = RESERVED.has(name) ? `message_${name}` : name;
|
|
678
750
|
seen.add(name);
|
|
679
751
|
names.push(name);
|
|
680
752
|
const message = new Parser(source, key).parse();
|
|
681
|
-
const emitter = new Emitter(key, message);
|
|
753
|
+
const emitter = new Emitter(key, message, formatters, used);
|
|
682
754
|
const body = emitter.body();
|
|
683
755
|
const args = [...emitter.arguments.entries()].toSorted(([a], [b]) => a.localeCompare(b));
|
|
684
756
|
const doc = source.replace(/\*\//g, "* /").split(/\r?\n/).map((line) => ` * ${line}`.trimEnd()).join("\n");
|
|
685
|
-
const argumentType = `{ ${args.map(([argument, type]) => `${argument}: ${typeOf(type)}`).join("; ")} }`;
|
|
757
|
+
const argumentType = `{ ${args.map(([argument, type]) => `${property(argument)}: ${typeOf(type)}`).join("; ")} }`;
|
|
686
758
|
if (javascript) {
|
|
687
759
|
out.push(`/**\n${doc}${args.length > 0 ? `\n * @param {${argumentType}} args` : ""}\n * @returns {string}\n */`);
|
|
688
|
-
out.push(
|
|
760
|
+
out.push(`${binding === name ? "export " : ""}function ${binding}(${args.length > 0 ? "args" : ""})`);
|
|
689
761
|
}
|
|
690
762
|
else {
|
|
691
763
|
out.push(`/**\n${doc}\n */`);
|
|
692
|
-
out.push(
|
|
764
|
+
out.push(`${binding === name ? "export " : ""}function ${binding}(${args.length > 0 ? `args: ${argumentType}` : ""}): string`);
|
|
693
765
|
}
|
|
694
766
|
out.push("{");
|
|
695
767
|
for (const line of emitter.prelude) {
|
|
@@ -697,8 +769,14 @@ export function compileMessages(locale, catalog, options = {}) {
|
|
|
697
769
|
}
|
|
698
770
|
out.push(` return ${body};`);
|
|
699
771
|
out.push("}");
|
|
772
|
+
if (binding !== name) {
|
|
773
|
+
out.push(`export { ${binding} as ${name} };`);
|
|
774
|
+
}
|
|
700
775
|
out.push("");
|
|
701
776
|
}
|
|
777
|
+
const helpers = helpersFor(used, javascript);
|
|
778
|
+
const shared = Array.from(formatters, ([create, binding]) => `const ${binding} = $once(() => ${create});`);
|
|
779
|
+
out.splice(head, 0, ...(helpers === "" ? [] : [helpers, ""]), ...(shared.length === 0 ? [] : [...shared, ""]));
|
|
702
780
|
return { code: out.join("\n"), names };
|
|
703
781
|
}
|
|
704
782
|
/** The keys of a catalog as function names, for comparing locales. */
|