@tuwaio/docs-ui 0.5.2 → 0.5.4
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/components/Footer.cjs +10 -9
- package/dist/components/Footer.d.cts +30 -2
- package/dist/components/Footer.d.ts +30 -2
- package/dist/components/Footer.js +15 -10
- package/dist/components/Logo.cjs +55 -2
- package/dist/components/Logo.d.cts +25 -2
- package/dist/components/Logo.d.ts +25 -2
- package/dist/components/Logo.js +52 -1
- package/dist/components/NavBar.cjs +21 -18
- package/dist/components/NavBar.d.cts +3 -1
- package/dist/components/NavBar.d.ts +3 -1
- package/dist/components/NavBar.js +21 -18
- package/dist/components/Orb.cjs +4 -4
- package/dist/components/Orb.js +4 -4
- package/dist/components/PolicyDocument.cjs +1 -1
- package/dist/components/PolicyDocument.js +2 -1
- package/dist/components/ThemeSwitcher.cjs +3 -3
- package/dist/components/ThemeSwitcher.d.cts +3 -1
- package/dist/components/ThemeSwitcher.d.ts +3 -1
- package/dist/components/ThemeSwitcher.js +3 -3
- package/dist/i18n.cjs +114 -0
- package/dist/i18n.d.cts +81 -0
- package/dist/i18n.d.ts +81 -0
- package/dist/i18n.js +101 -0
- package/dist/index.cjs +4 -0
- package/dist/index.css +183 -49
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -0
- package/dist/locale.cjs +131 -0
- package/dist/locale.d.cts +81 -0
- package/dist/locale.d.ts +81 -0
- package/dist/locale.js +100 -0
- package/dist/policies.cjs +37 -0
- package/dist/policies.d.cts +9 -1
- package/dist/policies.d.ts +9 -1
- package/dist/policies.js +36 -0
- package/package.json +12 -2
|
@@ -26,7 +26,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
26
26
|
var import_solid = require("@heroicons/react/24/solid");
|
|
27
27
|
var import_utils = require("@tuwaio/nova-core/utils");
|
|
28
28
|
var import_react = require("react");
|
|
29
|
-
function ThemeSwitcher({ theme, onToggle }) {
|
|
29
|
+
function ThemeSwitcher({ theme, onToggle, ariaLabel = "Toggle theme" }) {
|
|
30
30
|
const [mounted, setMounted] = (0, import_react.useState)(false);
|
|
31
31
|
(0, import_react.useEffect)(() => {
|
|
32
32
|
setMounted(true);
|
|
@@ -52,7 +52,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
52
52
|
"tuwadocs:group tuwadocs:relative tuwadocs:flex tuwadocs:h-10 tuwadocs:w-20 tuwadocs:md:h-8 tuwadocs:md:w-16 tuwadocs:cursor-pointer tuwadocs:items-center tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:p-1 tuwadocs:transition-all tuwadocs:duration-300 tuwadocs:ease-in-out tuwadocs:backdrop-blur-sm tuwadocs:border",
|
|
53
53
|
defaultStyles
|
|
54
54
|
),
|
|
55
|
-
"aria-label":
|
|
55
|
+
"aria-label": ariaLabel,
|
|
56
56
|
children: [
|
|
57
57
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
58
58
|
"div",
|
|
@@ -82,7 +82,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
82
82
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:flex tuwadocs:flex-1 tuwadocs:justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
83
83
|
import_solid.MoonIcon,
|
|
84
84
|
{
|
|
85
|
-
className: `tuwadocs:h-6 tuwadocs:w-6 tuwadocs:relative tuwadocs:left-[2px] tuwadocs:md:h-5 tuwadocs:md:w-5 tuwadocs:transition-colors tuwadocs:duration-300 ${isDark ? "tuwadocs:text-
|
|
85
|
+
className: `tuwadocs:h-6 tuwadocs:w-6 tuwadocs:relative tuwadocs:left-[2px] tuwadocs:md:h-5 tuwadocs:md:w-5 tuwadocs:transition-colors tuwadocs:duration-300 ${isDark ? "tuwadocs:text-cyan-400" : "tuwadocs:text-slate-500 tuwadocs:group-hover:text-cyan-300"}`
|
|
86
86
|
}
|
|
87
87
|
) })
|
|
88
88
|
] })
|
|
@@ -8,6 +8,8 @@ interface ThemeSwitcherProps {
|
|
|
8
8
|
theme: string;
|
|
9
9
|
/** Called when the switch is clicked; switch the theme of the site here (for example with `next-themes`) */
|
|
10
10
|
onToggle: () => void;
|
|
11
|
+
/** Accessible name of the switch button (default: `'Toggle theme'`) */
|
|
12
|
+
ariaLabel?: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* A light/dark switch with a sliding indicator. It stores no theme itself: pass the resolved theme and a toggle. Until
|
|
@@ -17,7 +19,7 @@ interface ThemeSwitcherProps {
|
|
|
17
19
|
* @param props - See {@link ThemeSwitcherProps}.
|
|
18
20
|
* @returns The switch button.
|
|
19
21
|
*/
|
|
20
|
-
declare function ThemeSwitcher({ theme, onToggle }: ThemeSwitcherProps): react.JSX.Element;
|
|
22
|
+
declare function ThemeSwitcher({ theme, onToggle, ariaLabel }: ThemeSwitcherProps): react.JSX.Element;
|
|
21
23
|
|
|
22
24
|
export { ThemeSwitcher };
|
|
23
25
|
export type { ThemeSwitcherProps };
|
|
@@ -8,6 +8,8 @@ interface ThemeSwitcherProps {
|
|
|
8
8
|
theme: string;
|
|
9
9
|
/** Called when the switch is clicked; switch the theme of the site here (for example with `next-themes`) */
|
|
10
10
|
onToggle: () => void;
|
|
11
|
+
/** Accessible name of the switch button (default: `'Toggle theme'`) */
|
|
12
|
+
ariaLabel?: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* A light/dark switch with a sliding indicator. It stores no theme itself: pass the resolved theme and a toggle. Until
|
|
@@ -17,7 +19,7 @@ interface ThemeSwitcherProps {
|
|
|
17
19
|
* @param props - See {@link ThemeSwitcherProps}.
|
|
18
20
|
* @returns The switch button.
|
|
19
21
|
*/
|
|
20
|
-
declare function ThemeSwitcher({ theme, onToggle }: ThemeSwitcherProps): react.JSX.Element;
|
|
22
|
+
declare function ThemeSwitcher({ theme, onToggle, ariaLabel }: ThemeSwitcherProps): react.JSX.Element;
|
|
21
23
|
|
|
22
24
|
export { ThemeSwitcher };
|
|
23
25
|
export type { ThemeSwitcherProps };
|
|
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { MoonIcon, SunIcon } from "@heroicons/react/24/solid";
|
|
4
4
|
import { cn } from "@tuwaio/nova-core/utils";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
|
-
function ThemeSwitcher({ theme, onToggle }) {
|
|
6
|
+
function ThemeSwitcher({ theme, onToggle, ariaLabel = "Toggle theme" }) {
|
|
7
7
|
const [mounted, setMounted] = useState(false);
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
setMounted(true);
|
|
@@ -29,7 +29,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
29
29
|
"tuwadocs:group tuwadocs:relative tuwadocs:flex tuwadocs:h-10 tuwadocs:w-20 tuwadocs:md:h-8 tuwadocs:md:w-16 tuwadocs:cursor-pointer tuwadocs:items-center tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:p-1 tuwadocs:transition-all tuwadocs:duration-300 tuwadocs:ease-in-out tuwadocs:backdrop-blur-sm tuwadocs:border",
|
|
30
30
|
defaultStyles
|
|
31
31
|
),
|
|
32
|
-
"aria-label":
|
|
32
|
+
"aria-label": ariaLabel,
|
|
33
33
|
children: [
|
|
34
34
|
/* @__PURE__ */ jsx(
|
|
35
35
|
"div",
|
|
@@ -59,7 +59,7 @@ function ThemeSwitcher({ theme, onToggle }) {
|
|
|
59
59
|
/* @__PURE__ */ jsx("div", { className: "tuwadocs:flex tuwadocs:flex-1 tuwadocs:justify-center", children: /* @__PURE__ */ jsx(
|
|
60
60
|
MoonIcon,
|
|
61
61
|
{
|
|
62
|
-
className: `tuwadocs:h-6 tuwadocs:w-6 tuwadocs:relative tuwadocs:left-[2px] tuwadocs:md:h-5 tuwadocs:md:w-5 tuwadocs:transition-colors tuwadocs:duration-300 ${isDark ? "tuwadocs:text-
|
|
62
|
+
className: `tuwadocs:h-6 tuwadocs:w-6 tuwadocs:relative tuwadocs:left-[2px] tuwadocs:md:h-5 tuwadocs:md:w-5 tuwadocs:transition-colors tuwadocs:duration-300 ${isDark ? "tuwadocs:text-cyan-400" : "tuwadocs:text-slate-500 tuwadocs:group-hover:text-cyan-300"}`
|
|
63
63
|
}
|
|
64
64
|
) })
|
|
65
65
|
] })
|
package/dist/i18n.cjs
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var i18n_exports = {};
|
|
21
|
+
__export(i18n_exports, {
|
|
22
|
+
LocaleVariant: () => LocaleVariant,
|
|
23
|
+
createI18n: () => createI18n,
|
|
24
|
+
useLocaleState: () => useLocaleState
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(i18n_exports);
|
|
27
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
var import_locale = require("./locale");
|
|
30
|
+
function applyDocumentLanguage(locale) {
|
|
31
|
+
if (typeof document !== "undefined") document.documentElement.lang = locale;
|
|
32
|
+
}
|
|
33
|
+
function useLocaleState(options = {}) {
|
|
34
|
+
const { initialLocale, storageKey = import_locale.LOCALE_STORAGE_KEY } = options;
|
|
35
|
+
const [locale, setLocaleState] = (0, import_react.useState)(initialLocale ?? import_locale.DEFAULT_LOCALE);
|
|
36
|
+
const [resolved, setResolved] = (0, import_react.useState)(Boolean(initialLocale));
|
|
37
|
+
(0, import_react.useEffect)(() => {
|
|
38
|
+
const next = initialLocale ?? (0, import_locale.resolveClientLocale)(storageKey);
|
|
39
|
+
setLocaleState(next);
|
|
40
|
+
setResolved(true);
|
|
41
|
+
applyDocumentLanguage(next);
|
|
42
|
+
}, [initialLocale, storageKey]);
|
|
43
|
+
(0, import_react.useEffect)(() => {
|
|
44
|
+
if (initialLocale) return;
|
|
45
|
+
const onStorage = (event) => {
|
|
46
|
+
if (event.key !== storageKey) return;
|
|
47
|
+
const next = (0, import_locale.normalizeLocale)(event.newValue);
|
|
48
|
+
if (next) {
|
|
49
|
+
setLocaleState(next);
|
|
50
|
+
applyDocumentLanguage(next);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
window.addEventListener("storage", onStorage);
|
|
54
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
55
|
+
}, [initialLocale, storageKey]);
|
|
56
|
+
const setLocale = (0, import_react.useCallback)(
|
|
57
|
+
(target) => {
|
|
58
|
+
const next = (0, import_locale.normalizeLocale)(target) ?? import_locale.DEFAULT_LOCALE;
|
|
59
|
+
setLocaleState(next);
|
|
60
|
+
applyDocumentLanguage(next);
|
|
61
|
+
try {
|
|
62
|
+
window.localStorage.setItem(storageKey, next);
|
|
63
|
+
} catch {
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[storageKey]
|
|
67
|
+
);
|
|
68
|
+
return { locale, setLocale, resolved };
|
|
69
|
+
}
|
|
70
|
+
function createI18n(dictionaries, defaults = {}) {
|
|
71
|
+
const fallback = {
|
|
72
|
+
locale: import_locale.DEFAULT_LOCALE,
|
|
73
|
+
setLocale: () => {
|
|
74
|
+
},
|
|
75
|
+
resolved: false,
|
|
76
|
+
t: dictionaries[import_locale.DEFAULT_LOCALE],
|
|
77
|
+
format: import_locale.formatLabel
|
|
78
|
+
};
|
|
79
|
+
const Context = (0, import_react.createContext)(fallback);
|
|
80
|
+
function I18nProvider({ children, initialLocale, storageKey }) {
|
|
81
|
+
const state = useLocaleState({
|
|
82
|
+
initialLocale: initialLocale ?? defaults.initialLocale,
|
|
83
|
+
storageKey: storageKey ?? defaults.storageKey
|
|
84
|
+
});
|
|
85
|
+
const { locale, setLocale, resolved } = state;
|
|
86
|
+
const value = (0, import_react.useMemo)(
|
|
87
|
+
() => ({
|
|
88
|
+
locale,
|
|
89
|
+
setLocale,
|
|
90
|
+
resolved,
|
|
91
|
+
t: dictionaries[locale] ?? dictionaries[import_locale.DEFAULT_LOCALE],
|
|
92
|
+
format: import_locale.formatLabel
|
|
93
|
+
}),
|
|
94
|
+
[locale, setLocale, resolved]
|
|
95
|
+
);
|
|
96
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Context.Provider, { value, children });
|
|
97
|
+
}
|
|
98
|
+
function useI18n() {
|
|
99
|
+
return (0, import_react.useContext)(Context);
|
|
100
|
+
}
|
|
101
|
+
return { I18nProvider, useI18n, dictionaries };
|
|
102
|
+
}
|
|
103
|
+
function LocaleVariant({
|
|
104
|
+
locale,
|
|
105
|
+
variants
|
|
106
|
+
}) {
|
|
107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: variants[locale] ?? variants[import_locale.DEFAULT_LOCALE] });
|
|
108
|
+
}
|
|
109
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
110
|
+
0 && (module.exports = {
|
|
111
|
+
LocaleVariant,
|
|
112
|
+
createI18n,
|
|
113
|
+
useLocaleState
|
|
114
|
+
});
|
package/dist/i18n.d.cts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { TuwaLocale, formatLabel } from './locale.cjs';
|
|
4
|
+
|
|
5
|
+
/** State and setter returned by {@link useLocaleState}. */
|
|
6
|
+
interface LocaleState {
|
|
7
|
+
/** The active locale: {@link DEFAULT_LOCALE} on the server and in the first client render. */
|
|
8
|
+
locale: TuwaLocale;
|
|
9
|
+
/** Switches the locale, saves it under the storage key and updates `<html lang>`. Unknown values fall back. */
|
|
10
|
+
setLocale: (next: TuwaLocale | string) => void;
|
|
11
|
+
/** Whether the locale of the browser has been read (`false` during SSR and the first client render). */
|
|
12
|
+
resolved: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Options of {@link useLocaleState} and {@link createI18n}. */
|
|
15
|
+
interface LocaleStateOptions {
|
|
16
|
+
/** Fixed locale (tests, a page rendered in one language): detection is skipped. */
|
|
17
|
+
initialLocale?: TuwaLocale;
|
|
18
|
+
/** Local Storage key of the picked locale (default: {@link LOCALE_STORAGE_KEY}). */
|
|
19
|
+
storageKey?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Locale state without dictionaries: renders {@link DEFAULT_LOCALE} first, so the server HTML and the first client
|
|
23
|
+
* render match, then reads the locale of the browser after mount ({@link resolveClientLocale}). Use it directly when an
|
|
24
|
+
* app keeps its texts elsewhere; {@link createI18n} builds on it.
|
|
25
|
+
*
|
|
26
|
+
* @param options - See {@link LocaleStateOptions}.
|
|
27
|
+
* @returns The locale, its setter and whether detection ran.
|
|
28
|
+
*/
|
|
29
|
+
declare function useLocaleState(options?: LocaleStateOptions): LocaleState;
|
|
30
|
+
/** Value of the context created by {@link createI18n}. */
|
|
31
|
+
interface I18nContextValue<Labels> extends LocaleState {
|
|
32
|
+
/** Texts of the active locale. */
|
|
33
|
+
t: Labels;
|
|
34
|
+
/** {@link formatLabel}, for `{name}` tokens in the texts. */
|
|
35
|
+
format: typeof formatLabel;
|
|
36
|
+
}
|
|
37
|
+
/** Props of the provider created by {@link createI18n}. */
|
|
38
|
+
interface I18nProviderProps extends LocaleStateOptions {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/** What {@link createI18n} returns. */
|
|
42
|
+
interface I18nKit<Labels> {
|
|
43
|
+
/** Provides the locale and the texts to the tree. */
|
|
44
|
+
I18nProvider: ComponentType<I18nProviderProps>;
|
|
45
|
+
/** Reads the locale, its setter and the texts; outside the provider it returns the English texts and a no-op. */
|
|
46
|
+
useI18n: () => I18nContextValue<Labels>;
|
|
47
|
+
/** The dictionaries, by locale. */
|
|
48
|
+
dictionaries: Readonly<Record<TuwaLocale, Labels>>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a typed i18n kit for one app: a provider and a hook over its own dictionaries. Each TUWA site keeps its texts
|
|
52
|
+
* (and their schema) in the app, and shares detection, persistence and switching through this package.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* export const { I18nProvider, useI18n } = createI18n<SiteLabels>({ en, uk, 'zh-CN': zhCN });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param dictionaries - The texts of every {@link TuwaLocale}; the English ones are the fallback.
|
|
60
|
+
* @param defaults - Options applied to every provider of the kit.
|
|
61
|
+
* @returns The provider, the hook and the dictionaries.
|
|
62
|
+
*/
|
|
63
|
+
declare function createI18n<Labels>(dictionaries: Readonly<Record<TuwaLocale, Labels>>, defaults?: LocaleStateOptions): I18nKit<Labels>;
|
|
64
|
+
/**
|
|
65
|
+
* Renders the child of the active locale: content prepared for every locale on the server (translated MDX pages),
|
|
66
|
+
* switched on the client without a request. The server HTML holds the {@link DEFAULT_LOCALE} child, so crawlers and
|
|
67
|
+
* the search index read English.
|
|
68
|
+
*
|
|
69
|
+
* @param props.locale - The active locale.
|
|
70
|
+
* @param props.variants - Content by locale; a missing locale shows the default.
|
|
71
|
+
* @returns The content of the locale.
|
|
72
|
+
*/
|
|
73
|
+
declare function LocaleVariant({ locale, variants, }: {
|
|
74
|
+
locale: TuwaLocale;
|
|
75
|
+
variants: Partial<Record<TuwaLocale, ReactNode>> & {
|
|
76
|
+
en: ReactNode;
|
|
77
|
+
};
|
|
78
|
+
}): react.JSX.Element;
|
|
79
|
+
|
|
80
|
+
export { LocaleVariant, createI18n, useLocaleState };
|
|
81
|
+
export type { I18nContextValue, I18nKit, I18nProviderProps, LocaleState, LocaleStateOptions };
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import { TuwaLocale, formatLabel } from './locale.js';
|
|
4
|
+
|
|
5
|
+
/** State and setter returned by {@link useLocaleState}. */
|
|
6
|
+
interface LocaleState {
|
|
7
|
+
/** The active locale: {@link DEFAULT_LOCALE} on the server and in the first client render. */
|
|
8
|
+
locale: TuwaLocale;
|
|
9
|
+
/** Switches the locale, saves it under the storage key and updates `<html lang>`. Unknown values fall back. */
|
|
10
|
+
setLocale: (next: TuwaLocale | string) => void;
|
|
11
|
+
/** Whether the locale of the browser has been read (`false` during SSR and the first client render). */
|
|
12
|
+
resolved: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Options of {@link useLocaleState} and {@link createI18n}. */
|
|
15
|
+
interface LocaleStateOptions {
|
|
16
|
+
/** Fixed locale (tests, a page rendered in one language): detection is skipped. */
|
|
17
|
+
initialLocale?: TuwaLocale;
|
|
18
|
+
/** Local Storage key of the picked locale (default: {@link LOCALE_STORAGE_KEY}). */
|
|
19
|
+
storageKey?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Locale state without dictionaries: renders {@link DEFAULT_LOCALE} first, so the server HTML and the first client
|
|
23
|
+
* render match, then reads the locale of the browser after mount ({@link resolveClientLocale}). Use it directly when an
|
|
24
|
+
* app keeps its texts elsewhere; {@link createI18n} builds on it.
|
|
25
|
+
*
|
|
26
|
+
* @param options - See {@link LocaleStateOptions}.
|
|
27
|
+
* @returns The locale, its setter and whether detection ran.
|
|
28
|
+
*/
|
|
29
|
+
declare function useLocaleState(options?: LocaleStateOptions): LocaleState;
|
|
30
|
+
/** Value of the context created by {@link createI18n}. */
|
|
31
|
+
interface I18nContextValue<Labels> extends LocaleState {
|
|
32
|
+
/** Texts of the active locale. */
|
|
33
|
+
t: Labels;
|
|
34
|
+
/** {@link formatLabel}, for `{name}` tokens in the texts. */
|
|
35
|
+
format: typeof formatLabel;
|
|
36
|
+
}
|
|
37
|
+
/** Props of the provider created by {@link createI18n}. */
|
|
38
|
+
interface I18nProviderProps extends LocaleStateOptions {
|
|
39
|
+
children: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/** What {@link createI18n} returns. */
|
|
42
|
+
interface I18nKit<Labels> {
|
|
43
|
+
/** Provides the locale and the texts to the tree. */
|
|
44
|
+
I18nProvider: ComponentType<I18nProviderProps>;
|
|
45
|
+
/** Reads the locale, its setter and the texts; outside the provider it returns the English texts and a no-op. */
|
|
46
|
+
useI18n: () => I18nContextValue<Labels>;
|
|
47
|
+
/** The dictionaries, by locale. */
|
|
48
|
+
dictionaries: Readonly<Record<TuwaLocale, Labels>>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a typed i18n kit for one app: a provider and a hook over its own dictionaries. Each TUWA site keeps its texts
|
|
52
|
+
* (and their schema) in the app, and shares detection, persistence and switching through this package.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```tsx
|
|
56
|
+
* export const { I18nProvider, useI18n } = createI18n<SiteLabels>({ en, uk, 'zh-CN': zhCN });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param dictionaries - The texts of every {@link TuwaLocale}; the English ones are the fallback.
|
|
60
|
+
* @param defaults - Options applied to every provider of the kit.
|
|
61
|
+
* @returns The provider, the hook and the dictionaries.
|
|
62
|
+
*/
|
|
63
|
+
declare function createI18n<Labels>(dictionaries: Readonly<Record<TuwaLocale, Labels>>, defaults?: LocaleStateOptions): I18nKit<Labels>;
|
|
64
|
+
/**
|
|
65
|
+
* Renders the child of the active locale: content prepared for every locale on the server (translated MDX pages),
|
|
66
|
+
* switched on the client without a request. The server HTML holds the {@link DEFAULT_LOCALE} child, so crawlers and
|
|
67
|
+
* the search index read English.
|
|
68
|
+
*
|
|
69
|
+
* @param props.locale - The active locale.
|
|
70
|
+
* @param props.variants - Content by locale; a missing locale shows the default.
|
|
71
|
+
* @returns The content of the locale.
|
|
72
|
+
*/
|
|
73
|
+
declare function LocaleVariant({ locale, variants, }: {
|
|
74
|
+
locale: TuwaLocale;
|
|
75
|
+
variants: Partial<Record<TuwaLocale, ReactNode>> & {
|
|
76
|
+
en: ReactNode;
|
|
77
|
+
};
|
|
78
|
+
}): react.JSX.Element;
|
|
79
|
+
|
|
80
|
+
export { LocaleVariant, createI18n, useLocaleState };
|
|
81
|
+
export type { I18nContextValue, I18nKit, I18nProviderProps, LocaleState, LocaleStateOptions };
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState
|
|
10
|
+
} from "react";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_LOCALE,
|
|
13
|
+
formatLabel,
|
|
14
|
+
LOCALE_STORAGE_KEY,
|
|
15
|
+
normalizeLocale,
|
|
16
|
+
resolveClientLocale
|
|
17
|
+
} from "./locale";
|
|
18
|
+
function applyDocumentLanguage(locale) {
|
|
19
|
+
if (typeof document !== "undefined") document.documentElement.lang = locale;
|
|
20
|
+
}
|
|
21
|
+
function useLocaleState(options = {}) {
|
|
22
|
+
const { initialLocale, storageKey = LOCALE_STORAGE_KEY } = options;
|
|
23
|
+
const [locale, setLocaleState] = useState(initialLocale ?? DEFAULT_LOCALE);
|
|
24
|
+
const [resolved, setResolved] = useState(Boolean(initialLocale));
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const next = initialLocale ?? resolveClientLocale(storageKey);
|
|
27
|
+
setLocaleState(next);
|
|
28
|
+
setResolved(true);
|
|
29
|
+
applyDocumentLanguage(next);
|
|
30
|
+
}, [initialLocale, storageKey]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (initialLocale) return;
|
|
33
|
+
const onStorage = (event) => {
|
|
34
|
+
if (event.key !== storageKey) return;
|
|
35
|
+
const next = normalizeLocale(event.newValue);
|
|
36
|
+
if (next) {
|
|
37
|
+
setLocaleState(next);
|
|
38
|
+
applyDocumentLanguage(next);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
window.addEventListener("storage", onStorage);
|
|
42
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
43
|
+
}, [initialLocale, storageKey]);
|
|
44
|
+
const setLocale = useCallback(
|
|
45
|
+
(target) => {
|
|
46
|
+
const next = normalizeLocale(target) ?? DEFAULT_LOCALE;
|
|
47
|
+
setLocaleState(next);
|
|
48
|
+
applyDocumentLanguage(next);
|
|
49
|
+
try {
|
|
50
|
+
window.localStorage.setItem(storageKey, next);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
[storageKey]
|
|
55
|
+
);
|
|
56
|
+
return { locale, setLocale, resolved };
|
|
57
|
+
}
|
|
58
|
+
function createI18n(dictionaries, defaults = {}) {
|
|
59
|
+
const fallback = {
|
|
60
|
+
locale: DEFAULT_LOCALE,
|
|
61
|
+
setLocale: () => {
|
|
62
|
+
},
|
|
63
|
+
resolved: false,
|
|
64
|
+
t: dictionaries[DEFAULT_LOCALE],
|
|
65
|
+
format: formatLabel
|
|
66
|
+
};
|
|
67
|
+
const Context = createContext(fallback);
|
|
68
|
+
function I18nProvider({ children, initialLocale, storageKey }) {
|
|
69
|
+
const state = useLocaleState({
|
|
70
|
+
initialLocale: initialLocale ?? defaults.initialLocale,
|
|
71
|
+
storageKey: storageKey ?? defaults.storageKey
|
|
72
|
+
});
|
|
73
|
+
const { locale, setLocale, resolved } = state;
|
|
74
|
+
const value = useMemo(
|
|
75
|
+
() => ({
|
|
76
|
+
locale,
|
|
77
|
+
setLocale,
|
|
78
|
+
resolved,
|
|
79
|
+
t: dictionaries[locale] ?? dictionaries[DEFAULT_LOCALE],
|
|
80
|
+
format: formatLabel
|
|
81
|
+
}),
|
|
82
|
+
[locale, setLocale, resolved]
|
|
83
|
+
);
|
|
84
|
+
return /* @__PURE__ */ jsx(Context.Provider, { value, children });
|
|
85
|
+
}
|
|
86
|
+
function useI18n() {
|
|
87
|
+
return useContext(Context);
|
|
88
|
+
}
|
|
89
|
+
return { I18nProvider, useI18n, dictionaries };
|
|
90
|
+
}
|
|
91
|
+
function LocaleVariant({
|
|
92
|
+
locale,
|
|
93
|
+
variants
|
|
94
|
+
}) {
|
|
95
|
+
return /* @__PURE__ */ jsx(Fragment, { children: variants[locale] ?? variants[DEFAULT_LOCALE] });
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
LocaleVariant,
|
|
99
|
+
createI18n,
|
|
100
|
+
useLocaleState
|
|
101
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,8 @@ __reExport(index_exports, require("./components/Orb"), module.exports);
|
|
|
42
42
|
__reExport(index_exports, require("./components/PolicyDocument"), module.exports);
|
|
43
43
|
var import_StarryBackground = __toESM(require("./components/StarryBackground"), 1);
|
|
44
44
|
__reExport(index_exports, require("./components/ThemeSwitcher"), module.exports);
|
|
45
|
+
__reExport(index_exports, require("./i18n"), module.exports);
|
|
46
|
+
__reExport(index_exports, require("./locale"), module.exports);
|
|
45
47
|
__reExport(index_exports, require("./policies"), module.exports);
|
|
46
48
|
__reExport(index_exports, require("./preferences"), module.exports);
|
|
47
49
|
__reExport(index_exports, require("./utils"), module.exports);
|
|
@@ -56,6 +58,8 @@ __reExport(index_exports, require("./utils"), module.exports);
|
|
|
56
58
|
...require("./components/Orb"),
|
|
57
59
|
...require("./components/PolicyDocument"),
|
|
58
60
|
...require("./components/ThemeSwitcher"),
|
|
61
|
+
...require("./i18n"),
|
|
62
|
+
...require("./locale"),
|
|
59
63
|
...require("./policies"),
|
|
60
64
|
...require("./preferences"),
|
|
61
65
|
...require("./utils")
|