@havelaer/msgs 0.0.2

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 ADDED
@@ -0,0 +1,51 @@
1
+ # Msgs
2
+
3
+ **Under development.**
4
+
5
+ Component based i18n messages for React.
6
+
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.
11
+
12
+ ## Example
13
+
14
+ ```ts
15
+ // Greeting.msgs.ts
16
+ import msgs from "./msgs.config";
17
+
18
+ export default msgs.parse({
19
+ greeting: {
20
+ "en-US": "Hello {$name}",
21
+ "nl-NL": "Hallo {$name}",
22
+ "fr-FR": "Bonjour {$name}",
23
+ },
24
+ });
25
+ ```
26
+
27
+ ```tsx
28
+ // Greeting.tsx
29
+ import { useTranslator } from "@havelaer/msgs/react";
30
+ import msgs from "./Greeting.msgs";
31
+
32
+ export function Greeting() {
33
+ const t = useTranslator();
34
+
35
+ return <h1>{t(msgs.greeting, { name: "John" })}</h1>;
36
+ }
37
+ ```
38
+
39
+ ```tsx
40
+ // App.tsx
41
+ import { MsgsProvider } from "@havelaer/msgs/react";
42
+ import msgsConfig from "./msgs.config";
43
+ import { Greeting } from "./Greeting";
44
+
45
+ export default function App() {
46
+ return (
47
+ <MsgsProvider config={msgsConfig} locale={msgsConfig.resolveLocale(navigator.languages)}>
48
+ <Greeting />
49
+ </MsgsProvider>
50
+ );
51
+ }
@@ -0,0 +1,7 @@
1
+ import { MessageFunction } from "messageformat/functions";
2
+
3
+ //#region src/functions/index.d.ts
4
+ declare const currency: MessageFunction<any, any>, date: MessageFunction<any, any>, datetime: MessageFunction<any, any>, percent: MessageFunction<any, any>, time: MessageFunction<any, any>, unit: MessageFunction<any, any>;
5
+ declare const integer: MessageFunction<any, any>, number: MessageFunction<any, any>, offset: MessageFunction<any, any>, string: MessageFunction<any, any>;
6
+ //#endregion
7
+ export { currency, date, datetime, integer, number, offset, percent, string, time, unit };
@@ -0,0 +1,8 @@
1
+ import { DefaultFunctions, DraftFunctions } from "messageformat/functions";
2
+
3
+ //#region src/functions/index.ts
4
+ const { currency, date, datetime, percent, time, unit } = DraftFunctions;
5
+ const { integer, number, offset, string } = DefaultFunctions;
6
+
7
+ //#endregion
8
+ export { currency, date, datetime, integer, number, offset, percent, string, time, unit };
@@ -0,0 +1,22 @@
1
+ import { MessageFormatOptions, MessagePart, Model } from "messageformat";
2
+
3
+ //#region src/index.d.ts
4
+ type Messages<TLocales extends string> = {
5
+ [key: string]: Messages<TLocales>;
6
+ } | { [P in TLocales]: string };
7
+ type Config = {
8
+ messageFormatOptions?: MessageFormatOptions<any, any>;
9
+ };
10
+ type MsgsConfig<TLocales extends string> = {
11
+ parse: <T extends string = TLocales, const U extends Messages<T> = Messages<T>>(messages: U) => U;
12
+ format: (locale: TLocales, msgs: Record<TLocales, Model.Message | string>, args?: Record<string, unknown>) => string;
13
+ formatToParts: (locale: TLocales, msgs: Record<TLocales, Model.Message | string>, args?: Record<string, unknown>) => MessagePart<any>[];
14
+ resolveLocale: (userLocales: string[] | readonly string[]) => TLocales;
15
+ };
16
+ declare function defineConfig<TLocales extends string, TDefaultLocale extends TLocales>(config: {
17
+ defaultLocale: TDefaultLocale;
18
+ locales: Record<TLocales, MessageFormatOptions<any, any>>;
19
+ options?: MessageFormatOptions<any, any>;
20
+ }): MsgsConfig<TLocales>;
21
+ //#endregion
22
+ export { Config, Messages, MsgsConfig, defineConfig };
@@ -0,0 +1,2 @@
1
+ import { Config, Messages, MsgsConfig, defineConfig } from "./index-CrRmYrrh.js";
2
+ export { Config, Messages, MsgsConfig, defineConfig };
package/dist/index.js ADDED
@@ -0,0 +1,75 @@
1
+ import { MessageFormat, parseMessage } from "messageformat";
2
+
3
+ //#region src/resolveLocale.ts
4
+ function resolveLocale(userLocales, supportedLocales, localeMatcher = "best fit") {
5
+ if (!Array.isArray(userLocales) || userLocales.length === 0) throw new Error("userLocales must be a non-empty array");
6
+ if (!Array.isArray(supportedLocales) || supportedLocales.length === 0) throw new Error("supportedLocales must be a non-empty array");
7
+ const supportedCanon = Intl.getCanonicalLocales(supportedLocales);
8
+ const runtimeSupported = new Set(Intl.NumberFormat.supportedLocalesOf(supportedCanon, { localeMatcher }));
9
+ if (runtimeSupported.size === 0) return supportedCanon[0];
10
+ for (const raw of userLocales) {
11
+ const base = stripExtensions(raw);
12
+ const [canonical] = Intl.getCanonicalLocales(base);
13
+ const chain = fallbackChain(canonical);
14
+ for (const candidate of chain) if (runtimeSupported.has(candidate)) return candidate;
15
+ }
16
+ return runtimeSupported.values().next().value;
17
+ }
18
+ /** Remove Unicode (‘-u-…’) and transform (‘-t-…’) extensions from a BCP 47 tag */
19
+ function stripExtensions(tag) {
20
+ return tag.split(/-(?:u|t)-/i)[0];
21
+ }
22
+ /** Build multi-level fallback chain from most specific → least specific */
23
+ function fallbackChain(canonicalTag) {
24
+ const parts = canonicalTag.split("-");
25
+ const chain = [];
26
+ for (let i = parts.length; i >= 1; i--) chain.push(parts.slice(0, i).join("-"));
27
+ return chain;
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/index.ts
32
+ function walkAndParse(node, target) {
33
+ for (const key in node) {
34
+ const value = node[key];
35
+ if (typeof value === "object" && value !== null) {
36
+ target[key] = {};
37
+ walkAndParse(value, target[key]);
38
+ } else if (typeof value === "string") target[key] = parseMessage(value);
39
+ }
40
+ }
41
+ function defineConfig(config) {
42
+ function parse(messages) {
43
+ const parsed = {};
44
+ walkAndParse(messages, parsed);
45
+ return parsed;
46
+ }
47
+ function createMessageFormat(locale, msgs) {
48
+ if (!config.locales[locale]) throw new Error(`${locale} not found in config`);
49
+ const mfOptions = {
50
+ ...config.options,
51
+ ...config.locales[locale]
52
+ };
53
+ const msg = msgs[locale];
54
+ return new MessageFormat(locale, msg, mfOptions);
55
+ }
56
+ function resolveLocale$1(userLocales) {
57
+ const supportedLocales = new Set([config.defaultLocale, ...Object.keys(config.locales)]);
58
+ return resolveLocale(userLocales, Array.from(supportedLocales)) ?? config.defaultLocale;
59
+ }
60
+ function format(locale, msgs, args) {
61
+ return createMessageFormat(locale, msgs).format(args);
62
+ }
63
+ function formatToParts(locale, msgs, args) {
64
+ return createMessageFormat(locale, msgs).formatToParts(args);
65
+ }
66
+ return {
67
+ parse,
68
+ format,
69
+ formatToParts,
70
+ resolveLocale: resolveLocale$1
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ export { defineConfig };
@@ -0,0 +1,32 @@
1
+ import { MsgsConfig } from "./index-CrRmYrrh.js";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+ import { ReactNode } from "react";
4
+
5
+ //#region src/react/index.d.ts
6
+ type ArgValue = string | number | boolean | null | undefined;
7
+ type Translator = {
8
+ (msg: any, args?: Record<string, ArgValue>): string;
9
+ jsx(msg: any, args?: Record<string, ArgValue>): ReactNode;
10
+ locale: string;
11
+ };
12
+ declare function useTranslator(): Translator;
13
+ interface MsgsProviderProps<TLocales extends string> {
14
+ config: MsgsConfig<TLocales>;
15
+ locale: TLocales;
16
+ children: ReactNode;
17
+ }
18
+ declare function MsgsProvider<TLocales extends string>({
19
+ config,
20
+ locale,
21
+ children
22
+ }: MsgsProviderProps<TLocales>): react_jsx_runtime0.JSX.Element;
23
+ interface LocaleProviderProps<TLocales extends string> {
24
+ locale: TLocales;
25
+ children: ReactNode;
26
+ }
27
+ declare function LocaleProvider<TLocales extends string>({
28
+ locale,
29
+ children
30
+ }: LocaleProviderProps<TLocales>): react_jsx_runtime0.JSX.Element;
31
+ //#endregion
32
+ export { LocaleProvider, MsgsProvider, useTranslator };