@zerolith/i18n 0.1.0-alpha.1 → 0.1.0-alpha.10
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 +8 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +66 -8
- 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 +179 -85
- 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/dist/production/compile.js +167 -0
- package/dist/production/compile.js.map +1 -0
- package/dist/production/index.js +2 -0
- package/dist/production/index.js.map +1 -0
- package/package.json +9 -7
- package/src/cli.ts +87 -8
- package/src/compile.ts +203 -86
- package/src/index.ts +43 -16
package/src/cli.ts
CHANGED
|
@@ -6,11 +6,54 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
8
8
|
import { basename, join } from "node:path";
|
|
9
|
+
import { realpathSync } from "node:fs";
|
|
9
10
|
import process from "node:process";
|
|
11
|
+
import { pathToFileURL } from "node:url";
|
|
10
12
|
import { type Catalog, compileIndex, compileMessages, messageNames } from "./compile.js";
|
|
11
13
|
|
|
12
14
|
const USAGE = "usage: zerolith-i18n <folder> [--out <folder>] [--js] [--strict]\n";
|
|
13
15
|
|
|
16
|
+
/** The locale a catalog's file name gives, refusing a name that is not a BCP 47 language tag, such as `en_US.json`. */
|
|
17
|
+
function localeOf(file: string): string
|
|
18
|
+
{
|
|
19
|
+
const locale = basename(file, ".json");
|
|
20
|
+
try
|
|
21
|
+
{
|
|
22
|
+
Intl.getCanonicalLocales(locale);
|
|
23
|
+
}
|
|
24
|
+
catch
|
|
25
|
+
{
|
|
26
|
+
throw new Error(`${locale} is not a language tag; name the file as en.json or pt-BR.json`);
|
|
27
|
+
}
|
|
28
|
+
return locale;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A catalog's messages, refusing a file that is not a JSON object of strings. */
|
|
32
|
+
function catalogOf(text: string): Catalog
|
|
33
|
+
{
|
|
34
|
+
const parsed: unknown = JSON.parse(text);
|
|
35
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
36
|
+
{
|
|
37
|
+
throw new Error("a catalog is a JSON object of messages");
|
|
38
|
+
}
|
|
39
|
+
for (const [key, value] of Object.entries(parsed))
|
|
40
|
+
{
|
|
41
|
+
if (typeof value !== "string")
|
|
42
|
+
{
|
|
43
|
+
throw new Error(`the message "${key}" is not a string`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return parsed as Catalog;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Reports why a catalog cannot be compiled, naming its file, and gives the exit code; nothing has been written. */
|
|
50
|
+
function failure(file: string, error: unknown): number
|
|
51
|
+
{
|
|
52
|
+
const message = error instanceof Error ? error.message.replace(/^zerolith\/i18n: /, "") : String(error);
|
|
53
|
+
process.stderr.write(`zerolith-i18n: ${file}: ${message}\n`);
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
14
57
|
interface Arguments
|
|
15
58
|
{
|
|
16
59
|
folder: string | undefined;
|
|
@@ -84,10 +127,25 @@ export async function main(argv: readonly string[]): Promise<number>
|
|
|
84
127
|
return 1;
|
|
85
128
|
}
|
|
86
129
|
const out = parsed.out ?? parsed.folder;
|
|
87
|
-
await mkdir(out, { recursive: true });
|
|
88
130
|
const language = parsed.javascript ? "js" : "ts";
|
|
89
131
|
const folder = parsed.folder;
|
|
90
|
-
const
|
|
132
|
+
const read = await Promise.all(files.map(async (file) =>
|
|
133
|
+
{
|
|
134
|
+
try
|
|
135
|
+
{
|
|
136
|
+
return { file, locale: localeOf(file), catalog: catalogOf(await readFile(join(folder, file), "utf8")) };
|
|
137
|
+
}
|
|
138
|
+
catch (error)
|
|
139
|
+
{
|
|
140
|
+
return { file, error };
|
|
141
|
+
}
|
|
142
|
+
}));
|
|
143
|
+
const unreadable = read.find((entry) => "error" in entry);
|
|
144
|
+
if (unreadable !== undefined)
|
|
145
|
+
{
|
|
146
|
+
return failure(unreadable.file, unreadable.error);
|
|
147
|
+
}
|
|
148
|
+
const catalogs = new Map<string, Catalog>(read.map((entry) => [entry.locale ?? "", entry.catalog ?? {}]));
|
|
91
149
|
const all = new Set<string>();
|
|
92
150
|
for (const catalog of catalogs.values())
|
|
93
151
|
{
|
|
@@ -97,7 +155,7 @@ export async function main(argv: readonly string[]): Promise<number>
|
|
|
97
155
|
}
|
|
98
156
|
}
|
|
99
157
|
let missing = 0;
|
|
100
|
-
const
|
|
158
|
+
const modules = new Map<string, string>();
|
|
101
159
|
for (const [locale, catalog] of catalogs)
|
|
102
160
|
{
|
|
103
161
|
const present = new Set(messageNames(catalog));
|
|
@@ -109,16 +167,37 @@ export async function main(argv: readonly string[]): Promise<number>
|
|
|
109
167
|
process.stderr.write(`zerolith-i18n: ${locale} lacks ${name}\n`);
|
|
110
168
|
}
|
|
111
169
|
}
|
|
112
|
-
|
|
113
|
-
|
|
170
|
+
try
|
|
171
|
+
{
|
|
172
|
+
modules.set(`${locale}.${language}`, compileMessages(locale, catalog, { language, source: `${locale}.json` }).code);
|
|
173
|
+
}
|
|
174
|
+
catch (error)
|
|
175
|
+
{
|
|
176
|
+
return failure(`${locale}.json`, error);
|
|
177
|
+
}
|
|
114
178
|
}
|
|
115
|
-
|
|
116
|
-
await
|
|
179
|
+
modules.set(`index.${language}`, compileIndex([...catalogs.keys()], { language }));
|
|
180
|
+
await mkdir(out, { recursive: true });
|
|
181
|
+
await Promise.all([...modules].map(([name, code]) => writeFile(join(out, name), code)));
|
|
117
182
|
process.stdout.write(`zerolith-i18n: ${catalogs.size} locale(s), ${all.size} message(s), written to ${out}\n`);
|
|
118
183
|
return missing > 0 && parsed.strict ? 1 : 0;
|
|
119
184
|
}
|
|
120
185
|
|
|
121
|
-
|
|
186
|
+
/** True when node started this module, directly or through the bin link npm installs, which it resolves the way node resolves its entry. */
|
|
187
|
+
function started(): boolean
|
|
188
|
+
{
|
|
189
|
+
const entry = process.argv[1];
|
|
190
|
+
try
|
|
191
|
+
{
|
|
192
|
+
return entry !== undefined && pathToFileURL(realpathSync(entry)).href === import.meta.url;
|
|
193
|
+
}
|
|
194
|
+
catch
|
|
195
|
+
{
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (started())
|
|
122
201
|
{
|
|
123
202
|
process.exitCode = await main(process.argv.slice(2));
|
|
124
203
|
}
|