@saykit/react 0.0.0 → 0.2.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/README.md +34 -0
- package/dist/client.d.mts +33 -0
- package/dist/client.mjs +39 -0
- package/dist/index.client.mjs +92 -0
- package/dist/index.d.mts +85 -0
- package/dist/index.server.mjs +92 -0
- package/dist/server.d.mts +33 -0
- package/dist/server.mjs +56 -0
- package/package.json +62 -3
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @saykit/react
|
|
2
|
+
|
|
3
|
+
> React integration for [SayKit](https://saykit.js.org).
|
|
4
|
+
|
|
5
|
+
[](https://codecov.io/gh/k0d13/saykit?flags%5B0%5D=integration-react)
|
|
6
|
+
|
|
7
|
+
A `<Say>` component for rendering translated content in server and client components, a `<SayProvider>` and `useSay()` for client trees, and a small server runtime (`setSay`, `getSay`, `unstable_createWithSay`).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pnpm add @saykit/react saykit
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You will also need a SayKit build-tool plugin and a `saykit.config.ts`.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Say, SayProvider } from '@saykit/react/client';
|
|
21
|
+
|
|
22
|
+
function App() {
|
|
23
|
+
return (
|
|
24
|
+
<SayProvider locale="fr" messages={fr}>
|
|
25
|
+
<Say>Hello, {name}!</Say>
|
|
26
|
+
<Say.Plural _={count} one="# item" other="# items" />
|
|
27
|
+
</SayProvider>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Documentation
|
|
33
|
+
|
|
34
|
+
[React integration guide](https://saykit.js.org/integrations/react) at [saykit.js.org](https://saykit.js.org).
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as _$react from "react";
|
|
2
|
+
import { PropsWithChildren } from "react";
|
|
3
|
+
import { ReadonlySay, Say } from "saykit";
|
|
4
|
+
|
|
5
|
+
//#region src/runtime/client.d.ts
|
|
6
|
+
type SayRef = {
|
|
7
|
+
current: ReadonlySay | null;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Provide a localised {@link runtime.Say} instance to descendant **client** components via context.
|
|
11
|
+
* Must wrap any component tree using {@link useSay} or {@link Say}.
|
|
12
|
+
*
|
|
13
|
+
* @param props.locale The current locale
|
|
14
|
+
* @param props.messages The current messages for the locale
|
|
15
|
+
*/
|
|
16
|
+
declare function SayProvider({
|
|
17
|
+
locale,
|
|
18
|
+
messages,
|
|
19
|
+
children
|
|
20
|
+
}: PropsWithChildren<{
|
|
21
|
+
locale: string;
|
|
22
|
+
messages: Say.Messages;
|
|
23
|
+
}>): _$react.FunctionComponentElement<_$react.ProviderProps<SayRef>>;
|
|
24
|
+
/**
|
|
25
|
+
* Get the current {@link Say} **client** instance.
|
|
26
|
+
* Must be called within a {@link SayProvider}.
|
|
27
|
+
*
|
|
28
|
+
* @returns The current {@link Say} instance
|
|
29
|
+
* @throws If no provider is in the component tree
|
|
30
|
+
*/
|
|
31
|
+
declare function useSay(): any;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { SayProvider, useSay };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createContext, createElement, useContext, useState } from "react";
|
|
3
|
+
import { Say } from "saykit";
|
|
4
|
+
//#region src/runtime/client.ts
|
|
5
|
+
const SayContext = createContext({ current: null });
|
|
6
|
+
SayContext.displayName = "SayContext";
|
|
7
|
+
/**
|
|
8
|
+
* Provide a localised {@link runtime.Say} instance to descendant **client** components via context.
|
|
9
|
+
* Must wrap any component tree using {@link useSay} or {@link Say}.
|
|
10
|
+
*
|
|
11
|
+
* @param props.locale The current locale
|
|
12
|
+
* @param props.messages The current messages for the locale
|
|
13
|
+
*/
|
|
14
|
+
function SayProvider({ locale, messages, children }) {
|
|
15
|
+
const [say] = useState(() => {
|
|
16
|
+
const instance = new Say({
|
|
17
|
+
locales: [locale],
|
|
18
|
+
loader: () => messages
|
|
19
|
+
});
|
|
20
|
+
instance.load(locale);
|
|
21
|
+
instance.activate(locale);
|
|
22
|
+
return instance.freeze();
|
|
23
|
+
});
|
|
24
|
+
return createElement(SayContext.Provider, { value: { current: say } }, children);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the current {@link Say} **client** instance.
|
|
28
|
+
* Must be called within a {@link SayProvider}.
|
|
29
|
+
*
|
|
30
|
+
* @returns The current {@link Say} instance
|
|
31
|
+
* @throws If no provider is in the component tree
|
|
32
|
+
*/
|
|
33
|
+
function useSay() {
|
|
34
|
+
const ref = useContext(SayContext);
|
|
35
|
+
if (!ref.current) throw new Error("'useSay' must be used within a 'SayProvider'");
|
|
36
|
+
return ref.current;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { SayProvider, useSay };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use client"; import { useSay as GET_SAY } from "./client.mjs";import { Fragment, cloneElement, createElement, isValidElement } from "react";
|
|
2
|
+
//#region src/components/renderer.ts
|
|
3
|
+
function Renderer({ html, components, whitespace = true }) {
|
|
4
|
+
function getComponent(tag) {
|
|
5
|
+
if (typeof components === "function")
|
|
6
|
+
/* v8 ignore next */
|
|
7
|
+
return components(tag) ?? tag ?? Fragment;
|
|
8
|
+
return tag && tag in components ? components[tag] : Fragment;
|
|
9
|
+
}
|
|
10
|
+
let nodeKey = 0;
|
|
11
|
+
function parseNode(input) {
|
|
12
|
+
const stack = [];
|
|
13
|
+
let current = [];
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < input.length) if (input[i] === "<") {
|
|
16
|
+
const isClosing = input[i + 1] === "/";
|
|
17
|
+
const isSelfClosing = input.slice(i, input.indexOf(">", i) + 1).endsWith("/>");
|
|
18
|
+
const tagStart = i + (isClosing ? 2 : 1);
|
|
19
|
+
const tagEnd = input.indexOf(">", tagStart);
|
|
20
|
+
const tagName = input.slice(tagStart, tagEnd).replace("/", "").trim();
|
|
21
|
+
i = tagEnd + 1;
|
|
22
|
+
if (isSelfClosing) {
|
|
23
|
+
const Component = getComponent(tagName);
|
|
24
|
+
current.push(createElement(Component, { key: nodeKey++ }));
|
|
25
|
+
} else if (isClosing) {
|
|
26
|
+
const last = stack.pop();
|
|
27
|
+
const element = createElement(getComponent(last.tag), { key: nodeKey++ }, ...current);
|
|
28
|
+
current = last.children;
|
|
29
|
+
current.push(element);
|
|
30
|
+
} else {
|
|
31
|
+
stack.push({
|
|
32
|
+
tag: tagName,
|
|
33
|
+
children: current
|
|
34
|
+
});
|
|
35
|
+
current = [];
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
let textEnd = input.indexOf("<", i);
|
|
39
|
+
if (textEnd === -1) textEnd = input.length;
|
|
40
|
+
const text = input.slice(i, textEnd);
|
|
41
|
+
if (whitespace || text.trim() !== "") current.push(text);
|
|
42
|
+
i = textEnd;
|
|
43
|
+
}
|
|
44
|
+
return current;
|
|
45
|
+
}
|
|
46
|
+
return createElement(Fragment, void 0, ...parseNode(html));
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/types.ts
|
|
50
|
+
function resolveJsxSafePropKeys(props) {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key in props) if (/^_\d+$/.test(key)) result[key.slice(1)] = props[key];
|
|
53
|
+
else result[key] = props[key];
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/runtime/index.ts
|
|
58
|
+
function Say(props) {
|
|
59
|
+
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
|
+
const say = GET_SAY();
|
|
61
|
+
const { whitespace, ...rest } = props;
|
|
62
|
+
const descriptor = resolveJsxSafePropKeys(rest);
|
|
63
|
+
return createElement(Renderer, {
|
|
64
|
+
html: say.call(descriptor),
|
|
65
|
+
whitespace,
|
|
66
|
+
components(tag) {
|
|
67
|
+
if (tag && tag in descriptor && isValidElement(descriptor[tag])) {
|
|
68
|
+
const element = descriptor[tag];
|
|
69
|
+
return (props) => cloneElement(element, {
|
|
70
|
+
...element.props,
|
|
71
|
+
...props
|
|
72
|
+
});
|
|
73
|
+
} else return tag;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
(function(_Say) {
|
|
78
|
+
function Plural(props) {
|
|
79
|
+
throw new Error("'Say.Plural' is a macro and must be used with the relevant saykit plugin");
|
|
80
|
+
}
|
|
81
|
+
_Say.Plural = Plural;
|
|
82
|
+
function Ordinal(props) {
|
|
83
|
+
throw new Error("'Say.Ordinal' is a macro and must be used with the relevant saykit plugin");
|
|
84
|
+
}
|
|
85
|
+
_Say.Ordinal = Ordinal;
|
|
86
|
+
function Select(props) {
|
|
87
|
+
throw new Error("'Say.Select' is a macro and must be used with the relevant saykit plugin");
|
|
88
|
+
}
|
|
89
|
+
_Say.Select = Select;
|
|
90
|
+
})(Say || (Say = {}));
|
|
91
|
+
//#endregion
|
|
92
|
+
export { Say };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { PropsWithChildren, ReactElement, ReactNode } from "react";
|
|
2
|
+
import { Disallow, NumeralOptions, SelectOptions } from "saykit";
|
|
3
|
+
|
|
4
|
+
//#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] };
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/runtime/index.d.ts
|
|
8
|
+
/**
|
|
9
|
+
* Render the translation for a descriptor.
|
|
10
|
+
*
|
|
11
|
+
* @param descriptor Descriptor to render the translation for
|
|
12
|
+
* @returns The translation node for the descriptor
|
|
13
|
+
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
14
|
+
*/
|
|
15
|
+
declare function Say(props: PropsWithChildren<Disallow<{
|
|
16
|
+
context?: string;
|
|
17
|
+
whitespace?: boolean;
|
|
18
|
+
}, 'id'>>): ReactElement;
|
|
19
|
+
declare namespace Say {
|
|
20
|
+
/**
|
|
21
|
+
* Define a pluralised message.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <Say.Plural
|
|
26
|
+
* _={count}
|
|
27
|
+
* one="You have 1 item"
|
|
28
|
+
* other="You have # items"
|
|
29
|
+
* />
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @param props._ Number to determine the plural form of
|
|
33
|
+
* @param props Options pluralisation rules keyed by CLDR categories or specific numbers
|
|
34
|
+
* @returns The plural form of the number, as a React node
|
|
35
|
+
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
36
|
+
*/
|
|
37
|
+
function Plural(props: {
|
|
38
|
+
_: number;
|
|
39
|
+
} & PropsWithJSXSafeKeys<Disallow<NumeralOptions, 'id' | 'context'>>): ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* Define an ordinal message (e.g. "1st", "2nd", "3rd").
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <Say.Ordinal
|
|
46
|
+
* _={position}
|
|
47
|
+
* 1="#st"
|
|
48
|
+
* 2="#nd"
|
|
49
|
+
* 3="#rd"
|
|
50
|
+
* other="#th"
|
|
51
|
+
* />
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @param props._ Number to determine the ordinal form of
|
|
55
|
+
* @param props Options ordinal rules keyed by CLDR categories or specific numbers
|
|
56
|
+
* @returns The ordinal form of the number, as a React node
|
|
57
|
+
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
58
|
+
*/
|
|
59
|
+
function Ordinal(props: {
|
|
60
|
+
_: number;
|
|
61
|
+
} & PropsWithJSXSafeKeys<Disallow<NumeralOptions, 'id' | 'context'>>): ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* Define a select message, useful for handling gender, status, or other categories.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```tsx
|
|
67
|
+
* <Say.Select
|
|
68
|
+
* _={gender}
|
|
69
|
+
* male="He"
|
|
70
|
+
* female="She"
|
|
71
|
+
* other="They"
|
|
72
|
+
* />
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @param props._ Selector value to determine which option is chosen
|
|
76
|
+
* @param props Options a mapping of possible selector values to message strings
|
|
77
|
+
* @returns The select form of the value, as a React node
|
|
78
|
+
* @remark This is a macro and must be used with the relevant saykit plugin
|
|
79
|
+
*/
|
|
80
|
+
function Select(props: {
|
|
81
|
+
_: string;
|
|
82
|
+
} & PropsWithJSXSafeKeys<Disallow<SelectOptions, 'id' | 'context'>>): ReactNode;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
export { Say };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { getSay as GET_SAY } from "./server.mjs";import { Fragment, cloneElement, createElement, isValidElement } from "react";
|
|
2
|
+
//#region src/components/renderer.ts
|
|
3
|
+
function Renderer({ html, components, whitespace = true }) {
|
|
4
|
+
function getComponent(tag) {
|
|
5
|
+
if (typeof components === "function")
|
|
6
|
+
/* v8 ignore next */
|
|
7
|
+
return components(tag) ?? tag ?? Fragment;
|
|
8
|
+
return tag && tag in components ? components[tag] : Fragment;
|
|
9
|
+
}
|
|
10
|
+
let nodeKey = 0;
|
|
11
|
+
function parseNode(input) {
|
|
12
|
+
const stack = [];
|
|
13
|
+
let current = [];
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < input.length) if (input[i] === "<") {
|
|
16
|
+
const isClosing = input[i + 1] === "/";
|
|
17
|
+
const isSelfClosing = input.slice(i, input.indexOf(">", i) + 1).endsWith("/>");
|
|
18
|
+
const tagStart = i + (isClosing ? 2 : 1);
|
|
19
|
+
const tagEnd = input.indexOf(">", tagStart);
|
|
20
|
+
const tagName = input.slice(tagStart, tagEnd).replace("/", "").trim();
|
|
21
|
+
i = tagEnd + 1;
|
|
22
|
+
if (isSelfClosing) {
|
|
23
|
+
const Component = getComponent(tagName);
|
|
24
|
+
current.push(createElement(Component, { key: nodeKey++ }));
|
|
25
|
+
} else if (isClosing) {
|
|
26
|
+
const last = stack.pop();
|
|
27
|
+
const element = createElement(getComponent(last.tag), { key: nodeKey++ }, ...current);
|
|
28
|
+
current = last.children;
|
|
29
|
+
current.push(element);
|
|
30
|
+
} else {
|
|
31
|
+
stack.push({
|
|
32
|
+
tag: tagName,
|
|
33
|
+
children: current
|
|
34
|
+
});
|
|
35
|
+
current = [];
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
let textEnd = input.indexOf("<", i);
|
|
39
|
+
if (textEnd === -1) textEnd = input.length;
|
|
40
|
+
const text = input.slice(i, textEnd);
|
|
41
|
+
if (whitespace || text.trim() !== "") current.push(text);
|
|
42
|
+
i = textEnd;
|
|
43
|
+
}
|
|
44
|
+
return current;
|
|
45
|
+
}
|
|
46
|
+
return createElement(Fragment, void 0, ...parseNode(html));
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/types.ts
|
|
50
|
+
function resolveJsxSafePropKeys(props) {
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key in props) if (/^_\d+$/.test(key)) result[key.slice(1)] = props[key];
|
|
53
|
+
else result[key] = props[key];
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/runtime/index.ts
|
|
58
|
+
function Say(props) {
|
|
59
|
+
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
|
+
const say = GET_SAY();
|
|
61
|
+
const { whitespace, ...rest } = props;
|
|
62
|
+
const descriptor = resolveJsxSafePropKeys(rest);
|
|
63
|
+
return createElement(Renderer, {
|
|
64
|
+
html: say.call(descriptor),
|
|
65
|
+
whitespace,
|
|
66
|
+
components(tag) {
|
|
67
|
+
if (tag && tag in descriptor && isValidElement(descriptor[tag])) {
|
|
68
|
+
const element = descriptor[tag];
|
|
69
|
+
return (props) => cloneElement(element, {
|
|
70
|
+
...element.props,
|
|
71
|
+
...props
|
|
72
|
+
});
|
|
73
|
+
} else return tag;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
(function(_Say) {
|
|
78
|
+
function Plural(props) {
|
|
79
|
+
throw new Error("'Say.Plural' is a macro and must be used with the relevant saykit plugin");
|
|
80
|
+
}
|
|
81
|
+
_Say.Plural = Plural;
|
|
82
|
+
function Ordinal(props) {
|
|
83
|
+
throw new Error("'Say.Ordinal' is a macro and must be used with the relevant saykit plugin");
|
|
84
|
+
}
|
|
85
|
+
_Say.Ordinal = Ordinal;
|
|
86
|
+
function Select(props) {
|
|
87
|
+
throw new Error("'Say.Select' is a macro and must be used with the relevant saykit plugin");
|
|
88
|
+
}
|
|
89
|
+
_Say.Select = Select;
|
|
90
|
+
})(Say || (Say = {}));
|
|
91
|
+
//#endregion
|
|
92
|
+
export { Say };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as _$react from "react";
|
|
2
|
+
import { ReactNode } from "react";
|
|
3
|
+
import { ReadonlySay, Say } from "saykit";
|
|
4
|
+
//#region src/runtime/server.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Set the current {@link Say} **server** instance.
|
|
7
|
+
* Must be called before any {@link getSay} calls.
|
|
8
|
+
*
|
|
9
|
+
* @param say The current {@link Say} instance
|
|
10
|
+
*/
|
|
11
|
+
declare function setSay(say: Say | ReadonlySay | (() => Say | ReadonlySay)): void;
|
|
12
|
+
/**
|
|
13
|
+
* Get the current {@link Say} **server** instance.
|
|
14
|
+
* Must only be called after any {@link setSay} calls.
|
|
15
|
+
*
|
|
16
|
+
* @returns The current {@link Say} instance
|
|
17
|
+
* @throws If no {@link Say} instance has been set
|
|
18
|
+
*/
|
|
19
|
+
declare function getSay(): ReadonlySay;
|
|
20
|
+
/**
|
|
21
|
+
* Create a {@link withSay} higher-order component factory bound to a specific {@link Say} instance.
|
|
22
|
+
*
|
|
23
|
+
* @param say The {@link Say} instance to bind into the server context
|
|
24
|
+
*
|
|
25
|
+
* @returns A {@link withSay} higher-order component factory
|
|
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>>>;
|
|
28
|
+
type PropsWithSay<P = unknown> = P & {
|
|
29
|
+
locale: string;
|
|
30
|
+
messages: Say.Messages;
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { PropsWithSay, getSay, setSay, unstable_createWithSay };
|
package/dist/server.mjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { cache, createElement } from "react";
|
|
2
|
+
import { Say } from "saykit";
|
|
3
|
+
import "server-only";
|
|
4
|
+
//#region src/runtime/server.ts
|
|
5
|
+
const serverContext = cache(() => ({ current: null }));
|
|
6
|
+
/**
|
|
7
|
+
* Set the current {@link Say} **server** instance.
|
|
8
|
+
* Must be called before any {@link getSay} calls.
|
|
9
|
+
*
|
|
10
|
+
* @param say The current {@link Say} instance
|
|
11
|
+
*/
|
|
12
|
+
function setSay(say) {
|
|
13
|
+
const ref = serverContext();
|
|
14
|
+
if (say instanceof Say) ref.current = say.clone().freeze();
|
|
15
|
+
else ref.current = say().clone().freeze();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the current {@link Say} **server** instance.
|
|
19
|
+
* Must only be called after any {@link setSay} calls.
|
|
20
|
+
*
|
|
21
|
+
* @returns The current {@link Say} instance
|
|
22
|
+
* @throws If no {@link Say} instance has been set
|
|
23
|
+
*/
|
|
24
|
+
function getSay() {
|
|
25
|
+
const ref = serverContext();
|
|
26
|
+
if (!ref.current) throw new Error("Attempt to access the server-only Say instance before initialisation", { cause: /* @__PURE__ */ new Error("'getSay' must be called after 'setSay'") });
|
|
27
|
+
return ref.current;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a {@link withSay} higher-order component factory bound to a specific {@link Say} instance.
|
|
31
|
+
*
|
|
32
|
+
* @param say The {@link Say} instance to bind into the server context
|
|
33
|
+
*
|
|
34
|
+
* @returns A {@link withSay} higher-order component factory
|
|
35
|
+
*/
|
|
36
|
+
function unstable_createWithSay(say) {
|
|
37
|
+
/**
|
|
38
|
+
* Wrap a server component so that a {@link Say} instance is initialised before render.
|
|
39
|
+
*/
|
|
40
|
+
return function withSay(Component, getLocale) {
|
|
41
|
+
return async function WithSay(props) {
|
|
42
|
+
const guess = await getLocale(props);
|
|
43
|
+
const locale = say.match(guess);
|
|
44
|
+
await say.load(locale);
|
|
45
|
+
say.activate(locale);
|
|
46
|
+
setSay(say);
|
|
47
|
+
return createElement(Component, {
|
|
48
|
+
...props,
|
|
49
|
+
locale: say.locale,
|
|
50
|
+
messages: say.messages
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { getSay, setSay, unstable_createWithSay };
|
package/package.json
CHANGED
|
@@ -1,4 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@saykit/react",
|
|
3
|
-
"version": "0.
|
|
1
|
+
{
|
|
2
|
+
"name": "@saykit/react",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "React integration for saykit, i18n hooks and components",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"i18n",
|
|
7
|
+
"internationalization",
|
|
8
|
+
"react",
|
|
9
|
+
"saykit"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/k0d13/saykit#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/k0d13/saykit/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/k0d13/saykit.git",
|
|
19
|
+
"directory": "packages/integration-react"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"!dist/**/*.map"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"react-server": "./dist/index.server.mjs",
|
|
30
|
+
"default": "./dist/index.client.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./client": {
|
|
33
|
+
"types": "./dist/client.d.mts",
|
|
34
|
+
"default": "./dist/client.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./server": {
|
|
37
|
+
"types": "./dist/server.d.mts",
|
|
38
|
+
"default": "./dist/server.mjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"provenance": true
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"server-only": "^0.0.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/react": "^19.2.17",
|
|
50
|
+
"@types/react-dom": "^19.2.3",
|
|
51
|
+
"react": "^19.2.7",
|
|
52
|
+
"react-dom": "^19.2.7",
|
|
53
|
+
"saykit": "^0.2.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": "*",
|
|
57
|
+
"saykit": "*"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"check": "tsc --noEmit",
|
|
61
|
+
"build": "tsdown"
|
|
62
|
+
}
|
|
4
63
|
}
|