@saykit/react 0.0.0-beta-20261017001906 → 0.0.0-beta-20261030083913
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 +3 -3
- package/dist/client.d.mts +5 -14
- package/dist/client.mjs +3 -3
- package/dist/client.server.d.mts +5 -11
- package/dist/client.server.mjs +1 -2
- package/dist/index.d.mts +3 -7
- package/dist/index.mjs +5 -7
- package/dist/index.server.mjs +5 -7
- package/dist/server.d.mts +7 -13
- package/dist/server.mjs +2 -3
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A `<Say>` component for rendering translated content in server and client compon
|
|
|
12
12
|
pnpm add @saykit/react saykit
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
You will also need a SayKit build-tool plugin ([`unplugin-saykit`](https://github.com/k0d13/saykit/tree/main/packages/plugin-unplugin) or [`babel-plugin-saykit`](https://github.com/k0d13/saykit/tree/main/packages/plugin-babel)) and a `saykit.config.ts` with `@saykit/transform-jsx` in the bucket.
|
|
15
|
+
You will also need a SayKit build-tool plugin ([`unplugin-saykit`](https://github.com/k0d13/saykit/tree/main/packages/plugin-unplugin) or [`babel-plugin-saykit`](https://github.com/k0d13/saykit/tree/main/packages/plugin-babel)) and a `saykit.config.ts` with `@saykit/transform-jsx` in the bucket.
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
@@ -20,8 +20,8 @@ You will also need a SayKit build-tool plugin ([`unplugin-saykit`](https://githu
|
|
|
20
20
|
import { createCatalogue, createStore } from 'saykit';
|
|
21
21
|
|
|
22
22
|
export const catalogue = createCatalogue({
|
|
23
|
-
en: () => import('./locales/en.
|
|
24
|
-
fr: () => import('./locales/fr.
|
|
23
|
+
en: () => import('./locales/en.js'),
|
|
24
|
+
fr: () => import('./locales/fr.js'),
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
const initial = catalogue.match(navigator.languages);
|
package/dist/client.d.mts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { PropsWithChildren } from "react";
|
|
2
2
|
import { Store, View } from "saykit";
|
|
3
|
-
//#region src/runtime/client.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* Where a provider takes its view from.
|
|
6
5
|
*
|
|
7
|
-
* A store is
|
|
8
|
-
* and
|
|
9
|
-
* cross the server/client boundary.
|
|
10
|
-
*
|
|
11
|
-
* A locale and its messages are the serialisable form a server can hand across
|
|
12
|
-
* that boundary. Only that one locale comes over, so the provider built from
|
|
13
|
-
* it has nothing to switch to: switching is the server's to do, normally
|
|
14
|
-
* through navigation.
|
|
6
|
+
* A store is reactive and can switch locale, but cannot cross the server/client boundary.
|
|
7
|
+
* A locale and its messages can, though the provider built from them has nothing to switch to.
|
|
15
8
|
*/
|
|
16
|
-
type SayProviderProps = {
|
|
9
|
+
export type SayProviderProps = {
|
|
17
10
|
store: Store;
|
|
18
11
|
locale?: never;
|
|
19
12
|
messages?: never;
|
|
@@ -42,7 +35,7 @@ type SayProviderProps = {
|
|
|
42
35
|
* @param props.options How that locale's view formats its messages, as the
|
|
43
36
|
* server's view was built with
|
|
44
37
|
*/
|
|
45
|
-
declare function SayProvider({ store, locale, messages, options, children }: PropsWithChildren<SayProviderProps>): import("react").FunctionComponentElement<import("react").ProviderProps<Store<string> | null>>;
|
|
38
|
+
export declare function SayProvider({ store, locale, messages, options, children }: PropsWithChildren<SayProviderProps>): import("react").FunctionComponentElement<import("react").ProviderProps<Store<string> | null>>;
|
|
46
39
|
/**
|
|
47
40
|
* Get the current {@link View}, on the client.
|
|
48
41
|
* Must be called within a {@link SayProvider}.
|
|
@@ -58,6 +51,4 @@ declare function SayProvider({ store, locale, messages, options, children }: Pro
|
|
|
58
51
|
* @returns The current {@link View}
|
|
59
52
|
* @throws If no provider is in the component tree
|
|
60
53
|
*/
|
|
61
|
-
declare function useSay(): View;
|
|
62
|
-
//#endregion
|
|
63
|
-
export { SayProvider, SayProviderProps, useSay };
|
|
54
|
+
export declare function useSay(): View;
|
package/dist/client.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createContext, createElement, useContext, useMemo, useSyncExternalStore } from "react";
|
|
3
3
|
import { createCatalogue, createStore } from "saykit";
|
|
4
|
-
|
|
4
|
+
// src/runtime/client.ts
|
|
5
5
|
const SayContext = createContext(null);
|
|
6
6
|
SayContext.displayName = "SayContext";
|
|
7
7
|
function SayProvider({ store, locale, messages, options, children }) {
|
|
@@ -21,7 +21,7 @@ function SayProvider({ store, locale, messages, options, children }) {
|
|
|
21
21
|
function useSay() {
|
|
22
22
|
const store = useContext(SayContext);
|
|
23
23
|
if (!store) throw new Error("'useSay' must be used within a 'SayProvider'");
|
|
24
|
-
|
|
24
|
+
const subscribe = useMemo(() => (listener) => store.subscribe(listener), [store]);
|
|
25
|
+
return useSyncExternalStore(subscribe, () => store.say, () => store.say);
|
|
25
26
|
}
|
|
26
|
-
//#endregion
|
|
27
27
|
export { SayProvider, useSay };
|
package/dist/client.server.d.mts
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
import "server-only";
|
|
3
|
-
//#region src/runtime/client.server.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* The server build of `@saykit/react/client`.
|
|
6
5
|
*
|
|
7
|
-
* A store
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* off the view the enclosing segment established with
|
|
11
|
-
* {@link import('./server.js').setSay}, and passes them to the real provider,
|
|
12
|
-
* which is why `<SayProvider>` written on the server takes no props.
|
|
6
|
+
* A store cannot cross the server/client boundary, but the locale, messages and options can.
|
|
7
|
+
* This reads them off the view set by {@link import('./server.js').setSay} and passes them
|
|
8
|
+
* to the real provider, so `<SayProvider>` on the server takes no props.
|
|
13
9
|
*/
|
|
14
|
-
declare function SayProvider({ children }: {
|
|
10
|
+
export declare function SayProvider({ children }: {
|
|
15
11
|
children?: ReactNode;
|
|
16
|
-
}): import("react").FunctionComponentElement<import("react").PropsWithChildren<import("./client.js").SayProviderProps>>;
|
|
17
|
-
//#endregion
|
|
18
|
-
export { SayProvider };
|
|
12
|
+
}): import("react").FunctionComponentElement<import("react").PropsWithChildren<import("./client.js").SayProviderProps>>;
|
package/dist/client.server.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { createElement } from "react";
|
|
|
2
2
|
import "server-only";
|
|
3
3
|
import { SayProvider as SayProvider$1 } from "./client.mjs";
|
|
4
4
|
import { getSay } from "./server.mjs";
|
|
5
|
-
|
|
5
|
+
// src/runtime/client.server.ts
|
|
6
6
|
function SayProvider({ children }) {
|
|
7
7
|
const say = getSay();
|
|
8
8
|
return createElement(SayProvider$1, {
|
|
@@ -11,5 +11,4 @@ function SayProvider({ children }) {
|
|
|
11
11
|
options: say.options
|
|
12
12
|
}, children);
|
|
13
13
|
}
|
|
14
|
-
//#endregion
|
|
15
14
|
export { SayProvider };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from "react";
|
|
2
2
|
import { DateTimeOptions, Disallow, Named, NumberOptions, NumeralOptions, SelectOptions } from "saykit";
|
|
3
|
-
//#region src/types.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* What a message is allowed to contain. On top of everything React renders, a
|
|
6
5
|
* named placeholder, `{{ name: value }}`, reaches the type checker as a plain
|
|
@@ -13,8 +12,6 @@ type PropsWithSayChildren<P = unknown> = P & {
|
|
|
13
12
|
children?: SayNode;
|
|
14
13
|
};
|
|
15
14
|
type PropsWithJSXSafeKeys<T> = { [K in keyof T as K extends number | `${number}${string}` ? `_${K}` : K extends `_${number}${string}` ? never : K]: T[K]; };
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/runtime/index.d.ts
|
|
18
15
|
/**
|
|
19
16
|
* Render the translation for a descriptor.
|
|
20
17
|
*
|
|
@@ -22,12 +19,12 @@ type PropsWithJSXSafeKeys<T> = { [K in keyof T as K extends number | `${number}$
|
|
|
22
19
|
* @returns The translation node for the descriptor
|
|
23
20
|
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
24
21
|
*/
|
|
25
|
-
declare function Say(props: PropsWithSayChildren<{
|
|
22
|
+
export declare function Say(props: PropsWithSayChildren<{
|
|
26
23
|
id?: string;
|
|
27
24
|
context?: string;
|
|
28
25
|
whitespace?: boolean;
|
|
29
26
|
}>): ReactElement;
|
|
30
|
-
declare namespace Say {
|
|
27
|
+
export declare namespace Say {
|
|
31
28
|
/**
|
|
32
29
|
* Define a pluralised message.
|
|
33
30
|
*
|
|
@@ -148,5 +145,4 @@ declare namespace Say {
|
|
|
148
145
|
_: globalThis.Date | number | Named<globalThis.Date | number>;
|
|
149
146
|
} & Disallow<DateTimeOptions, 'id' | 'context'>): ReactNode;
|
|
150
147
|
}
|
|
151
|
-
|
|
152
|
-
export { Say };
|
|
148
|
+
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client"; import { useSay as GET_SAY } from "./client.mjs";import { Fragment, cloneElement, createElement, isValidElement } from "react";
|
|
2
|
-
|
|
2
|
+
// src/components/renderer.ts
|
|
3
3
|
function Renderer({ html, components, whitespace = true }) {
|
|
4
4
|
function getComponent(tag) {
|
|
5
5
|
if (typeof components === "function")
|
|
@@ -24,7 +24,8 @@ function Renderer({ html, components, whitespace = true }) {
|
|
|
24
24
|
current.push(createElement(Component, { key: nodeKey++ }));
|
|
25
25
|
} else if (isClosing) {
|
|
26
26
|
const last = stack.pop();
|
|
27
|
-
const
|
|
27
|
+
const Component = getComponent(last.tag);
|
|
28
|
+
const element = createElement(Component, { key: nodeKey++ }, ...current);
|
|
28
29
|
current = last.children;
|
|
29
30
|
current.push(element);
|
|
30
31
|
} else {
|
|
@@ -45,16 +46,14 @@ function Renderer({ html, components, whitespace = true }) {
|
|
|
45
46
|
}
|
|
46
47
|
return createElement(Fragment, void 0, ...parseNode(html));
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
//#region src/types.ts
|
|
49
|
+
// src/types.ts
|
|
50
50
|
function resolveValuePropKeys(props) {
|
|
51
51
|
const result = {};
|
|
52
52
|
for (const key in props) if (key.startsWith("_")) result[key.slice(1)] = props[key];
|
|
53
53
|
else result[key] = props[key];
|
|
54
54
|
return result;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
//#region src/runtime/index.ts
|
|
56
|
+
// src/runtime/index.ts
|
|
58
57
|
function Say(props) {
|
|
59
58
|
if (!("id" in props) && !("message" 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
59
|
const say = GET_SAY();
|
|
@@ -106,5 +105,4 @@ function Say(props) {
|
|
|
106
105
|
}
|
|
107
106
|
_Say.Time = Time;
|
|
108
107
|
})(Say || (Say = {}));
|
|
109
|
-
//#endregion
|
|
110
108
|
export { Say };
|
package/dist/index.server.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSay as GET_SAY } from "./server.mjs";import { Fragment, cloneElement, createElement, isValidElement } from "react";
|
|
2
|
-
|
|
2
|
+
// src/components/renderer.ts
|
|
3
3
|
function Renderer({ html, components, whitespace = true }) {
|
|
4
4
|
function getComponent(tag) {
|
|
5
5
|
if (typeof components === "function")
|
|
@@ -24,7 +24,8 @@ function Renderer({ html, components, whitespace = true }) {
|
|
|
24
24
|
current.push(createElement(Component, { key: nodeKey++ }));
|
|
25
25
|
} else if (isClosing) {
|
|
26
26
|
const last = stack.pop();
|
|
27
|
-
const
|
|
27
|
+
const Component = getComponent(last.tag);
|
|
28
|
+
const element = createElement(Component, { key: nodeKey++ }, ...current);
|
|
28
29
|
current = last.children;
|
|
29
30
|
current.push(element);
|
|
30
31
|
} else {
|
|
@@ -45,16 +46,14 @@ function Renderer({ html, components, whitespace = true }) {
|
|
|
45
46
|
}
|
|
46
47
|
return createElement(Fragment, void 0, ...parseNode(html));
|
|
47
48
|
}
|
|
48
|
-
|
|
49
|
-
//#region src/types.ts
|
|
49
|
+
// src/types.ts
|
|
50
50
|
function resolveValuePropKeys(props) {
|
|
51
51
|
const result = {};
|
|
52
52
|
for (const key in props) if (key.startsWith("_")) result[key.slice(1)] = props[key];
|
|
53
53
|
else result[key] = props[key];
|
|
54
54
|
return result;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
//#region src/runtime/index.ts
|
|
56
|
+
// src/runtime/index.ts
|
|
58
57
|
function Say(props) {
|
|
59
58
|
if (!("id" in props) && !("message" 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
59
|
const say = GET_SAY();
|
|
@@ -106,5 +105,4 @@ function Say(props) {
|
|
|
106
105
|
}
|
|
107
106
|
_Say.Time = Time;
|
|
108
107
|
})(Say || (Say = {}));
|
|
109
|
-
//#endregion
|
|
110
108
|
export { Say };
|
package/dist/server.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
2
|
import { Catalogue, View } from "saykit";
|
|
3
3
|
import "server-only";
|
|
4
|
-
//#region src/runtime/server.d.ts
|
|
5
4
|
/**
|
|
6
5
|
* Get the current {@link View}, on the server.
|
|
7
6
|
* Must be called below a {@link setSay}, which {@link createWithSay} does for
|
|
@@ -20,20 +19,18 @@ import "server-only";
|
|
|
20
19
|
* @returns The current {@link View}
|
|
21
20
|
* @throws If no view has been established for this request
|
|
22
21
|
*/
|
|
23
|
-
declare function getSay(): View;
|
|
22
|
+
export declare function getSay(): View;
|
|
24
23
|
/**
|
|
25
24
|
* Establish the {@link View} for everything rendered after this point, on the
|
|
26
25
|
* server. Reach for it when you already have a view; {@link createWithSay}
|
|
27
26
|
* negotiates and loads one for you.
|
|
28
27
|
*
|
|
29
|
-
*
|
|
30
|
-
* it
|
|
31
|
-
* over for everything rendered after it, including the messages
|
|
32
|
-
* `<SayProvider>` serialises. Development warns when that happens.
|
|
28
|
+
* A view lasts the whole request, not a subtree: a second view takes over everything rendered
|
|
29
|
+
* after it. Development warns when that happens.
|
|
33
30
|
*
|
|
34
31
|
* @param view The view to establish
|
|
35
32
|
*/
|
|
36
|
-
declare function setSay(view: View): void;
|
|
33
|
+
export declare function setSay(view: View): void;
|
|
37
34
|
/**
|
|
38
35
|
* Bind a `withSay` to a {@link Catalogue}, normally once beside the catalogue
|
|
39
36
|
* itself.
|
|
@@ -47,9 +44,8 @@ declare function setSay(view: View): void;
|
|
|
47
44
|
* layout above it - Next.js does - and a parent that has not run yet has
|
|
48
45
|
* established nothing.
|
|
49
46
|
*
|
|
50
|
-
* A `<SayProvider>`
|
|
51
|
-
*
|
|
52
|
-
* and serialises the locale and its messages across the boundary.
|
|
47
|
+
* A `<SayProvider>` inside a wrapped component takes no props: the server build of
|
|
48
|
+
* `@saykit/react/client` reads the established view.
|
|
53
49
|
*
|
|
54
50
|
* @example
|
|
55
51
|
* ```tsx
|
|
@@ -63,6 +59,4 @@ declare function setSay(view: View): void;
|
|
|
63
59
|
* @param catalogue The catalogue to take views from
|
|
64
60
|
* @returns A `withSay` bound to that catalogue
|
|
65
61
|
*/
|
|
66
|
-
declare function createWithSay<Locale extends string>(catalogue: Catalogue<Locale>): <P>(Component: (props: P) => ReactNode, locale: (props: P) => Catalogue.Guess | Promise<Catalogue.Guess>) => (props: P) => Promise<ReactNode>;
|
|
67
|
-
//#endregion
|
|
68
|
-
export { createWithSay, getSay, setSay };
|
|
62
|
+
export declare function createWithSay<Locale extends string>(catalogue: Catalogue<Locale>): <P>(Component: (props: P) => ReactNode, locale: (props: P) => Catalogue.Guess | Promise<Catalogue.Guess>) => (props: P) => Promise<ReactNode>;
|
package/dist/server.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { cache, createElement } from "react";
|
|
2
2
|
import "server-only";
|
|
3
|
-
|
|
3
|
+
// src/runtime/server.ts
|
|
4
4
|
const cell = cache(() => ({
|
|
5
5
|
view: void 0,
|
|
6
6
|
warned: false
|
|
7
7
|
}));
|
|
8
8
|
const NO_VIEW = "'getSay' must be called below a 'withSay'. Wrap the component in 'withSay(Component, (props) => props.params.then((params) => params.locale))'.";
|
|
9
|
-
const SECOND_VIEW = (established, next) => `A view for '${next}' was established while '${established}' was already established for this request. A view is per request rather than per subtree: React renders a server component's children after it returns, so there is nowhere to put the previous view back, and everything rendered after this point reads '${next}' - including components outside the one that established it, and the messages 'SayProvider'
|
|
9
|
+
const SECOND_VIEW = (established, next) => `A view for '${next}' was established while '${established}' was already established for this request. A view is per request rather than per subtree: React renders a server component's children after it returns, so there is nowhere to put the previous view back, and everything rendered after this point reads '${next}' - including components outside the one that established it, and the messages 'SayProvider' hands to the client. Render the other locale in its own request, or resolve its view yourself and pass it to the components that need it.`;
|
|
10
10
|
function getSay() {
|
|
11
11
|
const view = cell().view;
|
|
12
12
|
if (!view) throw new Error(NO_VIEW);
|
|
@@ -28,5 +28,4 @@ function createWithSay(catalogue) {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
//#endregion
|
|
32
31
|
export { createWithSay, getSay, setSay };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saykit/react",
|
|
3
|
-
"version": "0.0.0-beta-
|
|
3
|
+
"version": "0.0.0-beta-20261030083913",
|
|
4
4
|
"description": "React integration for saykit, i18n hooks and components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
"!dist/**/*.map"
|
|
24
24
|
],
|
|
25
25
|
"type": "module",
|
|
26
|
+
"imports": {
|
|
27
|
+
"#/*": "./src/*"
|
|
28
|
+
},
|
|
26
29
|
"exports": {
|
|
27
30
|
".": {
|
|
28
31
|
"types": "./dist/index.d.mts",
|
|
@@ -47,17 +50,17 @@
|
|
|
47
50
|
"server-only": "^0.0.1"
|
|
48
51
|
},
|
|
49
52
|
"devDependencies": {
|
|
50
|
-
"@testing-library/react": "^16.3.
|
|
51
|
-
"@types/react": "^19.
|
|
52
|
-
"@types/react-dom": "^19.
|
|
53
|
-
"jsdom": "^
|
|
54
|
-
"react": "^19.
|
|
55
|
-
"react-dom": "^19.
|
|
56
|
-
"saykit": "^0.0.0-beta-
|
|
53
|
+
"@testing-library/react": "^16.3.3",
|
|
54
|
+
"@types/react": "^19.3.0",
|
|
55
|
+
"@types/react-dom": "^19.3.0",
|
|
56
|
+
"jsdom": "^30.1.3",
|
|
57
|
+
"react": "^19.3.1",
|
|
58
|
+
"react-dom": "^19.3.1",
|
|
59
|
+
"saykit": "^0.0.0-beta-20261030083913"
|
|
57
60
|
},
|
|
58
61
|
"peerDependencies": {
|
|
59
62
|
"react": "*",
|
|
60
|
-
"saykit": "0.0.0-beta-
|
|
63
|
+
"saykit": "0.0.0-beta-20261030083913"
|
|
61
64
|
},
|
|
62
65
|
"scripts": {
|
|
63
66
|
"check": "tsc --noEmit",
|