saykit 0.11.0 → 0.11.1

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/dist/index.d.mts CHANGED
@@ -70,6 +70,20 @@ declare namespace View {
70
70
  }) & {
71
71
  [match: string | number]: unknown;
72
72
  };
73
+ interface Options {
74
+ /**
75
+ * Whether a substituted value is wrapped in Unicode bidi isolation marks,
76
+ * U+2068 and U+2069, so right-to-left text cannot reorder the sentence
77
+ * around it.
78
+ *
79
+ * `'none'` leaves values byte-for-byte as given, for output that is parsed
80
+ * after it is formatted: a Discord mention such as `<#123>` stops being
81
+ * one once the id inside it is isolated.
82
+ *
83
+ * @default 'default'
84
+ */
85
+ bidiIsolation?: 'default' | 'none';
86
+ }
73
87
  }
74
88
  /**
75
89
  * One locale, bound to the messages it formats against.
@@ -115,6 +129,11 @@ interface View<Locale extends string = string> {
115
129
  readonly locale: Locale;
116
130
  /** The messages this view formats against. */
117
131
  readonly messages: Readonly<View.Messages>;
132
+ /**
133
+ * How this view formats its messages. Plain data, so it can cross a
134
+ * server/client boundary alongside {@link locale} and {@link messages}.
135
+ */
136
+ readonly options: Readonly<View.Options>;
118
137
  /**
119
138
  * Format the message a descriptor names, or the message it carries.
120
139
  *
@@ -252,9 +271,10 @@ interface View<Locale extends string = string> {
252
271
  *
253
272
  * @param locale The locale to bind to
254
273
  * @param messages The messages this view formats against
274
+ * @param options How this view formats its messages
255
275
  * @returns The view
256
276
  */
257
- declare function createView<Locale extends string>(locale: Locale, messages: View.Messages): View<Locale>;
277
+ declare function createView<Locale extends string>(locale: Locale, messages: View.Messages, options?: View.Options): View<Locale>;
258
278
  //#endregion
259
279
  //#region src/catalogue.d.ts
260
280
  declare namespace Catalogue {
@@ -281,6 +301,8 @@ declare namespace Catalogue {
281
301
  type Produced = View.Messages | {
282
302
  default: View.Messages;
283
303
  };
304
+ /** How every view the catalogue hands back formats its messages. */
305
+ type Options = View.Options;
284
306
  }
285
307
  /**
286
308
  * Everything an application knows how to say: its locales and where their
@@ -365,9 +387,11 @@ interface Catalogue<Locale extends string = string> {
365
387
  * @param messages Where each locale's messages come from, keyed by locale. The
366
388
  * keys are the catalogue's locales, in the order they are written, and the
367
389
  * first of them is the default locale
390
+ * @param options How every view the catalogue hands back formats its
391
+ * messages, such as `{ bidiIsolation: 'none' }`
368
392
  * @returns The catalogue
369
393
  */
370
- declare function createCatalogue<const Locale extends string = string>(messages: Record<Locale, Catalogue.Source>): Catalogue<Locale>;
394
+ declare function createCatalogue<const Locale extends string = string>(messages: Record<Locale, Catalogue.Source>, options?: Catalogue.Options): Catalogue<Locale>;
371
395
  //#endregion
372
396
  //#region src/store.d.ts
373
397
  declare namespace Store {
package/dist/index.mjs CHANGED
@@ -420,8 +420,11 @@ const functions = {
420
420
  };
421
421
  //#endregion
422
422
  //#region src/messageformat/index.ts
423
- function compile(locale, source) {
424
- return new MessageFormat(locale, toMessage(parse(source)), { functions });
423
+ function compile(locale, source, options) {
424
+ return new MessageFormat(locale, toMessage(parse(source)), {
425
+ ...options,
426
+ functions
427
+ });
425
428
  }
426
429
  //#endregion
427
430
  //#region src/view.ts
@@ -431,7 +434,9 @@ function resolveDescriptorValues(descriptor) {
431
434
  function macro(name) {
432
435
  throw new Error(`'say.${name}' is a macro and must be used with the relevant saykit plugin`);
433
436
  }
434
- function createView(locale, messages) {
437
+ function createView(locale, messages, options = {}) {
438
+ const { bidiIsolation } = options;
439
+ const ownOptions = Object.freeze(bidiIsolation ? { bidiIsolation } : {});
435
440
  const own = Object.freeze({ ...messages });
436
441
  const formats = /* @__PURE__ */ new Map();
437
442
  const say = (() => {
@@ -441,7 +446,7 @@ function createView(locale, messages) {
441
446
  const { id, message = own[id] } = descriptor;
442
447
  if (typeof message !== "string") throw new Error(`Message for ${id} is not a string`);
443
448
  let format = formats.get(message);
444
- if (!format) formats.set(message, format = compile(locale, message));
449
+ if (!format) formats.set(message, format = compile(locale, message, { bidiIsolation }));
445
450
  return String(format.format(resolveDescriptorValues(descriptor)));
446
451
  }
447
452
  return Object.freeze(Object.defineProperties(say, {
@@ -453,6 +458,10 @@ function createView(locale, messages) {
453
458
  value: own,
454
459
  enumerable: true
455
460
  },
461
+ options: {
462
+ value: ownOptions,
463
+ enumerable: true
464
+ },
456
465
  call: { value: call },
457
466
  plural: { value: () => macro("plural") },
458
467
  ordinal: { value: () => macro("ordinal") },
@@ -465,7 +474,8 @@ function createView(locale, messages) {
465
474
  }
466
475
  //#endregion
467
476
  //#region src/catalogue.ts
468
- function createCatalogue(messages) {
477
+ function createCatalogue(messages, options = {}) {
478
+ const { bidiIsolation } = options;
469
479
  const sources = Object.assign(Object.create(null), messages);
470
480
  const keys = Object.freeze(Object.keys(sources));
471
481
  if (keys.length === 0) throw new Error("A catalogue needs at least one locale, none were given");
@@ -484,7 +494,7 @@ function createCatalogue(messages) {
484
494
  const messages = store.get(locale);
485
495
  if (!messages) throw new Error(typeof sources[locale] === "function" ? `Messages for locale '${locale}' have not been loaded yet` : `No messages for locale '${locale}'`);
486
496
  let view = views.get(locale);
487
- if (!view) views.set(locale, view = createView(locale, messages));
497
+ if (!view) views.set(locale, view = createView(locale, messages, { bidiIsolation }));
488
498
  return view;
489
499
  },
490
500
  loaded(locale) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "saykit",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Type-safe i18n library with compile-time macro transforms",
5
5
  "keywords": [
6
6
  "i18n",