saykit 0.11.1 → 0.13.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 +4 -4
- package/dist/index.d.mts +92 -35
- package/dist/index.mjs +211 -438
- package/package.json +3 -8
- package/skills/saykit/SKILL.md +375 -0
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://codecov.io/gh/k0d13/saykit?flags%5B0%5D=integration)
|
|
6
6
|
|
|
7
|
-
The core runtime for [SayKit](https://saykit.js.org). Exports `createCatalogue`, which holds your locales and where each one's messages come from, `createView`, which binds one locale and formats
|
|
7
|
+
The core runtime for [SayKit](https://saykit.js.org). Exports `createCatalogue`, which holds your locales and where each one's messages come from, `createView`, which binds one locale and formats its compiled messages, and `createStore`, which holds the current view and swaps it when you switch locale.
|
|
8
8
|
|
|
9
|
-
You author messages with the `` say`...` `` tagged template (and `say.plural`, `say.ordinal`, `say.select`), and format numbers, dates and times for the locale with `say.number`, `say.date` and `say.time`; a SayKit build-tool plugin rewrites them at build time into small runtime calls.
|
|
9
|
+
You author messages with the `` say`...` `` tagged template (and `say.plural`, `say.ordinal`, `say.select`), and format numbers, dates and times for the locale with `say.number`, `say.date` and `say.time`; a SayKit build-tool plugin rewrites them at build time into small runtime calls, and `saykit compile` turns each catalogue into a `.js` module of data that you import.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -20,8 +20,8 @@ You will normally also want [`@saykit/config`](https://github.com/k0d13/saykit/t
|
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
22
|
import { createCatalogue } from 'saykit';
|
|
23
|
-
import en from './locales/en.
|
|
24
|
-
import fr from './locales/fr.
|
|
23
|
+
import en from './locales/en.js';
|
|
24
|
+
import fr from './locales/fr.js';
|
|
25
25
|
|
|
26
26
|
const catalogue = createCatalogue({ en, fr });
|
|
27
27
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/**
|
|
2
|
+
* What each node in a compiled message is, by the number it starts with.
|
|
3
|
+
*
|
|
4
|
+
* The numbers are the format, so a kind is only ever appended, never renumbered or reused.
|
|
5
|
+
*/
|
|
6
|
+
export declare const Kind: {
|
|
7
|
+
readonly Value: 0;
|
|
8
|
+
readonly Number: 1;
|
|
9
|
+
readonly DateTime: 2;
|
|
10
|
+
readonly Plural: 3;
|
|
11
|
+
readonly Ordinal: 4;
|
|
12
|
+
readonly Select: 5;
|
|
13
|
+
readonly Hash: 6;
|
|
14
|
+
};
|
|
15
|
+
export type Kind = (typeof Kind)[keyof typeof Kind];
|
|
16
|
+
/**
|
|
17
|
+
* A compiled message.
|
|
18
|
+
*
|
|
19
|
+
* The CLI compiles a catalogue to this: strings and arrays, with styles already resolved to
|
|
20
|
+
* `Intl` options. It is plain JSON, so it can cross a server/client boundary.
|
|
21
|
+
*
|
|
22
|
+
* A node is an array starting with its {@link Kind}; an array starting with anything else is a
|
|
23
|
+
* concatenation. Operands are descriptor keys, never nodes. A message with no placeholders is its
|
|
24
|
+
* own string.
|
|
25
|
+
*/
|
|
26
|
+
type Message = string | Message[] | [typeof Kind.Value, string] | [typeof Kind.Number, string, Intl.NumberFormatOptions?, number?] | [typeof Kind.DateTime, string, Intl.DateTimeFormatOptions?] | [typeof Kind.Plural, string, Message.Cases, number?] | [typeof Kind.Ordinal, string, Message.Cases, number?] | [typeof Kind.Select, string, Message.Cases] | [typeof Kind.Hash];
|
|
27
|
+
declare namespace Message {
|
|
28
|
+
/**
|
|
29
|
+
* A plural's or a select's branches, by key. A plural's keys are CLDR
|
|
30
|
+
* categories or an exact `=n`, and `other` is the fallback for both.
|
|
31
|
+
*/
|
|
32
|
+
type Cases = {
|
|
33
|
+
[key: string]: Message;
|
|
34
|
+
};
|
|
35
|
+
/** What a compiled message is called with: the descriptor the transform built. */
|
|
36
|
+
type Values = Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compile one message into the function that formats it.
|
|
40
|
+
*
|
|
41
|
+
* Called once per message per locale; a view memoises the result.
|
|
42
|
+
*
|
|
43
|
+
* @param message The compiled message, as the CLI emitted it
|
|
44
|
+
* @param locale The locale to format in
|
|
45
|
+
* @param options Whether written values are wrapped in bidi isolation marks
|
|
46
|
+
* @returns A function from a descriptor to the formatted string
|
|
47
|
+
*/
|
|
48
|
+
export declare function compileMessage(message: Message, locale: string, options?: {
|
|
49
|
+
bidiIsolation?: 'default' | 'none';
|
|
50
|
+
}): (values: Message.Values) => string;
|
|
51
|
+
export type Tuple = [any, ...any[]];
|
|
52
|
+
export type Disallow<T, K extends PropertyKey> = T & Partial<Record<K, never>>;
|
|
53
|
+
export type Awaitable<T> = T | PromiseLike<T>;
|
|
5
54
|
/**
|
|
6
55
|
* A value wrapped in the name its ICU placeholder should take, written inline
|
|
7
56
|
* as a single-key object: `` say`Total: ${{ cartTotal: getTotal() }}` ``. The
|
|
8
57
|
* transform reads the key at build time and compiles only the value, so this
|
|
9
58
|
* is never a real object at runtime.
|
|
10
59
|
*/
|
|
11
|
-
type Named<T> = {
|
|
60
|
+
export type Named<T> = {
|
|
12
61
|
[name: string]: T;
|
|
13
62
|
};
|
|
14
63
|
/**
|
|
@@ -17,7 +66,7 @@ type Named<T> = {
|
|
|
17
66
|
* `Branch` is what a case may be written as: text everywhere the message is
|
|
18
67
|
* text, widening in JSX, where a case showing the number has to be a fragment.
|
|
19
68
|
*/
|
|
20
|
-
interface NumeralOptions<Branch = string> extends Omit<Partial<Record<Intl.LDMLPluralRule, Branch>>, 'other'> {
|
|
69
|
+
export interface NumeralOptions<Branch = string> extends Omit<Partial<Record<Intl.LDMLPluralRule, Branch>>, 'other'> {
|
|
21
70
|
other: Branch;
|
|
22
71
|
[digit: number]: Branch;
|
|
23
72
|
/**
|
|
@@ -26,7 +75,7 @@ interface NumeralOptions<Branch = string> extends Omit<Partial<Record<Intl.LDMLP
|
|
|
26
75
|
*/
|
|
27
76
|
offset?: number;
|
|
28
77
|
}
|
|
29
|
-
interface SelectOptions<Branch = string> {
|
|
78
|
+
export interface SelectOptions<Branch = string> {
|
|
30
79
|
other: Branch;
|
|
31
80
|
[match: string | number]: Branch;
|
|
32
81
|
}
|
|
@@ -38,35 +87,38 @@ interface SelectOptions<Branch = string> {
|
|
|
38
87
|
* A skeleton is where the formats the named styles have no word for live:
|
|
39
88
|
* currency, compact notation, a year and month with no day.
|
|
40
89
|
*/
|
|
41
|
-
type Skeleton = `::${string}`;
|
|
90
|
+
export type Skeleton = `::${string}`;
|
|
42
91
|
/**
|
|
43
92
|
* Formatting for a `{arg, number}` placeholder.
|
|
44
93
|
*
|
|
45
94
|
* A literal `NumberFormat` pattern such as `#,##0.00` is also accepted, for the
|
|
46
95
|
* cases neither the named styles nor a skeleton spell more clearly.
|
|
47
96
|
*/
|
|
48
|
-
interface NumberOptions {
|
|
97
|
+
export interface NumberOptions {
|
|
49
98
|
style?: 'integer' | 'percent' | Skeleton | (string & {});
|
|
50
99
|
}
|
|
51
100
|
/** Formatting for a `{arg, date}` or `{arg, time}` placeholder. */
|
|
52
|
-
interface DateTimeOptions {
|
|
101
|
+
export interface DateTimeOptions {
|
|
53
102
|
style?: 'short' | 'medium' | 'long' | 'full' | Skeleton;
|
|
54
103
|
}
|
|
55
|
-
//#endregion
|
|
56
|
-
//#region src/view.d.ts
|
|
57
104
|
declare namespace View {
|
|
105
|
+
/**
|
|
106
|
+
* One locale's messages, compiled.
|
|
107
|
+
*
|
|
108
|
+
* Each is a {@link Message} the CLI compiled from the catalogue. They are plain data, so a
|
|
109
|
+
* server can hand them to a client tree.
|
|
110
|
+
*/
|
|
58
111
|
type Messages = {
|
|
59
|
-
[key: string]:
|
|
112
|
+
[key: string]: Message;
|
|
60
113
|
};
|
|
61
114
|
/**
|
|
62
|
-
* What the transform compiles a message down to:
|
|
63
|
-
*
|
|
64
|
-
* its values behind one underscore each.
|
|
115
|
+
* What the transform compiles a message down to: its extracted id, or the compiled message
|
|
116
|
+
* itself when there was nothing to extract. Values sit behind one underscore each.
|
|
65
117
|
*/
|
|
66
118
|
type Descriptor = ({
|
|
67
119
|
id: string;
|
|
68
120
|
} | {
|
|
69
|
-
message:
|
|
121
|
+
message: Message;
|
|
70
122
|
}) & {
|
|
71
123
|
[match: string | number]: unknown;
|
|
72
124
|
};
|
|
@@ -137,9 +189,8 @@ interface View<Locale extends string = string> {
|
|
|
137
189
|
/**
|
|
138
190
|
* Format the message a descriptor names, or the message it carries.
|
|
139
191
|
*
|
|
140
|
-
* A message with nothing to translate, a lone `say.date(x)
|
|
141
|
-
*
|
|
142
|
-
* instead of an id.
|
|
192
|
+
* A message with nothing to translate, such as a lone `say.date(x)`, is never extracted:
|
|
193
|
+
* the transform writes the compiled message into the call instead of an id.
|
|
143
194
|
*
|
|
144
195
|
* @param descriptor Descriptor to format
|
|
145
196
|
* @returns The formatted message
|
|
@@ -265,18 +316,14 @@ interface View<Locale extends string = string> {
|
|
|
265
316
|
* Create a view over one locale and the messages it formats against.
|
|
266
317
|
*
|
|
267
318
|
* A catalogue memoises one per locale, which is how application code usually
|
|
268
|
-
* reaches one; a single-locale app can build one directly.
|
|
269
|
-
* belongs to the view, so a view built over one set of messages can never be
|
|
270
|
-
* served a format compiled from another.
|
|
319
|
+
* reaches one; a single-locale app can build one directly.
|
|
271
320
|
*
|
|
272
321
|
* @param locale The locale to bind to
|
|
273
322
|
* @param messages The messages this view formats against
|
|
274
323
|
* @param options How this view formats its messages
|
|
275
324
|
* @returns The view
|
|
276
325
|
*/
|
|
277
|
-
declare function createView<Locale extends string>(locale: Locale, messages: View.Messages, options?: View.Options): View<Locale>;
|
|
278
|
-
//#endregion
|
|
279
|
-
//#region src/catalogue.d.ts
|
|
326
|
+
export declare function createView<Locale extends string>(locale: Locale, messages: View.Messages, options?: View.Options): View<Locale>;
|
|
280
327
|
declare namespace Catalogue {
|
|
281
328
|
/**
|
|
282
329
|
* One guess at a locale, or several. Guesses are read from cookies, headers
|
|
@@ -355,8 +402,23 @@ interface Catalogue<Locale extends string = string> {
|
|
|
355
402
|
/**
|
|
356
403
|
* Matches the best locale from a list of guesses.
|
|
357
404
|
*
|
|
405
|
+
* Guesses are tried in order, and an earlier guess that matches at all wins
|
|
406
|
+
* over a later one that would match more closely. Each guess is
|
|
407
|
+
* canonicalised first, so case does not matter (`en-us` is `en-US`), and
|
|
408
|
+
* then compared in steps, most specific first:
|
|
409
|
+
*
|
|
410
|
+
* 1. The same tag
|
|
411
|
+
* 2. The same language and script, filling in the script a tag implies, so
|
|
412
|
+
* `zh-HK` (Traditional) finds `zh-TW` rather than `zh-CN`
|
|
413
|
+
* 3. The same language subtag, whole, so `fi` never matches `fil`
|
|
414
|
+
*
|
|
415
|
+
* Within a step, the locale written first wins, so with `pt-BR` before
|
|
416
|
+
* `pt-PT`, `pt` matches `pt-BR`.
|
|
417
|
+
*
|
|
358
418
|
* An absent or empty guess is skipped rather than throwing, which lets a
|
|
359
419
|
* caller write `match(fromCookie, fromHeader)` without narrowing each first.
|
|
420
|
+
* So is one that is not a well-formed locale tag, unless it is written
|
|
421
|
+
* exactly as one of {@link locales}.
|
|
360
422
|
*
|
|
361
423
|
* @param guesses List of locale guesses
|
|
362
424
|
* @returns The best matching locale, or the first of {@link locales} if no
|
|
@@ -391,9 +453,7 @@ interface Catalogue<Locale extends string = string> {
|
|
|
391
453
|
* messages, such as `{ bidiIsolation: 'none' }`
|
|
392
454
|
* @returns The catalogue
|
|
393
455
|
*/
|
|
394
|
-
declare function createCatalogue<const Locale extends string = string>(messages: Record<Locale, Catalogue.Source>, options?: Catalogue.Options): Catalogue<Locale>;
|
|
395
|
-
//#endregion
|
|
396
|
-
//#region src/store.d.ts
|
|
456
|
+
export declare function createCatalogue<const Locale extends string = string>(messages: Record<Locale, Catalogue.Source>, options?: Catalogue.Options): Catalogue<Locale>;
|
|
397
457
|
declare namespace Store {
|
|
398
458
|
/** Called after a successful switch, with the view that is now current. */
|
|
399
459
|
type Listener<Locale extends string> = (view: View<Locale>) => void;
|
|
@@ -474,9 +534,7 @@ interface Store<Locale extends string = string> {
|
|
|
474
534
|
* @returns The store
|
|
475
535
|
* @throws If the starting locale has no messages loaded
|
|
476
536
|
*/
|
|
477
|
-
declare function createStore<Locale extends string>(catalogue: Catalogue<Locale>, locale?: Locale): Store<Locale>;
|
|
478
|
-
//#endregion
|
|
479
|
-
//#region src/scope.d.ts
|
|
537
|
+
export declare function createStore<Locale extends string>(catalogue: Catalogue<Locale>, locale?: Locale): Store<Locale>;
|
|
480
538
|
declare namespace Scope {
|
|
481
539
|
/**
|
|
482
540
|
* Where a scope holds the view a piece of work is running under.
|
|
@@ -585,6 +643,5 @@ interface Scope {
|
|
|
585
643
|
* browser wants
|
|
586
644
|
* @returns The scope
|
|
587
645
|
*/
|
|
588
|
-
declare function createScope(storage?: Scope.Storage): Scope;
|
|
589
|
-
|
|
590
|
-
export { Awaitable, type Catalogue, DateTimeOptions, Disallow, Named, NumberOptions, NumeralOptions, type Scope, SelectOptions, Skeleton, type Store, Tuple, type View, createCatalogue, createScope, createStore, createView };
|
|
646
|
+
export declare function createScope(storage?: Scope.Storage): Scope;
|
|
647
|
+
export type { Catalogue, Message, Scope, Store, View };
|