prosebook 0.0.1 → 0.2.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/LICENSE +21 -0
- package/README.md +604 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +508 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-D--RITk2.js +1208 -0
- package/dist/config-D--RITk2.js.map +1 -0
- package/dist/config-_4HfnJQu.d.ts +517 -0
- package/dist/config-_4HfnJQu.d.ts.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +1 -0
- package/dist/copy-DXnzXsJa.d.ts +162 -0
- package/dist/copy-DXnzXsJa.d.ts.map +1 -0
- package/dist/helpers-BPuPGjQP.js +61 -0
- package/dist/helpers-BPuPGjQP.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +188 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +37 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/inspector.js +572 -0
- package/dist/inspector.js.map +1 -0
- package/dist/react/index.d.ts +37 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +86 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sarif-CI8HOokm.js +1033 -0
- package/dist/sarif-CI8HOokm.js.map +1 -0
- package/dist/scan-Ch2BfIUD.js +2561 -0
- package/dist/scan-Ch2BfIUD.js.map +1 -0
- package/dist/tools.d.ts +377 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +4 -0
- package/dist/vite.d.ts +24 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +264 -0
- package/dist/vite.js.map +1 -0
- package/package.json +148 -15
- package/src/cli.ts +528 -0
- package/src/index.ts +31 -0
- package/src/inspector-dialog.ts +450 -0
- package/src/inspector.ts +228 -0
- package/src/react/index.tsx +112 -0
- package/src/runtime/copy.ts +221 -0
- package/src/runtime/helpers.ts +66 -0
- package/src/runtime/locale.ts +49 -0
- package/src/runtime/types.ts +106 -0
- package/src/tools/catalog.ts +449 -0
- package/src/tools/compile.ts +121 -0
- package/src/tools/config.ts +211 -0
- package/src/tools/diff.ts +84 -0
- package/src/tools/entry.ts +325 -0
- package/src/tools/extract.ts +694 -0
- package/src/tools/generate.ts +257 -0
- package/src/tools/icu.ts +211 -0
- package/src/tools/index.ts +91 -0
- package/src/tools/lint.ts +865 -0
- package/src/tools/lock.ts +95 -0
- package/src/tools/mf1.ts +246 -0
- package/src/tools/mf2.ts +752 -0
- package/src/tools/migrate.ts +138 -0
- package/src/tools/presets.ts +159 -0
- package/src/tools/pseudo.ts +40 -0
- package/src/tools/sarif.ts +80 -0
- package/src/tools/scan.ts +303 -0
- package/src/tools/syntax.ts +24 -0
- package/src/tools/types.ts +137 -0
- package/src/vite.ts +296 -0
- package/index.js +0 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes the generated catalog and the runtime agree on.
|
|
3
|
+
*
|
|
4
|
+
* A catalog is compiled at build time: every ICU message becomes either a string (no placeholders)
|
|
5
|
+
* or a small function over its arguments and a set of locale-bound helpers. The runtime never parses
|
|
6
|
+
* ICU, so it carries no parser, needs no `eval`, and a message costs a property read or one call.
|
|
7
|
+
*/
|
|
8
|
+
/** What the generator knows about one message: the arguments it takes and the tags it wraps text in. */
|
|
9
|
+
interface MessageShape {
|
|
10
|
+
args: object;
|
|
11
|
+
tags: string;
|
|
12
|
+
}
|
|
13
|
+
/** Every key of a catalog, mapped to its shape. The generated `Messages` interface is one of these. */
|
|
14
|
+
type MessageShapes = {
|
|
15
|
+
[key: string]: MessageShape;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Augmented by the generated module, so hooks and components that are not handed a catalog
|
|
19
|
+
* (`useCopy()`, `<Text>`) still know its keys:
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* declare module 'prosebook' {
|
|
23
|
+
* interface Register { messages: Messages; locale: Locale }
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* Add `tags: 'b' | 'link'` to say which tags the provider renders by default, so a call site
|
|
28
|
+
* needs to supply only the others.
|
|
29
|
+
*/
|
|
30
|
+
interface Register {}
|
|
31
|
+
type RegisteredMessages = Register extends {
|
|
32
|
+
messages: infer M extends MessageShapes;
|
|
33
|
+
} ? M : MessageShapes;
|
|
34
|
+
type RegisteredLocale = Register extends {
|
|
35
|
+
locale: infer L extends string;
|
|
36
|
+
} ? L : string;
|
|
37
|
+
/** Tags the provider renders unless a call site says otherwise. */
|
|
38
|
+
type DefaultTags = Register extends {
|
|
39
|
+
tags: infer T extends string;
|
|
40
|
+
} ? T : never;
|
|
41
|
+
/**
|
|
42
|
+
* The helpers a compiled message calls, bound to the locale its words are in. Names are one letter
|
|
43
|
+
* because they appear once per placeholder in every generated module.
|
|
44
|
+
*/
|
|
45
|
+
interface Helpers {
|
|
46
|
+
readonly locale: string;
|
|
47
|
+
/** `{x, number, …}` and the `#` of a plural. */
|
|
48
|
+
n(value: number, options?: Intl.NumberFormatOptions): string;
|
|
49
|
+
/** `{x, date, …}` and `{x, time, …}`. */
|
|
50
|
+
d(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
51
|
+
/** `{x, plural, …}` and `{x, selectordinal, …}`: picks an arm and renders it. */
|
|
52
|
+
p(value: number, offset: number, ordinal: boolean, arms: Arms): unknown;
|
|
53
|
+
/** `{x, select, …}`. */
|
|
54
|
+
s(value: unknown, arms: Arms): unknown;
|
|
55
|
+
/** `<tag>…</tag>`: the tag's function from the values, or the bare children when there is none. */
|
|
56
|
+
t(values: Values, tag: string, children: unknown): unknown;
|
|
57
|
+
}
|
|
58
|
+
type Arms = {
|
|
59
|
+
[arm: string]: () => unknown;
|
|
60
|
+
};
|
|
61
|
+
type Values = {
|
|
62
|
+
readonly [name: string]: any;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Told of every message read: its key, the text it came to and the locale it was found in, which is
|
|
66
|
+
* the fallback's when the current locale lacks it. The Vite plugin's dev inspector sets one on
|
|
67
|
+
* `globalThis` to map text on screen back to keys; nothing sets it in a build.
|
|
68
|
+
*/
|
|
69
|
+
type ReadHook = (key: string, text: string, locale: string) => void;
|
|
70
|
+
/** Hands a copy a locale's messages, regenerated after a catalog edit, in place of the ones it loaded. */
|
|
71
|
+
type HotHook = (locale: string, messages: CompiledMessages) => void;
|
|
72
|
+
/**
|
|
73
|
+
* What the Vite plugin sets on `globalThis` in dev. `__PROSEBOOK_HOT__` is there before the app runs,
|
|
74
|
+
* so each copy adds itself, and the generated modules call every copy when they are updated.
|
|
75
|
+
*/
|
|
76
|
+
type DevHooks = {
|
|
77
|
+
__PROSEBOOK_READ__?: ReadHook;
|
|
78
|
+
__PROSEBOOK_HOT__?: Set<HotHook>;
|
|
79
|
+
};
|
|
80
|
+
/** One compiled message. */
|
|
81
|
+
type Compiled = string | ((values: Values, h: Helpers) => unknown);
|
|
82
|
+
/** One locale's compiled messages: what a generated `<locale>.ts` module exports by default. */
|
|
83
|
+
type CompiledMessages = {
|
|
84
|
+
readonly [key: string]: Compiled;
|
|
85
|
+
};
|
|
86
|
+
/** What a tag's function receives: the pieces it wraps, adjacent text already joined. */
|
|
87
|
+
type Chunks<N> = (string | N)[];
|
|
88
|
+
type TagFn<N> = (chunks: Chunks<N>) => N;
|
|
89
|
+
/** A tag renderer for any kind of node, as configured once on a `Copy`. */
|
|
90
|
+
type AnyTagFn = (chunks: never) => unknown;
|
|
91
|
+
type NoArgs<T> = {} extends T ? true : false;
|
|
92
|
+
/** The arguments of a plain read: optional when the message takes none. */
|
|
93
|
+
type ArgsParam<S extends MessageShape> = NoArgs<S['args']> extends true ? [args?: S['args']] : [args: S['args']];
|
|
94
|
+
type TagValues<S extends MessageShape, N> = { [T in Exclude<S['tags'], DefaultTags>]: TagFn<N>; } & { [T in Extract<S['tags'], DefaultTags>]?: TagFn<N>; };
|
|
95
|
+
/** A rich read takes the message's arguments and a function per tag it wraps, beside the defaults. */
|
|
96
|
+
type RichValues<S extends MessageShape, N> = S['args'] & TagValues<S, N>;
|
|
97
|
+
type RichParam<S extends MessageShape, N> = NoArgs<RichValues<S, N>> extends true ? [values?: RichValues<S, N>] : [values: RichValues<S, N>];
|
|
98
|
+
/** The keys whose message wraps part of itself in a tag. */
|
|
99
|
+
type RichKey<M extends MessageShapes> = { [K in keyof M]: [M[K]['tags']] extends [never] ? never : K; }[keyof M] & string;
|
|
100
|
+
type Loader = () => Promise<{
|
|
101
|
+
default: CompiledMessages;
|
|
102
|
+
}>;
|
|
103
|
+
/** A generated catalog: which locales exist, which ship in the bundle and how to fetch the rest. */
|
|
104
|
+
interface Catalog<M extends MessageShapes = MessageShapes, L extends string = string> {
|
|
105
|
+
/** The locale the catalog is written in, which every other falls back to. */
|
|
106
|
+
readonly source: L;
|
|
107
|
+
readonly locales: readonly L[];
|
|
108
|
+
/** Locales whose messages ship in the bundle. Always holds the source. */
|
|
109
|
+
readonly messages: { readonly [locale in L]?: CompiledMessages; };
|
|
110
|
+
/** Locales fetched when first asked for. */
|
|
111
|
+
readonly loaders: { readonly [locale in L]?: Loader; };
|
|
112
|
+
/** Type-only: carries the message shapes from the generated module to `createCopy`. */
|
|
113
|
+
readonly '~messages'?: M;
|
|
114
|
+
}
|
|
115
|
+
/** Ties a generated catalog to its message shapes. Called by generated code. */
|
|
116
|
+
declare function defineCatalog<M extends MessageShapes, L extends string = string>(catalog: Omit<Catalog<M, L>, '~messages'>): Catalog<M, L>;
|
|
117
|
+
interface CopyOptions<L extends string> {
|
|
118
|
+
/** The locale to start in, or the locales a reader prefers, most preferred first. Defaults to the source. */
|
|
119
|
+
locale?: L | (string & {}) | readonly string[];
|
|
120
|
+
/** Tag renderers every rich read gets unless it supplies its own, such as `b` or `link`. */
|
|
121
|
+
tags?: {
|
|
122
|
+
[tag: string]: AnyTagFn;
|
|
123
|
+
};
|
|
124
|
+
/** What a key the catalog lacks reads as. Defaults to the key itself. */
|
|
125
|
+
onMissing?: (key: string, locale: L) => string;
|
|
126
|
+
}
|
|
127
|
+
/** Its methods don't use `this`, so they can be passed around and destructured: `const { t } = copy`. */
|
|
128
|
+
interface Copy<M extends MessageShapes = MessageShapes, L extends string = string> {
|
|
129
|
+
/** The locale messages are coming back in. */
|
|
130
|
+
readonly locale: L;
|
|
131
|
+
readonly locales: readonly L[];
|
|
132
|
+
readonly source: L;
|
|
133
|
+
/** The tag renderers rich reads fall back to, from `CopyOptions.tags`. */
|
|
134
|
+
readonly tags: {
|
|
135
|
+
readonly [tag: string]: AnyTagFn;
|
|
136
|
+
};
|
|
137
|
+
/** Settles once the starting locale's messages are loaded. */
|
|
138
|
+
readonly ready: Promise<void>;
|
|
139
|
+
/** A message as plain text. Tags render as the text they wrap. */
|
|
140
|
+
t<K extends keyof M & string>(this: void, key: K, ...args: ArgsParam<M[K]>): string;
|
|
141
|
+
/** A message with elements inside it: each tag's function turns what it wraps into a node. */
|
|
142
|
+
rich<K extends RichKey<M>, N = unknown>(this: void, key: K, ...values: RichParam<M[K], N>): Chunks<N>;
|
|
143
|
+
has(this: void, key: string): key is keyof M & string;
|
|
144
|
+
/** Switches locale once its messages (and its fallbacks') are loaded. */
|
|
145
|
+
setLocale(this: void, next: L | (string & {}) | readonly string[]): Promise<void>;
|
|
146
|
+
/** Counts changes to what messages read as: each locale switch, and each catalog edit in dev. */
|
|
147
|
+
readonly version: number;
|
|
148
|
+
/** Called after each locale switch, and in dev after each catalog edit. Returns the unsubscribe. */
|
|
149
|
+
subscribe(this: void, listener: () => void): () => void;
|
|
150
|
+
/** Formats a number in the current locale. */
|
|
151
|
+
number(this: void, value: number | bigint, options?: Intl.NumberFormatOptions): string;
|
|
152
|
+
/** Formats a date in the current locale. */
|
|
153
|
+
date(this: void, value: Date | number, options?: Intl.DateTimeFormatOptions): string;
|
|
154
|
+
/** Joins items as a list in the current locale: `a, b and c`. */
|
|
155
|
+
list(this: void, items: Iterable<string>, options?: Intl.ListFormatOptions): string;
|
|
156
|
+
/** `in 3 days`, `2 hours ago`, in the current locale. */
|
|
157
|
+
relative(this: void, value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
|
|
158
|
+
}
|
|
159
|
+
/** A catalog reader with a current locale. Create one per app, or one per request on a server. */
|
|
160
|
+
declare function createCopy<M extends MessageShapes, L extends string>(catalog: Catalog<M, L>, options?: CopyOptions<L>): Copy<M, L>;
|
|
161
|
+
export { RichValues as C, RichParam as S, ReadHook as _, defineCatalog as a, RegisteredMessages as b, Chunks as c, DefaultTags as d, DevHooks as f, MessageShapes as g, MessageShape as h, createCopy as i, Compiled as l, HotHook as m, Copy as n, AnyTagFn as o, Helpers as p, CopyOptions as r, ArgsParam as s, Catalog as t, CompiledMessages as u, Register as v, TagFn as w, RichKey as x, RegisteredLocale as y };
|
|
162
|
+
//# sourceMappingURL=copy-DXnzXsJa.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy-DXnzXsJa.d.ts","names":[],"sources":["../src/runtime/types.ts","../src/runtime/copy.ts"],"mappings":";;;;;;;;UASiB;EACf;EACA;;;KAIU;GAAmB,cAAc;;;;;;;;;;;;;;;UAgB5B;KAEL,qBAAqB;EAAmB,gBAAgB,UAAU;IAAkB,IAAI;KACxF,mBAAmB;EAAmB,cAAc;IAAqB;;KAEzE,cAAc;EAAmB,YAAY;IAAqB;;;;;UAM7D;WACN;;EAET,EAAE,eAAe,UAAU,KAAK;;EAEhC,EAAE,OAAO,eAAe,UAAU,KAAK;;EAEvC,EAAE,eAAe,gBAAgB,kBAAkB,MAAM;;EAEzD,EAAE,gBAAgB,MAAM;;EAExB,EAAE,QAAQ,QAAQ,aAAa;;KAGrB;GAAU;;KAIV;YAAqB;;;;;;;KAOrB,YAAY,aAAa,cAAc;;KAGvC,WAAW,gBAAgB,UAAU;;;;;KAMrC;EAAa,qBAAqB;EAAU,oBAAoB,IAAI;;;KAGpE,sBAAsB,QAAQ,QAAQ,GAAG;;KAGzC;YAA+B,cAAc;;;KAG7C,OAAO,eAAe;KACtB,MAAM,MAAM,QAAQ,OAAO,OAAO;;KAElC,YAAY;KAEnB,OAAO,gBAAgB;;KAGhB,UAAU,UAAU,gBAAgB,OAAO,2BAA2B,OAAO,cAAc,MAAM;KAExG,UAAU,UAAU,cAAc,QAAQ,KAAK,QAAQ,WAAW,eAAe,MAAM,WACzF,KAAK,QAAQ,WAAW,gBAAgB,MAAM;;KAIrC,WAAW,UAAU,cAAc,KAAK,YAAY,UAAU,GAAG;KACjE,UAAU,UAAU,cAAc,KAC5C,OAAO,WAAW,GAAG,oBAAoB,SAAS,WAAW,GAAG,OAAO,QAAQ,WAAW,GAAG;;KAGnF,QAAQ,UAAU,oBAAoB,WAAW,KAAK,EAAE,sCAAsC,WAAU;KCzF/G,eAAe;EAAU,SAAS;;;UAGtB,QAAQ,UAAU,gBAAgB,eAAe;;WAEvD,QAAQ;WACR,kBAAkB;;WAElB,sBAAsB,UAAU,KAAK;;WAErC,qBAAqB,UAAU,KAAK;;WAEpC,cAAc;;;iBAIT,cAAc,UAAU,eAAe,2BACrD,SAAS,KAAK,QAAQ,GAAG,mBACxB,QAAQ,GAAG;UAIG,YAAY;;EAE3B,SAAS;;EAET;KAAU,cAAc;;;EAExB,aAAa,aAAa,QAAQ;;;UAInB,KAAK,UAAU,gBAAgB,eAAe;;WAEpD,QAAQ;WACR,kBAAkB;WAClB,QAAQ;;WAER;cAAkB,cAAc;;;WAEhC,OAAO;;EAEhB,EAAE,gBAAgB,YAAY,YAAY,KAAK,MAAM,MAAM,UAAU,EAAE;;EAEvE,KAAK,UAAU,QAAQ,IAAI,aAAa,YAAY,KAAK,MAAM,QAAQ,UAAU,EAAE,IAAI,KAAK,OAAO;EACnG,IAAI,YAAY,cAAc,aAAa;;EAE3C,UAAU,YAAY,MAAM,wCAAwC;;WAE3D;;EAET,UAAU,YAAY;;EAEtB,OAAO,YAAY,wBAAwB,UAAU,KAAK;;EAE1D,KAAK,YAAY,OAAO,eAAe,UAAU,KAAK;;EAEtD,KAAK,YAAY,OAAO,kBAAkB,UAAU,KAAK;;EAEzD,SAAS,YAAY,eAAe,MAAM,KAAK,wBAAwB,UAAU,KAAK;;;iBAaxE,WAAW,UAAU,eAAe,kBAAkB,SAAS,QAAQ,GAAG,IAAI,UAAS,YAAY,KAAU,KAAK,GAAG"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// src/runtime/helpers.ts
|
|
2
|
+
/**
|
|
3
|
+
* The helpers compiled messages call, for one locale. Formatters are built once per options object:
|
|
4
|
+
* the generator hoists each message's options to a module-level constant, so identity is a stable key.
|
|
5
|
+
*/
|
|
6
|
+
function createHelpers(locale) {
|
|
7
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
8
|
+
const dates = /* @__PURE__ */ new Map();
|
|
9
|
+
let cardinal;
|
|
10
|
+
let ordinal;
|
|
11
|
+
return {
|
|
12
|
+
locale,
|
|
13
|
+
n(value, options) {
|
|
14
|
+
let format = numbers.get(options);
|
|
15
|
+
if (format === void 0) numbers.set(options, format = new Intl.NumberFormat(locale, options));
|
|
16
|
+
return format.format(value);
|
|
17
|
+
},
|
|
18
|
+
d(value, options) {
|
|
19
|
+
let format = dates.get(options);
|
|
20
|
+
if (format === void 0) dates.set(options, format = new Intl.DateTimeFormat(locale, options));
|
|
21
|
+
return format.format(value);
|
|
22
|
+
},
|
|
23
|
+
p(value, offset, isOrdinal, arms) {
|
|
24
|
+
const exact = arms[`=${value}`];
|
|
25
|
+
if (exact !== void 0) return exact();
|
|
26
|
+
return pick(arms, (isOrdinal ? ordinal ??= new Intl.PluralRules(locale, { type: "ordinal" }) : cardinal ??= new Intl.PluralRules(locale)).select(value - offset));
|
|
27
|
+
},
|
|
28
|
+
s(value, arms) {
|
|
29
|
+
return pick(arms, String(value));
|
|
30
|
+
},
|
|
31
|
+
t(values, tag, children) {
|
|
32
|
+
const render = values[tag];
|
|
33
|
+
return typeof render === "function" ? render(flatten(children)) : children;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function pick(arms, arm) {
|
|
38
|
+
return (Object.hasOwn(arms, arm) ? arms[arm] : arms["other"])();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A compiled message's output as a flat list: nested arrays opened, adjacent text joined, and
|
|
42
|
+
* `null`, `undefined` and booleans dropped, as React drops them.
|
|
43
|
+
*/
|
|
44
|
+
function flatten(output) {
|
|
45
|
+
const out = [];
|
|
46
|
+
const walk = (part) => {
|
|
47
|
+
if (Array.isArray(part)) {
|
|
48
|
+
for (const p of part) walk(p);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (part === null || part === void 0 || typeof part === "boolean") return;
|
|
52
|
+
if (typeof part === "number" || typeof part === "bigint") part = String(part);
|
|
53
|
+
if (typeof part === "string" && typeof out.at(-1) === "string") out[out.length - 1] += part;
|
|
54
|
+
else out.push(part);
|
|
55
|
+
};
|
|
56
|
+
walk(output);
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
export { flatten as n, createHelpers as t };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=helpers-BPuPGjQP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers-BPuPGjQP.js","names":[],"sources":["../src/runtime/helpers.ts"],"sourcesContent":["import type { Arms, Helpers, Values } from './types.ts';\n\n/**\n * The helpers compiled messages call, for one locale. Formatters are built once per options object:\n * the generator hoists each message's options to a module-level constant, so identity is a stable key.\n */\nexport function createHelpers(locale: string): Helpers {\n const numbers = new Map<Intl.NumberFormatOptions | undefined, Intl.NumberFormat>();\n const dates = new Map<Intl.DateTimeFormatOptions | undefined, Intl.DateTimeFormat>();\n let cardinal: Intl.PluralRules | undefined;\n let ordinal: Intl.PluralRules | undefined;\n\n return {\n locale,\n n(value, options) {\n let format = numbers.get(options);\n if (format === undefined) numbers.set(options, (format = new Intl.NumberFormat(locale, options)));\n return format.format(value);\n },\n d(value, options) {\n let format = dates.get(options);\n if (format === undefined) dates.set(options, (format = new Intl.DateTimeFormat(locale, options)));\n return format.format(value);\n },\n p(value, offset, isOrdinal, arms) {\n const exact = arms[`=${value}`];\n if (exact !== undefined) return exact();\n const rules = isOrdinal\n ? (ordinal ??= new Intl.PluralRules(locale, { type: 'ordinal' }))\n : (cardinal ??= new Intl.PluralRules(locale));\n return pick(arms, rules.select(value - offset));\n },\n s(value, arms) {\n return pick(arms, String(value));\n },\n t(values: Values, tag, children) {\n const render = values[tag];\n return typeof render === 'function' ? (render as (chunks: unknown[]) => unknown)(flatten(children)) : children;\n },\n };\n}\n\nfunction pick(arms: Arms, arm: string): unknown {\n // ICU requires an `other` arm, and the generator refuses a message without one.\n return (Object.hasOwn(arms, arm) ? arms[arm]! : arms['other']!)();\n}\n\n/**\n * A compiled message's output as a flat list: nested arrays opened, adjacent text joined, and\n * `null`, `undefined` and booleans dropped, as React drops them.\n */\nexport function flatten(output: unknown): unknown[] {\n const out: unknown[] = [];\n const walk = (part: unknown) => {\n if (Array.isArray(part)) {\n for (const p of part) walk(p);\n return;\n }\n if (part === null || part === undefined || typeof part === 'boolean') return;\n if (typeof part === 'number' || typeof part === 'bigint') part = String(part);\n if (typeof part === 'string' && typeof out.at(-1) === 'string') out[out.length - 1] += part;\n else out.push(part);\n };\n walk(output);\n return out;\n}\n"],"mappings":";;;;;AAMA,SAAgB,cAAc,QAAyB;CACrD,MAAM,0BAAU,IAAI,IAA6D;CACjF,MAAM,wBAAQ,IAAI,IAAiE;CACnF,IAAI;CACJ,IAAI;CAEJ,OAAO;EACL;EACA,EAAE,OAAO,SAAS;GAChB,IAAI,SAAS,QAAQ,IAAI,OAAO;GAChC,IAAI,WAAW,KAAA,GAAW,QAAQ,IAAI,SAAU,SAAS,IAAI,KAAK,aAAa,QAAQ,OAAO,CAAE;GAChG,OAAO,OAAO,OAAO,KAAK;EAC5B;EACA,EAAE,OAAO,SAAS;GAChB,IAAI,SAAS,MAAM,IAAI,OAAO;GAC9B,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,SAAU,SAAS,IAAI,KAAK,eAAe,QAAQ,OAAO,CAAE;GAChG,OAAO,OAAO,OAAO,KAAK;EAC5B;EACA,EAAE,OAAO,QAAQ,WAAW,MAAM;GAChC,MAAM,QAAQ,KAAK,IAAI;GACvB,IAAI,UAAU,KAAA,GAAW,OAAO,MAAM;GAItC,OAAO,KAAK,OAHE,YACT,YAAY,IAAI,KAAK,YAAY,QAAQ,EAAE,MAAM,UAAU,CAAC,IAC5D,aAAa,IAAI,KAAK,YAAY,MAAM,EAAA,CACrB,OAAO,QAAQ,MAAM,CAAC;EAChD;EACA,EAAE,OAAO,MAAM;GACb,OAAO,KAAK,MAAM,OAAO,KAAK,CAAC;EACjC;EACA,EAAE,QAAgB,KAAK,UAAU;GAC/B,MAAM,SAAS,OAAO;GACtB,OAAO,OAAO,WAAW,aAAc,OAA0C,QAAQ,QAAQ,CAAC,IAAI;EACxG;CACF;AACF;AAEA,SAAS,KAAK,MAAY,KAAsB;CAE9C,QAAQ,OAAO,OAAO,MAAM,GAAG,IAAI,KAAK,OAAQ,KAAK,SAAA,CAAW;AAClE;;;;;AAMA,SAAgB,QAAQ,QAA4B;CAClD,MAAM,MAAiB,CAAC;CACxB,MAAM,QAAQ,SAAkB;EAC9B,IAAI,MAAM,QAAQ,IAAI,GAAG;GACvB,KAAK,MAAM,KAAK,MAAM,KAAK,CAAC;GAC5B;EACF;EACA,IAAI,SAAS,QAAQ,SAAS,KAAA,KAAa,OAAO,SAAS,WAAW;EACtE,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO,OAAO,IAAI;EAC5E,IAAI,OAAO,SAAS,YAAY,OAAO,IAAI,GAAG,EAAE,MAAM,UAAU,IAAI,IAAI,SAAS,MAAM;OAClF,IAAI,KAAK,IAAI;CACpB;CACA,KAAK,MAAM;CACX,OAAO;AACT"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { C as RichValues, S as RichParam, _ as ReadHook, a as defineCatalog, b as RegisteredMessages, c as Chunks, d as DefaultTags, f as DevHooks, g as MessageShapes, h as MessageShape, i as createCopy, l as Compiled, m as HotHook, n as Copy, o as AnyTagFn, p as Helpers, r as CopyOptions, s as ArgsParam, t as Catalog, u as CompiledMessages, v as Register, w as TagFn, x as RichKey, y as RegisteredLocale } from "./copy-DXnzXsJa.js";
|
|
2
|
+
/**
|
|
3
|
+
* Picks the best available locale for what was asked, in the order it was asked.
|
|
4
|
+
*
|
|
5
|
+
* Each requested tag tries an exact match, then its parents (`pt-BR` finds `pt`), then any locale
|
|
6
|
+
* of the same language (`pt` finds `pt-BR`). Matching ignores case, as BCP 47 does.
|
|
7
|
+
*/
|
|
8
|
+
export declare function negotiate<L extends string>(requested: string | readonly string[], available: readonly L[], fallback: L): L;
|
|
9
|
+
/** The locales a message is looked up in, in order: the locale, its available parents, then the source. */
|
|
10
|
+
export declare function chainOf<L extends string>(locale: L, available: readonly L[], source: L): L[];
|
|
11
|
+
/** The locales the browser or the environment asks for, most preferred first. */
|
|
12
|
+
export declare function preferredLocales(): string[];
|
|
13
|
+
/**
|
|
14
|
+
* The helpers compiled messages call, for one locale. Formatters are built once per options object:
|
|
15
|
+
* the generator hoists each message's options to a module-level constant, so identity is a stable key.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createHelpers(locale: string): Helpers;
|
|
18
|
+
/**
|
|
19
|
+
* A compiled message's output as a flat list: nested arrays opened, adjacent text joined, and
|
|
20
|
+
* `null`, `undefined` and booleans dropped, as React drops them.
|
|
21
|
+
*/
|
|
22
|
+
export declare function flatten(output: unknown): unknown[];
|
|
23
|
+
export { type AnyTagFn, type ArgsParam, type Catalog, type Chunks, type Compiled, type CompiledMessages, type Copy, type CopyOptions, type DefaultTags, type DevHooks, type Helpers, type HotHook, type MessageShape, type MessageShapes, type ReadHook, type Register, type RegisteredLocale, type RegisteredMessages, type RichKey, type RichParam, type RichValues, type TagFn, createCopy, defineCatalog };
|
|
24
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/runtime/locale.ts","../src/runtime/helpers.ts"],"mappings":";;;;;;;wBAMgB,UAAU,kBAAkB,uCAAuC,oBAAoB,KAAK,UAAU,IAAI;;wBAmB1G,QAAQ,kBAAkB,QAAQ,GAAG,oBAAoB,KAAK,QAAQ,IAAI;;wBAa1E;;;;;wBChCA,cAAc,iBAAiB;;;;;wBA6C/B,QAAQ"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { n as flatten, t as createHelpers } from "./helpers-BPuPGjQP.js";
|
|
2
|
+
// src/runtime/locale.ts
|
|
3
|
+
/**
|
|
4
|
+
* Picks the best available locale for what was asked, in the order it was asked.
|
|
5
|
+
*
|
|
6
|
+
* Each requested tag tries an exact match, then its parents (`pt-BR` finds `pt`), then any locale
|
|
7
|
+
* of the same language (`pt` finds `pt-BR`). Matching ignores case, as BCP 47 does.
|
|
8
|
+
*/
|
|
9
|
+
function negotiate(requested, available, fallback) {
|
|
10
|
+
const find = (tag) => available.find((a) => a.toLowerCase() === tag.toLowerCase());
|
|
11
|
+
for (const want of typeof requested === "string" ? [requested] : requested) {
|
|
12
|
+
let tag = want;
|
|
13
|
+
for (;;) {
|
|
14
|
+
const found = find(tag);
|
|
15
|
+
if (found !== void 0) return found;
|
|
16
|
+
const cut = tag.lastIndexOf("-");
|
|
17
|
+
if (cut < 0) break;
|
|
18
|
+
tag = tag.slice(0, cut);
|
|
19
|
+
}
|
|
20
|
+
const language = languageOf(want);
|
|
21
|
+
const sibling = available.find((a) => languageOf(a) === language);
|
|
22
|
+
if (sibling !== void 0) return sibling;
|
|
23
|
+
}
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
/** The locales a message is looked up in, in order: the locale, its available parents, then the source. */
|
|
27
|
+
function chainOf(locale, available, source) {
|
|
28
|
+
const chain = [locale];
|
|
29
|
+
let tag = locale;
|
|
30
|
+
while (tag.includes("-")) {
|
|
31
|
+
tag = tag.slice(0, tag.lastIndexOf("-"));
|
|
32
|
+
const parent = available.find((a) => a.toLowerCase() === tag.toLowerCase());
|
|
33
|
+
if (parent !== void 0 && !chain.includes(parent)) chain.push(parent);
|
|
34
|
+
}
|
|
35
|
+
if (!chain.includes(source)) chain.push(source);
|
|
36
|
+
return chain;
|
|
37
|
+
}
|
|
38
|
+
/** The locales the browser or the environment asks for, most preferred first. */
|
|
39
|
+
function preferredLocales() {
|
|
40
|
+
const nav = globalThis.navigator;
|
|
41
|
+
if (nav?.languages && nav.languages.length > 0) return [...nav.languages];
|
|
42
|
+
if (nav?.language) return [nav.language];
|
|
43
|
+
const resolved = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
44
|
+
return resolved ? [resolved] : [];
|
|
45
|
+
}
|
|
46
|
+
function languageOf(tag) {
|
|
47
|
+
return tag.split("-", 1)[0].toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
// src/runtime/copy.ts
|
|
50
|
+
/** Ties a generated catalog to its message shapes. Called by generated code. */
|
|
51
|
+
function defineCatalog(catalog) {
|
|
52
|
+
return catalog;
|
|
53
|
+
}
|
|
54
|
+
/** A message as the text it puts on screen: rendered again with its tags left as their text. */
|
|
55
|
+
function textOf(message, values, h) {
|
|
56
|
+
if (typeof message === "string") return message;
|
|
57
|
+
const plain = Object.fromEntries(Object.entries(values).filter(([, v]) => typeof v !== "function"));
|
|
58
|
+
return flatten(message(plain, h)).join("");
|
|
59
|
+
}
|
|
60
|
+
/** A catalog reader with a current locale. Create one per app, or one per request on a server. */
|
|
61
|
+
function createCopy(catalog, options = {}) {
|
|
62
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
63
|
+
const pending = /* @__PURE__ */ new Map();
|
|
64
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
65
|
+
const defaultTags = options.tags ?? {};
|
|
66
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const [locale, messages] of Object.entries(catalog.messages)) loaded.set(locale, {
|
|
68
|
+
messages,
|
|
69
|
+
h: createHelpers(locale)
|
|
70
|
+
});
|
|
71
|
+
let locale = catalog.source;
|
|
72
|
+
let chain = layers(chainOf(locale, catalog.locales, catalog.source));
|
|
73
|
+
let latest = 0;
|
|
74
|
+
let version = 0;
|
|
75
|
+
function changed() {
|
|
76
|
+
version++;
|
|
77
|
+
for (const listener of listeners) listener();
|
|
78
|
+
}
|
|
79
|
+
globalThis.__PROSEBOOK_HOT__?.add((l, messages) => {
|
|
80
|
+
const layer = loaded.get(l);
|
|
81
|
+
if (layer === void 0) return;
|
|
82
|
+
layer.messages = messages;
|
|
83
|
+
changed();
|
|
84
|
+
});
|
|
85
|
+
function layers(locales) {
|
|
86
|
+
return locales.flatMap((l) => loaded.get(l) ?? []);
|
|
87
|
+
}
|
|
88
|
+
function load(l) {
|
|
89
|
+
const done = loaded.get(l);
|
|
90
|
+
if (done) return Promise.resolve(done);
|
|
91
|
+
let p = pending.get(l);
|
|
92
|
+
if (p === void 0) {
|
|
93
|
+
const loader = catalog.loaders[l];
|
|
94
|
+
if (loader === void 0) return Promise.reject(/* @__PURE__ */ new Error(`prosebook: no messages for locale "${l}"`));
|
|
95
|
+
p = loader().then((mod) => {
|
|
96
|
+
const layer = {
|
|
97
|
+
messages: mod.default,
|
|
98
|
+
h: createHelpers(l)
|
|
99
|
+
};
|
|
100
|
+
loaded.set(l, layer);
|
|
101
|
+
pending.delete(l);
|
|
102
|
+
return layer;
|
|
103
|
+
});
|
|
104
|
+
p.catch(() => pending.delete(l));
|
|
105
|
+
pending.set(l, p);
|
|
106
|
+
}
|
|
107
|
+
return p;
|
|
108
|
+
}
|
|
109
|
+
async function setLocale(next) {
|
|
110
|
+
const target = negotiate(next, catalog.locales, catalog.source);
|
|
111
|
+
const ticket = ++latest;
|
|
112
|
+
const locales = chainOf(target, catalog.locales, catalog.source);
|
|
113
|
+
await Promise.all(locales.map(load));
|
|
114
|
+
if (ticket !== latest || target === locale) return;
|
|
115
|
+
locale = target;
|
|
116
|
+
chain = layers(locales);
|
|
117
|
+
formatters.clear();
|
|
118
|
+
changed();
|
|
119
|
+
}
|
|
120
|
+
function find(key) {
|
|
121
|
+
for (const layer of chain) {
|
|
122
|
+
const message = layer.messages[key];
|
|
123
|
+
if (message !== void 0) return [message, layer.h];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function render(key, values) {
|
|
127
|
+
const found = find(key);
|
|
128
|
+
if (found === void 0) return options.onMissing ? options.onMissing(key, locale) : key;
|
|
129
|
+
const [message, h] = found;
|
|
130
|
+
const out = typeof message === "string" ? message : message(values, h);
|
|
131
|
+
const read = globalThis.__PROSEBOOK_READ__;
|
|
132
|
+
if (read !== void 0) read(key, typeof out === "string" ? out : textOf(message, values, h), h.locale);
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
function formatter(kind, options, make) {
|
|
136
|
+
const id = `${kind}${JSON.stringify(options ?? {})}`;
|
|
137
|
+
let made = formatters.get(id);
|
|
138
|
+
if (made === void 0) formatters.set(id, made = make());
|
|
139
|
+
return made;
|
|
140
|
+
}
|
|
141
|
+
const ready = options.locale === void 0 ? Promise.resolve() : setLocale(options.locale);
|
|
142
|
+
return {
|
|
143
|
+
get locale() {
|
|
144
|
+
return locale;
|
|
145
|
+
},
|
|
146
|
+
get version() {
|
|
147
|
+
return version;
|
|
148
|
+
},
|
|
149
|
+
locales: catalog.locales,
|
|
150
|
+
source: catalog.source,
|
|
151
|
+
tags: defaultTags,
|
|
152
|
+
ready,
|
|
153
|
+
t(key, ...args) {
|
|
154
|
+
const out = render(key, args[0] ?? {});
|
|
155
|
+
return typeof out === "string" ? out : flatten(out).join("");
|
|
156
|
+
},
|
|
157
|
+
rich(key, ...values) {
|
|
158
|
+
const out = render(key, {
|
|
159
|
+
...defaultTags,
|
|
160
|
+
...values[0]
|
|
161
|
+
});
|
|
162
|
+
return flatten(out);
|
|
163
|
+
},
|
|
164
|
+
has(key) {
|
|
165
|
+
return find(key) !== void 0;
|
|
166
|
+
},
|
|
167
|
+
setLocale,
|
|
168
|
+
subscribe(listener) {
|
|
169
|
+
listeners.add(listener);
|
|
170
|
+
return () => listeners.delete(listener);
|
|
171
|
+
},
|
|
172
|
+
number(value, opts) {
|
|
173
|
+
return formatter("n", opts, () => new Intl.NumberFormat(locale, opts)).format(value);
|
|
174
|
+
},
|
|
175
|
+
date(value, opts) {
|
|
176
|
+
return formatter("d", opts, () => new Intl.DateTimeFormat(locale, opts)).format(value);
|
|
177
|
+
},
|
|
178
|
+
list(items, opts) {
|
|
179
|
+
return formatter("l", opts, () => new Intl.ListFormat(locale, opts)).format(items);
|
|
180
|
+
},
|
|
181
|
+
relative(value, unit, opts) {
|
|
182
|
+
return formatter("r", opts, () => new Intl.RelativeTimeFormat(locale, opts)).format(value, unit);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
export { chainOf, createCopy, createHelpers, defineCatalog, flatten, negotiate, preferredLocales };
|
|
187
|
+
|
|
188
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/runtime/locale.ts","../src/runtime/copy.ts"],"sourcesContent":["/**\n * Picks the best available locale for what was asked, in the order it was asked.\n *\n * Each requested tag tries an exact match, then its parents (`pt-BR` finds `pt`), then any locale\n * of the same language (`pt` finds `pt-BR`). Matching ignores case, as BCP 47 does.\n */\nexport function negotiate<L extends string>(requested: string | readonly string[], available: readonly L[], fallback: L): L {\n const find = (tag: string) => available.find((a) => a.toLowerCase() === tag.toLowerCase());\n for (const want of typeof requested === 'string' ? [requested] : requested) {\n let tag = want;\n for (;;) {\n const found = find(tag);\n if (found !== undefined) return found;\n const cut = tag.lastIndexOf('-');\n if (cut < 0) break;\n tag = tag.slice(0, cut);\n }\n const language = languageOf(want);\n const sibling = available.find((a) => languageOf(a) === language);\n if (sibling !== undefined) return sibling;\n }\n return fallback;\n}\n\n/** The locales a message is looked up in, in order: the locale, its available parents, then the source. */\nexport function chainOf<L extends string>(locale: L, available: readonly L[], source: L): L[] {\n const chain: L[] = [locale];\n let tag: string = locale;\n while (tag.includes('-')) {\n tag = tag.slice(0, tag.lastIndexOf('-'));\n const parent = available.find((a) => a.toLowerCase() === tag.toLowerCase());\n if (parent !== undefined && !chain.includes(parent)) chain.push(parent);\n }\n if (!chain.includes(source)) chain.push(source);\n return chain;\n}\n\n/** The locales the browser or the environment asks for, most preferred first. */\nexport function preferredLocales(): string[] {\n const nav = (globalThis as { navigator?: { languages?: readonly string[]; language?: string } }).navigator;\n if (nav?.languages && nav.languages.length > 0) return [...nav.languages];\n if (nav?.language) return [nav.language];\n const resolved = Intl.DateTimeFormat().resolvedOptions().locale;\n return resolved ? [resolved] : [];\n}\n\nfunction languageOf(tag: string): string {\n return tag.split('-', 1)[0]!.toLowerCase();\n}\n","import { createHelpers, flatten } from './helpers.ts';\nimport { chainOf, negotiate } from './locale.ts';\nimport type {\n AnyTagFn,\n ArgsParam,\n Chunks,\n Compiled,\n CompiledMessages,\n Helpers,\n DevHooks,\n MessageShapes,\n RichKey,\n RichParam,\n Values,\n} from './types.ts';\n\ntype Loader = () => Promise<{ default: CompiledMessages }>;\n\n/** A generated catalog: which locales exist, which ship in the bundle and how to fetch the rest. */\nexport interface Catalog<M extends MessageShapes = MessageShapes, L extends string = string> {\n /** The locale the catalog is written in, which every other falls back to. */\n readonly source: L;\n readonly locales: readonly L[];\n /** Locales whose messages ship in the bundle. Always holds the source. */\n readonly messages: { readonly [locale in L]?: CompiledMessages };\n /** Locales fetched when first asked for. */\n readonly loaders: { readonly [locale in L]?: Loader };\n /** Type-only: carries the message shapes from the generated module to `createCopy`. */\n readonly '~messages'?: M;\n}\n\n/** Ties a generated catalog to its message shapes. Called by generated code. */\nexport function defineCatalog<M extends MessageShapes, L extends string = string>(\n catalog: Omit<Catalog<M, L>, '~messages'>,\n): Catalog<M, L> {\n return catalog;\n}\n\nexport interface CopyOptions<L extends string> {\n /** The locale to start in, or the locales a reader prefers, most preferred first. Defaults to the source. */\n locale?: L | (string & {}) | readonly string[];\n /** Tag renderers every rich read gets unless it supplies its own, such as `b` or `link`. */\n tags?: { [tag: string]: AnyTagFn };\n /** What a key the catalog lacks reads as. Defaults to the key itself. */\n onMissing?: (key: string, locale: L) => string;\n}\n\n/** Its methods don't use `this`, so they can be passed around and destructured: `const { t } = copy`. */\nexport interface Copy<M extends MessageShapes = MessageShapes, L extends string = string> {\n /** The locale messages are coming back in. */\n readonly locale: L;\n readonly locales: readonly L[];\n readonly source: L;\n /** The tag renderers rich reads fall back to, from `CopyOptions.tags`. */\n readonly tags: { readonly [tag: string]: AnyTagFn };\n /** Settles once the starting locale's messages are loaded. */\n readonly ready: Promise<void>;\n /** A message as plain text. Tags render as the text they wrap. */\n t<K extends keyof M & string>(this: void, key: K, ...args: ArgsParam<M[K]>): string;\n /** A message with elements inside it: each tag's function turns what it wraps into a node. */\n rich<K extends RichKey<M>, N = unknown>(this: void, key: K, ...values: RichParam<M[K], N>): Chunks<N>;\n has(this: void, key: string): key is keyof M & string;\n /** Switches locale once its messages (and its fallbacks') are loaded. */\n setLocale(this: void, next: L | (string & {}) | readonly string[]): Promise<void>;\n /** Counts changes to what messages read as: each locale switch, and each catalog edit in dev. */\n readonly version: number;\n /** Called after each locale switch, and in dev after each catalog edit. Returns the unsubscribe. */\n subscribe(this: void, listener: () => void): () => void;\n /** Formats a number in the current locale. */\n number(this: void, value: number | bigint, options?: Intl.NumberFormatOptions): string;\n /** Formats a date in the current locale. */\n date(this: void, value: Date | number, options?: Intl.DateTimeFormatOptions): string;\n /** Joins items as a list in the current locale: `a, b and c`. */\n list(this: void, items: Iterable<string>, options?: Intl.ListFormatOptions): string;\n /** `in 3 days`, `2 hours ago`, in the current locale. */\n relative(this: void, value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;\n}\n\ntype Layer = { messages: CompiledMessages; h: Helpers };\n\n/** A message as the text it puts on screen: rendered again with its tags left as their text. */\nfunction textOf(message: Compiled, values: Values, h: Helpers): string {\n if (typeof message === 'string') return message;\n const plain = Object.fromEntries(Object.entries(values).filter(([, v]) => typeof v !== 'function'));\n return flatten(message(plain, h)).join('');\n}\n\n/** A catalog reader with a current locale. Create one per app, or one per request on a server. */\nexport function createCopy<M extends MessageShapes, L extends string>(catalog: Catalog<M, L>, options: CopyOptions<L> = {}): Copy<M, L> {\n const loaded = new Map<L, Layer>();\n const pending = new Map<L, Promise<Layer>>();\n const listeners = new Set<() => void>();\n const defaultTags = options.tags ?? {};\n const formatters = new Map<string, Intl.NumberFormat | Intl.DateTimeFormat | Intl.ListFormat | Intl.RelativeTimeFormat>();\n\n for (const [locale, messages] of Object.entries(catalog.messages) as [L, CompiledMessages][])\n loaded.set(locale, { messages, h: createHelpers(locale) });\n\n let locale = catalog.source;\n let chain: Layer[] = layers(chainOf(locale, catalog.locales, catalog.source));\n let latest = 0;\n let version = 0;\n\n function changed() {\n version++;\n for (const listener of listeners) listener();\n }\n\n // A layer's messages are swapped in place, so the chain that holds it reads the new ones.\n (globalThis as DevHooks).__PROSEBOOK_HOT__?.add((l, messages) => {\n const layer = loaded.get(l as L);\n if (layer === undefined) return;\n layer.messages = messages;\n changed();\n });\n\n function layers(locales: L[]): Layer[] {\n return locales.flatMap((l) => loaded.get(l) ?? []);\n }\n\n function load(l: L): Promise<Layer> {\n const done = loaded.get(l);\n if (done) return Promise.resolve(done);\n let p = pending.get(l);\n if (p === undefined) {\n // oxlint-disable-next-line typescript/no-unsafe-enum-assignment -- `L` is a string union, not an enum; the rule misreads the mapped type\n const loader = catalog.loaders[l];\n if (loader === undefined) return Promise.reject(new Error(`prosebook: no messages for locale \"${l}\"`));\n p = loader().then((mod) => {\n const layer = { messages: mod.default, h: createHelpers(l) };\n loaded.set(l, layer);\n pending.delete(l);\n return layer;\n });\n p.catch(() => pending.delete(l));\n pending.set(l, p);\n }\n return p;\n }\n\n async function setLocale(next: L | (string & {}) | readonly string[]): Promise<void> {\n const target = negotiate(next, catalog.locales, catalog.source);\n const ticket = ++latest;\n const locales = chainOf(target, catalog.locales, catalog.source);\n await Promise.all(locales.map(load));\n // A slower load must not undo a later switch.\n if (ticket !== latest || target === locale) return;\n locale = target;\n chain = layers(locales);\n formatters.clear();\n changed();\n }\n\n function find(key: string): [Compiled, Helpers] | undefined {\n for (const layer of chain) {\n const message = layer.messages[key];\n if (message !== undefined) return [message, layer.h];\n }\n return undefined;\n }\n\n function render(key: string, values: Values): unknown {\n const found = find(key);\n if (found === undefined) return options.onMissing ? options.onMissing(key, locale) : key;\n const [message, h] = found;\n const out = typeof message === 'string' ? message : message(values, h);\n const read = (globalThis as DevHooks).__PROSEBOOK_READ__;\n if (read !== undefined) read(key, typeof out === 'string' ? out : textOf(message, values, h), h.locale);\n return out;\n }\n\n function formatter<F>(kind: string, options: object | undefined, make: () => F): F {\n const id = `${kind}${JSON.stringify(options ?? {})}`;\n let made = formatters.get(id) as F | undefined;\n if (made === undefined) formatters.set(id, (made = make()) as never);\n return made;\n }\n\n const ready = options.locale === undefined ? Promise.resolve() : setLocale(options.locale);\n\n return {\n get locale() {\n return locale;\n },\n get version() {\n return version;\n },\n locales: catalog.locales,\n source: catalog.source,\n tags: defaultTags,\n ready,\n t(key, ...args) {\n const out = render(key, (args[0] as Values | undefined) ?? {});\n return typeof out === 'string' ? out : flatten(out).join('');\n },\n rich(key, ...values) {\n const out = render(key, { ...defaultTags, ...(values[0] as Values | undefined) });\n return flatten(out) as Chunks<never>;\n },\n has(key): key is keyof M & string {\n return find(key) !== undefined;\n },\n setLocale,\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n number(value, opts) {\n return formatter('n', opts, () => new Intl.NumberFormat(locale, opts)).format(value);\n },\n date(value, opts) {\n return formatter('d', opts, () => new Intl.DateTimeFormat(locale, opts)).format(value);\n },\n list(items, opts) {\n return formatter('l', opts, () => new Intl.ListFormat(locale, opts)).format(items);\n },\n relative(value, unit, opts) {\n return formatter('r', opts, () => new Intl.RelativeTimeFormat(locale, opts)).format(value, unit);\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface Read {
|
|
2
|
+
key: string;
|
|
3
|
+
/** The locale the message was found in. */
|
|
4
|
+
locale: string;
|
|
5
|
+
}
|
|
6
|
+
/** Recent reads by the text they came to, least recent text first; each text's keys most recent first. */
|
|
7
|
+
export type Reads = Map<string, Read[]>;
|
|
8
|
+
/** The character under the pointer: a text node and its index there. */
|
|
9
|
+
export interface Caret {
|
|
10
|
+
node: Node;
|
|
11
|
+
offset: number;
|
|
12
|
+
}
|
|
13
|
+
export interface Hit {
|
|
14
|
+
text: string;
|
|
15
|
+
read: Read;
|
|
16
|
+
/** How many other keys came to the same text. */
|
|
17
|
+
others: number;
|
|
18
|
+
/** The innermost element holding all of the text. */
|
|
19
|
+
element: Element;
|
|
20
|
+
}
|
|
21
|
+
export declare function record(reads: Reads, key: string, text: string, locale: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* The read under the caret: the longest text that covers it, in the innermost element holding all of
|
|
24
|
+
* it. A rich message runs across its elements, so it wins over a shorter text inside one of them.
|
|
25
|
+
* Without a caret (over an input, or padding) an element's whole text, or the target's readable
|
|
26
|
+
* attribute, must equal a read.
|
|
27
|
+
*/
|
|
28
|
+
export declare function keyAt(target: Element, caret: Caret | undefined, reads: Reads): Hit | undefined;
|
|
29
|
+
/** The character under a point on screen, where the browser can say. */
|
|
30
|
+
export declare function caretAt(x: number, y: number): Caret | undefined;
|
|
31
|
+
export interface InspectorOptions {
|
|
32
|
+
/** The dev server's base URL, ending in `/`. */
|
|
33
|
+
base: string;
|
|
34
|
+
}
|
|
35
|
+
/** Starts listening for reads and for Alt on the page. Called by the module the Vite plugin serves. */
|
|
36
|
+
export declare function install({ base }: InspectorOptions): void;
|
|
37
|
+
//# sourceMappingURL=inspector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspector.d.ts","names":[],"sources":["../src/inspector.ts"],"mappings":"A,iBAaiB;EACf;;EAEA;;;YAIU,QAAQ,YAAY;;iBAGf;EACf,MAAM;EACN;;iBAGe;EACf;EACA,MAAM;;EAEN;;EAEA,SAAS;;wBAOK,OAAO,OAAO,OAAO,aAAa,cAAc;;;;;;;wBAchD,MAAM,QAAQ,SAAS,OAAO,mBAAmB,OAAO,QAAQ;;wBAuChE,QAAQ,WAAW,YAAY;iBAgB9B;;EAEf;;;wBAIc,UAAU,QAAQ"}
|