saykit 0.7.0 → 0.9.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 +1 -1
- package/dist/runtime.d.mts +47 -23
- package/dist/runtime.mjs +457 -239
- package/package.json +6 -2
package/README.md
CHANGED
package/dist/runtime.d.mts
CHANGED
|
@@ -11,35 +11,50 @@ type Awaitable<T> = T | PromiseLike<T>;
|
|
|
11
11
|
type Named<T> = {
|
|
12
12
|
[name: string]: T;
|
|
13
13
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Branches of a choice, keyed by CLDR category or by exact number.
|
|
16
|
+
*
|
|
17
|
+
* `Branch` is what a case may be written as. It is text everywhere the message
|
|
18
|
+
* is text, and widens in JSX, where a case that shows the number has to be a
|
|
19
|
+
* fragment — a string attribute has nowhere to put a value.
|
|
20
|
+
*/
|
|
21
|
+
interface NumeralOptions<Branch = string> extends Omit<Partial<Record<Intl.LDMLPluralRule, Branch>>, 'other'> {
|
|
22
|
+
other: Branch;
|
|
23
|
+
[digit: number]: Branch;
|
|
17
24
|
/**
|
|
18
25
|
* Subtracted from the value before `#` is formatted, so "You and 2 others"
|
|
19
26
|
* can select on a total of three. Reserved — it never names a branch.
|
|
20
27
|
*/
|
|
21
28
|
offset?: number;
|
|
22
29
|
}
|
|
23
|
-
interface SelectOptions {
|
|
24
|
-
other:
|
|
25
|
-
[match: string | number]:
|
|
30
|
+
interface SelectOptions<Branch = string> {
|
|
31
|
+
other: Branch;
|
|
32
|
+
[match: string | number]: Branch;
|
|
26
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* An ICU skeleton: a `::`-prefixed description of the parts a value is written
|
|
36
|
+
* with, rather than a name for a whole format. `::currency/EUR` and
|
|
37
|
+
* `::yyyyMMdd` say what to show and leave the arrangement to the locale.
|
|
38
|
+
*
|
|
39
|
+
* A skeleton is where the formats the named styles have no word for live —
|
|
40
|
+
* currency, compact notation, a year and month with no day — so every argument
|
|
41
|
+
* type accepts one alongside its named styles.
|
|
42
|
+
*/
|
|
43
|
+
type Skeleton = `::${string}`;
|
|
27
44
|
/**
|
|
28
45
|
* Formatting for a `{arg, number}` placeholder.
|
|
29
46
|
*
|
|
30
|
-
* `
|
|
31
|
-
*
|
|
32
|
-
* amount. A literal pattern such as `#,##0.00` is accepted for the cases the
|
|
33
|
-
* named styles do not cover.
|
|
47
|
+
* A literal `NumberFormat` pattern such as `#,##0.00` is also accepted, for the
|
|
48
|
+
* cases neither the named styles nor a skeleton spell more clearly.
|
|
34
49
|
*/
|
|
35
50
|
interface NumberOptions {
|
|
36
|
-
style?: 'integer' | 'percent' | (string & {});
|
|
51
|
+
style?: 'integer' | 'percent' | Skeleton | (string & {});
|
|
37
52
|
}
|
|
38
53
|
/**
|
|
39
54
|
* Formatting for a `{arg, date}` or `{arg, time}` placeholder.
|
|
40
55
|
*/
|
|
41
56
|
interface DateTimeOptions {
|
|
42
|
-
style?: 'short' | 'medium' | 'long' | 'full';
|
|
57
|
+
style?: 'short' | 'medium' | 'long' | 'full' | Skeleton;
|
|
43
58
|
}
|
|
44
59
|
//#endregion
|
|
45
60
|
//#region src/runtime.d.ts
|
|
@@ -188,11 +203,12 @@ declare class Say<Locale extends string = string, Loader extends Say.Loader<Loca
|
|
|
188
203
|
* ```ts
|
|
189
204
|
* say.plural(count, {
|
|
190
205
|
* one: 'You have 1 item',
|
|
191
|
-
* other:
|
|
206
|
+
* other: `You have ${count} items`,
|
|
192
207
|
* })
|
|
193
208
|
* ```
|
|
194
209
|
*
|
|
195
|
-
*
|
|
210
|
+
* Interpolating the selector into a branch extracts as ICU's `#`, the number
|
|
211
|
+
* the message branched on. A `#` you write yourself is text.
|
|
196
212
|
* @param _ Number to determine the plural form of
|
|
197
213
|
* @param options Pluralisation rules keyed by CLDR categories or specific numbers
|
|
198
214
|
* @returns The plural form of the number
|
|
@@ -201,15 +217,17 @@ declare class Say<Locale extends string = string, Loader extends Say.Loader<Loca
|
|
|
201
217
|
plural(_: number | Named<number>, options: Disallow<NumeralOptions, 'id' | 'context'>): string;
|
|
202
218
|
/**
|
|
203
219
|
* Define an ordinal message (e.g. "1st", "2nd", "3rd").
|
|
204
|
-
*
|
|
220
|
+
*
|
|
221
|
+
* Interpolating the selector into a branch extracts as ICU's `#`, the number
|
|
222
|
+
* the message branched on. A `#` you write yourself is text.
|
|
205
223
|
*
|
|
206
224
|
* @example
|
|
207
225
|
* ```ts
|
|
208
226
|
* say.ordinal(position, {
|
|
209
|
-
* 1:
|
|
210
|
-
* 2:
|
|
211
|
-
* 3:
|
|
212
|
-
* other:
|
|
227
|
+
* 1: `${position}st`,
|
|
228
|
+
* 2: `${position}nd`,
|
|
229
|
+
* 3: `${position}rd`,
|
|
230
|
+
* other: `${position}th`,
|
|
213
231
|
* })
|
|
214
232
|
* ```
|
|
215
233
|
*
|
|
@@ -249,10 +267,12 @@ declare class Say<Locale extends string = string, Loader extends Say.Loader<Loca
|
|
|
249
267
|
* say`You have ${say.number(items.length)} items`
|
|
250
268
|
* say`Battery at ${say.number(level, { style: 'percent' })}`
|
|
251
269
|
* say`Total: ${say.number({ cartTotal: getTotal() }, { style: '#,##0.00' })}`
|
|
270
|
+
* say`Total: ${say.number(total, { style: '::currency/EUR' })}`
|
|
252
271
|
* ```
|
|
253
272
|
*
|
|
254
273
|
* @param _ Number to format
|
|
255
|
-
* @param options Formatting style
|
|
274
|
+
* @param options Formatting style: a named style, an ICU skeleton such as
|
|
275
|
+
* `::currency/EUR`, or a literal number pattern such as `#,##0.00`
|
|
256
276
|
* @returns The formatted number
|
|
257
277
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
258
278
|
*/
|
|
@@ -264,10 +284,12 @@ declare class Say<Locale extends string = string, Loader extends Say.Loader<Loca
|
|
|
264
284
|
* ```ts
|
|
265
285
|
* say`Published ${say.date(post.publishedAt)}`
|
|
266
286
|
* say`Published ${say.date(post.publishedAt, { style: 'full' })}`
|
|
287
|
+
* say`Published ${say.date(post.publishedAt, { style: '::yMMMM' })}`
|
|
267
288
|
* ```
|
|
268
289
|
*
|
|
269
290
|
* @param _ Date to format
|
|
270
|
-
* @param options Formatting style
|
|
291
|
+
* @param options Formatting style, either a named style or an ICU skeleton
|
|
292
|
+
* such as `::yyyyMMdd`
|
|
271
293
|
* @returns The formatted date
|
|
272
294
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
273
295
|
*/
|
|
@@ -279,14 +301,16 @@ declare class Say<Locale extends string = string, Loader extends Say.Loader<Loca
|
|
|
279
301
|
* ```ts
|
|
280
302
|
* say`Doors open at ${say.time(opensAt)}`
|
|
281
303
|
* say`Doors open at ${say.time(opensAt, { style: 'short' })}`
|
|
304
|
+
* say`Doors open at ${say.time(opensAt, { style: '::Hm' })}`
|
|
282
305
|
* ```
|
|
283
306
|
*
|
|
284
307
|
* @param _ Date to format
|
|
285
|
-
* @param options Formatting style
|
|
308
|
+
* @param options Formatting style, either a named style or an ICU skeleton
|
|
309
|
+
* such as `::Hm`
|
|
286
310
|
* @returns The formatted time
|
|
287
311
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
288
312
|
*/
|
|
289
313
|
time(_: Date | number | Named<Date | number>, options?: Disallow<DateTimeOptions, 'id' | 'context'>): string;
|
|
290
314
|
}
|
|
291
315
|
//#endregion
|
|
292
|
-
export { Awaitable, DateTimeOptions, Disallow, Named, NumberOptions, NumeralOptions, ReadonlySay, Say, SelectOptions, Tuple };
|
|
316
|
+
export { Awaitable, DateTimeOptions, Disallow, Named, NumberOptions, NumeralOptions, ReadonlySay, Say, SelectOptions, Skeleton, Tuple };
|
package/dist/runtime.mjs
CHANGED
|
@@ -1,117 +1,466 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { parse } from "@messageformat/parser";
|
|
2
|
+
import { MessageFormat } from "messageformat";
|
|
3
|
+
import { getDateTimeFormatOptions, parseDateTokens } from "@messageformat/date-skeleton";
|
|
4
|
+
import { getNumberFormatOptions, parseNumberPattern, parseNumberSkeleton } from "@messageformat/number-skeleton";
|
|
5
|
+
import { getLocaleDir } from "messageformat/functions";
|
|
6
|
+
//#region src/messageformat/options.ts
|
|
7
|
+
const literal = (value) => ({
|
|
8
|
+
type: "literal",
|
|
9
|
+
value
|
|
10
|
+
});
|
|
11
|
+
function options(value) {
|
|
12
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
5
13
|
}
|
|
6
14
|
//#endregion
|
|
7
|
-
//#region
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
//#region src/messageformat/styles.ts
|
|
16
|
+
var StyleError = class extends Error {};
|
|
17
|
+
const DATE_STYLES = {
|
|
18
|
+
short: {
|
|
19
|
+
year: "numeric",
|
|
20
|
+
month: "numeric",
|
|
21
|
+
day: "numeric"
|
|
22
|
+
},
|
|
23
|
+
medium: {
|
|
24
|
+
year: "numeric",
|
|
25
|
+
month: "short",
|
|
26
|
+
day: "numeric"
|
|
27
|
+
},
|
|
28
|
+
long: {
|
|
29
|
+
year: "numeric",
|
|
30
|
+
month: "long",
|
|
31
|
+
day: "numeric"
|
|
32
|
+
},
|
|
33
|
+
full: {
|
|
34
|
+
year: "numeric",
|
|
35
|
+
month: "long",
|
|
36
|
+
day: "numeric",
|
|
37
|
+
weekday: "long"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const TIME_STYLES = {
|
|
41
|
+
short: {
|
|
42
|
+
hour: "numeric",
|
|
43
|
+
minute: "numeric"
|
|
44
|
+
},
|
|
45
|
+
medium: {
|
|
46
|
+
hour: "numeric",
|
|
47
|
+
minute: "numeric",
|
|
48
|
+
second: "numeric"
|
|
49
|
+
},
|
|
50
|
+
long: {
|
|
51
|
+
hour: "numeric",
|
|
52
|
+
minute: "numeric",
|
|
53
|
+
second: "numeric",
|
|
54
|
+
timeZoneName: "short"
|
|
55
|
+
},
|
|
56
|
+
full: {
|
|
57
|
+
hour: "numeric",
|
|
58
|
+
minute: "numeric",
|
|
59
|
+
second: "numeric",
|
|
60
|
+
timeZoneName: "long"
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function dateTimeStyle(kind, style) {
|
|
64
|
+
const named = kind === "date" ? DATE_STYLES : TIME_STYLES;
|
|
65
|
+
if (style.startsWith("::")) {
|
|
66
|
+
const errors = [];
|
|
67
|
+
const opt = getDateTimeFormatOptions(parseDateTokens(style.slice(2)), (_type, message) => errors.push(message));
|
|
68
|
+
if (errors.length > 0) throw new StyleError(errors[0]);
|
|
69
|
+
if (Object.keys(opt).length === 0) throw new StyleError(`Empty skeleton ${style}`);
|
|
70
|
+
return opt;
|
|
71
|
+
}
|
|
72
|
+
if (style === "") return named.medium;
|
|
73
|
+
if (Object.hasOwn(named, style)) return named[style];
|
|
74
|
+
throw new StyleError(`Unsupported ${kind} style ${style}`);
|
|
10
75
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
76
|
+
function numberStyle(style) {
|
|
77
|
+
if (style === "") return {};
|
|
78
|
+
if (style === "integer") return { maximumFractionDigits: 0 };
|
|
79
|
+
if (style === "percent") return { style: "percent" };
|
|
80
|
+
const errors = [];
|
|
81
|
+
const onError = (error) => errors.push(String(error));
|
|
82
|
+
const skeleton = style.startsWith("::") ? parseNumberSkeleton(style.slice(2), onError) : parseNumberPattern(style, "XXX", onError);
|
|
83
|
+
let scale;
|
|
84
|
+
const opt = getNumberFormatOptions(skeleton, (stem, option) => {
|
|
85
|
+
if (stem === "scale") scale = Number(option);
|
|
86
|
+
else errors.push(`Unsupported number stem ${stem}`);
|
|
87
|
+
});
|
|
88
|
+
if (errors.length > 0) throw new StyleError(errors[0]);
|
|
89
|
+
return scale === void 0 ? opt : {
|
|
90
|
+
...opt,
|
|
91
|
+
scale
|
|
92
|
+
};
|
|
15
93
|
}
|
|
16
94
|
//#endregion
|
|
17
|
-
//#region
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
95
|
+
//#region src/messageformat/convert.ts
|
|
96
|
+
const isSelect = (token) => token.type === "plural" || token.type === "select" || token.type === "selectordinal";
|
|
97
|
+
const asKey = (key) => /^=\d+$/.test(key) ? Number(key.slice(1)) : key;
|
|
98
|
+
function functionRef(token) {
|
|
99
|
+
let style = "";
|
|
100
|
+
for (const part of token.param ?? []) {
|
|
101
|
+
if (part.type !== "content") throw new Error(`Unsupported style part: ${part.type}`);
|
|
102
|
+
style += part.value;
|
|
103
|
+
}
|
|
104
|
+
style = style.trim();
|
|
105
|
+
const ref = (name, value) => ({
|
|
106
|
+
type: "function",
|
|
107
|
+
name,
|
|
108
|
+
...value === void 0 ? {} : { options: { options: literal(value) } }
|
|
109
|
+
});
|
|
110
|
+
try {
|
|
111
|
+
switch (token.key) {
|
|
112
|
+
case "date":
|
|
113
|
+
case "time": return ref("say:datetime", dateTimeStyle(token.key, style));
|
|
114
|
+
case "number": return ref("say:number", numberStyle(style));
|
|
115
|
+
case "duration": return ref("say:duration");
|
|
116
|
+
default: throw new StyleError(`Unsupported argument type ${token.key}`);
|
|
117
|
+
}
|
|
118
|
+
} catch (error) {
|
|
119
|
+
/* v8 ignore next */
|
|
120
|
+
if (!(error instanceof StyleError)) throw error;
|
|
121
|
+
switch (token.key) {
|
|
122
|
+
case "date":
|
|
123
|
+
case "time": return ref("say:datetime", dateTimeStyle(token.key, ""));
|
|
124
|
+
case "number": return ref("say:number", {});
|
|
125
|
+
default: return ref("say:string");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function toPart(token, plural) {
|
|
130
|
+
switch (token.type) {
|
|
131
|
+
case "content": return token.value;
|
|
132
|
+
case "argument": return {
|
|
133
|
+
type: "expression",
|
|
134
|
+
arg: {
|
|
135
|
+
type: "variable",
|
|
136
|
+
name: token.arg
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
case "function": return {
|
|
140
|
+
type: "expression",
|
|
141
|
+
arg: {
|
|
142
|
+
type: "variable",
|
|
143
|
+
name: token.arg
|
|
144
|
+
},
|
|
145
|
+
functionRef: functionRef(token)
|
|
146
|
+
};
|
|
147
|
+
case "octothorpe":
|
|
148
|
+
/* v8 ignore next */
|
|
149
|
+
return plural ? {
|
|
150
|
+
type: "expression",
|
|
151
|
+
arg: {
|
|
152
|
+
type: "variable",
|
|
153
|
+
name: plural
|
|
154
|
+
}
|
|
155
|
+
} : "#";
|
|
156
|
+
/* v8 ignore next 2 -- the token union has no other members */
|
|
157
|
+
default: throw new Error(`Unsupported token type: ${token.type}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const sameSelector = (a) => (b) => a.arg === b.arg && a.type === b.type && a.offset === b.offset;
|
|
161
|
+
function findSelectors(tokens) {
|
|
162
|
+
const selectors = [];
|
|
163
|
+
const add = (selector) => {
|
|
164
|
+
const existing = selectors.find(sameSelector(selector));
|
|
165
|
+
if (existing) {
|
|
166
|
+
existing.keys.push(...selector.keys);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const taken = selectors.filter((s) => s.arg === selector.arg).length;
|
|
170
|
+
const name = taken === 0 ? selector.arg : `${selector.arg}#${taken}`;
|
|
171
|
+
selectors.push({
|
|
172
|
+
...selector,
|
|
173
|
+
name
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
for (const token of tokens) {
|
|
177
|
+
if (!isSelect(token)) continue;
|
|
178
|
+
add({
|
|
179
|
+
arg: token.arg,
|
|
180
|
+
type: token.type,
|
|
181
|
+
offset: token.pluralOffset ?? 0,
|
|
182
|
+
keys: token.cases.map((c) => token.type === "select" ? c.key : asKey(c.key))
|
|
183
|
+
});
|
|
184
|
+
for (const c of token.cases) for (const inner of findSelectors(c.tokens)) add(inner);
|
|
185
|
+
}
|
|
186
|
+
return selectors;
|
|
187
|
+
}
|
|
188
|
+
function declaration({ arg, name, type, offset }) {
|
|
189
|
+
const functionRef = type === "select" ? {
|
|
190
|
+
type: "function",
|
|
191
|
+
name: "say:string"
|
|
192
|
+
} : {
|
|
193
|
+
type: "function",
|
|
194
|
+
name: "say:plural",
|
|
195
|
+
options: { options: literal({
|
|
196
|
+
offset,
|
|
197
|
+
ordinal: type === "selectordinal"
|
|
198
|
+
}) }
|
|
199
|
+
};
|
|
200
|
+
const value = {
|
|
201
|
+
type: "expression",
|
|
202
|
+
arg: {
|
|
203
|
+
type: "variable",
|
|
204
|
+
name: arg
|
|
205
|
+
},
|
|
206
|
+
functionRef
|
|
207
|
+
};
|
|
208
|
+
return name === arg ? {
|
|
209
|
+
type: "input",
|
|
210
|
+
name,
|
|
211
|
+
value
|
|
212
|
+
} : {
|
|
213
|
+
type: "local",
|
|
214
|
+
name,
|
|
215
|
+
value
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function sortKeys(keys) {
|
|
219
|
+
const rank = (key) => typeof key === "number" ? 0 : key === "other" ? 2 : 1;
|
|
220
|
+
return Array.from(new Set(keys)).sort((a, b) => rank(a) - rank(b));
|
|
221
|
+
}
|
|
222
|
+
function toMessage(ast) {
|
|
223
|
+
const selectors = findSelectors(ast);
|
|
224
|
+
if (selectors.length === 0) return {
|
|
225
|
+
type: "message",
|
|
226
|
+
declarations: [],
|
|
227
|
+
pattern: ast.map((t) => toPart(t, null))
|
|
228
|
+
};
|
|
229
|
+
let tuples = [[]];
|
|
230
|
+
for (const selector of selectors) {
|
|
231
|
+
const keys = sortKeys(selector.keys);
|
|
232
|
+
tuples = tuples.flatMap((tuple) => keys.map((key) => [...tuple, key]));
|
|
233
|
+
}
|
|
234
|
+
const variants = tuples.map((tuple) => ({
|
|
235
|
+
keys: tuple.map((key) => key === "other" ? { type: "*" } : {
|
|
236
|
+
type: "literal",
|
|
237
|
+
quoted: false,
|
|
238
|
+
value: String(key)
|
|
239
|
+
}),
|
|
240
|
+
value: []
|
|
241
|
+
}));
|
|
242
|
+
function fill(tokens, plural, filter) {
|
|
243
|
+
for (const token of tokens) {
|
|
244
|
+
if (isSelect(token)) {
|
|
245
|
+
const index = selectors.findIndex(sameSelector({
|
|
246
|
+
arg: token.arg,
|
|
247
|
+
type: token.type,
|
|
248
|
+
offset: token.pluralOffset ?? 0
|
|
249
|
+
}));
|
|
250
|
+
const inner = token.type === "select" ? plural : selectors[index].name;
|
|
251
|
+
for (const c of token.cases) {
|
|
252
|
+
const key = token.type === "select" ? c.key : asKey(c.key);
|
|
253
|
+
fill(c.tokens, inner, [...filter, {
|
|
254
|
+
index,
|
|
255
|
+
key
|
|
256
|
+
}]);
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
for (const variant of variants) {
|
|
261
|
+
if (!filter.every(({ index, key }) => {
|
|
262
|
+
const vk = variant.keys[index];
|
|
263
|
+
return vk.type === "*" ? key === "other" : String(key) === vk.value;
|
|
264
|
+
})) continue;
|
|
265
|
+
const part = toPart(token, plural);
|
|
266
|
+
const last = variant.value.length - 1;
|
|
267
|
+
if (typeof part === "string" && typeof variant.value[last] === "string") variant.value[last] += part;
|
|
268
|
+
else variant.value.push(part);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
fill(ast, null, []);
|
|
273
|
+
return {
|
|
274
|
+
type: "select",
|
|
275
|
+
declarations: selectors.map(declaration),
|
|
276
|
+
selectors: selectors.map((s) => ({
|
|
277
|
+
type: "variable",
|
|
278
|
+
name: s.name
|
|
279
|
+
})),
|
|
280
|
+
variants
|
|
281
|
+
};
|
|
21
282
|
}
|
|
22
283
|
//#endregion
|
|
23
|
-
//#region
|
|
24
|
-
function
|
|
25
|
-
|
|
284
|
+
//#region src/messageformat/values.ts
|
|
285
|
+
function part(type, locale, parts) {
|
|
286
|
+
const dir = getLocaleDir(locale);
|
|
287
|
+
/* v8 ignore next 3 */
|
|
288
|
+
return dir === "ltr" || dir === "rtl" ? {
|
|
289
|
+
type,
|
|
290
|
+
dir,
|
|
291
|
+
locale,
|
|
292
|
+
parts
|
|
293
|
+
} : {
|
|
294
|
+
type,
|
|
295
|
+
locale,
|
|
296
|
+
parts
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function numeric(operand) {
|
|
300
|
+
const value = typeof operand === "object" && operand !== null ? operand.valueOf() : operand;
|
|
301
|
+
if (typeof value === "bigint") return value;
|
|
302
|
+
const number = Number(value);
|
|
303
|
+
if (Number.isNaN(number) && value !== void 0) throw new Error("Input is not numeric");
|
|
304
|
+
return number;
|
|
305
|
+
}
|
|
306
|
+
function temporal(operand) {
|
|
307
|
+
let value = operand;
|
|
308
|
+
if (typeof value === "object" && value !== null && !(value instanceof Date)) value = value.valueOf();
|
|
309
|
+
if (typeof value === "number" || typeof value === "string") value = new Date(value);
|
|
310
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) throw new Error("Input is not a valid date");
|
|
311
|
+
return value;
|
|
26
312
|
}
|
|
313
|
+
function number(locales, value, opt) {
|
|
314
|
+
let nf;
|
|
315
|
+
let locale;
|
|
316
|
+
const format = () => nf ??= new Intl.NumberFormat(locales, opt);
|
|
317
|
+
const resolved = () => locale ??= format().resolvedOptions().locale;
|
|
318
|
+
return {
|
|
319
|
+
type: "number",
|
|
320
|
+
get dir() {
|
|
321
|
+
return getLocaleDir(resolved());
|
|
322
|
+
},
|
|
323
|
+
get options() {
|
|
324
|
+
return { ...opt };
|
|
325
|
+
},
|
|
326
|
+
toParts: () => [part("number", resolved(), format().formatToParts(value))],
|
|
327
|
+
toString: () => format().format(value),
|
|
328
|
+
valueOf: () => value
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function datetime(locales, value, opt) {
|
|
332
|
+
let dtf;
|
|
333
|
+
let locale;
|
|
334
|
+
const format = () => dtf ??= new Intl.DateTimeFormat(locales, opt);
|
|
335
|
+
const resolved = () => locale ??= format().resolvedOptions().locale;
|
|
336
|
+
return {
|
|
337
|
+
type: "datetime",
|
|
338
|
+
get dir() {
|
|
339
|
+
return getLocaleDir(resolved());
|
|
340
|
+
},
|
|
341
|
+
get options() {
|
|
342
|
+
return { ...opt };
|
|
343
|
+
},
|
|
344
|
+
toParts: () => [part("datetime", resolved(), format().formatToParts(value))],
|
|
345
|
+
toString: () => format().format(value),
|
|
346
|
+
valueOf: () => value
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function duration(seconds) {
|
|
350
|
+
if (!Number.isFinite(seconds)) return String(seconds);
|
|
351
|
+
const sign = seconds < 0 ? "-" : "";
|
|
352
|
+
const total = Math.round(Math.abs(seconds) * 1e3) / 1e3;
|
|
353
|
+
const secs = total % 60;
|
|
354
|
+
const minutes = Math.floor(total / 60);
|
|
355
|
+
const hours = Math.floor(minutes / 60);
|
|
356
|
+
const written = Math.round(secs) === secs ? String(secs) : secs.toFixed(3);
|
|
357
|
+
const parts = hours > 0 ? [
|
|
358
|
+
hours,
|
|
359
|
+
minutes % 60,
|
|
360
|
+
written
|
|
361
|
+
] : [minutes, written];
|
|
362
|
+
const first = parts.shift();
|
|
363
|
+
const pad = (n) => Number(n) < 10 ? `0${n}` : String(n);
|
|
364
|
+
return sign + [first, ...parts.map(pad)].join(":");
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/messageformat/functions.ts
|
|
368
|
+
const functions = {
|
|
369
|
+
"say:number": (ctx, opt, operand) => {
|
|
370
|
+
const { scale, ...nf } = options(opt.options);
|
|
371
|
+
const value = numeric(operand);
|
|
372
|
+
return number(ctx.locales, scale ? Number(value) * scale : value, nf);
|
|
373
|
+
},
|
|
374
|
+
"say:datetime": (ctx, opt, operand) => datetime(ctx.locales, temporal(operand), options(opt.options)),
|
|
375
|
+
"say:duration": (_ctx, _opt, operand) => {
|
|
376
|
+
const value = Number(numeric(operand));
|
|
377
|
+
const str = duration(value);
|
|
378
|
+
return {
|
|
379
|
+
type: "say:duration",
|
|
380
|
+
toParts: () => [{
|
|
381
|
+
type: "say:duration",
|
|
382
|
+
value: str
|
|
383
|
+
}],
|
|
384
|
+
toString: () => str,
|
|
385
|
+
valueOf: () => value
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
"say:plural": (ctx, opt, operand) => {
|
|
389
|
+
const { offset = 0, ordinal = false } = options(opt.options);
|
|
390
|
+
const value = numeric(operand);
|
|
391
|
+
const shifted = typeof value === "bigint" ? value - BigInt(offset) : value - offset;
|
|
392
|
+
const result = number(ctx.locales, shifted, {});
|
|
393
|
+
result.valueOf = () => value;
|
|
394
|
+
let rules;
|
|
395
|
+
result.selectKey = (keys) => {
|
|
396
|
+
const exact = String(value);
|
|
397
|
+
if (keys.has(exact)) return exact;
|
|
398
|
+
rules ??= new Intl.PluralRules(ctx.locales, {
|
|
399
|
+
localeMatcher: ctx.localeMatcher,
|
|
400
|
+
type: ordinal ? "ordinal" : "cardinal"
|
|
401
|
+
});
|
|
402
|
+
const category = rules.select(Number(shifted));
|
|
403
|
+
return keys.has(category) ? category : null;
|
|
404
|
+
};
|
|
405
|
+
return result;
|
|
406
|
+
},
|
|
407
|
+
"say:string": (_ctx, _opt, operand) => {
|
|
408
|
+
const str = operand === void 0 ? "" : String(operand);
|
|
409
|
+
return {
|
|
410
|
+
type: "string",
|
|
411
|
+
selectKey: (keys) => keys.has(str) ? str : null,
|
|
412
|
+
toParts: () => [{
|
|
413
|
+
type: "string",
|
|
414
|
+
value: str
|
|
415
|
+
}],
|
|
416
|
+
toString: () => str,
|
|
417
|
+
valueOf: () => str
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
};
|
|
27
421
|
//#endregion
|
|
28
|
-
//#region
|
|
29
|
-
function
|
|
30
|
-
return
|
|
422
|
+
//#region src/messageformat/index.ts
|
|
423
|
+
function compile(locale, source) {
|
|
424
|
+
return new MessageFormat(locale, toMessage(parse(source)), { functions });
|
|
31
425
|
}
|
|
32
426
|
//#endregion
|
|
33
427
|
//#region src/runtime.ts
|
|
34
|
-
let _Symbol$iterator;
|
|
35
|
-
let _Symbol$for;
|
|
36
|
-
/**
|
|
37
|
-
* Map a descriptor's keys back to the placeholders the message names. The
|
|
38
|
-
* transform emits every value with one underscore in front, which keeps a
|
|
39
|
-
* message's values out of the descriptor's own namespace, so stripping exactly
|
|
40
|
-
* one is the whole inverse — `_0` is `0`, and `__total` is a placeholder named
|
|
41
|
-
* `_total`. Keys without one are passed through, so a hand-written
|
|
42
|
-
* `call({ id, name })` still formats `{name}`.
|
|
43
|
-
*
|
|
44
|
-
* Built from the descriptor's own entries so a value named `__proto__` stays a
|
|
45
|
-
* placeholder rather than reaching through to the prototype.
|
|
46
|
-
*/
|
|
47
428
|
function resolveDescriptorValues(descriptor) {
|
|
48
429
|
return Object.fromEntries(Object.entries(descriptor).filter(([key]) => key !== "id").map(([key, value]) => [key.startsWith("_") ? key.slice(1) : key, value]));
|
|
49
430
|
}
|
|
50
|
-
var _locales = /* @__PURE__ */ new WeakMap();
|
|
51
|
-
var _loader = /* @__PURE__ */ new WeakMap();
|
|
52
|
-
var _messages = /* @__PURE__ */ new WeakMap();
|
|
53
|
-
var _formats = /* @__PURE__ */ new WeakMap();
|
|
54
|
-
var _active = /* @__PURE__ */ new WeakMap();
|
|
55
|
-
var _Say_brand = /* @__PURE__ */ new WeakSet();
|
|
56
|
-
_Symbol$iterator = Symbol.iterator;
|
|
57
|
-
_Symbol$for = Symbol.for("nodejs.util.inspect.custom");
|
|
58
431
|
var Say = class Say {
|
|
432
|
+
#locales;
|
|
433
|
+
#loader;
|
|
434
|
+
#messages;
|
|
435
|
+
#formats;
|
|
436
|
+
#active;
|
|
59
437
|
constructor(options) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
_classPrivateFieldInitSpec(this, _formats, void 0);
|
|
65
|
-
_classPrivateFieldInitSpec(this, _active, void 0);
|
|
66
|
-
_classPrivateFieldSet2(_locales, this, options.locales);
|
|
67
|
-
_classPrivateFieldSet2(_loader, this, options.loader);
|
|
68
|
-
_classPrivateFieldSet2(_messages, this, /* @__PURE__ */ new Map());
|
|
69
|
-
_classPrivateFieldSet2(_formats, this, /* @__PURE__ */ new Map());
|
|
438
|
+
this.#locales = options.locales;
|
|
439
|
+
this.#loader = options.loader;
|
|
440
|
+
this.#messages = /* @__PURE__ */ new Map();
|
|
441
|
+
this.#formats = /* @__PURE__ */ new Map();
|
|
70
442
|
if (options.messages) this.assign(options.messages);
|
|
71
443
|
}
|
|
72
|
-
/**
|
|
73
|
-
* The currently active locale.
|
|
74
|
-
*
|
|
75
|
-
* @throws If no locale is active
|
|
76
|
-
*/
|
|
77
444
|
get locale() {
|
|
78
|
-
if (!
|
|
79
|
-
return
|
|
445
|
+
if (!this.#active) throw new Error("No active locale");
|
|
446
|
+
return this.#active;
|
|
80
447
|
}
|
|
81
|
-
/**
|
|
82
|
-
* All available messages mapped by locale.
|
|
83
|
-
*
|
|
84
|
-
* @throws If no locale is active
|
|
85
|
-
* @throws If no messages are available for the active locale
|
|
86
|
-
*/
|
|
87
448
|
get messages() {
|
|
88
449
|
/* v8 ignore next */
|
|
89
|
-
if (!
|
|
90
|
-
return
|
|
450
|
+
if (!this.#messages.has(this.locale)) throw new Error("No messages loaded for locale");
|
|
451
|
+
return this.#messages.get(this.locale);
|
|
91
452
|
}
|
|
92
|
-
/**
|
|
93
|
-
* All available locales.
|
|
94
|
-
*/
|
|
95
453
|
get locales() {
|
|
96
|
-
return
|
|
454
|
+
return this.#locales;
|
|
97
455
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Loads messages for the given locales.
|
|
100
|
-
* If no locales are provided, all available locales are loaded.
|
|
101
|
-
* Requires a {@link Say.Loader} to be provided.
|
|
102
|
-
* If `loader` returns a promise, so will this method.
|
|
103
|
-
*
|
|
104
|
-
* @param locales Locales to load messages for, defaults to {@link Say.locales}
|
|
105
|
-
* @returns This
|
|
106
|
-
*/
|
|
107
456
|
load(...locales) {
|
|
108
457
|
if (Object.isFrozen(this)) throw new Error("Cannot load messages on a frozen Say");
|
|
109
|
-
if (locales.length === 0) locales =
|
|
458
|
+
if (locales.length === 0) locales = this.#locales;
|
|
110
459
|
const tasks = [];
|
|
111
460
|
for (const locale of locales) {
|
|
112
|
-
if (
|
|
113
|
-
if (!
|
|
114
|
-
const result =
|
|
461
|
+
if (this.#messages.has(locale)) continue;
|
|
462
|
+
if (!this.#loader) throw new Error("No loader provided, cannot load messages");
|
|
463
|
+
const result = this.#loader(locale);
|
|
115
464
|
if (result instanceof Promise) {
|
|
116
465
|
const task = result.then((m) => this.assign(locale, m));
|
|
117
466
|
tasks.push(task);
|
|
@@ -121,206 +470,75 @@ var Say = class Say {
|
|
|
121
470
|
}
|
|
122
471
|
assign(localeOrMessages, maybeMessages) {
|
|
123
472
|
if (Object.isFrozen(this)) throw new Error("Cannot assign messages on a frozen Say");
|
|
124
|
-
if (typeof localeOrMessages === "string")
|
|
125
|
-
else for (const locale in localeOrMessages)
|
|
473
|
+
if (typeof localeOrMessages === "string") this.#messages.set(localeOrMessages, maybeMessages);
|
|
474
|
+
else for (const locale in localeOrMessages) this.#messages.set(locale, localeOrMessages[locale]);
|
|
126
475
|
return this;
|
|
127
476
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Set the active locale.
|
|
130
|
-
*
|
|
131
|
-
* @param locale Locale to set
|
|
132
|
-
* @returns This
|
|
133
|
-
* @throws If locale is not available
|
|
134
|
-
*/
|
|
135
477
|
activate(locale) {
|
|
136
478
|
if (Object.isFrozen(this)) throw new Error("Cannot activate locale on a frozen Say");
|
|
137
|
-
if (!
|
|
138
|
-
|
|
479
|
+
if (!this.#messages.has(locale)) throw new Error("No messages loaded for locale");
|
|
480
|
+
this.#active = locale;
|
|
139
481
|
return this;
|
|
140
482
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Creates a clone of the Say instance, with the same locales and messages.
|
|
143
|
-
*
|
|
144
|
-
* @returns A clone of the Say instance
|
|
145
|
-
*/
|
|
146
483
|
clone() {
|
|
147
484
|
const copy = new Say({
|
|
148
|
-
locales:
|
|
149
|
-
messages: Object.fromEntries(
|
|
150
|
-
loader:
|
|
485
|
+
locales: this.#locales,
|
|
486
|
+
messages: Object.fromEntries(this.#messages),
|
|
487
|
+
loader: this.#loader
|
|
151
488
|
});
|
|
152
|
-
|
|
489
|
+
copy.#active = this.#active;
|
|
153
490
|
return copy;
|
|
154
491
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Make this `Say` instance immutable.
|
|
157
|
-
*/
|
|
158
492
|
freeze() {
|
|
159
493
|
return Object.freeze(this);
|
|
160
494
|
}
|
|
161
|
-
*[
|
|
162
|
-
for (const l of
|
|
495
|
+
*[Symbol.iterator]() {
|
|
496
|
+
for (const l of this.#locales) yield [this.clone().activate(l).freeze(), l];
|
|
163
497
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Matches the best locale from a list of guesses.
|
|
166
|
-
*
|
|
167
|
-
* @param guesses List of locale guesses
|
|
168
|
-
*
|
|
169
|
-
* @returns The best matching locale, or the first locale if no matches are found
|
|
170
|
-
*/
|
|
171
498
|
match(...guesses) {
|
|
172
499
|
const flat = guesses.flat();
|
|
173
|
-
if (flat.length === 0) return
|
|
500
|
+
if (flat.length === 0) return this.#locales[0];
|
|
174
501
|
for (const guess of flat) {
|
|
175
|
-
if (
|
|
502
|
+
if (this.#locales.includes(guess)) return guess;
|
|
176
503
|
const prefix = guess.split("-")[0];
|
|
177
504
|
if (!prefix) continue;
|
|
178
|
-
const match =
|
|
505
|
+
const match = this.#locales.find((l) => l.startsWith(prefix));
|
|
179
506
|
if (match) return match;
|
|
180
507
|
}
|
|
181
|
-
return
|
|
508
|
+
return this.#locales[0];
|
|
182
509
|
}
|
|
183
|
-
/**
|
|
184
|
-
* Get the translation for a descriptor.
|
|
185
|
-
*
|
|
186
|
-
* @param descriptor Descriptor to get the translation for
|
|
187
|
-
* @returns The translation string for the descriptor
|
|
188
|
-
* @throws If no locale is active
|
|
189
|
-
* @throws If no messages are available for the active locale
|
|
190
|
-
* @throws If descriptor id is not found
|
|
191
|
-
*/
|
|
192
510
|
call(descriptor) {
|
|
193
|
-
return
|
|
511
|
+
return this.#call(this.locale, this.messages, descriptor);
|
|
194
512
|
}
|
|
195
|
-
|
|
196
|
-
|
|
513
|
+
#call(locale, messages, descriptor) {
|
|
514
|
+
const message = messages[descriptor.id];
|
|
515
|
+
if (typeof message !== "string") throw new Error(`Message for ${descriptor.id} is not a string`);
|
|
516
|
+
const key = `${locale}:${descriptor.id}`;
|
|
517
|
+
const format = this.#formats.get(key) ?? this.#formats.set(key, compile(locale, message)).get(key);
|
|
518
|
+
return String(format.format(resolveDescriptorValues(descriptor)));
|
|
519
|
+
}
|
|
520
|
+
[Symbol.for("nodejs.util.inspect.custom")](_depth, context, inspect) {
|
|
521
|
+
if (this.#active) return `${this.constructor.name}<${inspect(this.#active, context)}> {}`;
|
|
197
522
|
else return `${this.constructor.name} {}`;
|
|
198
523
|
}
|
|
199
|
-
/**
|
|
200
|
-
* Define a pluralised message.
|
|
201
|
-
*
|
|
202
|
-
* @example
|
|
203
|
-
* ```ts
|
|
204
|
-
* say.plural(count, {
|
|
205
|
-
* one: 'You have 1 item',
|
|
206
|
-
* other: 'You have # items',
|
|
207
|
-
* })
|
|
208
|
-
* ```
|
|
209
|
-
*
|
|
210
|
-
* The `#` symbol inside options is replaced with the numeric value.
|
|
211
|
-
* @param _ Number to determine the plural form of
|
|
212
|
-
* @param options Pluralisation rules keyed by CLDR categories or specific numbers
|
|
213
|
-
* @returns The plural form of the number
|
|
214
|
-
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
215
|
-
*/
|
|
216
524
|
plural(_, options) {
|
|
217
525
|
throw new Error("'Say#plural' is a macro and must be used with the relevant saykit plugin");
|
|
218
526
|
}
|
|
219
|
-
/**
|
|
220
|
-
* Define an ordinal message (e.g. "1st", "2nd", "3rd").
|
|
221
|
-
* The `#` symbol inside options is replaced with the numeric value.
|
|
222
|
-
*
|
|
223
|
-
* @example
|
|
224
|
-
* ```ts
|
|
225
|
-
* say.ordinal(position, {
|
|
226
|
-
* 1: '#st',
|
|
227
|
-
* 2: '#nd',
|
|
228
|
-
* 3: '#rd',
|
|
229
|
-
* other: '#th',
|
|
230
|
-
* })
|
|
231
|
-
* ```
|
|
232
|
-
*
|
|
233
|
-
* @param _ Number to determine the ordinal form of
|
|
234
|
-
* @param options Ordinal rules keyed by CLDR categories or specific numbers
|
|
235
|
-
* @returns The ordinal form of the number
|
|
236
|
-
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
237
|
-
*/
|
|
238
527
|
ordinal(_, options) {
|
|
239
528
|
throw new Error("'Say#ordinal' is a macro and must be used with the relevant saykit plugin");
|
|
240
529
|
}
|
|
241
|
-
/**
|
|
242
|
-
* Define a select message, useful for handling gender, status, or other categories.
|
|
243
|
-
*
|
|
244
|
-
* @example
|
|
245
|
-
* ```ts
|
|
246
|
-
* say.select(gender, {
|
|
247
|
-
* male: 'He',
|
|
248
|
-
* female: 'She',
|
|
249
|
-
* other: 'They',
|
|
250
|
-
* })
|
|
251
|
-
* ```
|
|
252
|
-
*
|
|
253
|
-
* @param _ Selector value to determine which option is chosen
|
|
254
|
-
* @param options A mapping of possible selector values to message strings
|
|
255
|
-
* @returns The select form of the value
|
|
256
|
-
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
257
|
-
*/
|
|
258
530
|
select(_, options) {
|
|
259
531
|
throw new Error("'Say#select' is a macro and must be used with the relevant saykit plugin");
|
|
260
532
|
}
|
|
261
|
-
/**
|
|
262
|
-
* Format a number the way the active locale writes one, with its own grouping
|
|
263
|
-
* separators and decimal mark.
|
|
264
|
-
*
|
|
265
|
-
* Unlike `plural`, `ordinal`, and `select`, this is a fragment rather than a
|
|
266
|
-
* whole message, and is normally written inside one.
|
|
267
|
-
*
|
|
268
|
-
* @example
|
|
269
|
-
* ```ts
|
|
270
|
-
* say`You have ${say.number(items.length)} items`
|
|
271
|
-
* say`Battery at ${say.number(level, { style: 'percent' })}`
|
|
272
|
-
* say`Total: ${say.number({ cartTotal: getTotal() }, { style: '#,##0.00' })}`
|
|
273
|
-
* ```
|
|
274
|
-
*
|
|
275
|
-
* @param _ Number to format
|
|
276
|
-
* @param options Formatting style, either a named style or a literal number pattern
|
|
277
|
-
* @returns The formatted number
|
|
278
|
-
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
279
|
-
*/
|
|
280
533
|
number(_, options) {
|
|
281
534
|
throw new Error("'Say#number' is a macro and must be used with the relevant saykit plugin");
|
|
282
535
|
}
|
|
283
|
-
/**
|
|
284
|
-
* Format the date portion of a value the way the active locale writes one.
|
|
285
|
-
*
|
|
286
|
-
* @example
|
|
287
|
-
* ```ts
|
|
288
|
-
* say`Published ${say.date(post.publishedAt)}`
|
|
289
|
-
* say`Published ${say.date(post.publishedAt, { style: 'full' })}`
|
|
290
|
-
* ```
|
|
291
|
-
*
|
|
292
|
-
* @param _ Date to format
|
|
293
|
-
* @param options Formatting style
|
|
294
|
-
* @returns The formatted date
|
|
295
|
-
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
296
|
-
*/
|
|
297
536
|
date(_, options) {
|
|
298
537
|
throw new Error("'Say#date' is a macro and must be used with the relevant saykit plugin");
|
|
299
538
|
}
|
|
300
|
-
/**
|
|
301
|
-
* Format the time portion of a value the way the active locale writes one.
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```ts
|
|
305
|
-
* say`Doors open at ${say.time(opensAt)}`
|
|
306
|
-
* say`Doors open at ${say.time(opensAt, { style: 'short' })}`
|
|
307
|
-
* ```
|
|
308
|
-
*
|
|
309
|
-
* @param _ Date to format
|
|
310
|
-
* @param options Formatting style
|
|
311
|
-
* @returns The formatted time
|
|
312
|
-
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
313
|
-
*/
|
|
314
539
|
time(_, options) {
|
|
315
540
|
throw new Error("'Say#time' is a macro and must be used with the relevant saykit plugin");
|
|
316
541
|
}
|
|
317
542
|
};
|
|
318
|
-
function _call(locale, messages, descriptor) {
|
|
319
|
-
const message = messages[descriptor.id];
|
|
320
|
-
if (typeof message !== "string") throw new Error(`Message for ${descriptor.id} is not a string`);
|
|
321
|
-
const key = `${locale}:${descriptor.id}`;
|
|
322
|
-
const format = _classPrivateFieldGet2(_formats, this).get(key) ?? _classPrivateFieldGet2(_formats, this).set(key, mf1ToMessage(locale, message)).get(key);
|
|
323
|
-
return String(format.format(resolveDescriptorValues(descriptor)));
|
|
324
|
-
}
|
|
325
543
|
//#endregion
|
|
326
544
|
export { Say };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saykit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Type-safe i18n library with compile-time macro transforms",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"!dist/**/*.map"
|
|
26
26
|
],
|
|
27
27
|
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
28
29
|
"exports": {
|
|
29
30
|
".": {
|
|
30
31
|
"types": "./dist/runtime.d.mts",
|
|
@@ -36,7 +37,10 @@
|
|
|
36
37
|
"provenance": true
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
39
|
-
"@messageformat/
|
|
40
|
+
"@messageformat/date-skeleton": "2.0.0-0",
|
|
41
|
+
"@messageformat/number-skeleton": "2.0.0-0",
|
|
42
|
+
"@messageformat/parser": "^5.1.1",
|
|
43
|
+
"messageformat": "^4.0.0"
|
|
40
44
|
},
|
|
41
45
|
"scripts": {
|
|
42
46
|
"check": "tsc --noEmit",
|