@saykit/react 0.2.1 → 0.6.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/dist/client.d.mts CHANGED
@@ -1,7 +1,5 @@
1
- import * as _$react from "react";
2
1
  import { PropsWithChildren } from "react";
3
2
  import { ReadonlySay, Say } from "saykit";
4
-
5
3
  //#region src/runtime/client.d.ts
6
4
  type SayRef = {
7
5
  current: ReadonlySay | null;
@@ -10,17 +8,17 @@ type SayRef = {
10
8
  * Provide a localised {@link runtime.Say} instance to descendant **client** components via context.
11
9
  * Must wrap any component tree using {@link useSay} or {@link Say}.
12
10
  *
11
+ * The instance is rebuilt whenever `locale` or `messages` changes, so keep `messages`
12
+ * referentially stable (module scope, or memoised) rather than passing a fresh object
13
+ * literal on every render.
14
+ *
13
15
  * @param props.locale The current locale
14
16
  * @param props.messages The current messages for the locale
15
17
  */
16
- declare function SayProvider({
17
- locale,
18
- messages,
19
- children
20
- }: PropsWithChildren<{
18
+ declare function SayProvider({ locale, messages, children }: PropsWithChildren<{
21
19
  locale: string;
22
20
  messages: Say.Messages;
23
- }>): _$react.FunctionComponentElement<_$react.ProviderProps<SayRef>>;
21
+ }>): import("react").FunctionComponentElement<import("react").ProviderProps<SayRef>>;
24
22
  /**
25
23
  * Get the current {@link Say} **client** instance.
26
24
  * Must be called within a {@link SayProvider}.
package/dist/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { createContext, createElement, useContext, useState } from "react";
2
+ import { createContext, createElement, useContext, useMemo } from "react";
3
3
  import { Say } from "saykit";
4
4
  //#region src/runtime/client.ts
5
5
  const SayContext = createContext({ current: null });
@@ -8,20 +8,24 @@ SayContext.displayName = "SayContext";
8
8
  * Provide a localised {@link runtime.Say} instance to descendant **client** components via context.
9
9
  * Must wrap any component tree using {@link useSay} or {@link Say}.
10
10
  *
11
+ * The instance is rebuilt whenever `locale` or `messages` changes, so keep `messages`
12
+ * referentially stable (module scope, or memoised) rather than passing a fresh object
13
+ * literal on every render.
14
+ *
11
15
  * @param props.locale The current locale
12
16
  * @param props.messages The current messages for the locale
13
17
  */
14
18
  function SayProvider({ locale, messages, children }) {
15
- const [say] = useState(() => {
19
+ const ref = useMemo(() => {
16
20
  const instance = new Say({
17
21
  locales: [locale],
18
22
  loader: () => messages
19
23
  });
20
24
  instance.load(locale);
21
25
  instance.activate(locale);
22
- return instance.freeze();
23
- });
24
- return createElement(SayContext.Provider, { value: { current: say } }, children);
26
+ return { current: instance.freeze() };
27
+ }, [locale, messages]);
28
+ return createElement(SayContext.Provider, { value: ref }, children);
25
29
  }
26
30
  /**
27
31
  * Get the current {@link Say} **client** instance.
package/dist/index.d.mts CHANGED
@@ -1,8 +1,7 @@
1
1
  import { PropsWithChildren, ReactElement, ReactNode } from "react";
2
- import { Disallow, NumeralOptions, SelectOptions } from "saykit";
3
-
2
+ import { Disallow, Named, NumeralOptions, SelectOptions } from "saykit";
4
3
  //#region src/types.d.ts
5
- type PropsWithJSXSafeKeys<T> = { [K in keyof T as K extends number | `${number}${string}` ? `_${K}` : K extends `_${number}${string}` ? never : K]: T[K] };
4
+ type PropsWithJSXSafeKeys<T> = { [K in keyof T as K extends number | `${number}${string}` ? `_${K}` : K extends `_${number}${string}` ? never : K]: T[K]; };
6
5
  //#endregion
7
6
  //#region src/runtime/index.d.ts
8
7
  /**
@@ -35,7 +34,7 @@ declare namespace Say {
35
34
  * @remark This is a macro and must be used with the relevant saykit plugin
36
35
  */
37
36
  function Plural(props: {
38
- _: number;
37
+ _: number | Named<number>;
39
38
  } & PropsWithJSXSafeKeys<Disallow<NumeralOptions, 'id' | 'context'>>): ReactNode;
40
39
  /**
41
40
  * Define an ordinal message (e.g. "1st", "2nd", "3rd").
@@ -57,7 +56,7 @@ declare namespace Say {
57
56
  * @remark This is a macro and must be used with the relevant saykit plugin
58
57
  */
59
58
  function Ordinal(props: {
60
- _: number;
59
+ _: number | Named<number>;
61
60
  } & PropsWithJSXSafeKeys<Disallow<NumeralOptions, 'id' | 'context'>>): ReactNode;
62
61
  /**
63
62
  * Define a select message, useful for handling gender, status, or other categories.
@@ -78,7 +77,7 @@ declare namespace Say {
78
77
  * @remark This is a macro and must be used with the relevant saykit plugin
79
78
  */
80
79
  function Select(props: {
81
- _: string;
80
+ _: string | Named<string>;
82
81
  } & PropsWithJSXSafeKeys<Disallow<SelectOptions, 'id' | 'context'>>): ReactNode;
83
82
  }
84
83
  //#endregion
package/dist/index.mjs CHANGED
@@ -47,9 +47,20 @@ function Renderer({ html, components, whitespace = true }) {
47
47
  }
48
48
  //#endregion
49
49
  //#region src/types.ts
50
- function resolveJsxSafePropKeys(props) {
50
+ /**
51
+ * Map the props the transform compiled a message's values into back to the
52
+ * identifiers they carry. Every value is emitted with one underscore in front,
53
+ * which is what keeps a numbered identifier a valid prop name and what keeps
54
+ * any name a message chooses out of `Say`'s own namespace, so stripping exactly
55
+ * one is the whole inverse — `_0` is `0`, and `__link` is a tag named `_link`.
56
+ *
57
+ * The result is deliberately not typed as the props that went in: renaming the
58
+ * keys is the point, so claiming they survived would be a lie the only caller
59
+ * cannot use anyway, it looks tags up by a name that comes from a translation.
60
+ */
61
+ function resolveValuePropKeys(props) {
51
62
  const result = {};
52
- for (const key in props) if (/^_\d+$/.test(key)) result[key.slice(1)] = props[key];
63
+ for (const key in props) if (key.startsWith("_")) result[key.slice(1)] = props[key];
53
64
  else result[key] = props[key];
54
65
  return result;
55
66
  }
@@ -58,14 +69,17 @@ function resolveJsxSafePropKeys(props) {
58
69
  function Say(props) {
59
70
  if (!("id" in props)) throw new Error("'Say' is a macro and must be used with the relevant saykit plugin", { cause: /* @__PURE__ */ new Error("The 'id' property is required for a descriptor") });
60
71
  const say = GET_SAY();
61
- const { whitespace, ...rest } = props;
62
- const descriptor = resolveJsxSafePropKeys(rest);
72
+ const { id, whitespace, ...rest } = props;
73
+ const values = resolveValuePropKeys(rest);
63
74
  return createElement(Renderer, {
64
- html: say.call(descriptor),
75
+ html: say.call({
76
+ ...rest,
77
+ id
78
+ }),
65
79
  whitespace,
66
80
  components(tag) {
67
- if (tag && tag in descriptor && isValidElement(descriptor[tag])) {
68
- const element = descriptor[tag];
81
+ if (tag && tag in values && isValidElement(values[tag])) {
82
+ const element = values[tag];
69
83
  return (props) => cloneElement(element, {
70
84
  ...element.props,
71
85
  ...props
@@ -47,9 +47,20 @@ function Renderer({ html, components, whitespace = true }) {
47
47
  }
48
48
  //#endregion
49
49
  //#region src/types.ts
50
- function resolveJsxSafePropKeys(props) {
50
+ /**
51
+ * Map the props the transform compiled a message's values into back to the
52
+ * identifiers they carry. Every value is emitted with one underscore in front,
53
+ * which is what keeps a numbered identifier a valid prop name and what keeps
54
+ * any name a message chooses out of `Say`'s own namespace, so stripping exactly
55
+ * one is the whole inverse — `_0` is `0`, and `__link` is a tag named `_link`.
56
+ *
57
+ * The result is deliberately not typed as the props that went in: renaming the
58
+ * keys is the point, so claiming they survived would be a lie the only caller
59
+ * cannot use anyway, it looks tags up by a name that comes from a translation.
60
+ */
61
+ function resolveValuePropKeys(props) {
51
62
  const result = {};
52
- for (const key in props) if (/^_\d+$/.test(key)) result[key.slice(1)] = props[key];
63
+ for (const key in props) if (key.startsWith("_")) result[key.slice(1)] = props[key];
53
64
  else result[key] = props[key];
54
65
  return result;
55
66
  }
@@ -58,14 +69,17 @@ function resolveJsxSafePropKeys(props) {
58
69
  function Say(props) {
59
70
  if (!("id" in props)) throw new Error("'Say' is a macro and must be used with the relevant saykit plugin", { cause: /* @__PURE__ */ new Error("The 'id' property is required for a descriptor") });
60
71
  const say = GET_SAY();
61
- const { whitespace, ...rest } = props;
62
- const descriptor = resolveJsxSafePropKeys(rest);
72
+ const { id, whitespace, ...rest } = props;
73
+ const values = resolveValuePropKeys(rest);
63
74
  return createElement(Renderer, {
64
- html: say.call(descriptor),
75
+ html: say.call({
76
+ ...rest,
77
+ id
78
+ }),
65
79
  whitespace,
66
80
  components(tag) {
67
- if (tag && tag in descriptor && isValidElement(descriptor[tag])) {
68
- const element = descriptor[tag];
81
+ if (tag && tag in values && isValidElement(values[tag])) {
82
+ const element = values[tag];
69
83
  return (props) => cloneElement(element, {
70
84
  ...element.props,
71
85
  ...props
package/dist/server.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import * as _$react from "react";
2
1
  import { ReactNode } from "react";
3
2
  import { ReadonlySay, Say } from "saykit";
3
+ import "server-only";
4
4
  //#region src/runtime/server.d.ts
5
5
  /**
6
6
  * Set the current {@link Say} **server** instance.
@@ -24,7 +24,7 @@ declare function getSay(): ReadonlySay;
24
24
  *
25
25
  * @returns A {@link withSay} higher-order component factory
26
26
  */
27
- declare function unstable_createWithSay(say: Say): <P = unknown>(Component: (props: PropsWithSay<P>) => ReactNode, getLocale: (props: P) => string | Promise<string>) => (props: P) => Promise<_$react.FunctionComponentElement<PropsWithSay<P>>>;
27
+ declare function unstable_createWithSay(say: Say): <P = unknown>(Component: (props: PropsWithSay<P>) => ReactNode, getLocale: (props: P) => string | Promise<string>) => (props: P) => Promise<import("react").FunctionComponentElement<PropsWithSay<P>>>;
28
28
  type PropsWithSay<P = unknown> = P & {
29
29
  locale: string;
30
30
  messages: Say.Messages;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saykit/react",
3
- "version": "0.2.1",
3
+ "version": "0.6.0",
4
4
  "description": "React integration for saykit, i18n hooks and components",
5
5
  "keywords": [
6
6
  "i18n",
@@ -46,11 +46,13 @@
46
46
  "server-only": "^0.0.1"
47
47
  },
48
48
  "devDependencies": {
49
+ "@testing-library/react": "^16.3.2",
49
50
  "@types/react": "^19.2.17",
50
51
  "@types/react-dom": "^19.2.3",
51
- "react": "^19.2.7",
52
- "react-dom": "^19.2.7",
53
- "saykit": "^0.2.1"
52
+ "jsdom": "^29.1.1",
53
+ "react": "^19.2.8",
54
+ "react-dom": "^19.2.8",
55
+ "saykit": "^0.6.0"
54
56
  },
55
57
  "peerDependencies": {
56
58
  "react": "*",