@saykit/react 0.5.0 → 0.6.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/client.d.mts +4 -0
- package/dist/client.mjs +9 -5
- package/dist/index.d.mts +17 -6
- package/dist/index.mjs +21 -7
- package/dist/index.server.mjs +21 -7
- package/package.json +4 -2
package/dist/client.d.mts
CHANGED
|
@@ -8,6 +8,10 @@ type SayRef = {
|
|
|
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
|
*/
|
package/dist/client.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, createElement, useContext,
|
|
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
|
|
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:
|
|
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,6 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Disallow, NumeralOptions, SelectOptions } from "saykit";
|
|
1
|
+
import { ReactElement, ReactNode } from "react";
|
|
2
|
+
import { Disallow, Named, NumeralOptions, SelectOptions } from "saykit";
|
|
3
3
|
//#region src/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* What a message is allowed to contain. On top of everything React renders, a
|
|
6
|
+
* named placeholder — `{{ name: value }}` — reaches the type checker as a plain
|
|
7
|
+
* object child, so the object form has to be part of the contract even though
|
|
8
|
+
* nothing ever renders it: the transform reads the name off it and compiles the
|
|
9
|
+
* child away before React sees the tree.
|
|
10
|
+
*/
|
|
11
|
+
type SayNode = ReactNode | Named<unknown> | Iterable<SayNode>;
|
|
12
|
+
type PropsWithSayChildren<P = unknown> = P & {
|
|
13
|
+
children?: SayNode;
|
|
14
|
+
};
|
|
4
15
|
type PropsWithJSXSafeKeys<T> = { [K in keyof T as K extends number | `${number}${string}` ? `_${K}` : K extends `_${number}${string}` ? never : K]: T[K]; };
|
|
5
16
|
//#endregion
|
|
6
17
|
//#region src/runtime/index.d.ts
|
|
@@ -11,7 +22,7 @@ type PropsWithJSXSafeKeys<T> = { [K in keyof T as K extends number | `${number}$
|
|
|
11
22
|
* @returns The translation node for the descriptor
|
|
12
23
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
13
24
|
*/
|
|
14
|
-
declare function Say(props:
|
|
25
|
+
declare function Say(props: PropsWithSayChildren<Disallow<{
|
|
15
26
|
context?: string;
|
|
16
27
|
whitespace?: boolean;
|
|
17
28
|
}, 'id'>>): ReactElement;
|
|
@@ -34,7 +45,7 @@ declare namespace Say {
|
|
|
34
45
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
35
46
|
*/
|
|
36
47
|
function Plural(props: {
|
|
37
|
-
_: number
|
|
48
|
+
_: number | Named<number>;
|
|
38
49
|
} & PropsWithJSXSafeKeys<Disallow<NumeralOptions, 'id' | 'context'>>): ReactNode;
|
|
39
50
|
/**
|
|
40
51
|
* Define an ordinal message (e.g. "1st", "2nd", "3rd").
|
|
@@ -56,7 +67,7 @@ declare namespace Say {
|
|
|
56
67
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
57
68
|
*/
|
|
58
69
|
function Ordinal(props: {
|
|
59
|
-
_: number
|
|
70
|
+
_: number | Named<number>;
|
|
60
71
|
} & PropsWithJSXSafeKeys<Disallow<NumeralOptions, 'id' | 'context'>>): ReactNode;
|
|
61
72
|
/**
|
|
62
73
|
* Define a select message, useful for handling gender, status, or other categories.
|
|
@@ -77,7 +88,7 @@ declare namespace Say {
|
|
|
77
88
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
78
89
|
*/
|
|
79
90
|
function Select(props: {
|
|
80
|
-
_: string
|
|
91
|
+
_: string | Named<string>;
|
|
81
92
|
} & PropsWithJSXSafeKeys<Disallow<SelectOptions, 'id' | 'context'>>): ReactNode;
|
|
82
93
|
}
|
|
83
94
|
//#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
|
-
|
|
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 (
|
|
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
|
|
72
|
+
const { id, whitespace, ...rest } = props;
|
|
73
|
+
const values = resolveValuePropKeys(rest);
|
|
63
74
|
return createElement(Renderer, {
|
|
64
|
-
html: say.call(
|
|
75
|
+
html: say.call({
|
|
76
|
+
...rest,
|
|
77
|
+
id
|
|
78
|
+
}),
|
|
65
79
|
whitespace,
|
|
66
80
|
components(tag) {
|
|
67
|
-
if (tag && tag in
|
|
68
|
-
const element =
|
|
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/index.server.mjs
CHANGED
|
@@ -47,9 +47,20 @@ function Renderer({ html, components, whitespace = true }) {
|
|
|
47
47
|
}
|
|
48
48
|
//#endregion
|
|
49
49
|
//#region src/types.ts
|
|
50
|
-
|
|
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 (
|
|
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
|
|
72
|
+
const { id, whitespace, ...rest } = props;
|
|
73
|
+
const values = resolveValuePropKeys(rest);
|
|
63
74
|
return createElement(Renderer, {
|
|
64
|
-
html: say.call(
|
|
75
|
+
html: say.call({
|
|
76
|
+
...rest,
|
|
77
|
+
id
|
|
78
|
+
}),
|
|
65
79
|
whitespace,
|
|
66
80
|
components(tag) {
|
|
67
|
-
if (tag && tag in
|
|
68
|
-
const element =
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saykit/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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",
|
|
52
|
+
"jsdom": "^29.1.1",
|
|
51
53
|
"react": "^19.2.8",
|
|
52
54
|
"react-dom": "^19.2.8",
|
|
53
|
-
"saykit": "^0.
|
|
55
|
+
"saykit": "^0.6.1"
|
|
54
56
|
},
|
|
55
57
|
"peerDependencies": {
|
|
56
58
|
"react": "*",
|