prosebook 0.2.0 → 0.4.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.
Files changed (115) hide show
  1. package/README.md +345 -29
  2. package/dist/bun.d.ts +34 -0
  3. package/dist/bun.d.ts.map +1 -0
  4. package/dist/bun.js +98 -0
  5. package/dist/bun.js.map +1 -0
  6. package/dist/bundle-CpUIWb9j.d.ts +6 -0
  7. package/dist/bundle-CpUIWb9j.d.ts.map +1 -0
  8. package/dist/bundle-DboItAre.js +111 -0
  9. package/dist/bundle-DboItAre.js.map +1 -0
  10. package/dist/cli.js +51 -56
  11. package/dist/cli.js.map +1 -1
  12. package/dist/compile-ClK9htKr.d.ts +16 -0
  13. package/dist/compile-ClK9htKr.d.ts.map +1 -0
  14. package/dist/{config-_4HfnJQu.d.ts → config-CzaJJQ60.d.ts} +75 -2
  15. package/dist/config-CzaJJQ60.d.ts.map +1 -0
  16. package/dist/{config-D--RITk2.js → config-nWZf9QmF.js} +42 -212
  17. package/dist/config-nWZf9QmF.js.map +1 -0
  18. package/dist/config.d.ts +2 -2
  19. package/dist/config.js +1 -1
  20. package/dist/{copy-DXnzXsJa.d.ts → copy-B3V_QRkT.d.ts} +20 -7
  21. package/dist/copy-B3V_QRkT.d.ts.map +1 -0
  22. package/dist/copy-Bo_FIoLy.js +154 -0
  23. package/dist/copy-Bo_FIoLy.js.map +1 -0
  24. package/dist/endpoints-BgG_5Vpm.js +96 -0
  25. package/dist/endpoints-BgG_5Vpm.js.map +1 -0
  26. package/dist/generate-BW3N8EGb.js +5488 -0
  27. package/dist/generate-BW3N8EGb.js.map +1 -0
  28. package/dist/helpers-BWGcD0kS.js +96 -0
  29. package/dist/helpers-BWGcD0kS.js.map +1 -0
  30. package/dist/icu-Dej0mNsF.js +218 -0
  31. package/dist/icu-Dej0mNsF.js.map +1 -0
  32. package/dist/index.d.ts +12 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +4 -188
  35. package/dist/inspector.d.ts +4 -1
  36. package/dist/inspector.d.ts.map +1 -1
  37. package/dist/inspector.js +7 -2
  38. package/dist/inspector.js.map +1 -1
  39. package/dist/locale-DpQA1pqH.js +70 -0
  40. package/dist/locale-DpQA1pqH.js.map +1 -0
  41. package/dist/next-loader.d.ts +11 -0
  42. package/dist/next-loader.d.ts.map +1 -0
  43. package/dist/next-loader.js +17 -0
  44. package/dist/next-loader.js.map +1 -0
  45. package/dist/next.d.ts +30 -0
  46. package/dist/next.d.ts.map +1 -0
  47. package/dist/next.js +160 -0
  48. package/dist/next.js.map +1 -0
  49. package/dist/react/index.d.ts +3 -3
  50. package/dist/react/index.d.ts.map +1 -1
  51. package/dist/react/index.js.map +1 -1
  52. package/dist/react/server.d.ts +20 -0
  53. package/dist/react/server.d.ts.map +1 -0
  54. package/dist/react/server.js +34 -0
  55. package/dist/react/server.js.map +1 -0
  56. package/dist/{sarif-CI8HOokm.js → sarif-B5nrMqrT.js} +103 -87
  57. package/dist/sarif-B5nrMqrT.js.map +1 -0
  58. package/dist/scan-Bx0_yDSi.js +387 -0
  59. package/dist/scan-Bx0_yDSi.js.map +1 -0
  60. package/dist/tools.d.ts +83 -90
  61. package/dist/tools.d.ts.map +1 -1
  62. package/dist/tools.js +5 -4
  63. package/dist/vite.d.ts +3 -2
  64. package/dist/vite.d.ts.map +1 -1
  65. package/dist/vite.js +29 -109
  66. package/dist/vite.js.map +1 -1
  67. package/package.json +37 -4
  68. package/src/bun.ts +132 -0
  69. package/src/cli.ts +60 -58
  70. package/src/index.ts +1 -1
  71. package/src/inspector.ts +8 -3
  72. package/src/next-loader.ts +16 -0
  73. package/src/next.ts +188 -0
  74. package/src/react/index.tsx +6 -6
  75. package/src/react/server.ts +37 -0
  76. package/src/runtime/copy.ts +22 -3
  77. package/src/runtime/helpers.ts +54 -18
  78. package/src/runtime/locale.ts +19 -0
  79. package/src/runtime/types.ts +21 -6
  80. package/src/tools/bundle.ts +131 -0
  81. package/src/tools/catalog.ts +2 -2
  82. package/src/tools/compile.ts +60 -23
  83. package/src/tools/config.ts +57 -0
  84. package/src/tools/endpoints.ts +121 -0
  85. package/src/tools/entry.ts +77 -0
  86. package/src/tools/generate.ts +2 -0
  87. package/src/tools/icu.ts +63 -7
  88. package/src/tools/index.ts +6 -3
  89. package/src/tools/lock.ts +7 -0
  90. package/src/tools/mf1.ts +21 -7
  91. package/src/tools/mf2.ts +249 -57
  92. package/src/tools/migrate.ts +3 -2
  93. package/src/tools/native/android.ts +201 -0
  94. package/src/tools/native/apple.ts +139 -0
  95. package/src/tools/native/emit.ts +87 -0
  96. package/src/tools/native/index.ts +48 -0
  97. package/src/tools/native/kotlin.ts +269 -0
  98. package/src/tools/native/model.ts +292 -0
  99. package/src/tools/native/number.ts +210 -0
  100. package/src/tools/native/plurals.ts +103 -0
  101. package/src/tools/native/printf.ts +95 -0
  102. package/src/tools/native/rust.ts +367 -0
  103. package/src/tools/native/swift.ts +326 -0
  104. package/src/tools/syntax.ts +3 -3
  105. package/src/tools/todo.ts +118 -0
  106. package/src/vite.ts +28 -114
  107. package/dist/config-D--RITk2.js.map +0 -1
  108. package/dist/config-_4HfnJQu.d.ts.map +0 -1
  109. package/dist/copy-DXnzXsJa.d.ts.map +0 -1
  110. package/dist/helpers-BPuPGjQP.js +0 -61
  111. package/dist/helpers-BPuPGjQP.js.map +0 -1
  112. package/dist/index.js.map +0 -1
  113. package/dist/sarif-CI8HOokm.js.map +0 -1
  114. package/dist/scan-Ch2BfIUD.js +0 -2561
  115. package/dist/scan-Ch2BfIUD.js.map +0 -1
@@ -0,0 +1,96 @@
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
+ * Options an argument gives are built per call, so those are told apart by value.
6
+ *
7
+ * A value missing, or one `Intl` refuses, never throws: `n` and `d` give nothing back, and the message
8
+ * shows the placeholder's fallback through `e`, which hands `report` what went wrong.
9
+ */
10
+ function createHelpers(locale, report) {
11
+ const numbers = /* @__PURE__ */ new Map();
12
+ const dates = /* @__PURE__ */ new Map();
13
+ const rules = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
14
+ let failure;
15
+ function attempt(value, format) {
16
+ failure = void 0;
17
+ if (value === void 0 || value === null || value !== value) return void 0;
18
+ try {
19
+ return format();
20
+ } catch (error) {
21
+ failure = error;
22
+ return;
23
+ }
24
+ }
25
+ return {
26
+ locale,
27
+ n(value, options, given) {
28
+ return attempt(value, () => formatter(numbers, options, given, (o) => new Intl.NumberFormat(locale, o)).format(value));
29
+ },
30
+ d(value, options, given) {
31
+ return attempt(value, () => formatter(dates, options, given, (o) => new Intl.DateTimeFormat(locale, o)).format(value));
32
+ },
33
+ e(fallback) {
34
+ const why = failure instanceof Error ? `could not be formatted: ${failure.message}` : "has no value";
35
+ report?.(new Error(`${fallback} ${why}`, { cause: failure }));
36
+ failure = void 0;
37
+ return fallback;
38
+ },
39
+ p(value, offset, isOrdinal, arms, options, given) {
40
+ const exact = arms[`=${value}`];
41
+ if (exact !== void 0) return exact();
42
+ let category = "other";
43
+ try {
44
+ const type = isOrdinal ? "ordinal" : "cardinal";
45
+ category = formatter(rules[+isOrdinal], options, given, (o) => new Intl.PluralRules(locale, {
46
+ ...o,
47
+ type
48
+ })).select(value - offset);
49
+ } catch {}
50
+ return pick(arms, category);
51
+ },
52
+ s(value, arms) {
53
+ return pick(arms, String(value));
54
+ },
55
+ t(values, tag, children) {
56
+ const render = values[tag];
57
+ return typeof render === "function" ? render(flatten(children)) : children;
58
+ }
59
+ };
60
+ }
61
+ function formatter(cache, options, given, make) {
62
+ let byGiven = cache.get(options);
63
+ if (byGiven === void 0) cache.set(options, byGiven = /* @__PURE__ */ new Map());
64
+ const key = given === void 0 ? "" : JSON.stringify(given);
65
+ let format = byGiven.get(key);
66
+ if (format === void 0) byGiven.set(key, format = make(given === void 0 ? options : {
67
+ ...options,
68
+ ...given
69
+ }));
70
+ return format;
71
+ }
72
+ function pick(arms, arm) {
73
+ return (Object.hasOwn(arms, arm) ? arms[arm] : arms["other"])();
74
+ }
75
+ /**
76
+ * A compiled message's output as a flat list: nested arrays opened, adjacent text joined, and
77
+ * `null`, `undefined` and booleans dropped, as React drops them.
78
+ */
79
+ function flatten(output) {
80
+ const out = [];
81
+ const walk = (part) => {
82
+ if (Array.isArray(part)) {
83
+ for (const p of part) walk(p);
84
+ return;
85
+ }
86
+ if (part === null || part === void 0 || typeof part === "boolean") return;
87
+ if (typeof part === "number" || typeof part === "bigint") part = String(part);
88
+ if (typeof part === "string" && typeof out.at(-1) === "string") out[out.length - 1] += part;
89
+ else out.push(part);
90
+ };
91
+ walk(output);
92
+ return out;
93
+ }
94
+ export { flatten as n, createHelpers as t };
95
+
96
+ //# sourceMappingURL=helpers-BWGcD0kS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers-BWGcD0kS.js","names":[],"sources":["../src/runtime/helpers.ts"],"sourcesContent":["import type { Arms, Helpers, Values } from './types.ts';\n\n/** Formatters by options object, then by the options arguments gave, as JSON (`''` for none). */\ntype Formatters<F> = Map<object | undefined, Map<string, F>>;\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 * Options an argument gives are built per call, so those are told apart by value.\n *\n * A value missing, or one `Intl` refuses, never throws: `n` and `d` give nothing back, and the message\n * shows the placeholder's fallback through `e`, which hands `report` what went wrong.\n */\nexport function createHelpers(locale: string, report?: (error: Error) => void): Helpers {\n const numbers: Formatters<Intl.NumberFormat> = new Map();\n const dates: Formatters<Intl.DateTimeFormat> = new Map();\n const rules: Formatters<Intl.PluralRules>[] = [new Map(), new Map()];\n let failure: unknown;\n\n function attempt(value: unknown, format: () => string): string | undefined {\n failure = undefined;\n // `value !== value`: NaN, which `Intl` prints as \"NaN\".\n if (value === undefined || value === null || value !== value) return undefined;\n try {\n return format();\n } catch (error) {\n failure = error;\n return undefined;\n }\n }\n\n return {\n locale,\n n(value, options, given) {\n return attempt(value, () => formatter(numbers, options, given, (o) => new Intl.NumberFormat(locale, o)).format(value));\n },\n d(value, options, given) {\n return attempt(value, () => formatter(dates, options, given, (o) => new Intl.DateTimeFormat(locale, o)).format(value));\n },\n e(fallback) {\n const why = failure instanceof Error ? `could not be formatted: ${failure.message}` : 'has no value';\n report?.(new Error(`${fallback} ${why}`, { cause: failure }));\n failure = undefined;\n return fallback;\n },\n p(value, offset, isOrdinal, arms, options, given) {\n const exact = arms[`=${value}`];\n if (exact !== undefined) return exact();\n let category = 'other';\n try {\n const type = isOrdinal ? 'ordinal' : 'cardinal';\n category = formatter(rules[+isOrdinal]!, options, given, (o) => new Intl.PluralRules(locale, { ...o, type })).select(\n value - offset,\n );\n } catch {\n // Options an argument gave that `Intl` refuses: the catch-all arm, as MF2 falls back to `*`.\n }\n return pick(arms, category);\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 formatter<O extends object, F>(cache: Formatters<F>, options: O | undefined, given: O | undefined, make: (o?: O) => F): F {\n let byGiven = cache.get(options);\n if (byGiven === undefined) cache.set(options, (byGiven = new Map()));\n const key = given === undefined ? '' : JSON.stringify(given);\n let format = byGiven.get(key);\n if (format === undefined) byGiven.set(key, (format = make(given === undefined ? options : { ...options, ...given })));\n return format;\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":";;;;;;;;;AAaA,SAAgB,cAAc,QAAgB,QAA0C;CACtF,MAAM,0BAAyC,IAAI,IAAI;CACvD,MAAM,wBAAyC,IAAI,IAAI;CACvD,MAAM,QAAwC,iBAAC,IAAI,IAAI,mBAAG,IAAI,IAAI,CAAC;CACnE,IAAI;CAEJ,SAAS,QAAQ,OAAgB,QAA0C;EACzE,UAAU,KAAA;EAEV,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,OAAO,OAAO,KAAA;EACrE,IAAI;GACF,OAAO,OAAO;EAChB,SAAS,OAAO;GACd,UAAU;GACV;EACF;CACF;CAEA,OAAO;EACL;EACA,EAAE,OAAO,SAAS,OAAO;GACvB,OAAO,QAAQ,aAAa,UAAU,SAAS,SAAS,QAAQ,MAAM,IAAI,KAAK,aAAa,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC;EACvH;EACA,EAAE,OAAO,SAAS,OAAO;GACvB,OAAO,QAAQ,aAAa,UAAU,OAAO,SAAS,QAAQ,MAAM,IAAI,KAAK,eAAe,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC;EACvH;EACA,EAAE,UAAU;GACV,MAAM,MAAM,mBAAmB,QAAQ,2BAA2B,QAAQ,YAAY;GACtF,SAAS,IAAI,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,OAAO,QAAQ,CAAC,CAAC;GAC5D,UAAU,KAAA;GACV,OAAO;EACT;EACA,EAAE,OAAO,QAAQ,WAAW,MAAM,SAAS,OAAO;GAChD,MAAM,QAAQ,KAAK,IAAI;GACvB,IAAI,UAAU,KAAA,GAAW,OAAO,MAAM;GACtC,IAAI,WAAW;GACf,IAAI;IACF,MAAM,OAAO,YAAY,YAAY;IACrC,WAAW,UAAU,MAAM,CAAC,YAAa,SAAS,QAAQ,MAAM,IAAI,KAAK,YAAY,QAAQ;KAAE,GAAG;KAAG;IAAK,CAAC,CAAC,CAAC,CAAC,OAC5G,QAAQ,MACV;GACF,QAAQ,CAER;GACA,OAAO,KAAK,MAAM,QAAQ;EAC5B;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,UAA+B,OAAsB,SAAwB,OAAsB,MAAuB;CACjI,IAAI,UAAU,MAAM,IAAI,OAAO;CAC/B,IAAI,YAAY,KAAA,GAAW,MAAM,IAAI,SAAU,0BAAU,IAAI,IAAI,CAAE;CACnE,MAAM,MAAM,UAAU,KAAA,IAAY,KAAK,KAAK,UAAU,KAAK;CAC3D,IAAI,SAAS,QAAQ,IAAI,GAAG;CAC5B,IAAI,WAAW,KAAA,GAAW,QAAQ,IAAI,KAAM,SAAS,KAAK,UAAU,KAAA,IAAY,UAAU;EAAE,GAAG;EAAS,GAAG;CAAM,CAAC,CAAE;CACpH,OAAO;AACT;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"}
@@ -0,0 +1,218 @@
1
+ import { TYPE as TYPE$1, isDateElement, isNumberElement, isTimeElement, parse } from "@formatjs/icu-messageformat-parser";
2
+ // src/tools/icu.ts
3
+ /**
4
+ * What an ICU message declares, read off its AST: argument types, tags, and whether each number and
5
+ * date style is one the formatter honors. Shared by the loader, the compiler and the lint.
6
+ */
7
+ function parseMessage(text) {
8
+ const ast = parse(text, {
9
+ shouldParseSkeletons: true,
10
+ requiresOtherClause: true
11
+ });
12
+ walk(ast, (el) => {
13
+ if ((isDateElement(el) || isTimeElement(el)) && typeof el.style === "object" && el.style !== null && el.style.pattern.includes("j")) el.style.parsedOptions = localHour(el.style.pattern);
14
+ });
15
+ return ast;
16
+ }
17
+ /**
18
+ * A skeleton's `j` is the hour as the locale writes it, 12 or 24. The parser resolves it only when
19
+ * given a locale and drops it otherwise, so `::yMMMdjmm` showed the minute alone. Read here as an
20
+ * hour with no cycle, which `Intl` gives the cycle of whatever locale formats it: "2:05 PM", "14:05".
21
+ */
22
+ function localHour(pattern) {
23
+ const run = /j+/.exec(pattern)[0];
24
+ const rest = pattern.replaceAll("j", "");
25
+ return {
26
+ ...rest === "" ? {} : dateFields(rest),
27
+ hour: run.length % 2 === 0 ? "2-digit" : "numeric"
28
+ };
29
+ }
30
+ /** What the parser reads from a date skeleton on its own; nothing where it can't parse it. */
31
+ function dateFields(skeleton) {
32
+ try {
33
+ const el = parse(`{d, date, ::${skeleton}}`, { shouldParseSkeletons: true })[0];
34
+ return el !== void 0 && isDateElement(el) && typeof el.style === "object" && el.style !== null ? el.style.parsedOptions : {};
35
+ } catch {
36
+ return {};
37
+ }
38
+ }
39
+ /**
40
+ * ICU argument types, as TypeScript. A bare `{x}` is a string: a number spliced in raw is printed by
41
+ * `String()`, which knows no locale, so a quantity says `{x, number}` and the type is what asks for it.
42
+ * A `select` narrows to the arms it names, and still takes any string, since `other` catches the rest.
43
+ */
44
+ function argType(el) {
45
+ switch (el.type) {
46
+ case TYPE$1.argument: return "string";
47
+ case TYPE$1.number:
48
+ case TYPE$1.plural: return "number";
49
+ case TYPE$1.date:
50
+ case TYPE$1.time: return "Date | number";
51
+ case TYPE$1.select: {
52
+ const named = Object.keys(el.options).filter((o) => o !== "other");
53
+ return named.length > 0 ? `${named.map((o) => JSON.stringify(o)).join(" | ")} | (string & {})` : "string";
54
+ }
55
+ case TYPE$1.literal:
56
+ case TYPE$1.pound:
57
+ case TYPE$1.tag:
58
+ default: return;
59
+ }
60
+ }
61
+ function optionArgsOf(el) {
62
+ if (el.type === TYPE$1.plural) return selectionOf(el)?.args;
63
+ if (!isNumberElement(el) && !isDateElement(el) && !isTimeElement(el)) return void 0;
64
+ const style = el.style;
65
+ return typeof style === "object" && style !== null && "args" in style ? style.args : void 0;
66
+ }
67
+ function selectionOf(el) {
68
+ return el.type === TYPE$1.plural ? el.selection : void 0;
69
+ }
70
+ function placeholderOf(el, name) {
71
+ return el.placeholder ?? { fallback: `{${name}}` };
72
+ }
73
+ /** Calls `visit` on every element, inside plural and select arms and tags too. */
74
+ function walk(elements, visit) {
75
+ for (const el of elements) {
76
+ visit(el);
77
+ if (el.type === TYPE$1.select || el.type === TYPE$1.plural) for (const option of Object.values(el.options)) walk(option.value, visit);
78
+ if (el.type === TYPE$1.tag) walk(el.children, visit);
79
+ }
80
+ }
81
+ function argsOf(elements, clash = () => {}) {
82
+ const args = /* @__PURE__ */ new Map();
83
+ const add = (name, type) => {
84
+ const seen = args.get(name);
85
+ if (seen !== void 0 && seen !== type) clash(name);
86
+ else args.set(name, type);
87
+ };
88
+ walk(elements, (el) => {
89
+ for (const { arg, type } of Object.values(optionArgsOf(el) ?? {})) add(arg, type);
90
+ const type = argType(el);
91
+ if (type !== void 0 && "value" in el && typeof el.value === "string") add(el.value, type);
92
+ });
93
+ return args;
94
+ }
95
+ function tagsOf(elements) {
96
+ const tags = /* @__PURE__ */ new Set();
97
+ walk(elements, (el) => {
98
+ if (el.type === TYPE$1.tag) tags.add(el.value);
99
+ });
100
+ return [...tags].sort();
101
+ }
102
+ /** A message with no placeholders and no tags renders to one literal, which ships as a plain string. */
103
+ function staticLiteral(ast) {
104
+ if (ast.length === 0) return "";
105
+ if (ast.length === 1 && ast[0].type === TYPE$1.literal) return ast[0].value;
106
+ }
107
+ const NUMBER_STYLES = {
108
+ integer: { maximumFractionDigits: 0 },
109
+ percent: { style: "percent" }
110
+ };
111
+ const DATE_STYLES = [
112
+ "short",
113
+ "medium",
114
+ "long",
115
+ "full"
116
+ ];
117
+ /** The `Intl` options a number element formats with. Throws, with the fix, where the style is wrong. */
118
+ function numberOptions(el) {
119
+ const style = el.style;
120
+ if (style === null || style === void 0) return void 0;
121
+ if (typeof style === "string") {
122
+ if (style === "currency") throw new Error(`{${el.value}} needs its currency named: {${el.value}, number, ::currency/EUR}`);
123
+ const named = NUMBER_STYLES[style];
124
+ if (named === void 0) throw new Error(`{${el.value}} asks for number style "${style}": integer, percent, or a ::skeleton`);
125
+ return named;
126
+ }
127
+ const unknown = style.tokens.filter((t) => !stemIsKnown(t.stem, t.options)).map((t) => t.stem);
128
+ if (unknown.length > 0) throw new Error(`{${el.value}} has skeleton stems the formatter drops without a word: ${unknown.join(", ")}`);
129
+ const options = style.parsedOptions;
130
+ const args = optionArgsOf(el) ?? {};
131
+ const given = {
132
+ ...args.currency && { currency: "EUR" },
133
+ ...args.unit && { unit: "meter" }
134
+ };
135
+ try {
136
+ new Intl.NumberFormat("en", {
137
+ ...options,
138
+ ...given
139
+ }).resolvedOptions();
140
+ } catch (e) {
141
+ throw new Error(`{${el.value}} has a skeleton Intl refuses: ${e.message}`, { cause: e });
142
+ }
143
+ return options;
144
+ }
145
+ /** The `Intl` options a date or time element formats with. Throws, with the fix, where the style is wrong. */
146
+ function dateOptions(el) {
147
+ const style = el.style;
148
+ const field = el.type === TYPE$1.time ? "timeStyle" : "dateStyle";
149
+ if (style === null || style === void 0) return el.type === TYPE$1.time ? { timeStyle: "short" } : void 0;
150
+ if (typeof style === "string") {
151
+ if (!DATE_STYLES.includes(style)) {
152
+ const kind = el.type === TYPE$1.time ? "time" : "date";
153
+ throw new Error(`{${el.value}} asks for ${kind} style "${style}": ${DATE_STYLES.join(", ")}, or a ::skeleton`);
154
+ }
155
+ return { [field]: style };
156
+ }
157
+ const dropped = (style.pattern.match(/([A-Za-z])*/g) ?? []).filter((f) => !f.startsWith("j") && Object.keys(dateFields(f)).length === 0);
158
+ if (dropped.length > 0) throw new Error(`{${el.value}} has date fields the formatter drops without a word: ${dropped.join(", ")}`);
159
+ if (Object.keys(style.parsedOptions).length === 0) throw new Error(`{${el.value}} has a date skeleton the formatter reads nothing from: "${style.pattern}"`);
160
+ try {
161
+ new Intl.DateTimeFormat("en", style.parsedOptions).resolvedOptions();
162
+ } catch (e) {
163
+ throw new Error(`{${el.value}} has a skeleton Intl refuses: ${e.message}`, { cause: e });
164
+ }
165
+ return style.parsedOptions;
166
+ }
167
+ /**
168
+ * Whether the formatter understands a skeleton stem. One it does not is dropped without a word, so
169
+ * `::compact-shortt` reaches the screen as `5` where `5K` was written. Every stem it understands sets
170
+ * at least one option, so an empty result is the answer.
171
+ */
172
+ function stemIsKnown(stem, options) {
173
+ try {
174
+ const el = parse(`{n, number, ::${[stem, ...options].join("/")}}`, { shouldParseSkeletons: true })[0];
175
+ if (el === void 0 || !isNumberElement(el) || typeof el.style !== "object" || el.style === null) return false;
176
+ return Object.keys(el.style.parsedOptions).length > 0;
177
+ } catch {
178
+ return false;
179
+ }
180
+ }
181
+ /** Every format problem in a message, as sentences. */
182
+ function formatProblems(ast) {
183
+ const found = [];
184
+ walk(ast, (el) => {
185
+ try {
186
+ if (isNumberElement(el)) numberOptions(el);
187
+ if (isDateElement(el) || isTimeElement(el)) dateOptions(el);
188
+ } catch (e) {
189
+ found.push(e.message);
190
+ }
191
+ });
192
+ return found;
193
+ }
194
+ /** How each number or date argument is formatted, for the generated doc comment: `date medium, time short`. */
195
+ function formatsOf(ast) {
196
+ const formats = /* @__PURE__ */ new Map();
197
+ walk(ast, (el) => {
198
+ if (!isNumberElement(el) && !isDateElement(el) && !isTimeElement(el)) return;
199
+ const style = el.style;
200
+ if (style === null || style === void 0) return;
201
+ const kind = el.type === TYPE$1.number ? "number" : el.type === TYPE$1.date ? "date" : "time";
202
+ let said;
203
+ if (typeof style === "string") said = `${kind} \`${style}\``;
204
+ else if ("mf2" in style) {
205
+ const { name, options, args = {} } = style.mf2;
206
+ const written = [...Object.entries(options), ...Object.entries(args).map(([k, v]) => [k, `$${v}`])];
207
+ said = `\`${[`:${name}`, ...written.map(([k, v]) => `${k}=${v}`)].join(" ")}\``;
208
+ } else if ("tokens" in style) said = `${kind} \`::${style.tokens.map((t) => [t.stem, ...t.options].join("/")).join(" ")}\``;
209
+ else said = `${kind} \`::${style.pattern}\``;
210
+ const list = formats.get(el.value) ?? [];
211
+ if (!list.includes(said)) list.push(said);
212
+ formats.set(el.value, list);
213
+ });
214
+ return new Map([...formats].map(([name, list]) => [name, list.join(", ")]));
215
+ }
216
+ export { formatsOf as a, parseMessage as c, staticLiteral as d, tagsOf as f, formatProblems as i, placeholderOf as l, argsOf as n, numberOptions as o, walk as p, dateOptions as r, optionArgsOf as s, TYPE$1 as t, selectionOf as u };
217
+
218
+ //# sourceMappingURL=icu-Dej0mNsF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icu-Dej0mNsF.js","names":["TYPE"],"sources":["../src/tools/icu.ts"],"sourcesContent":["/**\n * What an ICU message declares, read off its AST: argument types, tags, and whether each number and\n * date style is one the formatter honors. Shared by the loader, the compiler and the lint.\n */\nimport { isDateElement, isNumberElement, isTimeElement, parse, TYPE } from '@formatjs/icu-messageformat-parser';\nimport type { DateElement, MessageFormatElement, NumberElement, TimeElement } from '@formatjs/icu-messageformat-parser';\n\nexport type { MessageFormatElement };\nexport { TYPE };\n\nexport function parseMessage(text: string): MessageFormatElement[] {\n const ast = parse(text, { shouldParseSkeletons: true, requiresOtherClause: true });\n walk(ast, (el) => {\n if ((isDateElement(el) || isTimeElement(el)) && typeof el.style === 'object' && el.style !== null && el.style.pattern.includes('j'))\n el.style.parsedOptions = localHour(el.style.pattern);\n });\n return ast;\n}\n\n/**\n * A skeleton's `j` is the hour as the locale writes it, 12 or 24. The parser resolves it only when\n * given a locale and drops it otherwise, so `::yMMMdjmm` showed the minute alone. Read here as an\n * hour with no cycle, which `Intl` gives the cycle of whatever locale formats it: \"2:05 PM\", \"14:05\".\n */\nfunction localHour(pattern: string): Intl.DateTimeFormatOptions {\n const run = /j+/.exec(pattern)![0];\n const rest = pattern.replaceAll('j', '');\n return { ...(rest === '' ? {} : dateFields(rest)), hour: run.length % 2 === 0 ? '2-digit' : 'numeric' };\n}\n\n/** What the parser reads from a date skeleton on its own; nothing where it can't parse it. */\nfunction dateFields(skeleton: string): Intl.DateTimeFormatOptions {\n try {\n const el = parse(`{d, date, ::${skeleton}}`, { shouldParseSkeletons: true })[0];\n return el !== undefined && isDateElement(el) && typeof el.style === 'object' && el.style !== null ? el.style.parsedOptions : {};\n } catch {\n return {};\n }\n}\n\n/**\n * ICU argument types, as TypeScript. A bare `{x}` is a string: a number spliced in raw is printed by\n * `String()`, which knows no locale, so a quantity says `{x, number}` and the type is what asks for it.\n * A `select` narrows to the arms it names, and still takes any string, since `other` catches the rest.\n */\nfunction argType(el: MessageFormatElement): string | undefined {\n switch (el.type) {\n case TYPE.argument:\n return 'string';\n case TYPE.number:\n case TYPE.plural:\n return 'number';\n case TYPE.date:\n case TYPE.time:\n return 'Date | number';\n case TYPE.select: {\n const named = Object.keys(el.options).filter((o) => o !== 'other');\n return named.length > 0 ? `${named.map((o) => JSON.stringify(o)).join(' | ')} | (string & {})` : 'string';\n }\n case TYPE.literal:\n case TYPE.pound:\n case TYPE.tag:\n default:\n return undefined;\n }\n}\n\n/**\n * Formatting options read from arguments as the message renders, by `Intl` option: MF2's\n * `{$price :currency currency=$code}`. MF1 has no way to say this, so only a tree read from MF2 has them.\n */\nexport type OptionArgs = Record<string, { arg: string; type: string }>;\n\nexport function optionArgsOf(el: MessageFormatElement): OptionArgs | undefined {\n if (el.type === TYPE.plural) return selectionOf(el)?.args;\n if (!isNumberElement(el) && !isDateElement(el) && !isTimeElement(el)) return undefined;\n const style = el.style;\n return typeof style === 'object' && style !== null && 'args' in style ? (style as { args: OptionArgs }).args : undefined;\n}\n\n/**\n * How an MF2 `.match` selects on a number, beyond its plural type: on the number as formatted, so\n * the selector's digit options (`Intl.PluralRules` takes the same ones, some from arguments) decide\n * the category, and on the value `:integer` rounds or `:percent` multiplies by 100. Kept with the\n * selector's function as written, to write it back. MF1 selects on the bare value.\n */\nexport interface Selection {\n fn: { name: string; options: Record<string, string>; args?: Record<string, string> };\n options: Intl.PluralRulesOptions;\n args?: OptionArgs;\n value?: 'round' | 'percent';\n}\n\nexport function selectionOf(el: MessageFormatElement): Selection | undefined {\n return el.type === TYPE.plural ? (el as { selection?: Selection }).selection : undefined;\n}\n\n/**\n * How a placeholder renders around its value: the text it shows when it has none, or one `Intl`\n * can't format, and the marks isolating its direction from the text around it. MF2 sets these\n * (`{$name}`, and isolation in a right-to-left locale); an MF1 placeholder shows `{name}`.\n */\nexport interface Placeholder {\n fallback: string;\n isolate?: readonly [open: string, close: string];\n}\n\nexport function placeholderOf(el: MessageFormatElement, name: string): Placeholder {\n return (el as { placeholder?: Placeholder }).placeholder ?? { fallback: `{${name}}` };\n}\n\n/** Calls `visit` on every element, inside plural and select arms and tags too. */\nexport function walk(elements: MessageFormatElement[], visit: (el: MessageFormatElement) => void): void {\n for (const el of elements) {\n visit(el);\n if (el.type === TYPE.select || el.type === TYPE.plural) for (const option of Object.values(el.options)) walk(option.value, visit);\n if (el.type === TYPE.tag) walk(el.children, visit);\n }\n}\n\nexport function argsOf(elements: MessageFormatElement[], clash: (name: string) => void = () => {}): Map<string, string> {\n const args = new Map<string, string>();\n const add = (name: string, type: string) => {\n const seen = args.get(name);\n if (seen !== undefined && seen !== type) clash(name);\n else args.set(name, type);\n };\n walk(elements, (el) => {\n for (const { arg, type } of Object.values(optionArgsOf(el) ?? {})) add(arg, type);\n const type = argType(el);\n if (type !== undefined && 'value' in el && typeof el.value === 'string') add(el.value, type);\n });\n return args;\n}\n\nexport function tagsOf(elements: MessageFormatElement[]): string[] {\n const tags = new Set<string>();\n walk(elements, (el) => {\n if (el.type === TYPE.tag) tags.add(el.value);\n });\n return [...tags].sort();\n}\n\n/** A message with no placeholders and no tags renders to one literal, which ships as a plain string. */\nexport function staticLiteral(ast: MessageFormatElement[]): string | undefined {\n if (ast.length === 0) return '';\n if (ast.length === 1 && ast[0]!.type === TYPE.literal) return ast[0]!.value;\n return undefined;\n}\n\nconst NUMBER_STYLES: Record<string, Intl.NumberFormatOptions> = {\n integer: { maximumFractionDigits: 0 },\n percent: { style: 'percent' },\n};\n\nconst DATE_STYLES = ['short', 'medium', 'long', 'full'] as const;\n\n/** The `Intl` options a number element formats with. Throws, with the fix, where the style is wrong. */\nexport function numberOptions(el: NumberElement): Intl.NumberFormatOptions | undefined {\n const style = el.style;\n if (style === null || style === undefined) return undefined;\n if (typeof style === 'string') {\n if (style === 'currency') throw new Error(`{${el.value}} needs its currency named: {${el.value}, number, ::currency/EUR}`);\n const named = NUMBER_STYLES[style];\n if (named === undefined) throw new Error(`{${el.value}} asks for number style \"${style}\": integer, percent, or a ::skeleton`);\n return named;\n }\n const unknown = style.tokens.filter((t) => !stemIsKnown(t.stem, t.options)).map((t) => t.stem);\n if (unknown.length > 0) throw new Error(`{${el.value}} has skeleton stems the formatter drops without a word: ${unknown.join(', ')}`);\n const options = style.parsedOptions as Intl.NumberFormatOptions;\n // An option an argument gives is checked with a stand-in: `Intl` refuses a currency style without\n // its currency. Whether the argument's value suits it is known only as the message renders.\n const args = optionArgsOf(el) ?? {};\n const given = { ...(args.currency && { currency: 'EUR' }), ...(args.unit && { unit: 'meter' }) };\n try {\n new Intl.NumberFormat('en', { ...options, ...given }).resolvedOptions();\n } catch (e) {\n throw new Error(`{${el.value}} has a skeleton Intl refuses: ${(e as Error).message}`, { cause: e });\n }\n return options;\n}\n\n/** The `Intl` options a date or time element formats with. Throws, with the fix, where the style is wrong. */\nexport function dateOptions(el: DateElement | TimeElement): Intl.DateTimeFormatOptions | undefined {\n const style = el.style;\n const field = el.type === TYPE.time ? 'timeStyle' : 'dateStyle';\n if (style === null || style === undefined) return el.type === TYPE.time ? { timeStyle: 'short' } : undefined;\n if (typeof style === 'string') {\n if (!(DATE_STYLES as readonly string[]).includes(style)) {\n const kind = el.type === TYPE.time ? 'time' : 'date';\n throw new Error(`{${el.value}} asks for ${kind} style \"${style}\": ${DATE_STYLES.join(', ')}, or a ::skeleton`);\n }\n return { [field]: style };\n }\n // Every field the parser understands sets an option, and one it doesn't is dropped without a word.\n const dropped = (style.pattern.match(/([A-Za-z])\u0001*/g) ?? []).filter((f) => !f.startsWith('j') && Object.keys(dateFields(f)).length === 0);\n if (dropped.length > 0) throw new Error(`{${el.value}} has date fields the formatter drops without a word: ${dropped.join(', ')}`);\n if (Object.keys(style.parsedOptions).length === 0)\n throw new Error(`{${el.value}} has a date skeleton the formatter reads nothing from: \"${style.pattern}\"`);\n try {\n new Intl.DateTimeFormat('en', style.parsedOptions).resolvedOptions();\n } catch (e) {\n throw new Error(`{${el.value}} has a skeleton Intl refuses: ${(e as Error).message}`, { cause: e });\n }\n return style.parsedOptions;\n}\n\n/**\n * Whether the formatter understands a skeleton stem. One it does not is dropped without a word, so\n * `::compact-shortt` reaches the screen as `5` where `5K` was written. Every stem it understands sets\n * at least one option, so an empty result is the answer.\n */\nfunction stemIsKnown(stem: string, options: readonly string[]): boolean {\n try {\n const el = parse(`{n, number, ::${[stem, ...options].join('/')}}`, { shouldParseSkeletons: true })[0];\n if (el === undefined || !isNumberElement(el) || typeof el.style !== 'object' || el.style === null) return false;\n return Object.keys(el.style.parsedOptions).length > 0;\n } catch {\n return false;\n }\n}\n\n/** Every format problem in a message, as sentences. */\nexport function formatProblems(ast: MessageFormatElement[]): string[] {\n const found: string[] = [];\n walk(ast, (el) => {\n try {\n if (isNumberElement(el)) numberOptions(el);\n if (isDateElement(el) || isTimeElement(el)) dateOptions(el);\n } catch (e) {\n found.push((e as Error).message);\n }\n });\n return found;\n}\n\n/** How each number or date argument is formatted, for the generated doc comment: `date medium, time short`. */\nexport function formatsOf(ast: MessageFormatElement[]): Map<string, string> {\n const formats = new Map<string, string[]>();\n walk(ast, (el) => {\n if (!isNumberElement(el) && !isDateElement(el) && !isTimeElement(el)) return;\n const style = el.style;\n if (style === null || style === undefined) return;\n const kind = el.type === TYPE.number ? 'number' : el.type === TYPE.date ? 'date' : 'time';\n let said: string;\n if (typeof style === 'string') {\n said = `${kind} \\`${style}\\``;\n } else if ('mf2' in style) {\n // Read from MF2: the function as written.\n const {\n name,\n options,\n args = {},\n } = (style as { mf2: { name: string; options: Record<string, string>; args?: Record<string, string> } }).mf2;\n const written = [...Object.entries(options), ...Object.entries(args).map(([k, v]) => [k, `$${v}`])];\n said = `\\`${[`:${name}`, ...written.map(([k, v]) => `${k}=${v}`)].join(' ')}\\``;\n } else if ('tokens' in style) {\n said = `${kind} \\`::${style.tokens.map((t) => [t.stem, ...t.options].join('/')).join(' ')}\\``;\n } else {\n said = `${kind} \\`::${style.pattern}\\``;\n }\n const list = formats.get(el.value) ?? [];\n if (!list.includes(said)) list.push(said);\n formats.set(el.value, list);\n });\n return new Map([...formats].map(([name, list]) => [name, list.join(', ')]));\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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";
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-B3V_QRkT.js";
2
2
  /**
3
3
  * Picks the best available locale for what was asked, in the order it was asked.
4
4
  *
@@ -10,11 +10,21 @@ export declare function negotiate<L extends string>(requested: string | readonly
10
10
  export declare function chainOf<L extends string>(locale: L, available: readonly L[], source: L): L[];
11
11
  /** The locales the browser or the environment asks for, most preferred first. */
12
12
  export declare function preferredLocales(): string[];
13
+ /**
14
+ * The locales an `Accept-Language` header asks for, most preferred first: ordered by `q`, ties in
15
+ * the header's order. `q=0` means "not this one", and `*` asks for nothing `negotiate`'s fallback
16
+ * doesn't already give, so both are left out, as is a part whose `q` isn't a number.
17
+ */
18
+ export declare function acceptedLocales(header: string | null | undefined): string[];
13
19
  /**
14
20
  * The helpers compiled messages call, for one locale. Formatters are built once per options object:
15
21
  * the generator hoists each message's options to a module-level constant, so identity is a stable key.
22
+ * Options an argument gives are built per call, so those are told apart by value.
23
+ *
24
+ * A value missing, or one `Intl` refuses, never throws: `n` and `d` give nothing back, and the message
25
+ * shows the placeholder's fallback through `e`, which hands `report` what went wrong.
16
26
  */
17
- export declare function createHelpers(locale: string): Helpers;
27
+ export declare function createHelpers(locale: string, report?: (error: Error) => void): Helpers;
18
28
  /**
19
29
  * A compiled message's output as a flat list: nested arrays opened, adjacent text joined, and
20
30
  * `null`, `undefined` and booleans dropped, as React drops them.
@@ -1 +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"}
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;;;;;;wBAaA,gBAAgB;;;;;;;;;wBCtChB,cAAc,gBAAgB,UAAU,OAAO,iBAAiB;;;;;wBA0EhE,QAAQ"}
package/dist/index.js CHANGED
@@ -1,188 +1,4 @@
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
1
+ import { n as defineCatalog, t as createCopy } from "./copy-Bo_FIoLy.js";
2
+ import { i as preferredLocales, n as chainOf, r as negotiate, t as acceptedLocales } from "./locale-DpQA1pqH.js";
3
+ import { n as flatten, t as createHelpers } from "./helpers-BWGcD0kS.js";
4
+ export { acceptedLocales, chainOf, createCopy, createHelpers, defineCatalog, flatten, negotiate, preferredLocales };
@@ -32,6 +32,9 @@ export interface InspectorOptions {
32
32
  /** The dev server's base URL, ending in `/`. */
33
33
  base: string;
34
34
  }
35
- /** Starts listening for reads and for Alt on the page. Called by the module the Vite plugin serves. */
35
+ /**
36
+ * Starts listening for reads and for Alt on the page, once: Bun and Next call it from the generated
37
+ * index, which runs again whenever it changes.
38
+ */
36
39
  export declare function install({ base }: InspectorOptions): void;
37
40
  //# sourceMappingURL=inspector.d.ts.map
@@ -1 +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"}
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;;;;;;wBAOc,UAAU,QAAQ"}
package/dist/inspector.js CHANGED
@@ -456,10 +456,15 @@ function caretAt(x, y) {
456
456
  offset: !range.collapsed && x >= r.left && x <= r.right && y >= r.top && y <= r.bottom ? offset : offset - 1
457
457
  };
458
458
  }
459
- /** Starts listening for reads and for Alt on the page. Called by the module the Vite plugin serves. */
459
+ /**
460
+ * Starts listening for reads and for Alt on the page, once: Bun and Next call it from the generated
461
+ * index, which runs again whenever it changes.
462
+ */
460
463
  function install({ base }) {
464
+ const hooks = globalThis;
465
+ if (hooks.__PROSEBOOK_READ__ !== void 0) return;
461
466
  const reads = /* @__PURE__ */ new Map();
462
- globalThis.__PROSEBOOK_READ__ = (key, text, locale) => record(reads, key, text, locale);
467
+ hooks.__PROSEBOOK_READ__ = (key, text, locale) => record(reads, key, text, locale);
463
468
  const box = document.createElement("div");
464
469
  const label = document.createElement("div");
465
470
  box.append(label);