@saykit/config 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![Coverage](https://codecov.io/gh/k0d13/saykit/graph/badge.svg?flag=config)](https://codecov.io/gh/k0d13/saykit?flags%5B0%5D=config)
6
6
 
7
- Provides the `defineConfig` helper, the Zod-validated schema for `saykit.config.ts`, and the `saykit` CLI used to extract messages from your source.
7
+ Provides the `defineConfig` helper, the Zod-validated schema for `saykit.config.ts`, and the `saykit` CLI that extracts messages from your source and compiles catalogues into the modules your app imports.
8
8
 
9
9
  ## Install
10
10
 
@@ -43,6 +43,7 @@ export default defineConfig({
43
43
  ```sh
44
44
  saykit extract # extract messages once
45
45
  saykit extract --watch # extract and watch for changes
46
+ saykit compile # compile catalogues to {locale}.js modules
46
47
  saykit clean # prune dead entries from other locales
47
48
  ```
48
49
 
@@ -50,9 +51,10 @@ Extraction only writes the **source** locale (the first entry in `locales`). Loc
50
51
  have a file yet are bootstrapped with an empty, header-only catalogue so a TMS can register them;
51
52
  existing translation files are left untouched. Translated content is owned by your TMS.
52
53
 
53
- At load time (via the unplugin or Babel plugin), each locale module is filled from its fallback
54
- chain, so an untranslated key resolves to a fallback locale and ultimately the source string, the
55
- runtime still loads a single locale.
54
+ `saykit compile` (which `extract` also runs) writes a `{locale}.js` beside each catalogue, filled
55
+ from its fallback chain so an untranslated key resolves to a fallback locale and ultimately the
56
+ source string. Ignore the `.js` modules in git and compile before dev and build; commit the
57
+ `{locale}.d.ts` beside them.
56
58
 
57
59
  `saykit clean` only ever subtracts from non-source locales: it drops entries that no longer exist in
58
60
  the source catalogue and entries with an empty translation, and leaves everything else exactly as it
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- const require_storage = require("../storage-DSdC-TYq.cjs");
3
- const require_loader = require("../loader-XE7SprlF.cjs");
2
+ const require_emit = require("../emit-NFgnvdzK.cjs");
3
+ const require_loader = require("../loader-V92j1gfD.cjs");
4
4
  let _commander_js_extra_typings = require("@commander-js/extra-typings");
5
5
  let node_fs_promises = require("node:fs/promises");
6
6
  let node_path = require("node:path");
7
7
  let node_async_hooks = require("node:async_hooks");
8
- //#region src/features/logger.ts
8
+ // src/features/logger.ts
9
9
  const RESET = "\x1B[0m";
10
10
  const DIM = "\x1B[2m";
11
11
  const BRIGHT = "\x1B[1m";
@@ -43,28 +43,54 @@ var Logger = class {
43
43
  }
44
44
  };
45
45
  new node_async_hooks.AsyncLocalStorage({ defaultValue: new Logger() });
46
- //#endregion
47
- //#region src/commands/clean.ts
46
+ // src/commands/clean.ts
48
47
  var clean_default = new _commander_js_extra_typings.Command("clean").description("Remove orphaned and untranslated entries from non-source locale files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
49
48
  const config = require_loader.resolveConfig();
50
49
  const logger = new Logger(options);
51
50
  logger.header("🧹 Cleaning Locales");
52
51
  const [sourceLocale, ...otherLocales] = config.locales;
53
52
  for (const bucket of config.buckets) {
54
- const sourceMessages = await require_storage.readCatalogueMessages(bucket, sourceLocale);
53
+ const sourceMessages = await require_emit.readCatalogueMessages(bucket, sourceLocale);
55
54
  logger.info(`Cleaning ${otherLocales.length} locale(s) against ${sourceLocale}`);
56
55
  for (const locale of otherLocales) {
57
56
  logger.step(`Cleaning ${locale}`);
58
- const existingMessages = await require_storage.readCatalogueMessages(bucket, locale);
59
- const prunedMessages = require_storage.pruneLocaleMessages(existingMessages, sourceMessages);
57
+ const existingMessages = await require_emit.readCatalogueMessages(bucket, locale);
58
+ const prunedMessages = require_emit.pruneLocaleMessages(existingMessages, sourceMessages);
60
59
  logger.step(`Removed ${existingMessages.length - prunedMessages.length} entries from ${locale}`);
61
- await require_storage.writeCatalogueMessages(bucket, locale, prunedMessages);
60
+ await require_emit.writeCatalogueMessages(bucket, locale, prunedMessages);
62
61
  }
63
62
  }
64
63
  logger.success("Locales cleaned");
65
64
  });
66
- //#endregion
67
- //#region src/features/catalogue/extractor.ts
65
+ // src/features/workers/shared.ts
66
+ function normalisePathForLogs(path) {
67
+ return (0, node_path.relative)(process.cwd(), path).replaceAll("\\", "/");
68
+ }
69
+ var BucketWorker = class {
70
+ config;
71
+ bucket;
72
+ logger;
73
+ constructor(config, bucket, logger) {
74
+ this.config = config;
75
+ this.bucket = bucket;
76
+ this.logger = logger;
77
+ }
78
+ };
79
+ // src/commands/compile.ts
80
+ var compile_default = new _commander_js_extra_typings.Command("compile").description("Compile catalogues into locale modules").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
81
+ const config = require_loader.resolveConfig();
82
+ const logger = new Logger(options);
83
+ logger.header("⚙ Compiling Catalogues");
84
+ for (const bucket of config.buckets) {
85
+ logger.info(`Compiling ${config.locales.length} locale(s): ${bucket.include}`);
86
+ for (const locale of config.locales) {
87
+ const { path, count } = await require_emit.emitCatalogueModule(config, bucket, locale);
88
+ logger.step(`Wrote ${count} message(s) to ${normalisePathForLogs(path)}`);
89
+ }
90
+ }
91
+ logger.success("Catalogues compiled");
92
+ });
93
+ // src/features/catalogue/extractor.ts
68
94
  async function extractMessagesFromFile(path, bucket) {
69
95
  const content = await (0, node_fs_promises.readFile)(path, "utf8").catch(() => "");
70
96
  if (!content) return [];
@@ -74,8 +100,7 @@ async function extractMessagesFromFile(path, bucket) {
74
100
  references: message.references.map((reference) => (0, node_path.relative)(process.cwd(), reference).replaceAll("\\", "/"))
75
101
  }));
76
102
  }
77
- //#endregion
78
- //#region src/features/watch.ts
103
+ // src/features/watch.ts
79
104
  async function globBucket(bucket) {
80
105
  const paths = [];
81
106
  for await (const path of (0, node_fs_promises.glob)(bucket.include, { exclude: bucket.exclude })) try {
@@ -107,23 +132,7 @@ async function* watchDebounced(path, options, delay = 300) {
107
132
  yield next;
108
133
  } else await new Promise((r) => setTimeout(r, 10));
109
134
  }
110
- //#endregion
111
- //#region src/features/workers/shared.ts
112
- function normalisePathForLogs(path) {
113
- return (0, node_path.relative)(process.cwd(), path).replaceAll("\\", "/");
114
- }
115
- var BucketWorker = class {
116
- config;
117
- bucket;
118
- logger;
119
- constructor(config, bucket, logger) {
120
- this.config = config;
121
- this.bucket = bucket;
122
- this.logger = logger;
123
- }
124
- };
125
- //#endregion
126
- //#region src/features/workers/extract-worker.ts
135
+ // src/features/workers/extract-worker.ts
127
136
  function exists(path) {
128
137
  return (0, node_fs_promises.access)(path).then(() => true, () => false);
129
138
  }
@@ -158,21 +167,28 @@ var BucketExtractWorker = class extends BucketWorker {
158
167
  this.logger.info(`Total extracted messages: ${extracted}`);
159
168
  }
160
169
  async write() {
161
- const mergedMessages = require_storage.mergeExtractedMessages(this.#indexedMessages);
170
+ const mergedMessages = require_emit.mergeExtractedMessages(this.#indexedMessages);
162
171
  const [sourceLocale, ...otherLocales] = this.config.locales;
163
172
  this.logger.info(`Writing ${mergedMessages.length} messages to ${sourceLocale}`);
164
173
  this.logger.step(`Writing locale file for ${sourceLocale} to disk`);
165
- await require_storage.writeCatalogueMessages(this.bucket, sourceLocale, mergedMessages);
174
+ await require_emit.writeCatalogueMessages(this.bucket, sourceLocale, mergedMessages);
166
175
  for (const locale of otherLocales) {
167
- if (await exists(require_storage.expandBucketOutputPath(this.bucket, locale))) {
176
+ if (await exists(require_emit.expandBucketOutputPath(this.bucket, locale))) {
168
177
  this.logger.step(`Skipping existing locale file for ${locale}`);
169
178
  continue;
170
179
  }
171
180
  this.logger.step(`Creating empty locale file for ${locale}`);
172
- await require_storage.writeCatalogueMessages(this.bucket, locale, []);
181
+ await require_emit.writeCatalogueMessages(this.bucket, locale, []);
173
182
  }
183
+ await this.compile();
174
184
  this.logger.success(`Extraction complete for bucket: ${this.bucket.include}`);
175
185
  }
186
+ async compile() {
187
+ for (const locale of this.config.locales) {
188
+ const { path, count } = await require_emit.emitCatalogueModule(this.config, this.bucket, locale);
189
+ this.logger.step(`Compiled ${count} message(s) to ${normalisePathForLogs(path)}`);
190
+ }
191
+ }
176
192
  async update(path) {
177
193
  const changed = await this.#indexPath(path);
178
194
  if (changed) await this.write();
@@ -181,14 +197,17 @@ var BucketExtractWorker = class extends BucketWorker {
181
197
  async watch() {
182
198
  this.logger.header(`👀 Watching bucket for changes: ${this.bucket.include}`);
183
199
  for await (const event of watchDebounced(".", { recursive: true })) {
184
- if (!event.filename || !this.bucket.match(event.filename)) continue;
200
+ if (!event.filename) continue;
185
201
  const path = (0, node_path.join)(process.cwd(), event.filename);
186
- await this.update(path);
202
+ if (this.bucket.output.match(event.filename)) {
203
+ await this.compile();
204
+ continue;
205
+ }
206
+ if (this.bucket.match(event.filename)) await this.update(path);
187
207
  }
188
208
  }
189
209
  };
190
- //#endregion
191
- //#region src/commands/extract.ts
210
+ // src/commands/extract.ts
192
211
  var extract_default = new _commander_js_extra_typings.Command("extract").description("Extract messages from source files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).option("-w, --watch", "watch source files for changes", false).action(async (options) => {
193
212
  const config = require_loader.resolveConfig();
194
213
  const logger = new Logger(options);
@@ -197,7 +216,5 @@ var extract_default = new _commander_js_extra_typings.Command("extract").descrip
197
216
  await Promise.all(workers.map((w) => w.scan().then(() => w.write())));
198
217
  if (options.watch) await Promise.all(workers.map((w) => w.watch()));
199
218
  });
200
- //#endregion
201
- //#region src/commands/index.ts
202
- _commander_js_extra_typings.program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(clean_default).parse();
203
- //#endregion
219
+ // src/commands/index.ts
220
+ _commander_js_extra_typings.program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(compile_default).addCommand(clean_default).parse();
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { a as mergeExtractedMessages, i as expandBucketOutputPath, n as writeCatalogueMessages, o as pruneLocaleMessages, t as readCatalogueMessages } from "../storage-8485YdBh.mjs";
3
- import { t as resolveConfig } from "../loader-C6XxYhMl.mjs";
2
+ import { c as writeCatalogueMessages, d as pruneLocaleMessages, l as expandBucketOutputPath, s as readCatalogueMessages, t as emitCatalogueModule, u as mergeExtractedMessages } from "../emit-q78c-G_t.mjs";
3
+ import { t as resolveConfig } from "../loader-1PBbw7yb.mjs";
4
4
  import { Command, program } from "@commander-js/extra-typings";
5
5
  import { access, glob, readFile, stat, watch } from "node:fs/promises";
6
6
  import { join, relative } from "node:path";
7
7
  import { AsyncLocalStorage } from "node:async_hooks";
8
- //#region src/features/logger.ts
8
+ // src/features/logger.ts
9
9
  const RESET = "\x1B[0m";
10
10
  const DIM = "\x1B[2m";
11
11
  const BRIGHT = "\x1B[1m";
@@ -43,8 +43,7 @@ var Logger = class {
43
43
  }
44
44
  };
45
45
  new AsyncLocalStorage({ defaultValue: new Logger() });
46
- //#endregion
47
- //#region src/commands/clean.ts
46
+ // src/commands/clean.ts
48
47
  var clean_default = new Command("clean").description("Remove orphaned and untranslated entries from non-source locale files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
49
48
  const config = resolveConfig();
50
49
  const logger = new Logger(options);
@@ -63,8 +62,35 @@ var clean_default = new Command("clean").description("Remove orphaned and untran
63
62
  }
64
63
  logger.success("Locales cleaned");
65
64
  });
66
- //#endregion
67
- //#region src/features/catalogue/extractor.ts
65
+ // src/features/workers/shared.ts
66
+ function normalisePathForLogs(path) {
67
+ return relative(process.cwd(), path).replaceAll("\\", "/");
68
+ }
69
+ var BucketWorker = class {
70
+ config;
71
+ bucket;
72
+ logger;
73
+ constructor(config, bucket, logger) {
74
+ this.config = config;
75
+ this.bucket = bucket;
76
+ this.logger = logger;
77
+ }
78
+ };
79
+ // src/commands/compile.ts
80
+ var compile_default = new Command("compile").description("Compile catalogues into locale modules").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).action(async (options) => {
81
+ const config = resolveConfig();
82
+ const logger = new Logger(options);
83
+ logger.header("⚙ Compiling Catalogues");
84
+ for (const bucket of config.buckets) {
85
+ logger.info(`Compiling ${config.locales.length} locale(s): ${bucket.include}`);
86
+ for (const locale of config.locales) {
87
+ const { path, count } = await emitCatalogueModule(config, bucket, locale);
88
+ logger.step(`Wrote ${count} message(s) to ${normalisePathForLogs(path)}`);
89
+ }
90
+ }
91
+ logger.success("Catalogues compiled");
92
+ });
93
+ // src/features/catalogue/extractor.ts
68
94
  async function extractMessagesFromFile(path, bucket) {
69
95
  const content = await readFile(path, "utf8").catch(() => "");
70
96
  if (!content) return [];
@@ -74,8 +100,7 @@ async function extractMessagesFromFile(path, bucket) {
74
100
  references: message.references.map((reference) => relative(process.cwd(), reference).replaceAll("\\", "/"))
75
101
  }));
76
102
  }
77
- //#endregion
78
- //#region src/features/watch.ts
103
+ // src/features/watch.ts
79
104
  async function globBucket(bucket) {
80
105
  const paths = [];
81
106
  for await (const path of glob(bucket.include, { exclude: bucket.exclude })) try {
@@ -107,23 +132,7 @@ async function* watchDebounced(path, options, delay = 300) {
107
132
  yield next;
108
133
  } else await new Promise((r) => setTimeout(r, 10));
109
134
  }
110
- //#endregion
111
- //#region src/features/workers/shared.ts
112
- function normalisePathForLogs(path) {
113
- return relative(process.cwd(), path).replaceAll("\\", "/");
114
- }
115
- var BucketWorker = class {
116
- config;
117
- bucket;
118
- logger;
119
- constructor(config, bucket, logger) {
120
- this.config = config;
121
- this.bucket = bucket;
122
- this.logger = logger;
123
- }
124
- };
125
- //#endregion
126
- //#region src/features/workers/extract-worker.ts
135
+ // src/features/workers/extract-worker.ts
127
136
  function exists(path) {
128
137
  return access(path).then(() => true, () => false);
129
138
  }
@@ -171,8 +180,15 @@ var BucketExtractWorker = class extends BucketWorker {
171
180
  this.logger.step(`Creating empty locale file for ${locale}`);
172
181
  await writeCatalogueMessages(this.bucket, locale, []);
173
182
  }
183
+ await this.compile();
174
184
  this.logger.success(`Extraction complete for bucket: ${this.bucket.include}`);
175
185
  }
186
+ async compile() {
187
+ for (const locale of this.config.locales) {
188
+ const { path, count } = await emitCatalogueModule(this.config, this.bucket, locale);
189
+ this.logger.step(`Compiled ${count} message(s) to ${normalisePathForLogs(path)}`);
190
+ }
191
+ }
176
192
  async update(path) {
177
193
  const changed = await this.#indexPath(path);
178
194
  if (changed) await this.write();
@@ -181,14 +197,17 @@ var BucketExtractWorker = class extends BucketWorker {
181
197
  async watch() {
182
198
  this.logger.header(`👀 Watching bucket for changes: ${this.bucket.include}`);
183
199
  for await (const event of watchDebounced(".", { recursive: true })) {
184
- if (!event.filename || !this.bucket.match(event.filename)) continue;
200
+ if (!event.filename) continue;
185
201
  const path = join(process.cwd(), event.filename);
186
- await this.update(path);
202
+ if (this.bucket.output.match(event.filename)) {
203
+ await this.compile();
204
+ continue;
205
+ }
206
+ if (this.bucket.match(event.filename)) await this.update(path);
187
207
  }
188
208
  }
189
209
  };
190
- //#endregion
191
- //#region src/commands/extract.ts
210
+ // src/commands/extract.ts
192
211
  var extract_default = new Command("extract").description("Extract messages from source files").option("-v, --verbose", "enable verbose logging", false).option("-q, --quiet", "suppress all logging", false).option("-w, --watch", "watch source files for changes", false).action(async (options) => {
193
212
  const config = resolveConfig();
194
213
  const logger = new Logger(options);
@@ -197,8 +216,6 @@ var extract_default = new Command("extract").description("Extract messages from
197
216
  await Promise.all(workers.map((w) => w.scan().then(() => w.write())));
198
217
  if (options.watch) await Promise.all(workers.map((w) => w.watch()));
199
218
  });
200
- //#endregion
201
- //#region src/commands/index.ts
202
- program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(clean_default).parse();
203
- //#endregion
219
+ // src/commands/index.ts
220
+ program.name("saykit").helpOption("-h, --help", "Display help for command").helpCommand("help [command]", "Display help for command").addCommand(extract_default).addCommand(compile_default).addCommand(clean_default).parse();
204
221
  export {};
@@ -0,0 +1,286 @@
1
+ let js_sha256 = require("js-sha256");
2
+ let _messageformat_parser = require("@messageformat/parser");
3
+ let _messageformat_date_skeleton = require("@messageformat/date-skeleton");
4
+ let _messageformat_number_skeleton = require("@messageformat/number-skeleton");
5
+ // src/features/messages/hash.ts
6
+ function generateHash(input, context) {
7
+ const hasher = js_sha256.sha256.create();
8
+ hasher.update(`${input}\u{001F}${context || ""}`);
9
+ const elements = hasher.toString().match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) || [];
10
+ const bytes = Uint8Array.from(elements);
11
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "").slice(0, 6);
12
+ }
13
+ // src/features/messages/styles.ts
14
+ const ARGUMENT_STYLES = {
15
+ number: ["integer", "percent"],
16
+ date: [
17
+ "short",
18
+ "medium",
19
+ "long",
20
+ "full"
21
+ ],
22
+ time: [
23
+ "short",
24
+ "medium",
25
+ "long",
26
+ "full"
27
+ ]
28
+ };
29
+ const ARGUMENT_TYPES = Object.keys(ARGUMENT_STYLES);
30
+ function isArgumentType(kind) {
31
+ return Object.hasOwn(ARGUMENT_STYLES, kind);
32
+ }
33
+ var StyleError = class extends Error {};
34
+ const SKELETON = "::";
35
+ const LITERAL_STYLE_PATTERN = /^[^{}\r\n]*[#0][^{}\r\n]*$/;
36
+ const DATE_STYLES = {
37
+ short: {
38
+ year: "numeric",
39
+ month: "numeric",
40
+ day: "numeric"
41
+ },
42
+ medium: {
43
+ year: "numeric",
44
+ month: "short",
45
+ day: "numeric"
46
+ },
47
+ long: {
48
+ year: "numeric",
49
+ month: "long",
50
+ day: "numeric"
51
+ },
52
+ full: {
53
+ year: "numeric",
54
+ month: "long",
55
+ day: "numeric",
56
+ weekday: "long"
57
+ }
58
+ };
59
+ const TIME_STYLES = {
60
+ short: {
61
+ hour: "numeric",
62
+ minute: "numeric"
63
+ },
64
+ medium: {
65
+ hour: "numeric",
66
+ minute: "numeric",
67
+ second: "numeric"
68
+ },
69
+ long: {
70
+ hour: "numeric",
71
+ minute: "numeric",
72
+ second: "numeric",
73
+ timeZoneName: "short"
74
+ },
75
+ full: {
76
+ hour: "numeric",
77
+ minute: "numeric",
78
+ second: "numeric",
79
+ timeZoneName: "long"
80
+ }
81
+ };
82
+ function dateTimeStyle(kind, style) {
83
+ const named = kind === "date" ? DATE_STYLES : TIME_STYLES;
84
+ if (style.startsWith(SKELETON)) {
85
+ const errors = [];
86
+ const tokens = (0, _messageformat_date_skeleton.parseDateTokens)(style.slice(2));
87
+ const opt = (0, _messageformat_date_skeleton.getDateTimeFormatOptions)(tokens, (_type, message) => errors.push(message));
88
+ if (errors.length > 0) throw new StyleError(errors[0]);
89
+ if (Object.keys(opt).length === 0) throw new StyleError("it names no fields");
90
+ return opt;
91
+ }
92
+ if (style === "") return named.medium;
93
+ if (Object.hasOwn(named, style)) return named[style];
94
+ throw new StyleError(`Unsupported ${kind} style ${style}`);
95
+ }
96
+ function numberStyle(style) {
97
+ if (style === "") return {};
98
+ if (style === "integer") return { maximumFractionDigits: 0 };
99
+ if (style === "percent") return { style: "percent" };
100
+ if (style === SKELETON) throw new StyleError("it names no fields");
101
+ const errors = [];
102
+ const onError = (error) => errors.push(String(error));
103
+ const skeleton = style.startsWith(SKELETON) ? (0, _messageformat_number_skeleton.parseNumberSkeleton)(style.slice(2), onError) : (0, _messageformat_number_skeleton.parseNumberPattern)(style, "XXX", onError);
104
+ let scale;
105
+ const opt = (0, _messageformat_number_skeleton.getNumberFormatOptions)(skeleton, (stem, option) => {
106
+ if (stem === "scale") scale = Number(option);
107
+ else errors.push(`unsupported stem '${stem}'`);
108
+ });
109
+ if (errors.length > 0) throw new StyleError(errors[0]);
110
+ return scale === void 0 ? opt : {
111
+ ...opt,
112
+ scale
113
+ };
114
+ }
115
+ function validateArgumentStyle(type, style) {
116
+ const named = ARGUMENT_STYLES[type];
117
+ if (named.includes(style)) return;
118
+ const skeleton = style.startsWith(SKELETON);
119
+ if (skeleton || type === "number" && LITERAL_STYLE_PATTERN.test(style)) try {
120
+ if (type === "number") numberStyle(style);
121
+ else dateTimeStyle(type, style);
122
+ return;
123
+ } catch (error) {
124
+ if (!(error instanceof StyleError)) throw error;
125
+ throw new Error(`Invalid ${type} ${skeleton ? "skeleton" : "pattern"} '${style}': ${error.message}`);
126
+ }
127
+ const expected = named.map((s) => `'${s}'`).join(", ");
128
+ throw new Error(`Invalid ${type} style '${style}', expected ${expected}, or a skeleton such as ` + (type === "number" ? "::currency/EUR" : "::yyyyMMdd") + (type === "number" ? ", or a literal number pattern such as #,##0.00" : ""));
129
+ }
130
+ // src/features/messages/compile.ts
131
+ const Kind = {
132
+ Value: 0,
133
+ Number: 1,
134
+ DateTime: 2,
135
+ Plural: 3,
136
+ Ordinal: 4,
137
+ Select: 5,
138
+ Hash: 6
139
+ };
140
+ const EXACT = /^=\d+$/;
141
+ function key(argument) {
142
+ return `_${argument}`;
143
+ }
144
+ function concat(nodes) {
145
+ if (nodes.length === 0) return "";
146
+ if (nodes.length === 1) return nodes[0];
147
+ return nodes;
148
+ }
149
+ function compileMessage(icu) {
150
+ function argument(token) {
151
+ const value = key(token.arg);
152
+ let style = "";
153
+ for (const part of token.param ?? []) {
154
+ if (part.type !== "content") throw new Error(`Unsupported style part: ${part.type}`);
155
+ style += part.value;
156
+ }
157
+ style = style.trim();
158
+ const resolve = (read, fallback) => {
159
+ try {
160
+ return read(style);
161
+ } catch (error) {
162
+ if (!(error instanceof StyleError)) throw error;
163
+ return fallback();
164
+ }
165
+ };
166
+ switch (token.key) {
167
+ case "date":
168
+ case "time": {
169
+ const kind = token.key;
170
+ const options = resolve((s) => dateTimeStyle(kind, s), () => dateTimeStyle(kind, ""));
171
+ return [
172
+ Kind.DateTime,
173
+ value,
174
+ options
175
+ ];
176
+ }
177
+ case "number": {
178
+ const { scale, ...options } = resolve(numberStyle, () => ({}));
179
+ if (scale !== void 0) return [
180
+ Kind.Number,
181
+ value,
182
+ options,
183
+ scale
184
+ ];
185
+ return Object.keys(options).length === 0 ? [Kind.Number, value] : [
186
+ Kind.Number,
187
+ value,
188
+ options
189
+ ];
190
+ }
191
+ default: return [Kind.Value, value];
192
+ }
193
+ }
194
+ function choice(token) {
195
+ const value = key(token.arg);
196
+ const cases = Object.create(null);
197
+ for (const branch of token.cases) {
198
+ const key = EXACT.test(branch.key) ? `=${Number(branch.key.slice(1))}` : branch.key;
199
+ if (!Object.hasOwn(cases, key)) cases[key] = pattern(branch.tokens);
200
+ }
201
+ if (token.type === "select") return [
202
+ Kind.Select,
203
+ value,
204
+ cases
205
+ ];
206
+ const kind = token.type === "selectordinal" ? Kind.Ordinal : Kind.Plural;
207
+ const offset = token.pluralOffset ?? 0;
208
+ return offset === 0 ? [
209
+ kind,
210
+ value,
211
+ cases
212
+ ] : [
213
+ kind,
214
+ value,
215
+ cases,
216
+ offset
217
+ ];
218
+ }
219
+ function fragment(token) {
220
+ switch (token.type) {
221
+ case "content": return token.value;
222
+ case "argument": return [Kind.Value, key(token.arg)];
223
+ case "function": return argument(token);
224
+ case "octothorpe": return [Kind.Hash];
225
+ default: return choice(token);
226
+ }
227
+ }
228
+ function pattern(tokens) {
229
+ return concat(tokens.map(fragment));
230
+ }
231
+ return pattern((0, _messageformat_parser.parse)(icu));
232
+ }
233
+ Object.defineProperty(exports, "ARGUMENT_STYLES", {
234
+ enumerable: true,
235
+ get: function() {
236
+ return ARGUMENT_STYLES;
237
+ }
238
+ });
239
+ Object.defineProperty(exports, "ARGUMENT_TYPES", {
240
+ enumerable: true,
241
+ get: function() {
242
+ return ARGUMENT_TYPES;
243
+ }
244
+ });
245
+ Object.defineProperty(exports, "StyleError", {
246
+ enumerable: true,
247
+ get: function() {
248
+ return StyleError;
249
+ }
250
+ });
251
+ Object.defineProperty(exports, "compileMessage", {
252
+ enumerable: true,
253
+ get: function() {
254
+ return compileMessage;
255
+ }
256
+ });
257
+ Object.defineProperty(exports, "dateTimeStyle", {
258
+ enumerable: true,
259
+ get: function() {
260
+ return dateTimeStyle;
261
+ }
262
+ });
263
+ Object.defineProperty(exports, "generateHash", {
264
+ enumerable: true,
265
+ get: function() {
266
+ return generateHash;
267
+ }
268
+ });
269
+ Object.defineProperty(exports, "isArgumentType", {
270
+ enumerable: true,
271
+ get: function() {
272
+ return isArgumentType;
273
+ }
274
+ });
275
+ Object.defineProperty(exports, "numberStyle", {
276
+ enumerable: true,
277
+ get: function() {
278
+ return numberStyle;
279
+ }
280
+ });
281
+ Object.defineProperty(exports, "validateArgumentStyle", {
282
+ enumerable: true,
283
+ get: function() {
284
+ return validateArgumentStyle;
285
+ }
286
+ });