@havelaer/msgs 0.0.6 → 0.0.8

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
@@ -1,22 +1,38 @@
1
1
  # Msgs
2
2
 
3
- **Under development.**
3
+ Msgs is a library for internationalization of TypeScript/JavaScript applications. It is built on top of [MessageFormat 2 (MF2)](https://messageformat.unicode.org) and provides a set of functions to format numbers (currency, percentage, unit, etc.), date and time, and other types.
4
4
 
5
- Component based i18n messages for React.
5
+ - Uses [MessageFormat 2 (MF2)](https://messageformat.unicode.org) for message formatting.
6
+ - TypeScript support.
7
+ - Formatting functions for [numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat), [date and time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat), [relative time](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat), [list](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat), and more.
8
+ - Optional React hook for React applications.
6
9
 
7
- - Uses [MessageFormat 2 (MF2)](https://messageformat.unicode.org) for formatting.
8
- - Optional Vite plugin for precompilation.
9
- - React hook for using the messages.
10
- - Typesafe messages.
10
+ ## React Example
11
11
 
12
- ## Example
12
+ ```ts
13
+ // formatter.ts
14
+ import { createFormatter } from "@havelaer/msgs";
15
+ import * as functions from "@havelaer/msgs/functions";
16
+
17
+ export default createFormatter({
18
+ locales: {
19
+ "en-US": {},
20
+ "nl-NL": {},
21
+ "fr-FR": {},
22
+ },
23
+ defaultLocale: "en-US",
24
+ options: {
25
+ functions, // several Intl formatters
26
+ },
27
+ });
28
+ ```
13
29
 
14
30
  ```ts
15
31
  // Greeting.msgs.ts
16
- import msgs from "./msgs.config";
32
+ import formatter from "~/formatter";
17
33
 
18
- export default msgs.parse({
19
- greeting: {
34
+ export default formatter.parse({
35
+ hello: {
20
36
  "en-US": "Hello {$name}",
21
37
  "nl-NL": "Hallo {$name}",
22
38
  "fr-FR": "Bonjour {$name}",
@@ -32,20 +48,86 @@ import msgs from "./Greeting.msgs";
32
48
  export function Greeting() {
33
49
  const t = useTranslator();
34
50
 
35
- return <h1>{t(msgs.greeting, { name: "John" })}</h1>;
51
+ return <h1>{t(msgs.hello, { name: "John" })}</h1>;
36
52
  }
37
53
  ```
38
54
 
39
55
  ```tsx
40
56
  // App.tsx
41
57
  import { MsgsProvider } from "@havelaer/msgs/react";
42
- import msgsConfig from "./msgs.config";
43
- import { Greeting } from "./Greeting";
58
+ import formatter from "~/formatter";
59
+ import { Greeting } from "~/components/Greeting";
44
60
 
45
61
  export default function App() {
62
+ const locale = formatter.resolveLocale(navigator.languages);
63
+
46
64
  return (
47
- <MsgsProvider config={msgsConfig} locale={msgsConfig.resolveLocale(navigator.languages)}>
65
+ <MsgsProvider formatter={formatter} locale={locale}>
48
66
  <Greeting />
49
67
  </MsgsProvider>
50
68
  );
51
69
  }
70
+ ```
71
+
72
+ ## Formatters
73
+
74
+ ### `:number`
75
+
76
+ See [NumberFormat documentation (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) for more details.
77
+
78
+ ```ts
79
+ import formatter from "~/formatter";
80
+
81
+ export default formatter.parse({
82
+ decimalExample: {
83
+ "en-US": "Decimal: {$amount :number style=decimal}", // decimal is the default
84
+ // Example output: "Decimal: 1,234.56"
85
+ },
86
+ currencyExample: {
87
+ "en-US": "Currency: {$amount :number style=currency currency=USD}",
88
+ // Example output: "Currency: $1,234.56"
89
+ },
90
+ percentExample: {
91
+ "en-US": "Percentage: {$amount :number style=percent}",
92
+ // Example output: "Percentage: 12%"
93
+ },
94
+ unitExample: {
95
+ "en-US": "Unit: {$amount :number style=unit unit=liter}",
96
+ // Example output: "Unit: 1.2L"
97
+ },
98
+ });
99
+ ```
100
+
101
+ ### `:datetime`
102
+
103
+ See [DateTimeFormat documentation (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat) for more details.
104
+
105
+ ```ts
106
+ import formatter from "~/formatter";
107
+
108
+ export default formatter.parse({
109
+ dateExample: {
110
+ "en-US": "Date: {$eventDate :datetime dateStyle=long}",
111
+ // Example output: "Date: January 15, 2024"
112
+ },
113
+ timeExample: {
114
+ "en-US": "Date: {$eventDate :datetime timeStyle=long}",
115
+ // Example output: "Date: 2:30:00 PM"
116
+ },
117
+ });
118
+ ```
119
+
120
+ ### `:relativeTime`
121
+
122
+ See [RelativeTimeFormat documentation (MDN)](https://developer.mozilla.org/en-US/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) for more details.
123
+
124
+ ```ts
125
+ import formatter from "~/formatter";
126
+
127
+ export default formatter.parse({
128
+ relativeTimeExample: {
129
+ "en-US": "Relative Time: {$days :relativeTime unit=day}",
130
+ // Example output: "Relative Time: 1 day ago"
131
+ },
132
+ });
133
+ ```
@@ -3,116 +3,81 @@ import { MessageFunction } from "messageformat/functions";
3
3
  //#region src/functions/index.d.ts
4
4
 
5
5
  /**
6
- * Currency formatting function for MessageFormat.
7
- * Formats numbers as currency values.
6
+ * Date and/or time formatting function for MessageFormat.
7
+ * It's a wrapper around the `Intl.DateTimeFormat` API.
8
8
  *
9
- * @beta
9
+ * @param options - The Intl.DateTimeFormatOptions object.
10
+ * @param arg - Date string
10
11
  *
11
- * @example
12
- * ```ts
13
- * "Price: {$price :currency currency=USD}"
14
- * ```
15
- */
16
- declare const currency: MessageFunction<any, any>;
17
- /**
18
- * Date formatting function for MessageFormat.
19
- * Formats dates according to locale conventions.
20
- *
21
- * @beta
12
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
22
13
  *
23
14
  * @example
24
15
  * ```ts
25
- * "Today is {$date :date dateStyle=medium}"
16
+ * "{$date :datetime dateStyle=short timeStyle=short}" { date: new Date("2024-01-15T14:30:00") };
17
+ * // Expected output: "Jan 15, 2024, 2:30 PM"
26
18
  * ```
27
19
  */
28
- declare const date: MessageFunction<any, any>;
20
+ declare const datetime: MessageFunction<any, any>;
29
21
  /**
30
- * Date and time formatting function for MessageFormat.
31
- * Formats both date and time components.
22
+ * Number formatting function for MessageFormat.
23
+ * It's a wrapper around the `Intl.NumberFormat` API.
32
24
  *
33
- * @beta
25
+ * @param options - The Intl.NumberFormatOptions object.
26
+ * @param arg - An number or a string that can be parsed as a number.
27
+ *
28
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
34
29
  *
35
30
  * @example
36
31
  * ```ts
37
- * "Event at {$datetime :datetime dateStyle=short timeStyle=short}"
38
- * ```
39
- */
40
- declare const datetime: MessageFunction<any, any>;
41
- /**
42
- * Percentage formatting function for MessageFormat.
43
- * Formats numbers as percentages.
32
+ * "{$amount :number style=decimal}" { amount: 0.1234 };
33
+ * // Expected output: "0.1234"
44
34
  *
45
- * @beta
35
+ * "{$amount :number style=currency currency=USD}" { amount: 0.1234 };
36
+ * // Expected output: "$0.12"
46
37
  *
47
- * @example
48
- * ```ts
49
- * "Progress: {$progress :percent}"
50
- * ```
51
- */
52
- declare const percent: MessageFunction<any, any>;
53
- /**
54
- * Number formatting function for MessageFormat.
55
- * Formats numbers with locale-specific formatting.
38
+ * "{$amount :number style=percentage}" { amount: 0.1234 };
39
+ * // Expected output: "12%"
56
40
  *
57
- * @example
58
- * ```ts
59
- * "Count: {$count :number}"
41
+ * "{$amount :number style=unit unit=liter}" { amount: 0.1234 };
42
+ * // Expected output: "0.12L"
60
43
  * ```
61
44
  */
62
45
  declare const number: MessageFunction<any, any>;
63
46
  /**
64
- * Time formatting function for MessageFormat.
65
- * Formats time values according to locale conventions.
66
- *
67
- * @beta
47
+ * Relative time formatting function for MessageFormat.
48
+ * It's a wrapper around the `Intl.RelativeTimeFormat` API.
68
49
  *
69
- * @example
70
- * ```ts
71
- * "Time: {$time :time timeStyle=short}"
72
- * ```
73
- */
74
- declare const time: MessageFunction<any, any>;
75
- /**
76
- * Unit formatting function for MessageFormat.
77
- * Formats numbers with units (length, weight, etc.).
50
+ * @param options - The Intl.RelativeTimeFormatOptions object.
51
+ * @param arg - An number or a string that can be parsed as a number.
78
52
  *
79
- * @beta
53
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
80
54
  *
81
55
  * @example
82
56
  * ```ts
83
- * "Distance: {$distance :unit unit=mile}"
57
+ * "{$days :relativeTime unit=day}" { days: -1 };
58
+ * // Expected output: "1 day ago"
84
59
  * ```
85
60
  */
86
- declare const unit: MessageFunction<any, any>;
61
+ declare const relativeTime: MessageFunction<any, any>;
87
62
  /**
88
- * Integer formatting function for MessageFormat.
89
- * Formats numbers as integers.
63
+ * List formatting function for MessageFormat.
64
+ * It's a wrapper around the `Intl.ListFormat` API.
90
65
  *
91
- * @example
92
- * ```ts
93
- * "Items: {$count :integer}"
94
- * ```
95
- */
96
- declare const integer: MessageFunction<any, any>;
97
- /**
98
- * Offset formatting function for MessageFormat.
99
- * Formats timezone offsets.
66
+ * @remarks
67
+ * Needs tsconfig.json/compilerOptions.lib to include "ES2021" or higher.
100
68
  *
101
- * @example
102
- * ```ts
103
- * "Offset: {$offset :offset}"
104
- * ```
105
- */
106
- declare const offset: MessageFunction<any, any>;
107
- /**
108
- * String formatting function for MessageFormat.
109
- * Formats values as strings with optional transformations.
69
+ * @param options - The Intl.ListFormatOptions object.
70
+ * @param arg - An number
71
+ *
72
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat
110
73
  *
111
74
  * @example
112
75
  * ```ts
113
- * "Name: {$name :string}"
76
+ * "{$vehicles :list style=long type=conjunction}"
77
+ * { vehicles: ["Motorcycle", "Bus", "Car"] }
78
+ * // Expected output: "Motorcycle, Bus, and Car"
114
79
  * ```
115
80
  */
116
- declare const string: MessageFunction<any, any>;
81
+ declare const list: MessageFunction<any, any>;
117
82
  //#endregion
118
- export { currency, date, datetime, integer, number, offset, percent, string, time, unit };
83
+ export { datetime, list, number, relativeTime };
package/dist/functions.js CHANGED
@@ -1,118 +1,114 @@
1
- import { DefaultFunctions, DraftFunctions } from "messageformat/functions";
2
-
3
1
  //#region src/functions/index.ts
4
2
  /**
5
- * Currency formatting function for MessageFormat.
6
- * Formats numbers as currency values.
7
- *
8
- * @beta
3
+ * Date and/or time formatting function for MessageFormat.
4
+ * It's a wrapper around the `Intl.DateTimeFormat` API.
9
5
  *
10
- * @example
11
- * ```ts
12
- * "Price: {$price :currency currency=USD}"
13
- * ```
14
- */
15
- const currency = DraftFunctions.currency;
16
- /**
17
- * Date formatting function for MessageFormat.
18
- * Formats dates according to locale conventions.
6
+ * @param options - The Intl.DateTimeFormatOptions object.
7
+ * @param arg - Date string
19
8
  *
20
- * @beta
9
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
21
10
  *
22
11
  * @example
23
12
  * ```ts
24
- * "Today is {$date :date dateStyle=medium}"
13
+ * "{$date :datetime dateStyle=short timeStyle=short}" { date: new Date("2024-01-15T14:30:00") };
14
+ * // Expected output: "Jan 15, 2024, 2:30 PM"
25
15
  * ```
26
16
  */
27
- const date = DraftFunctions.date;
17
+ const datetime = (ctx, options, arg) => {
18
+ const formatter = new Intl.DateTimeFormat(ctx.locales, options);
19
+ const date = new Date(arg);
20
+ return {
21
+ type: "datetime",
22
+ toString: () => formatter.format(date),
23
+ toParts: () => formatter.formatToParts(date)
24
+ };
25
+ };
28
26
  /**
29
- * Date and time formatting function for MessageFormat.
30
- * Formats both date and time components.
27
+ * Number formatting function for MessageFormat.
28
+ * It's a wrapper around the `Intl.NumberFormat` API.
31
29
  *
32
- * @beta
30
+ * @param options - The Intl.NumberFormatOptions object.
31
+ * @param arg - An number or a string that can be parsed as a number.
33
32
  *
34
- * @example
35
- * ```ts
36
- * "Event at {$datetime :datetime dateStyle=short timeStyle=short}"
37
- * ```
38
- */
39
- const datetime = DraftFunctions.datetime;
40
- /**
41
- * Percentage formatting function for MessageFormat.
42
- * Formats numbers as percentages.
43
- *
44
- * @beta
33
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
45
34
  *
46
35
  * @example
47
36
  * ```ts
48
- * "Progress: {$progress :percent}"
49
- * ```
50
- */
51
- const percent = DraftFunctions.percent;
52
- /**
53
- * Number formatting function for MessageFormat.
54
- * Formats numbers with locale-specific formatting.
37
+ * "{$amount :number style=decimal}" { amount: 0.1234 };
38
+ * // Expected output: "0.1234"
55
39
  *
56
- * @example
57
- * ```ts
58
- * "Count: {$count :number}"
59
- * ```
60
- */
61
- const number = DefaultFunctions.number;
62
- /**
63
- * Time formatting function for MessageFormat.
64
- * Formats time values according to locale conventions.
40
+ * "{$amount :number style=currency currency=USD}" { amount: 0.1234 };
41
+ * // Expected output: "$0.12"
65
42
  *
66
- * @beta
43
+ * "{$amount :number style=percentage}" { amount: 0.1234 };
44
+ * // Expected output: "12%"
67
45
  *
68
- * @example
69
- * ```ts
70
- * "Time: {$time :time timeStyle=short}"
46
+ * "{$amount :number style=unit unit=liter}" { amount: 0.1234 };
47
+ * // Expected output: "0.12L"
71
48
  * ```
72
49
  */
73
- const time = DraftFunctions.time;
50
+ const number = (ctx, options, arg) => {
51
+ const formatter = new Intl.NumberFormat(ctx.locales, options);
52
+ const number$1 = Number.parseFloat(arg);
53
+ return {
54
+ type: "number",
55
+ toString: () => formatter.format(number$1),
56
+ toParts: () => formatter.formatToParts(number$1)
57
+ };
58
+ };
74
59
  /**
75
- * Unit formatting function for MessageFormat.
76
- * Formats numbers with units (length, weight, etc.).
60
+ * Relative time formatting function for MessageFormat.
61
+ * It's a wrapper around the `Intl.RelativeTimeFormat` API.
77
62
  *
78
- * @beta
63
+ * @param options - The Intl.RelativeTimeFormatOptions object.
64
+ * @param arg - An number or a string that can be parsed as a number.
79
65
  *
80
- * @example
81
- * ```ts
82
- * "Distance: {$distance :unit unit=mile}"
83
- * ```
84
- */
85
- const unit = DraftFunctions.unit;
86
- /**
87
- * Integer formatting function for MessageFormat.
88
- * Formats numbers as integers.
66
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat
89
67
  *
90
68
  * @example
91
69
  * ```ts
92
- * "Items: {$count :integer}"
70
+ * "{$days :relativeTime unit=day}" { days: -1 };
71
+ * // Expected output: "1 day ago"
93
72
  * ```
94
73
  */
95
- const integer = DefaultFunctions.integer;
74
+ const relativeTime = (ctx, options, arg) => {
75
+ const formatter = new Intl.RelativeTimeFormat(ctx.locales, options);
76
+ const number$1 = Number.parseInt(arg);
77
+ if (!options.unit) throw new Error(":relativeTime requires a unit parameter");
78
+ return {
79
+ type: "relativeTime",
80
+ toString: () => formatter.format(number$1, options.unit),
81
+ toParts: () => formatter.formatToParts(number$1, options.unit)
82
+ };
83
+ };
96
84
  /**
97
- * Offset formatting function for MessageFormat.
98
- * Formats timezone offsets.
85
+ * List formatting function for MessageFormat.
86
+ * It's a wrapper around the `Intl.ListFormat` API.
99
87
  *
100
- * @example
101
- * ```ts
102
- * "Offset: {$offset :offset}"
103
- * ```
104
- */
105
- const offset = DefaultFunctions.offset;
106
- /**
107
- * String formatting function for MessageFormat.
108
- * Formats values as strings with optional transformations.
88
+ * @remarks
89
+ * Needs tsconfig.json/compilerOptions.lib to include "ES2021" or higher.
90
+ *
91
+ * @param options - The Intl.ListFormatOptions object.
92
+ * @param arg - An number
93
+ *
94
+ * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat
109
95
  *
110
96
  * @example
111
97
  * ```ts
112
- * "Name: {$name :string}"
98
+ * "{$vehicles :list style=long type=conjunction}"
99
+ * { vehicles: ["Motorcycle", "Bus", "Car"] }
100
+ * // Expected output: "Motorcycle, Bus, and Car"
113
101
  * ```
114
102
  */
115
- const string = DefaultFunctions.string;
103
+ const list = (ctx, options, arg) => {
104
+ const formatter = new Intl.ListFormat(ctx.locales, options);
105
+ const list$1 = typeof arg === "string" ? arg.split(",") : Array.isArray(arg) ? arg : [];
106
+ return {
107
+ type: "list",
108
+ toString: () => formatter.format(list$1),
109
+ toParts: () => formatter.formatToParts(list$1)
110
+ };
111
+ };
116
112
 
117
113
  //#endregion
118
- export { currency, date, datetime, integer, number, offset, percent, string, time, unit };
114
+ export { datetime, list, number, relativeTime };
@@ -2,6 +2,10 @@ import { MessageFormatOptions, MessagePart, Model } from "messageformat";
2
2
 
3
3
  //#region src/index.d.ts
4
4
 
5
+ /**
6
+ * Valid argument values for message formatting.
7
+ */
8
+ type ArgValue = string | number | boolean | null | undefined;
5
9
  /**
6
10
  * Represents a nested structure of messages for different locales.
7
11
  * Can be either a nested object with string keys or a flat object with locale keys.
@@ -40,7 +44,7 @@ type Messages<TLocales extends string> = {
40
44
  * const formatted = config.format("en-US", parsed.greeting, { name: "John" });
41
45
  * ```
42
46
  */
43
- type MsgsConfig<TLocales extends string> = {
47
+ type Formatter<TLocales extends string> = {
44
48
  /** Parse message strings into MessageFormat objects */
45
49
  parse: <T extends string = TLocales, const U extends Messages<T> = Messages<T>>(messages: U) => U;
46
50
  /** Format a message to a string */
@@ -51,7 +55,7 @@ type MsgsConfig<TLocales extends string> = {
51
55
  resolveLocale: (userLocales: string[] | readonly string[]) => TLocales;
52
56
  };
53
57
  /**
54
- * Creates a message configuration object for internationalization.
58
+ * Creates a message formatter object for internationalization.
55
59
  *
56
60
  * @template TLocales - The supported locale strings
57
61
  * @template TDefaultLocale - The default locale (must be one of TLocales)
@@ -61,24 +65,24 @@ type MsgsConfig<TLocales extends string> = {
61
65
  * @param config.locales - Locale-specific MessageFormat options
62
66
  * @param config.options - Global MessageFormat options applied to all locales
63
67
  *
64
- * @returns A MsgsConfig object with parsing, formatting, and locale resolution methods
68
+ * @returns A Formatter object with parsing, formatting, and locale resolution methods
65
69
  *
66
70
  * @example
67
71
  * ```ts
68
- * const msgsConfig = defineConfig({
72
+ * const formatter = createFormatter({
69
73
  * defaultLocale: "en-US",
70
74
  * locales: {
71
- * "en-US": { currency: "USD" },
72
- * "nl-NL": { currency: "EUR" }
75
+ * "en-US": { --locale specific MessageFormatOptions-- },
76
+ * "nl-NL": { --locale specific MessageFormatOptions-- }
73
77
  * },
74
- * options: { date: { dateStyle: "medium" } }
78
+ * options: { --global MessageFormatOptions-- }
75
79
  * });
76
80
  * ```
77
81
  */
78
- declare function defineConfig<TLocales extends string, TDefaultLocale extends TLocales>(config: {
82
+ declare function createFormatter<TLocales extends string, TDefaultLocale extends TLocales>(config: {
79
83
  defaultLocale: TDefaultLocale;
80
84
  locales: Record<TLocales, MessageFormatOptions<any, any>>;
81
85
  options?: MessageFormatOptions<any, any>;
82
- }): MsgsConfig<TLocales>;
86
+ }): Formatter<TLocales>;
83
87
  //#endregion
84
- export { Messages, MsgsConfig, defineConfig };
88
+ export { ArgValue, Formatter, Messages, createFormatter };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Messages, MsgsConfig, defineConfig } from "./index-DqWXabHs.js";
2
- export { Messages, MsgsConfig, defineConfig };
1
+ import { ArgValue, Formatter, Messages, createFormatter } from "./index-ChgITPMW.js";
2
+ export { ArgValue, Formatter, Messages, createFormatter };
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ function walkAndParse(node, target) {
60
60
  }
61
61
  }
62
62
  /**
63
- * Creates a message configuration object for internationalization.
63
+ * Creates a message formatter object for internationalization.
64
64
  *
65
65
  * @template TLocales - The supported locale strings
66
66
  * @template TDefaultLocale - The default locale (must be one of TLocales)
@@ -70,21 +70,21 @@ function walkAndParse(node, target) {
70
70
  * @param config.locales - Locale-specific MessageFormat options
71
71
  * @param config.options - Global MessageFormat options applied to all locales
72
72
  *
73
- * @returns A MsgsConfig object with parsing, formatting, and locale resolution methods
73
+ * @returns A Formatter object with parsing, formatting, and locale resolution methods
74
74
  *
75
75
  * @example
76
76
  * ```ts
77
- * const msgsConfig = defineConfig({
77
+ * const formatter = createFormatter({
78
78
  * defaultLocale: "en-US",
79
79
  * locales: {
80
- * "en-US": { currency: "USD" },
81
- * "nl-NL": { currency: "EUR" }
80
+ * "en-US": { --locale specific MessageFormatOptions-- },
81
+ * "nl-NL": { --locale specific MessageFormatOptions-- }
82
82
  * },
83
- * options: { date: { dateStyle: "medium" } }
83
+ * options: { --global MessageFormatOptions-- }
84
84
  * });
85
85
  * ```
86
86
  */
87
- function defineConfig(config) {
87
+ function createFormatter(config) {
88
88
  function parse(messages) {
89
89
  const parsed = {};
90
90
  walkAndParse(messages, parsed);
@@ -118,4 +118,4 @@ function defineConfig(config) {
118
118
  }
119
119
 
120
120
  //#endregion
121
- export { defineConfig };
121
+ export { createFormatter };
package/dist/react.d.ts CHANGED
@@ -1,12 +1,9 @@
1
- import { MsgsConfig } from "./index-DqWXabHs.js";
1
+ import { ArgValue, Formatter } from "./index-ChgITPMW.js";
2
2
  import { ReactNode } from "react";
3
3
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
4
 
5
5
  //#region src/react/index.d.ts
6
- /**
7
- * Valid argument values for message formatting.
8
- */
9
- type ArgValue = string | number | boolean | null | undefined;
6
+
10
7
  /**
11
8
  * Translator object returned by useTranslator hook.
12
9
  * Provides methods for formatting messages as strings or JSX.
@@ -49,20 +46,20 @@ declare function useTranslator(): Translator;
49
46
  * @template TLocales - The supported locale strings
50
47
  */
51
48
  interface MsgsProviderProps<TLocales extends string> {
52
- /** The message configuration object */
53
- config: MsgsConfig<TLocales>;
49
+ /** The message formatter object */
50
+ formatter: Formatter<TLocales>;
54
51
  /** The current locale */
55
52
  locale: TLocales;
56
53
  /** Child components */
57
54
  children: ReactNode;
58
55
  }
59
56
  /**
60
- * React provider component that makes message configuration available to child components.
57
+ * React provider component that makes message formatter and localeavailable to child components.
61
58
  *
62
59
  * @template TLocales - The supported locale strings
63
60
  *
64
61
  * @param props - Component props
65
- * @param props.config - The message configuration object
62
+ * @param props.formatter - The message formatter object
66
63
  * @param props.locale - The current locale
67
64
  * @param props.children - Child components
68
65
  *
@@ -70,7 +67,7 @@ interface MsgsProviderProps<TLocales extends string> {
70
67
  * ```tsx
71
68
  * function App() {
72
69
  * return (
73
- * <MsgsProvider config={msgsConfig} locale="en-US">
70
+ * <MsgsProvider formatter={Formatter} locale="en-US">
74
71
  * <MyComponent />
75
72
  * </MsgsProvider>
76
73
  * );
@@ -78,7 +75,7 @@ interface MsgsProviderProps<TLocales extends string> {
78
75
  * ```
79
76
  */
80
77
  declare function MsgsProvider<TLocales extends string>({
81
- config,
78
+ formatter,
82
79
  locale,
83
80
  children
84
81
  }: MsgsProviderProps<TLocales>): react_jsx_runtime0.JSX.Element;
package/dist/react.js CHANGED
@@ -1,10 +1,28 @@
1
1
  import { Fragment, createContext, useContext } from "react";
2
- import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
2
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
3
3
 
4
4
  //#region src/messageParts.ts
5
+ function isTextPart(part) {
6
+ return part.type === "text";
7
+ }
8
+ function isStringPart(part) {
9
+ return part.type === "string";
10
+ }
5
11
  function isMarkupPart(part) {
6
12
  return part.type === "markup";
7
13
  }
14
+ function isBiDiIsolationPart(part) {
15
+ return part.type === "bidiIsolation";
16
+ }
17
+ function isNumberPart(part) {
18
+ return part.type === "number";
19
+ }
20
+ function isDateTimePart(part) {
21
+ return part.type === "datetime";
22
+ }
23
+ function isFallbackPart(part) {
24
+ return part.type === "fallback";
25
+ }
8
26
 
9
27
  //#endregion
10
28
  //#region src/react/partsToJSX.tsx
@@ -30,13 +48,20 @@ function partsToJSX(parts, args, start = 0, stop) {
30
48
  let i = start;
31
49
  while (i < (stop ?? parts.length)) {
32
50
  const part = parts[i];
33
- if (isMarkupPart(part) && part.kind === "open") {
51
+ if (isTextPart(part)) {
52
+ result.push(/* @__PURE__ */ jsx(Fragment, { children: part.value }, createKey(part, i)));
53
+ i++;
54
+ } else if (isBiDiIsolationPart(part)) i++;
55
+ else if (isStringPart(part)) {
56
+ result.push(/* @__PURE__ */ jsx(Fragment, { children: part.value }, createKey(part, i)));
57
+ i++;
58
+ } else if (isMarkupPart(part) && part.kind === "open") {
34
59
  const tagName = part.name;
35
60
  const Tag = args?.[tagName] ?? tagName;
36
61
  const jsxProps = part.options ?? {};
37
62
  let depth = 1;
38
63
  let j = i + 1;
39
- while (j < parts.length) {
64
+ while (j < (stop ?? parts.length)) {
40
65
  const p = parts[j];
41
66
  if (isMarkupPart(p) && p.name === tagName) {
42
67
  if (p.kind === "open") depth++;
@@ -52,17 +77,27 @@ function partsToJSX(parts, args, start = 0, stop) {
52
77
  }, createKey(part, i)));
53
78
  i = j + 1;
54
79
  } else if (isMarkupPart(part) && part.kind === "close") break;
55
- else if ("type" in part && "parts" in part && Array.isArray(part.parts)) {
80
+ else if (isNumberPart(part) && part.parts) {
56
81
  result.push(/* @__PURE__ */ jsx(Fragment, { children: partsToJSX(part.parts, args) }, createKey(part, i)));
57
82
  i++;
58
- } else if ("type" in part && "value" in part && typeof part.value === "string") {
59
- result.push(/* @__PURE__ */ jsx(Fragment, { children: part.value }, createKey(part, i)));
83
+ } else if (isDateTimePart(part) && part.parts) {
84
+ result.push(/* @__PURE__ */ jsx(Fragment, { children: partsToJSX(part.parts, args) }, createKey(part, i)));
60
85
  i++;
61
- } else {
86
+ } else if (isFallbackPart(part)) {
62
87
  console.warn("Unsupported MessagePart", part);
63
- result.push(null);
88
+ result.push(/* @__PURE__ */ jsxs(Fragment, { children: [
89
+ "[",
90
+ part.source,
91
+ "]"
92
+ ] }, createKey(part, i)));
93
+ i++;
94
+ } else if ("type" in part && "parts" in part && Array.isArray(part.parts)) {
95
+ result.push(/* @__PURE__ */ jsx(Fragment, { children: partsToJSX(part.parts, args) }, createKey(part, i)));
96
+ i++;
97
+ } else if ("type" in part && "value" in part && typeof part.value === "string") {
98
+ result.push(/* @__PURE__ */ jsx(Fragment, { children: part.value }, createKey(part, i)));
64
99
  i++;
65
- }
100
+ } else throw new Error(`Unhandled MessagePart ${JSON.stringify(part)}`);
66
101
  }
67
102
  return result;
68
103
  }
@@ -70,7 +105,7 @@ function partsToJSX(parts, args, start = 0, stop) {
70
105
  //#endregion
71
106
  //#region src/react/index.tsx
72
107
  const localeContext = createContext(null);
73
- const configContext = createContext(null);
108
+ const formatterContext = createContext(null);
74
109
  /**
75
110
  * React hook for accessing the message translator.
76
111
  * Must be used within a MsgsProvider context.
@@ -95,10 +130,9 @@ const configContext = createContext(null);
95
130
  * ```
96
131
  */
97
132
  function useTranslator() {
98
- const config = useContext(configContext);
133
+ const config = useContext(formatterContext);
99
134
  const locale = useContext(localeContext);
100
- if (!config) throw new Error("config not set by a MsgsProvider");
101
- if (typeof locale !== "string") throw new Error("locale not set by a LocaleProvider");
135
+ if (!config) throw new Error("msgs config not set by a MsgsProvider");
102
136
  const translator = function translate(msg, args) {
103
137
  return config.format(locale, msg, args);
104
138
  };
@@ -110,12 +144,12 @@ function useTranslator() {
110
144
  return translator;
111
145
  }
112
146
  /**
113
- * React provider component that makes message configuration available to child components.
147
+ * React provider component that makes message formatter and localeavailable to child components.
114
148
  *
115
149
  * @template TLocales - The supported locale strings
116
150
  *
117
151
  * @param props - Component props
118
- * @param props.config - The message configuration object
152
+ * @param props.formatter - The message formatter object
119
153
  * @param props.locale - The current locale
120
154
  * @param props.children - Child components
121
155
  *
@@ -123,16 +157,16 @@ function useTranslator() {
123
157
  * ```tsx
124
158
  * function App() {
125
159
  * return (
126
- * <MsgsProvider config={msgsConfig} locale="en-US">
160
+ * <MsgsProvider formatter={Formatter} locale="en-US">
127
161
  * <MyComponent />
128
162
  * </MsgsProvider>
129
163
  * );
130
164
  * }
131
165
  * ```
132
166
  */
133
- function MsgsProvider({ config, locale, children }) {
134
- return /* @__PURE__ */ jsx(configContext.Provider, {
135
- value: config,
167
+ function MsgsProvider({ formatter, locale, children }) {
168
+ return /* @__PURE__ */ jsx(formatterContext.Provider, {
169
+ value: formatter,
136
170
  children: /* @__PURE__ */ jsx(localeContext.Provider, {
137
171
  value: locale,
138
172
  children
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@havelaer/msgs",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -19,6 +19,10 @@
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/havelaer/msgs.git"
25
+ },
22
26
  "keywords": [
23
27
  "messageformat",
24
28
  "MessageFormat2",
@@ -31,7 +35,9 @@
31
35
  "scripts": {
32
36
  "check": "biome check .",
33
37
  "prepublishOnly": "npm run check && npm run build && npm version patch -m 'chore: publishing version %s'",
34
- "build": "tsdown"
38
+ "build": "tsdown",
39
+ "test": "vitest run",
40
+ "test:watch": "vitest watch"
35
41
  },
36
42
  "files": [
37
43
  "dist"
@@ -42,12 +48,18 @@
42
48
  },
43
49
  "devDependencies": {
44
50
  "@biomejs/biome": "^1.9.4",
51
+ "@testing-library/jest-dom": "^6.8.0",
52
+ "@testing-library/react": "^16.3.0",
45
53
  "@types/node": "^22.13.9",
46
54
  "@types/react": "^19.0.10",
47
55
  "@types/react-dom": "^19.0.4",
56
+ "@vitest/coverage-v8": "^3.2.4",
57
+ "happy-dom": "^18.0.1",
48
58
  "react": "^19.0.0",
49
59
  "react-dom": "^19.0.0",
50
- "typescript": "~5.7.2"
60
+ "tsdown": "^0.15.0",
61
+ "typescript": "~5.7.2",
62
+ "vitest": "^3.2.4"
51
63
  },
52
64
  "workspaces": [
53
65
  ".",