@saykit/config 0.6.1 → 0.7.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/dist/commands/index.cjs +1 -1
- package/dist/commands/index.mjs +1 -1
- package/dist/features/loader/index.cjs +2 -1
- package/dist/features/loader/index.d.cts +7 -1
- package/dist/features/loader/index.d.mts +7 -1
- package/dist/features/loader/index.mjs +2 -2
- package/dist/features/messages/index.cjs +95 -10
- package/dist/features/messages/index.d.cts +69 -6
- package/dist/features/messages/index.d.mts +69 -6
- package/dist/features/messages/index.mjs +92 -11
- package/dist/{loader-A1uS7qkK.cjs → loader-BdCeoj3-.cjs} +18 -3
- package/dist/{loader-DLyuZzF2.mjs → loader-DwF1lM3f.mjs} +13 -4
- package/package.json +1 -1
package/dist/commands/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const require_storage = require("../storage-DqhzA5H8.cjs");
|
|
3
|
-
const require_loader = require("../loader-
|
|
3
|
+
const require_loader = require("../loader-BdCeoj3-.cjs");
|
|
4
4
|
let _commander_js_extra_typings = require("@commander-js/extra-typings");
|
|
5
5
|
let node_fs_promises = require("node:fs/promises");
|
|
6
6
|
let node_path = require("node:path");
|
package/dist/commands/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as mergeExtractedMessages, i as expandBucketOutputPath, n as writeCatalogueMessages, o as pruneLocaleMessages, t as readCatalogueMessages } from "../storage-B6mn0s3V.mjs";
|
|
3
|
-
import { t as resolveConfig } from "../loader-
|
|
3
|
+
import { t as resolveConfig } from "../loader-DwF1lM3f.mjs";
|
|
4
4
|
import { Command, program } from "@commander-js/extra-typings";
|
|
5
5
|
import { access, glob, readFile, stat, watch } from "node:fs/promises";
|
|
6
6
|
import { join, relative } from "node:path";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_loader = require("../../loader-
|
|
2
|
+
const require_loader = require("../../loader-BdCeoj3-.cjs");
|
|
3
3
|
exports.resolveConfig = require_loader.resolveConfig;
|
|
4
|
+
exports.resolveConfigFile = require_loader.resolveConfigFile;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { n as Config } from "../../shapes-CRlXtss0.cjs";
|
|
2
2
|
//#region src/features/loader/resolve.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The config file {@link resolveConfig} would load, for callers that need the
|
|
5
|
+
* path itself rather than its contents — salting a bundler's cache key with it,
|
|
6
|
+
* for one, since what a catalogue assembles into depends on the config.
|
|
7
|
+
*/
|
|
8
|
+
declare function resolveConfigFile(name?: string): string;
|
|
3
9
|
declare function resolveConfig(name?: string): Config;
|
|
4
10
|
//#endregion
|
|
5
|
-
export { resolveConfig };
|
|
11
|
+
export { resolveConfig, resolveConfigFile };
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { n as Config } from "../../shapes-CRlXtss0.mjs";
|
|
2
2
|
//#region src/features/loader/resolve.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The config file {@link resolveConfig} would load, for callers that need the
|
|
5
|
+
* path itself rather than its contents — salting a bundler's cache key with it,
|
|
6
|
+
* for one, since what a catalogue assembles into depends on the config.
|
|
7
|
+
*/
|
|
8
|
+
declare function resolveConfigFile(name?: string): string;
|
|
3
9
|
declare function resolveConfig(name?: string): Config;
|
|
4
10
|
//#endregion
|
|
5
|
-
export { resolveConfig };
|
|
11
|
+
export { resolveConfig, resolveConfigFile };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as resolveConfig } from "../../loader-
|
|
2
|
-
export { resolveConfig };
|
|
1
|
+
import { n as resolveConfigFile, t as resolveConfig } from "../../loader-DwF1lM3f.mjs";
|
|
2
|
+
export { resolveConfig, resolveConfigFile };
|
|
@@ -20,10 +20,12 @@ var LiteralMessage = class extends Base {
|
|
|
20
20
|
var ArgumentMessage = class extends Base {
|
|
21
21
|
identifier;
|
|
22
22
|
expression;
|
|
23
|
-
|
|
23
|
+
format;
|
|
24
|
+
constructor(identifier, expression, format) {
|
|
24
25
|
super();
|
|
25
26
|
this.identifier = identifier;
|
|
26
27
|
this.expression = expression;
|
|
28
|
+
this.format = format;
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
var ElementMessage = class extends Base {
|
|
@@ -42,12 +44,14 @@ var ChoiceMessage = class extends Base {
|
|
|
42
44
|
identifier;
|
|
43
45
|
branches;
|
|
44
46
|
expression;
|
|
45
|
-
|
|
47
|
+
offset;
|
|
48
|
+
constructor(kind, identifier, branches, expression, offset) {
|
|
46
49
|
super();
|
|
47
50
|
this.kind = kind;
|
|
48
51
|
this.identifier = identifier;
|
|
49
52
|
this.branches = branches;
|
|
50
53
|
this.expression = expression;
|
|
54
|
+
this.offset = offset;
|
|
51
55
|
}
|
|
52
56
|
};
|
|
53
57
|
var CompositeMessage = class extends Base {
|
|
@@ -76,8 +80,13 @@ const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
|
|
|
76
80
|
*/
|
|
77
81
|
const BRANCH_PATTERN = /^(?:=\d+|[^\p{Pattern_Syntax}\p{Pattern_White_Space}]+)$/u;
|
|
78
82
|
/**
|
|
79
|
-
* The ICU case a branch is written as.
|
|
80
|
-
*
|
|
83
|
+
* The ICU case a branch is written as.
|
|
84
|
+
*
|
|
85
|
+
* Under `plural` and `ordinal` a number names an exact value, spelled `=0`, and
|
|
86
|
+
* so is distinct from the CLDR category that would otherwise match it. `select`
|
|
87
|
+
* has no such syntax — its cases are literal string matches, and `=0` there is
|
|
88
|
+
* a parse error — so a numeric key stays bare, where it matches both `0` and
|
|
89
|
+
* `'0'`.
|
|
81
90
|
*
|
|
82
91
|
* Digits are read literally rather than coerced, because everything JavaScript
|
|
83
92
|
* is willing to call a number is not: `''`, `' '`, and `'+0'` all coerce to
|
|
@@ -85,8 +94,9 @@ const BRANCH_PATTERN = /^(?:=\d+|[^\p{Pattern_Syntax}\p{Pattern_White_Space}]+)$
|
|
|
85
94
|
* author's own key out of the catalogue. Anything else stays a key, where the
|
|
86
95
|
* whitespace or punctuation that made it numeric-looking is caught.
|
|
87
96
|
*/
|
|
88
|
-
function getBranchCase(identifier) {
|
|
97
|
+
function getBranchCase(kind, identifier) {
|
|
89
98
|
const key = String(identifier);
|
|
99
|
+
if (kind === "select") return key;
|
|
90
100
|
return /^\d+$/u.test(key) ? `=${+key}` : key;
|
|
91
101
|
}
|
|
92
102
|
/**
|
|
@@ -100,12 +110,12 @@ function getBranchCase(identifier) {
|
|
|
100
110
|
*/
|
|
101
111
|
function validateBranchIdentifier(kind, identifier) {
|
|
102
112
|
if (typeof identifier !== "string") return;
|
|
103
|
-
const branch = getBranchCase(identifier);
|
|
113
|
+
const branch = getBranchCase(kind, identifier);
|
|
104
114
|
if (!BRANCH_PATTERN.test(branch)) {
|
|
105
115
|
const suggestion = suggestBranchIdentifier(identifier);
|
|
106
116
|
throw new Error(`Invalid ${kind} branch key '${identifier}', an ICU key cannot contain punctuation or whitespace` + (suggestion ? `, try '${suggestion}'` : ""));
|
|
107
117
|
}
|
|
108
|
-
if (kind === "select" &&
|
|
118
|
+
if (kind === "select" && /^=\d+$/u.test(branch)) throw new Error(`Invalid select branch key '${identifier}', an exact value is only meaningful to 'plural' and 'ordinal', write it as '${branch.slice(1)}'`);
|
|
109
119
|
}
|
|
110
120
|
/**
|
|
111
121
|
* The nearest identifier-safe form of a key, so the error names the fix as well
|
|
@@ -170,7 +180,12 @@ function convertMessageToIcu(message) {
|
|
|
170
180
|
function internalConvertMessageToIcu(message) {
|
|
171
181
|
switch (true) {
|
|
172
182
|
case message instanceof LiteralMessage: return String(message.text);
|
|
173
|
-
case message instanceof ArgumentMessage:
|
|
183
|
+
case message instanceof ArgumentMessage: {
|
|
184
|
+
const parts = [String(message.identifier)];
|
|
185
|
+
if (message.format) parts.push(message.format.type);
|
|
186
|
+
if (message.format?.style) parts.push(message.format.style);
|
|
187
|
+
return `{${parts.join(", ")}}`;
|
|
188
|
+
}
|
|
174
189
|
case message instanceof ElementMessage: {
|
|
175
190
|
if (message.children.length === 0) return `<${String(message.identifier)}/>`;
|
|
176
191
|
const children = message.children.map((m) => internalConvertMessageToIcu(m)).join("");
|
|
@@ -178,11 +193,12 @@ function convertMessageToIcu(message) {
|
|
|
178
193
|
}
|
|
179
194
|
case message instanceof ChoiceMessage: {
|
|
180
195
|
const branches = message.branches.map(({ identifier, value }) => ({
|
|
181
|
-
identifier: getBranchCase(identifier),
|
|
196
|
+
identifier: getBranchCase(message.kind, identifier),
|
|
182
197
|
value: internalConvertMessageToIcu(value)
|
|
183
198
|
})).map(({ identifier, value }) => ` ${identifier} {${value}}\n`).join("");
|
|
184
199
|
const format = message.kind === "ordinal" ? "selectordinal" : message.kind;
|
|
185
|
-
|
|
200
|
+
const offset = message.offset === void 0 || message.kind === "select" ? "" : ` offset:${message.offset}`;
|
|
201
|
+
return `{${String(message.identifier)}, ${format},${offset}\n${branches}}`;
|
|
186
202
|
}
|
|
187
203
|
case message instanceof CompositeMessage: return Object.entries(message.children).map(([, m]) => internalConvertMessageToIcu(m)).join("");
|
|
188
204
|
default: throw new Error("Unknown message type", { cause: message });
|
|
@@ -191,6 +207,73 @@ function convertMessageToIcu(message) {
|
|
|
191
207
|
return internalConvertMessageToIcu(message).trim();
|
|
192
208
|
}
|
|
193
209
|
//#endregion
|
|
210
|
+
//#region src/features/messages/format.ts
|
|
211
|
+
/**
|
|
212
|
+
* The ICU argument types a macro can author, and the named styles each accepts.
|
|
213
|
+
*
|
|
214
|
+
* `currency` is deliberately absent from `number`. MF1 has nowhere to write the
|
|
215
|
+
* currency code — it comes from the formatter's configuration, not the message
|
|
216
|
+
* — so `{price, number, currency}` formats as a literal `{$price}` at runtime
|
|
217
|
+
* rather than an amount. Currency belongs to number skeletons, which the
|
|
218
|
+
* formatter does not accept yet either.
|
|
219
|
+
*
|
|
220
|
+
* `spellout`, RBNF `ordinal`, and `choice` are absent by decision rather than
|
|
221
|
+
* oversight: the first two are ICU4J/ICU4C rule-based formats with no `Intl`
|
|
222
|
+
* equivalent, and the third is deprecated in ICU itself in favour of `plural`.
|
|
223
|
+
*/
|
|
224
|
+
const ARGUMENT_STYLES = {
|
|
225
|
+
number: ["integer", "percent"],
|
|
226
|
+
date: [
|
|
227
|
+
"short",
|
|
228
|
+
"medium",
|
|
229
|
+
"long",
|
|
230
|
+
"full"
|
|
231
|
+
],
|
|
232
|
+
time: [
|
|
233
|
+
"short",
|
|
234
|
+
"medium",
|
|
235
|
+
"long",
|
|
236
|
+
"full"
|
|
237
|
+
]
|
|
238
|
+
};
|
|
239
|
+
const ARGUMENT_TYPES = Object.keys(ARGUMENT_STYLES);
|
|
240
|
+
function isArgumentType(kind) {
|
|
241
|
+
return Object.hasOwn(ARGUMENT_STYLES, kind);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* A literal `NumberFormat` pattern, e.g. `#,##0.00`.
|
|
245
|
+
*
|
|
246
|
+
* A pattern has to carry a digit placeholder — `#` or `0` — because that is
|
|
247
|
+
* what makes it a pattern rather than a word. Without that requirement any
|
|
248
|
+
* brace-free string qualifies, which quietly readmits the named styles this
|
|
249
|
+
* module exists to reject: `currency` would sail through as a "pattern" and
|
|
250
|
+
* extract to the `{price, number, currency}` the formatter cannot honour, and
|
|
251
|
+
* so would a plain typo.
|
|
252
|
+
*
|
|
253
|
+
* Braces are excluded separately: ICU reserves them for its own pattern syntax,
|
|
254
|
+
* so a style carrying one would close the argument early and take the rest of
|
|
255
|
+
* the message with it.
|
|
256
|
+
*/
|
|
257
|
+
const LITERAL_STYLE_PATTERN = /^[^{}\r\n]*[#0][^{}\r\n]*$/;
|
|
258
|
+
/**
|
|
259
|
+
* Reject an argument style the formatter cannot honour, while the style is
|
|
260
|
+
* still attached to a file and a line.
|
|
261
|
+
*
|
|
262
|
+
* A style is a bare string in the source and a bare string in the catalogue, so
|
|
263
|
+
* nothing between here and the runtime has an opinion about it. Left unchecked,
|
|
264
|
+
* a typo like `{d, date, meduim}` extracts to a catalogue entry that looks
|
|
265
|
+
* perfectly normal and only misformats once it reaches a user.
|
|
266
|
+
*/
|
|
267
|
+
function validateArgumentStyle(type, style) {
|
|
268
|
+
const named = ARGUMENT_STYLES[type];
|
|
269
|
+
if (named.includes(style)) return;
|
|
270
|
+
if (type === "number" && LITERAL_STYLE_PATTERN.test(style)) return;
|
|
271
|
+
const expected = named.map((s) => `'${s}'`).join(", ");
|
|
272
|
+
throw new Error(`Invalid ${type} style '${style}', expected ${expected}` + (type === "number" ? ", or a literal number pattern such as #,##0.00" : ""));
|
|
273
|
+
}
|
|
274
|
+
//#endregion
|
|
275
|
+
exports.ARGUMENT_STYLES = ARGUMENT_STYLES;
|
|
276
|
+
exports.ARGUMENT_TYPES = ARGUMENT_TYPES;
|
|
194
277
|
exports.AUTO_INCREMENT_IDENTIFIER = AUTO_INCREMENT_IDENTIFIER;
|
|
195
278
|
exports.ArgumentMessage = ArgumentMessage;
|
|
196
279
|
exports.ChoiceMessage = ChoiceMessage;
|
|
@@ -201,4 +284,6 @@ exports.assignSequenceIdentifiers = assignSequenceIdentifiers;
|
|
|
201
284
|
exports.convertMessageToIcu = convertMessageToIcu;
|
|
202
285
|
exports.generateHash = require_hash.generateHash;
|
|
203
286
|
exports.getBranchCase = getBranchCase;
|
|
287
|
+
exports.isArgumentType = isArgumentType;
|
|
288
|
+
exports.validateArgumentStyle = validateArgumentStyle;
|
|
204
289
|
exports.validateBranchIdentifier = validateBranchIdentifier;
|
|
@@ -1,8 +1,46 @@
|
|
|
1
|
+
//#region src/features/messages/format.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The ICU argument types a macro can author, and the named styles each accepts.
|
|
4
|
+
*
|
|
5
|
+
* `currency` is deliberately absent from `number`. MF1 has nowhere to write the
|
|
6
|
+
* currency code — it comes from the formatter's configuration, not the message
|
|
7
|
+
* — so `{price, number, currency}` formats as a literal `{$price}` at runtime
|
|
8
|
+
* rather than an amount. Currency belongs to number skeletons, which the
|
|
9
|
+
* formatter does not accept yet either.
|
|
10
|
+
*
|
|
11
|
+
* `spellout`, RBNF `ordinal`, and `choice` are absent by decision rather than
|
|
12
|
+
* oversight: the first two are ICU4J/ICU4C rule-based formats with no `Intl`
|
|
13
|
+
* equivalent, and the third is deprecated in ICU itself in favour of `plural`.
|
|
14
|
+
*/
|
|
15
|
+
declare const ARGUMENT_STYLES: {
|
|
16
|
+
readonly number: readonly ["integer", "percent"];
|
|
17
|
+
readonly date: readonly ["short", "medium", "long", "full"];
|
|
18
|
+
readonly time: readonly ["short", "medium", "long", "full"];
|
|
19
|
+
};
|
|
20
|
+
type ArgumentType = keyof typeof ARGUMENT_STYLES;
|
|
21
|
+
declare const ARGUMENT_TYPES: ArgumentType[];
|
|
22
|
+
declare function isArgumentType(kind: string): kind is ArgumentType;
|
|
23
|
+
/**
|
|
24
|
+
* Reject an argument style the formatter cannot honour, while the style is
|
|
25
|
+
* still attached to a file and a line.
|
|
26
|
+
*
|
|
27
|
+
* A style is a bare string in the source and a bare string in the catalogue, so
|
|
28
|
+
* nothing between here and the runtime has an opinion about it. Left unchecked,
|
|
29
|
+
* a typo like `{d, date, meduim}` extracts to a catalogue entry that looks
|
|
30
|
+
* perfectly normal and only misformats once it reaches a user.
|
|
31
|
+
*/
|
|
32
|
+
declare function validateArgumentStyle(type: ArgumentType, style: string): void;
|
|
33
|
+
//#endregion
|
|
1
34
|
//#region src/features/messages/identifier.d.ts
|
|
2
35
|
declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
|
|
3
36
|
/**
|
|
4
|
-
* The ICU case a branch is written as.
|
|
5
|
-
*
|
|
37
|
+
* The ICU case a branch is written as.
|
|
38
|
+
*
|
|
39
|
+
* Under `plural` and `ordinal` a number names an exact value, spelled `=0`, and
|
|
40
|
+
* so is distinct from the CLDR category that would otherwise match it. `select`
|
|
41
|
+
* has no such syntax — its cases are literal string matches, and `=0` there is
|
|
42
|
+
* a parse error — so a numeric key stays bare, where it matches both `0` and
|
|
43
|
+
* `'0'`.
|
|
6
44
|
*
|
|
7
45
|
* Digits are read literally rather than coerced, because everything JavaScript
|
|
8
46
|
* is willing to call a number is not: `''`, `' '`, and `'+0'` all coerce to
|
|
@@ -10,7 +48,7 @@ declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
|
|
|
10
48
|
* author's own key out of the catalogue. Anything else stays a key, where the
|
|
11
49
|
* whitespace or punctuation that made it numeric-looking is caught.
|
|
12
50
|
*/
|
|
13
|
-
declare function getBranchCase(identifier: string | typeof AUTO_INCREMENT_IDENTIFIER): string;
|
|
51
|
+
declare function getBranchCase(kind: string, identifier: string | typeof AUTO_INCREMENT_IDENTIFIER): string;
|
|
14
52
|
/**
|
|
15
53
|
* Reject a branch key ICU cannot express, while the key is still attached to a
|
|
16
54
|
* file and a line.
|
|
@@ -40,10 +78,23 @@ declare class LiteralMessage extends Base {
|
|
|
40
78
|
readonly text: string;
|
|
41
79
|
constructor(text: string);
|
|
42
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* An ICU argument type and style, e.g. `{n, number, percent}`. A style is
|
|
83
|
+
* optional — `{n, number}` is the type's default formatting.
|
|
84
|
+
*
|
|
85
|
+
* Both are kept as the ICU strings they are written as, rather than as `Intl`
|
|
86
|
+
* options, because the catalogue is the source of truth and only the ICU
|
|
87
|
+
* spelling round-trips back out of it.
|
|
88
|
+
*/
|
|
89
|
+
interface ArgumentFormat {
|
|
90
|
+
type: ArgumentType;
|
|
91
|
+
style?: string;
|
|
92
|
+
}
|
|
43
93
|
declare class ArgumentMessage extends Base {
|
|
44
94
|
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
45
95
|
readonly expression: any;
|
|
46
|
-
|
|
96
|
+
readonly format?: ArgumentFormat | undefined;
|
|
97
|
+
constructor(identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, expression: any, format?: ArgumentFormat | undefined);
|
|
47
98
|
}
|
|
48
99
|
declare class ElementMessage extends Base {
|
|
49
100
|
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
@@ -59,10 +110,22 @@ declare class ChoiceMessage extends Base {
|
|
|
59
110
|
readonly value: Message;
|
|
60
111
|
}[];
|
|
61
112
|
readonly expression: any;
|
|
113
|
+
/**
|
|
114
|
+
* Subtracted from the selector before `#` is formatted, so "You and 2
|
|
115
|
+
* others" can select on a total of three. Only `plural` and `ordinal`
|
|
116
|
+
* accept one; `select` has no number to offset.
|
|
117
|
+
*/
|
|
118
|
+
readonly offset?: number | undefined;
|
|
62
119
|
constructor(kind: string, identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, branches: {
|
|
63
120
|
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
64
121
|
readonly value: Message;
|
|
65
|
-
}[], expression: any
|
|
122
|
+
}[], expression: any,
|
|
123
|
+
/**
|
|
124
|
+
* Subtracted from the selector before `#` is formatted, so "You and 2
|
|
125
|
+
* others" can select on a total of three. Only `plural` and `ordinal`
|
|
126
|
+
* accept one; `select` has no number to offset.
|
|
127
|
+
*/
|
|
128
|
+
offset?: number | undefined);
|
|
66
129
|
}
|
|
67
130
|
declare class CompositeMessage extends Base {
|
|
68
131
|
readonly descriptor: {
|
|
@@ -87,4 +150,4 @@ declare function convertMessageToIcu(message: Message): string;
|
|
|
87
150
|
//#region src/features/messages/hash.d.ts
|
|
88
151
|
declare function generateHash(input: string, context?: string): string;
|
|
89
152
|
//#endregion
|
|
90
|
-
export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, PlaceholderEquivalence, assignSequenceIdentifiers, convertMessageToIcu, generateHash, getBranchCase, validateBranchIdentifier };
|
|
153
|
+
export { ARGUMENT_STYLES, ARGUMENT_TYPES, AUTO_INCREMENT_IDENTIFIER, ArgumentFormat, ArgumentMessage, ArgumentType, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, PlaceholderEquivalence, assignSequenceIdentifiers, convertMessageToIcu, generateHash, getBranchCase, isArgumentType, validateArgumentStyle, validateBranchIdentifier };
|
|
@@ -1,8 +1,46 @@
|
|
|
1
|
+
//#region src/features/messages/format.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The ICU argument types a macro can author, and the named styles each accepts.
|
|
4
|
+
*
|
|
5
|
+
* `currency` is deliberately absent from `number`. MF1 has nowhere to write the
|
|
6
|
+
* currency code — it comes from the formatter's configuration, not the message
|
|
7
|
+
* — so `{price, number, currency}` formats as a literal `{$price}` at runtime
|
|
8
|
+
* rather than an amount. Currency belongs to number skeletons, which the
|
|
9
|
+
* formatter does not accept yet either.
|
|
10
|
+
*
|
|
11
|
+
* `spellout`, RBNF `ordinal`, and `choice` are absent by decision rather than
|
|
12
|
+
* oversight: the first two are ICU4J/ICU4C rule-based formats with no `Intl`
|
|
13
|
+
* equivalent, and the third is deprecated in ICU itself in favour of `plural`.
|
|
14
|
+
*/
|
|
15
|
+
declare const ARGUMENT_STYLES: {
|
|
16
|
+
readonly number: readonly ["integer", "percent"];
|
|
17
|
+
readonly date: readonly ["short", "medium", "long", "full"];
|
|
18
|
+
readonly time: readonly ["short", "medium", "long", "full"];
|
|
19
|
+
};
|
|
20
|
+
type ArgumentType = keyof typeof ARGUMENT_STYLES;
|
|
21
|
+
declare const ARGUMENT_TYPES: ArgumentType[];
|
|
22
|
+
declare function isArgumentType(kind: string): kind is ArgumentType;
|
|
23
|
+
/**
|
|
24
|
+
* Reject an argument style the formatter cannot honour, while the style is
|
|
25
|
+
* still attached to a file and a line.
|
|
26
|
+
*
|
|
27
|
+
* A style is a bare string in the source and a bare string in the catalogue, so
|
|
28
|
+
* nothing between here and the runtime has an opinion about it. Left unchecked,
|
|
29
|
+
* a typo like `{d, date, meduim}` extracts to a catalogue entry that looks
|
|
30
|
+
* perfectly normal and only misformats once it reaches a user.
|
|
31
|
+
*/
|
|
32
|
+
declare function validateArgumentStyle(type: ArgumentType, style: string): void;
|
|
33
|
+
//#endregion
|
|
1
34
|
//#region src/features/messages/identifier.d.ts
|
|
2
35
|
declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
|
|
3
36
|
/**
|
|
4
|
-
* The ICU case a branch is written as.
|
|
5
|
-
*
|
|
37
|
+
* The ICU case a branch is written as.
|
|
38
|
+
*
|
|
39
|
+
* Under `plural` and `ordinal` a number names an exact value, spelled `=0`, and
|
|
40
|
+
* so is distinct from the CLDR category that would otherwise match it. `select`
|
|
41
|
+
* has no such syntax — its cases are literal string matches, and `=0` there is
|
|
42
|
+
* a parse error — so a numeric key stays bare, where it matches both `0` and
|
|
43
|
+
* `'0'`.
|
|
6
44
|
*
|
|
7
45
|
* Digits are read literally rather than coerced, because everything JavaScript
|
|
8
46
|
* is willing to call a number is not: `''`, `' '`, and `'+0'` all coerce to
|
|
@@ -10,7 +48,7 @@ declare const AUTO_INCREMENT_IDENTIFIER: unique symbol;
|
|
|
10
48
|
* author's own key out of the catalogue. Anything else stays a key, where the
|
|
11
49
|
* whitespace or punctuation that made it numeric-looking is caught.
|
|
12
50
|
*/
|
|
13
|
-
declare function getBranchCase(identifier: string | typeof AUTO_INCREMENT_IDENTIFIER): string;
|
|
51
|
+
declare function getBranchCase(kind: string, identifier: string | typeof AUTO_INCREMENT_IDENTIFIER): string;
|
|
14
52
|
/**
|
|
15
53
|
* Reject a branch key ICU cannot express, while the key is still attached to a
|
|
16
54
|
* file and a line.
|
|
@@ -40,10 +78,23 @@ declare class LiteralMessage extends Base {
|
|
|
40
78
|
readonly text: string;
|
|
41
79
|
constructor(text: string);
|
|
42
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* An ICU argument type and style, e.g. `{n, number, percent}`. A style is
|
|
83
|
+
* optional — `{n, number}` is the type's default formatting.
|
|
84
|
+
*
|
|
85
|
+
* Both are kept as the ICU strings they are written as, rather than as `Intl`
|
|
86
|
+
* options, because the catalogue is the source of truth and only the ICU
|
|
87
|
+
* spelling round-trips back out of it.
|
|
88
|
+
*/
|
|
89
|
+
interface ArgumentFormat {
|
|
90
|
+
type: ArgumentType;
|
|
91
|
+
style?: string;
|
|
92
|
+
}
|
|
43
93
|
declare class ArgumentMessage extends Base {
|
|
44
94
|
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
45
95
|
readonly expression: any;
|
|
46
|
-
|
|
96
|
+
readonly format?: ArgumentFormat | undefined;
|
|
97
|
+
constructor(identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, expression: any, format?: ArgumentFormat | undefined);
|
|
47
98
|
}
|
|
48
99
|
declare class ElementMessage extends Base {
|
|
49
100
|
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
@@ -59,10 +110,22 @@ declare class ChoiceMessage extends Base {
|
|
|
59
110
|
readonly value: Message;
|
|
60
111
|
}[];
|
|
61
112
|
readonly expression: any;
|
|
113
|
+
/**
|
|
114
|
+
* Subtracted from the selector before `#` is formatted, so "You and 2
|
|
115
|
+
* others" can select on a total of three. Only `plural` and `ordinal`
|
|
116
|
+
* accept one; `select` has no number to offset.
|
|
117
|
+
*/
|
|
118
|
+
readonly offset?: number | undefined;
|
|
62
119
|
constructor(kind: string, identifier: string | typeof AUTO_INCREMENT_IDENTIFIER, branches: {
|
|
63
120
|
identifier: string | typeof AUTO_INCREMENT_IDENTIFIER;
|
|
64
121
|
readonly value: Message;
|
|
65
|
-
}[], expression: any
|
|
122
|
+
}[], expression: any,
|
|
123
|
+
/**
|
|
124
|
+
* Subtracted from the selector before `#` is formatted, so "You and 2
|
|
125
|
+
* others" can select on a total of three. Only `plural` and `ordinal`
|
|
126
|
+
* accept one; `select` has no number to offset.
|
|
127
|
+
*/
|
|
128
|
+
offset?: number | undefined);
|
|
66
129
|
}
|
|
67
130
|
declare class CompositeMessage extends Base {
|
|
68
131
|
readonly descriptor: {
|
|
@@ -87,4 +150,4 @@ declare function convertMessageToIcu(message: Message): string;
|
|
|
87
150
|
//#region src/features/messages/hash.d.ts
|
|
88
151
|
declare function generateHash(input: string, context?: string): string;
|
|
89
152
|
//#endregion
|
|
90
|
-
export { AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, PlaceholderEquivalence, assignSequenceIdentifiers, convertMessageToIcu, generateHash, getBranchCase, validateBranchIdentifier };
|
|
153
|
+
export { ARGUMENT_STYLES, ARGUMENT_TYPES, AUTO_INCREMENT_IDENTIFIER, ArgumentFormat, ArgumentMessage, ArgumentType, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, Message, PlaceholderEquivalence, assignSequenceIdentifiers, convertMessageToIcu, generateHash, getBranchCase, isArgumentType, validateArgumentStyle, validateBranchIdentifier };
|
|
@@ -19,10 +19,12 @@ var LiteralMessage = class extends Base {
|
|
|
19
19
|
var ArgumentMessage = class extends Base {
|
|
20
20
|
identifier;
|
|
21
21
|
expression;
|
|
22
|
-
|
|
22
|
+
format;
|
|
23
|
+
constructor(identifier, expression, format) {
|
|
23
24
|
super();
|
|
24
25
|
this.identifier = identifier;
|
|
25
26
|
this.expression = expression;
|
|
27
|
+
this.format = format;
|
|
26
28
|
}
|
|
27
29
|
};
|
|
28
30
|
var ElementMessage = class extends Base {
|
|
@@ -41,12 +43,14 @@ var ChoiceMessage = class extends Base {
|
|
|
41
43
|
identifier;
|
|
42
44
|
branches;
|
|
43
45
|
expression;
|
|
44
|
-
|
|
46
|
+
offset;
|
|
47
|
+
constructor(kind, identifier, branches, expression, offset) {
|
|
45
48
|
super();
|
|
46
49
|
this.kind = kind;
|
|
47
50
|
this.identifier = identifier;
|
|
48
51
|
this.branches = branches;
|
|
49
52
|
this.expression = expression;
|
|
53
|
+
this.offset = offset;
|
|
50
54
|
}
|
|
51
55
|
};
|
|
52
56
|
var CompositeMessage = class extends Base {
|
|
@@ -75,8 +79,13 @@ const AUTO_INCREMENT_IDENTIFIER = Symbol("auto-increment");
|
|
|
75
79
|
*/
|
|
76
80
|
const BRANCH_PATTERN = /^(?:=\d+|[^\p{Pattern_Syntax}\p{Pattern_White_Space}]+)$/u;
|
|
77
81
|
/**
|
|
78
|
-
* The ICU case a branch is written as.
|
|
79
|
-
*
|
|
82
|
+
* The ICU case a branch is written as.
|
|
83
|
+
*
|
|
84
|
+
* Under `plural` and `ordinal` a number names an exact value, spelled `=0`, and
|
|
85
|
+
* so is distinct from the CLDR category that would otherwise match it. `select`
|
|
86
|
+
* has no such syntax — its cases are literal string matches, and `=0` there is
|
|
87
|
+
* a parse error — so a numeric key stays bare, where it matches both `0` and
|
|
88
|
+
* `'0'`.
|
|
80
89
|
*
|
|
81
90
|
* Digits are read literally rather than coerced, because everything JavaScript
|
|
82
91
|
* is willing to call a number is not: `''`, `' '`, and `'+0'` all coerce to
|
|
@@ -84,8 +93,9 @@ const BRANCH_PATTERN = /^(?:=\d+|[^\p{Pattern_Syntax}\p{Pattern_White_Space}]+)$
|
|
|
84
93
|
* author's own key out of the catalogue. Anything else stays a key, where the
|
|
85
94
|
* whitespace or punctuation that made it numeric-looking is caught.
|
|
86
95
|
*/
|
|
87
|
-
function getBranchCase(identifier) {
|
|
96
|
+
function getBranchCase(kind, identifier) {
|
|
88
97
|
const key = String(identifier);
|
|
98
|
+
if (kind === "select") return key;
|
|
89
99
|
return /^\d+$/u.test(key) ? `=${+key}` : key;
|
|
90
100
|
}
|
|
91
101
|
/**
|
|
@@ -99,12 +109,12 @@ function getBranchCase(identifier) {
|
|
|
99
109
|
*/
|
|
100
110
|
function validateBranchIdentifier(kind, identifier) {
|
|
101
111
|
if (typeof identifier !== "string") return;
|
|
102
|
-
const branch = getBranchCase(identifier);
|
|
112
|
+
const branch = getBranchCase(kind, identifier);
|
|
103
113
|
if (!BRANCH_PATTERN.test(branch)) {
|
|
104
114
|
const suggestion = suggestBranchIdentifier(identifier);
|
|
105
115
|
throw new Error(`Invalid ${kind} branch key '${identifier}', an ICU key cannot contain punctuation or whitespace` + (suggestion ? `, try '${suggestion}'` : ""));
|
|
106
116
|
}
|
|
107
|
-
if (kind === "select" &&
|
|
117
|
+
if (kind === "select" && /^=\d+$/u.test(branch)) throw new Error(`Invalid select branch key '${identifier}', an exact value is only meaningful to 'plural' and 'ordinal', write it as '${branch.slice(1)}'`);
|
|
108
118
|
}
|
|
109
119
|
/**
|
|
110
120
|
* The nearest identifier-safe form of a key, so the error names the fix as well
|
|
@@ -169,7 +179,12 @@ function convertMessageToIcu(message) {
|
|
|
169
179
|
function internalConvertMessageToIcu(message) {
|
|
170
180
|
switch (true) {
|
|
171
181
|
case message instanceof LiteralMessage: return String(message.text);
|
|
172
|
-
case message instanceof ArgumentMessage:
|
|
182
|
+
case message instanceof ArgumentMessage: {
|
|
183
|
+
const parts = [String(message.identifier)];
|
|
184
|
+
if (message.format) parts.push(message.format.type);
|
|
185
|
+
if (message.format?.style) parts.push(message.format.style);
|
|
186
|
+
return `{${parts.join(", ")}}`;
|
|
187
|
+
}
|
|
173
188
|
case message instanceof ElementMessage: {
|
|
174
189
|
if (message.children.length === 0) return `<${String(message.identifier)}/>`;
|
|
175
190
|
const children = message.children.map((m) => internalConvertMessageToIcu(m)).join("");
|
|
@@ -177,11 +192,12 @@ function convertMessageToIcu(message) {
|
|
|
177
192
|
}
|
|
178
193
|
case message instanceof ChoiceMessage: {
|
|
179
194
|
const branches = message.branches.map(({ identifier, value }) => ({
|
|
180
|
-
identifier: getBranchCase(identifier),
|
|
195
|
+
identifier: getBranchCase(message.kind, identifier),
|
|
181
196
|
value: internalConvertMessageToIcu(value)
|
|
182
197
|
})).map(({ identifier, value }) => ` ${identifier} {${value}}\n`).join("");
|
|
183
198
|
const format = message.kind === "ordinal" ? "selectordinal" : message.kind;
|
|
184
|
-
|
|
199
|
+
const offset = message.offset === void 0 || message.kind === "select" ? "" : ` offset:${message.offset}`;
|
|
200
|
+
return `{${String(message.identifier)}, ${format},${offset}\n${branches}}`;
|
|
185
201
|
}
|
|
186
202
|
case message instanceof CompositeMessage: return Object.entries(message.children).map(([, m]) => internalConvertMessageToIcu(m)).join("");
|
|
187
203
|
default: throw new Error("Unknown message type", { cause: message });
|
|
@@ -190,4 +206,69 @@ function convertMessageToIcu(message) {
|
|
|
190
206
|
return internalConvertMessageToIcu(message).trim();
|
|
191
207
|
}
|
|
192
208
|
//#endregion
|
|
193
|
-
|
|
209
|
+
//#region src/features/messages/format.ts
|
|
210
|
+
/**
|
|
211
|
+
* The ICU argument types a macro can author, and the named styles each accepts.
|
|
212
|
+
*
|
|
213
|
+
* `currency` is deliberately absent from `number`. MF1 has nowhere to write the
|
|
214
|
+
* currency code — it comes from the formatter's configuration, not the message
|
|
215
|
+
* — so `{price, number, currency}` formats as a literal `{$price}` at runtime
|
|
216
|
+
* rather than an amount. Currency belongs to number skeletons, which the
|
|
217
|
+
* formatter does not accept yet either.
|
|
218
|
+
*
|
|
219
|
+
* `spellout`, RBNF `ordinal`, and `choice` are absent by decision rather than
|
|
220
|
+
* oversight: the first two are ICU4J/ICU4C rule-based formats with no `Intl`
|
|
221
|
+
* equivalent, and the third is deprecated in ICU itself in favour of `plural`.
|
|
222
|
+
*/
|
|
223
|
+
const ARGUMENT_STYLES = {
|
|
224
|
+
number: ["integer", "percent"],
|
|
225
|
+
date: [
|
|
226
|
+
"short",
|
|
227
|
+
"medium",
|
|
228
|
+
"long",
|
|
229
|
+
"full"
|
|
230
|
+
],
|
|
231
|
+
time: [
|
|
232
|
+
"short",
|
|
233
|
+
"medium",
|
|
234
|
+
"long",
|
|
235
|
+
"full"
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
const ARGUMENT_TYPES = Object.keys(ARGUMENT_STYLES);
|
|
239
|
+
function isArgumentType(kind) {
|
|
240
|
+
return Object.hasOwn(ARGUMENT_STYLES, kind);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* A literal `NumberFormat` pattern, e.g. `#,##0.00`.
|
|
244
|
+
*
|
|
245
|
+
* A pattern has to carry a digit placeholder — `#` or `0` — because that is
|
|
246
|
+
* what makes it a pattern rather than a word. Without that requirement any
|
|
247
|
+
* brace-free string qualifies, which quietly readmits the named styles this
|
|
248
|
+
* module exists to reject: `currency` would sail through as a "pattern" and
|
|
249
|
+
* extract to the `{price, number, currency}` the formatter cannot honour, and
|
|
250
|
+
* so would a plain typo.
|
|
251
|
+
*
|
|
252
|
+
* Braces are excluded separately: ICU reserves them for its own pattern syntax,
|
|
253
|
+
* so a style carrying one would close the argument early and take the rest of
|
|
254
|
+
* the message with it.
|
|
255
|
+
*/
|
|
256
|
+
const LITERAL_STYLE_PATTERN = /^[^{}\r\n]*[#0][^{}\r\n]*$/;
|
|
257
|
+
/**
|
|
258
|
+
* Reject an argument style the formatter cannot honour, while the style is
|
|
259
|
+
* still attached to a file and a line.
|
|
260
|
+
*
|
|
261
|
+
* A style is a bare string in the source and a bare string in the catalogue, so
|
|
262
|
+
* nothing between here and the runtime has an opinion about it. Left unchecked,
|
|
263
|
+
* a typo like `{d, date, meduim}` extracts to a catalogue entry that looks
|
|
264
|
+
* perfectly normal and only misformats once it reaches a user.
|
|
265
|
+
*/
|
|
266
|
+
function validateArgumentStyle(type, style) {
|
|
267
|
+
const named = ARGUMENT_STYLES[type];
|
|
268
|
+
if (named.includes(style)) return;
|
|
269
|
+
if (type === "number" && LITERAL_STYLE_PATTERN.test(style)) return;
|
|
270
|
+
const expected = named.map((s) => `'${s}'`).join(", ");
|
|
271
|
+
throw new Error(`Invalid ${type} style '${style}', expected ${expected}` + (type === "number" ? ", or a literal number pattern such as #,##0.00" : ""));
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
export { ARGUMENT_STYLES, ARGUMENT_TYPES, AUTO_INCREMENT_IDENTIFIER, ArgumentMessage, ChoiceMessage, CompositeMessage, ElementMessage, LiteralMessage, assignSequenceIdentifiers, convertMessageToIcu, generateHash, getBranchCase, isArgumentType, validateArgumentStyle, validateBranchIdentifier };
|
|
@@ -73,13 +73,22 @@ const configLoaders = Object.freeze({
|
|
|
73
73
|
});
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/features/loader/resolve.ts
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The config file {@link resolveConfig} would load, for callers that need the
|
|
78
|
+
* path itself rather than its contents — salting a bundler's cache key with it,
|
|
79
|
+
* for one, since what a catalogue assembles into depends on the config.
|
|
80
|
+
*/
|
|
81
|
+
function resolveConfigFile(name = "saykit") {
|
|
77
82
|
const file = findConfigFile(name, process.cwd());
|
|
78
83
|
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
79
|
-
|
|
84
|
+
return file.id;
|
|
85
|
+
}
|
|
86
|
+
function resolveConfig(name = "saykit") {
|
|
87
|
+
const id = resolveConfigFile(name);
|
|
88
|
+
const ext = (0, node_path.extname)(id).toLowerCase();
|
|
80
89
|
const load = ext in configLoaders ? configLoaders[ext] : null;
|
|
81
90
|
if (!load) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
82
|
-
const config = load(
|
|
91
|
+
const config = load(id);
|
|
83
92
|
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
84
93
|
return config;
|
|
85
94
|
}
|
|
@@ -90,3 +99,9 @@ Object.defineProperty(exports, "resolveConfig", {
|
|
|
90
99
|
return resolveConfig;
|
|
91
100
|
}
|
|
92
101
|
});
|
|
102
|
+
Object.defineProperty(exports, "resolveConfigFile", {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function() {
|
|
105
|
+
return resolveConfigFile;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
@@ -73,15 +73,24 @@ const configLoaders = Object.freeze({
|
|
|
73
73
|
});
|
|
74
74
|
//#endregion
|
|
75
75
|
//#region src/features/loader/resolve.ts
|
|
76
|
-
|
|
76
|
+
/**
|
|
77
|
+
* The config file {@link resolveConfig} would load, for callers that need the
|
|
78
|
+
* path itself rather than its contents — salting a bundler's cache key with it,
|
|
79
|
+
* for one, since what a catalogue assembles into depends on the config.
|
|
80
|
+
*/
|
|
81
|
+
function resolveConfigFile(name = "saykit") {
|
|
77
82
|
const file = findConfigFile(name, process.cwd());
|
|
78
83
|
if (!file) throw new Error(`Could not find config file for "${name}"`);
|
|
79
|
-
|
|
84
|
+
return file.id;
|
|
85
|
+
}
|
|
86
|
+
function resolveConfig(name = "saykit") {
|
|
87
|
+
const id = resolveConfigFile(name);
|
|
88
|
+
const ext = extname(id).toLowerCase();
|
|
80
89
|
const load = ext in configLoaders ? configLoaders[ext] : null;
|
|
81
90
|
if (!load) throw new Error(`Unsupported config file type "${ext}" for "${name}"`);
|
|
82
|
-
const config = load(
|
|
91
|
+
const config = load(id);
|
|
83
92
|
if (!config || typeof config !== "object") throw new Error(`Invalid config file for "${name}"`);
|
|
84
93
|
return config;
|
|
85
94
|
}
|
|
86
95
|
//#endregion
|
|
87
|
-
export { resolveConfig as t };
|
|
96
|
+
export { resolveConfigFile as n, resolveConfig as t };
|