@tuwaio/docs-ui 0.5.7 → 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/README.md +91 -3
- package/bin/i18n-check.js +19 -0
- package/dist/components/DocsLayout.cjs +82 -0
- package/dist/components/DocsLayout.d.cts +50 -0
- package/dist/components/DocsLayout.d.ts +50 -0
- package/dist/components/DocsLayout.js +58 -0
- package/dist/components/LocalizedPage.cjs +42 -0
- package/dist/components/LocalizedPage.d.cts +37 -0
- package/dist/components/LocalizedPage.d.ts +37 -0
- package/dist/components/LocalizedPage.js +18 -0
- package/dist/docsI18n.cjs +33 -0
- package/dist/docsI18n.d.cts +16 -0
- package/dist/docsI18n.d.ts +16 -0
- package/dist/docsI18n.js +8 -0
- package/dist/docsLabels.cjs +140 -0
- package/dist/docsLabels.d.cts +72 -0
- package/dist/docsLabels.d.ts +72 -0
- package/dist/docsLabels.js +116 -0
- package/dist/docsPage.cjs +70 -0
- package/dist/docsPage.d.cts +56 -0
- package/dist/docsPage.d.ts +56 -0
- package/dist/docsPage.js +46 -0
- package/dist/i18n.d.cts +1 -1
- package/dist/i18n.d.ts +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.css +3 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/nextra.cjs +26 -0
- package/dist/nextra.d.cts +10 -0
- package/dist/nextra.d.ts +10 -0
- package/dist/nextra.js +3 -0
- package/dist/pageMap.cjs +55 -0
- package/dist/pageMap.d.cts +20 -0
- package/dist/pageMap.d.ts +20 -0
- package/dist/pageMap.js +31 -0
- package/dist/translationCheck.cjs +186 -0
- package/dist/translationCheck.d.cts +89 -0
- package/dist/translationCheck.d.ts +89 -0
- package/dist/translationCheck.js +157 -0
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- **Logo:** `RemoteLogo` is an async React Server Component that fetches an SVG (by default the TUWA logo) and renders it as React elements, so it takes `className` and `currentColor`.
|
|
14
14
|
- **Theme switch:** `ThemeSwitcher` is a light/dark switch for any theme state (for example `next-themes`).
|
|
15
15
|
- **Language select:** `LocaleSwitcher` is an accessible glassmorphic select dropdown for switching languages (English, Українська, 简体中文, etc.), styled to match `ThemeSwitcher`. Features zero layout shift, Nova UI Kit animated directional chevron, collision detection with auto-flip (`placement="auto"`), multi-layout Latin typeahead navigation (independent of user OS keyboard language), and an invisible native `<select>` overlay on touch devices (`pointer: coarse`) for native mobile system pickers (iOS wheel, Android sheet). Full WCAG 2.1/2.2 AA/AAA compliance. It renders an invisible placeholder during SSR to avoid hydration shifts.
|
|
16
|
+
- **Three languages (project sites):** `@tuwaio/docs-ui/nextra` and `@tuwaio/docs-ui/nextra/page` turn a Nextra site into an English, Ukrainian and Simplified Chinese site with one URL per page, as on the [docs hub](https://docs.tuwa.io). `DocsLayout` is the Nextra layout with the navbar (the language switch after the links; the theme switch stays in the Nextra sidebar and the mobile menu), the footer, the search, the table of contents, the edit, feedback and last-updated texts and the sidebar titles in the language of the reader (`docsLabels`, `localizePageMap`). `createDocsPage` creates the `[[...mdxPath]]` route: each page is prerendered in English and in the languages it is translated into, and `LocalizedPage` shows the reader's language without a request; pages without a translation (package READMEs, the generated API reference) show English with a note in the reader's language. The language is detected after mount (`tuwa-locale`, then the browser languages and the time zone; no cookie, no request). The `tuwa-docs-i18n-check` command keeps the translations in line with their English pages.
|
|
16
17
|
- **Interface settings for 30 days:** `PreferenceExpiry` is an inline script for the `<head>` of a site's root layout. It forgets the Local Storage values of the given keys (by default `theme`) 30 days after they were written, as the [Cookie Policy](https://tuwa.io/cookie) states, and dates each later write, whatever library writes it (`next-themes`, the `Tabs` of Nextra).
|
|
17
18
|
- **Decoration:** `Orb` (an animated sphere in the brand color of a TUWA project), `StarryBackground` (an animated star field on a canvas) and `NoSSR` (renders its children only on the client).
|
|
18
19
|
- **Policy pages:** `policies` lists the TUWA policies (Terms, Privacy, Cookie, Refund, Security, Data Processing Addendum) with their paths and whether they are in force (`publishedPolicies`; footers use `footerPolicies`). `loadPolicy` loads the text of a policy in a language, and `PolicyDocument` renders it with the date of the version, a Download PDF button, a language switch (English, Українська) when the policy is translated, anchored sections (the same anchors in every language) and a list of sections. The texts and PDFs come from [`TuwaIO/workflows/docs`](https://github.com/TuwaIO/workflows/tree/main/docs). Code that needs only the list or `loadPolicy` (footers, sitemaps, route handlers) imports them from `@tuwaio/docs-ui/policies`, which has no React, Next.js or Nextra imports.
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
pnpm add @tuwaio/docs-ui next nextra nextra-theme-docs react react-dom @heroicons/react
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
`nextra-theme-docs`
|
|
31
|
+
`nextra-theme-docs` is needed by `Navbar`, `Footer` and `DocsLayout`, `nextra` by `DocsLayout`, `LocalizedPage` and `createDocsPage`, `next` by `RemoteLogo` (a Server Component that uses the `fetch` cache of Next.js) and `createDocsPage`, and `@heroicons/react` by `ThemeSwitcher`.
|
|
31
32
|
|
|
32
33
|
Import the stylesheet after the Nextra theme:
|
|
33
34
|
|
|
@@ -122,6 +123,93 @@ export function PolicyLinks() {
|
|
|
122
123
|
|
|
123
124
|
Sections have stable anchors (`/refund#how-to-request-a-refund`), so hints and FAQ answers can link to a section.
|
|
124
125
|
|
|
126
|
+
### Languages of a project site
|
|
127
|
+
|
|
128
|
+
A TUWA project site (Orbit, SIWX, Satellite Connect, Pulsar, SDK) in English, Ukrainian and Simplified Chinese. The root layout renders `DocsLayout` around the page and keeps the language for 30 days:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { PreferenceExpiry, SiteLogo } from '@tuwaio/docs-ui';
|
|
132
|
+
import { DocsLayout } from '@tuwaio/docs-ui/nextra';
|
|
133
|
+
import { Head } from 'nextra/components';
|
|
134
|
+
import { getPageMap } from 'nextra/page-map';
|
|
135
|
+
import type { ReactNode } from 'react';
|
|
136
|
+
|
|
137
|
+
const logo = <SiteLogo width={126} height={40} />;
|
|
138
|
+
|
|
139
|
+
export default async function RootLayout({ children }: { children: ReactNode }) {
|
|
140
|
+
return (
|
|
141
|
+
<html lang="en" dir="ltr" suppressHydrationWarning>
|
|
142
|
+
<Head>
|
|
143
|
+
<PreferenceExpiry keys={['theme', 'tuwa-locale']} />
|
|
144
|
+
</Head>
|
|
145
|
+
<body>
|
|
146
|
+
<DocsLayout
|
|
147
|
+
logo={logo}
|
|
148
|
+
pageMap={await getPageMap()}
|
|
149
|
+
docsRepositoryBase="https://github.com/TuwaIO/pulsar-core/tree/main/apps/docs"
|
|
150
|
+
sidebarTitles={{ uk: { 'EVM Trackers Standalone': 'EVM-трекери окремо' } }}
|
|
151
|
+
>
|
|
152
|
+
{children}
|
|
153
|
+
</DocsLayout>
|
|
154
|
+
</body>
|
|
155
|
+
</html>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`src/app/[[...mdxPath]]/page.tsx` renders every page of `src/content` with its translations:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import { createDocsPage } from '@tuwaio/docs-ui/nextra/page';
|
|
164
|
+
|
|
165
|
+
import { localizedPages } from '@/i18n/pages';
|
|
166
|
+
|
|
167
|
+
const { generateStaticParams, generateMetadata, Page } = createDocsPage({ localizedPages });
|
|
168
|
+
|
|
169
|
+
export { generateMetadata, generateStaticParams };
|
|
170
|
+
export default Page;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The translations are MDX files with the path of their English page under `src/i18n/<language>` (`src/i18n/uk/index.mdx` translates `src/content/index.mdx`), listed by route in `src/i18n/pages.ts`:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
import type { LocalizedPageLoaders } from '@tuwaio/docs-ui/nextra/page';
|
|
177
|
+
|
|
178
|
+
export const localizedPages: Record<string, LocalizedPageLoaders> = {
|
|
179
|
+
'/': {
|
|
180
|
+
uk: () => import('./uk/index.mdx'),
|
|
181
|
+
'zh-CN': () => import('./zh-CN/index.mdx'),
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
TypeScript types the imported MDX files with a declaration next to it (`src/i18n/mdx.d.ts`):
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
declare module '*.mdx' {
|
|
190
|
+
import type { EvaluateResult } from 'nextra';
|
|
191
|
+
|
|
192
|
+
const MDXContent: EvaluateResult['default'];
|
|
193
|
+
export default MDXContent;
|
|
194
|
+
export const toc: EvaluateResult['toc'];
|
|
195
|
+
export const metadata: EvaluateResult['metadata'];
|
|
196
|
+
export const sourceCode: EvaluateResult['sourceCode'];
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
A translation keeps the structure of its English page: the same code blocks in the same order, the same heading levels with the English heading ids written as `## Встановлення [#-installation]` (so anchors work in every language) and the same links. `tuwa-docs-i18n-check`, run from the docs app, checks it and that every translated page exists in every language; add it to the build and to the pre-commit hook:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"scripts": {
|
|
205
|
+
"build": "tuwa-docs-i18n-check && next build && pnpm postbuild",
|
|
206
|
+
"i18n:check": "tuwa-docs-i18n-check"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Client components inside `DocsLayout` read the language and the interface texts with `useDocsI18n()`.
|
|
212
|
+
|
|
125
213
|
### Search index
|
|
126
214
|
|
|
127
215
|
Run the command after `next build`. It uses the `pagefind` package of the site, so add it to the site's dependencies:
|
|
@@ -144,11 +232,11 @@ It writes the index to `public/_pagefind` (keep that directory in `.gitignore`)
|
|
|
144
232
|
|
|
145
233
|
## 🗄️ Browser Storage
|
|
146
234
|
|
|
147
|
-
`LocaleSwitcher` and `ThemeSwitcher` store nothing; they only call `onSelect` / `onToggle`, and the site keeps the choice. `PreferenceExpiry` stores `<key>:expires` (the time in epoch milliseconds) next to each key it is given, removes both once that time has passed, and wraps `Storage.prototype.setItem` on the page to date later writes of those keys.
|
|
235
|
+
`LocaleSwitcher` and `ThemeSwitcher` store nothing; they only call `onSelect` / `onToggle`, and the site keeps the choice. `DocsLayout`, `useLocaleState` and the providers of `createI18n` save the language the reader picks to `tuwa-locale` (`en`, `uk` or `zh-CN`) and read it on the next visit; list the key in `PreferenceExpiry` to forget it after 30 days. `PreferenceExpiry` stores `<key>:expires` (the time in epoch milliseconds) next to each key it is given, removes both once that time has passed, and wraps `Storage.prototype.setItem` on the page to date later writes of those keys.
|
|
148
236
|
|
|
149
237
|
## 🌐 External Services
|
|
150
238
|
|
|
151
|
-
`RemoteLogo` requests its `url` on the server (by default `tuwa.io/brand/logo_v2.svg`), cached by Next.js for 24 hours. `loadPolicy` requests `raw.githubusercontent.com/TuwaIO/workflows/main/docs/<slug>.json` (a translation: `docs/<language>/<slug>.json`, and the English file when it does not exist) on the server, cached by Next.js for an hour by default. The Download PDF button of `PolicyDocument` links to the PDF on the same host; the browser requests it only when it is clicked. The other components send no requests.
|
|
239
|
+
`RemoteLogo` requests its `url` on the server (by default `tuwa.io/brand/logo_v2.svg`), cached by Next.js for 24 hours. `loadPolicy` requests `raw.githubusercontent.com/TuwaIO/workflows/main/docs/<slug>.json` (a translation: `docs/<language>/<slug>.json`, and the English file when it does not exist) on the server, cached by Next.js for an hour by default. The Download PDF button of `PolicyDocument` links to the PDF on the same host; the browser requests it only when it is clicked. `createDocsPage` and `LocalizedPage` send no requests: the translations are part of the page. The other components send no requests.
|
|
152
240
|
|
|
153
241
|
---
|
|
154
242
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `tuwa-docs-i18n-check`: checks the translated pages of a TUWA documentation site (src/i18n/<locale>) against their
|
|
3
|
+
// English pages (src/content), run from the docs app (see README)
|
|
4
|
+
import { checkSiteTranslations } from '../dist/translationCheck.js';
|
|
5
|
+
|
|
6
|
+
checkSiteTranslations().then(
|
|
7
|
+
({ files, issues }) => {
|
|
8
|
+
for (const { file, message } of issues) console.error(`${file}: ${message}`);
|
|
9
|
+
if (issues.length > 0) {
|
|
10
|
+
console.error(`Translations: ${issues.length} problem(s) in ${files} translated page(s)`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
console.log(`Translations: ${files} translated page(s) match their English pages`);
|
|
14
|
+
},
|
|
15
|
+
(error) => {
|
|
16
|
+
console.error(error instanceof Error ? error.message : error);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
},
|
|
19
|
+
);
|
|
@@ -0,0 +1,82 @@
|
|
|
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 DocsLayout_exports = {};
|
|
21
|
+
__export(DocsLayout_exports, {
|
|
22
|
+
DocsLayout: () => DocsLayout
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(DocsLayout_exports);
|
|
25
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
26
|
+
var import_components = require("nextra/components");
|
|
27
|
+
var import_nextra_theme_docs = require("nextra-theme-docs");
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
var import_docsI18n = require("../docsI18n");
|
|
30
|
+
var import_locale = require("../locale");
|
|
31
|
+
var import_pageMap = require("../pageMap");
|
|
32
|
+
var import_Footer = require("./Footer");
|
|
33
|
+
var import_LocaleSwitcher = require("./LocaleSwitcher");
|
|
34
|
+
var import_NavBar = require("./NavBar");
|
|
35
|
+
function LocalizedLayout({
|
|
36
|
+
logo,
|
|
37
|
+
links,
|
|
38
|
+
footerLinks,
|
|
39
|
+
pageMap,
|
|
40
|
+
docsRepositoryBase,
|
|
41
|
+
sidebarTitles,
|
|
42
|
+
children
|
|
43
|
+
}) {
|
|
44
|
+
const { locale, setLocale, t } = (0, import_docsI18n.useDocsI18n)();
|
|
45
|
+
const siteTitles = locale === "en" ? void 0 : sidebarTitles?.[locale];
|
|
46
|
+
const localizedPageMap = (0, import_react.useMemo)(
|
|
47
|
+
() => (0, import_pageMap.localizePageMap)(pageMap, { ...t.sidebar, ...siteTitles }),
|
|
48
|
+
[pageMap, t.sidebar, siteTitles]
|
|
49
|
+
);
|
|
50
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
51
|
+
import_nextra_theme_docs.Layout,
|
|
52
|
+
{
|
|
53
|
+
navbar: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_NavBar.Navbar, { logo, links, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_LocaleSwitcher.LocaleSwitcher, { locale, onSelect: setLocale, ariaLabel: t.selectLanguage }) }),
|
|
54
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Footer.Footer, { logo, links: footerLinks, labels: t.footer, policyLanguage: (0, import_locale.policyLanguageOf)(locale) }),
|
|
55
|
+
pageMap: localizedPageMap,
|
|
56
|
+
docsRepositoryBase,
|
|
57
|
+
navigation: { prev: true, next: true },
|
|
58
|
+
editLink: t.nextra.editThisPage,
|
|
59
|
+
feedback: { content: t.nextra.feedback },
|
|
60
|
+
lastUpdated: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_nextra_theme_docs.LastUpdated, { locale, children: t.nextra.lastUpdated }),
|
|
61
|
+
toc: { title: t.nextra.onThisPage, backToTop: t.nextra.scrollToTop },
|
|
62
|
+
themeSwitch: { light: t.nextra.themeLight, dark: t.nextra.themeDark, system: t.nextra.themeSystem },
|
|
63
|
+
search: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
64
|
+
import_components.Search,
|
|
65
|
+
{
|
|
66
|
+
placeholder: t.nextra.searchPlaceholder,
|
|
67
|
+
emptyResult: t.nextra.searchEmpty,
|
|
68
|
+
errorText: t.nextra.searchError,
|
|
69
|
+
loading: t.nextra.searchLoading
|
|
70
|
+
}
|
|
71
|
+
),
|
|
72
|
+
children
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
function DocsLayout({ initialLocale, ...props }) {
|
|
77
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_docsI18n.DocsI18nProvider, { initialLocale, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LocalizedLayout, { ...props }) });
|
|
78
|
+
}
|
|
79
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
80
|
+
0 && (module.exports = {
|
|
81
|
+
DocsLayout
|
|
82
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { PageMapItem } from 'nextra';
|
|
4
|
+
import { TuwaLocale } from '../locale.cjs';
|
|
5
|
+
import { SocialLink } from './NavBar.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props of {@link DocsLayout}.
|
|
9
|
+
*/
|
|
10
|
+
interface DocsLayoutProps {
|
|
11
|
+
/** Logo of the site, for example `SiteLogo`; rendered in the navbar and the footer. */
|
|
12
|
+
logo: ReactNode;
|
|
13
|
+
/** External links of the navbar (default: `baseNavLinks`). */
|
|
14
|
+
links?: SocialLink[];
|
|
15
|
+
/** External links of the footer (default: `baseFooterLinks`). */
|
|
16
|
+
footerLinks?: SocialLink[];
|
|
17
|
+
/** The page map of the site, the result of `getPageMap()` of `nextra/page-map`. */
|
|
18
|
+
pageMap: PageMapItem[];
|
|
19
|
+
/** URL of the docs app in the repository, for the edit links (`https://github.com/TuwaIO/<repo>/tree/main/apps/docs`). */
|
|
20
|
+
docsRepositoryBase: string;
|
|
21
|
+
/**
|
|
22
|
+
* Sidebar titles of the site's own pages, by language and English title (for example `{ uk: { 'EVM Trackers
|
|
23
|
+
* Standalone': '…' } }`). `Introduction`, `Packages`, `Guides` and `Overview` are translated already.
|
|
24
|
+
*/
|
|
25
|
+
sidebarTitles?: Partial<Record<Exclude<TuwaLocale, 'en'>, Record<string, string>>>;
|
|
26
|
+
/** Fixed language (tests, previews): detection and the stored choice are skipped. */
|
|
27
|
+
initialLocale?: TuwaLocale;
|
|
28
|
+
/** The page and anything rendered next to it, such as a top loading bar. */
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The layout of a TUWA project documentation site in three languages (English, Ukrainian, Simplified Chinese): the
|
|
33
|
+
* Nextra `Layout` with the docs-ui {@link Navbar} (the language switch after the links; the theme switch stays in the
|
|
34
|
+
* Nextra sidebar and the mobile menu), the {@link Footer}, the search, the table of contents, the edit, feedback and
|
|
35
|
+
* last-updated texts and the sidebar titles in the language of the reader. Render it in the `<body>` of the root
|
|
36
|
+
* layout, around the page; pages rendered with `createDocsPage` (`@tuwaio/docs-ui/nextra/page`) switch to their
|
|
37
|
+
* translation.
|
|
38
|
+
*
|
|
39
|
+
* The URL never changes with the language. The server renders English; after mount the language is read from
|
|
40
|
+
* `tuwa-locale` in Local Storage, then the browser languages and the time zone (no cookie, no request), and a pick in
|
|
41
|
+
* the switch is saved to `tuwa-locale` (add the key to `PreferenceExpiry`, so it is forgotten after 30 days) and sets
|
|
42
|
+
* `<html lang>`.
|
|
43
|
+
*
|
|
44
|
+
* @param props - See {@link DocsLayoutProps}.
|
|
45
|
+
* @returns The layout.
|
|
46
|
+
*/
|
|
47
|
+
declare function DocsLayout({ initialLocale, ...props }: DocsLayoutProps): react.JSX.Element;
|
|
48
|
+
|
|
49
|
+
export { DocsLayout };
|
|
50
|
+
export type { DocsLayoutProps };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { PageMapItem } from 'nextra';
|
|
4
|
+
import { TuwaLocale } from '../locale.js';
|
|
5
|
+
import { SocialLink } from './NavBar.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props of {@link DocsLayout}.
|
|
9
|
+
*/
|
|
10
|
+
interface DocsLayoutProps {
|
|
11
|
+
/** Logo of the site, for example `SiteLogo`; rendered in the navbar and the footer. */
|
|
12
|
+
logo: ReactNode;
|
|
13
|
+
/** External links of the navbar (default: `baseNavLinks`). */
|
|
14
|
+
links?: SocialLink[];
|
|
15
|
+
/** External links of the footer (default: `baseFooterLinks`). */
|
|
16
|
+
footerLinks?: SocialLink[];
|
|
17
|
+
/** The page map of the site, the result of `getPageMap()` of `nextra/page-map`. */
|
|
18
|
+
pageMap: PageMapItem[];
|
|
19
|
+
/** URL of the docs app in the repository, for the edit links (`https://github.com/TuwaIO/<repo>/tree/main/apps/docs`). */
|
|
20
|
+
docsRepositoryBase: string;
|
|
21
|
+
/**
|
|
22
|
+
* Sidebar titles of the site's own pages, by language and English title (for example `{ uk: { 'EVM Trackers
|
|
23
|
+
* Standalone': '…' } }`). `Introduction`, `Packages`, `Guides` and `Overview` are translated already.
|
|
24
|
+
*/
|
|
25
|
+
sidebarTitles?: Partial<Record<Exclude<TuwaLocale, 'en'>, Record<string, string>>>;
|
|
26
|
+
/** Fixed language (tests, previews): detection and the stored choice are skipped. */
|
|
27
|
+
initialLocale?: TuwaLocale;
|
|
28
|
+
/** The page and anything rendered next to it, such as a top loading bar. */
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The layout of a TUWA project documentation site in three languages (English, Ukrainian, Simplified Chinese): the
|
|
33
|
+
* Nextra `Layout` with the docs-ui {@link Navbar} (the language switch after the links; the theme switch stays in the
|
|
34
|
+
* Nextra sidebar and the mobile menu), the {@link Footer}, the search, the table of contents, the edit, feedback and
|
|
35
|
+
* last-updated texts and the sidebar titles in the language of the reader. Render it in the `<body>` of the root
|
|
36
|
+
* layout, around the page; pages rendered with `createDocsPage` (`@tuwaio/docs-ui/nextra/page`) switch to their
|
|
37
|
+
* translation.
|
|
38
|
+
*
|
|
39
|
+
* The URL never changes with the language. The server renders English; after mount the language is read from
|
|
40
|
+
* `tuwa-locale` in Local Storage, then the browser languages and the time zone (no cookie, no request), and a pick in
|
|
41
|
+
* the switch is saved to `tuwa-locale` (add the key to `PreferenceExpiry`, so it is forgotten after 30 days) and sets
|
|
42
|
+
* `<html lang>`.
|
|
43
|
+
*
|
|
44
|
+
* @param props - See {@link DocsLayoutProps}.
|
|
45
|
+
* @returns The layout.
|
|
46
|
+
*/
|
|
47
|
+
declare function DocsLayout({ initialLocale, ...props }: DocsLayoutProps): react.JSX.Element;
|
|
48
|
+
|
|
49
|
+
export { DocsLayout };
|
|
50
|
+
export type { DocsLayoutProps };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Search } from "nextra/components";
|
|
4
|
+
import { LastUpdated, Layout } from "nextra-theme-docs";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { DocsI18nProvider, useDocsI18n } from "../docsI18n";
|
|
7
|
+
import { policyLanguageOf } from "../locale";
|
|
8
|
+
import { localizePageMap } from "../pageMap";
|
|
9
|
+
import { Footer } from "./Footer";
|
|
10
|
+
import { LocaleSwitcher } from "./LocaleSwitcher";
|
|
11
|
+
import { Navbar } from "./NavBar";
|
|
12
|
+
function LocalizedLayout({
|
|
13
|
+
logo,
|
|
14
|
+
links,
|
|
15
|
+
footerLinks,
|
|
16
|
+
pageMap,
|
|
17
|
+
docsRepositoryBase,
|
|
18
|
+
sidebarTitles,
|
|
19
|
+
children
|
|
20
|
+
}) {
|
|
21
|
+
const { locale, setLocale, t } = useDocsI18n();
|
|
22
|
+
const siteTitles = locale === "en" ? void 0 : sidebarTitles?.[locale];
|
|
23
|
+
const localizedPageMap = useMemo(
|
|
24
|
+
() => localizePageMap(pageMap, { ...t.sidebar, ...siteTitles }),
|
|
25
|
+
[pageMap, t.sidebar, siteTitles]
|
|
26
|
+
);
|
|
27
|
+
return /* @__PURE__ */ jsx(
|
|
28
|
+
Layout,
|
|
29
|
+
{
|
|
30
|
+
navbar: /* @__PURE__ */ jsx(Navbar, { logo, links, children: /* @__PURE__ */ jsx(LocaleSwitcher, { locale, onSelect: setLocale, ariaLabel: t.selectLanguage }) }),
|
|
31
|
+
footer: /* @__PURE__ */ jsx(Footer, { logo, links: footerLinks, labels: t.footer, policyLanguage: policyLanguageOf(locale) }),
|
|
32
|
+
pageMap: localizedPageMap,
|
|
33
|
+
docsRepositoryBase,
|
|
34
|
+
navigation: { prev: true, next: true },
|
|
35
|
+
editLink: t.nextra.editThisPage,
|
|
36
|
+
feedback: { content: t.nextra.feedback },
|
|
37
|
+
lastUpdated: /* @__PURE__ */ jsx(LastUpdated, { locale, children: t.nextra.lastUpdated }),
|
|
38
|
+
toc: { title: t.nextra.onThisPage, backToTop: t.nextra.scrollToTop },
|
|
39
|
+
themeSwitch: { light: t.nextra.themeLight, dark: t.nextra.themeDark, system: t.nextra.themeSystem },
|
|
40
|
+
search: /* @__PURE__ */ jsx(
|
|
41
|
+
Search,
|
|
42
|
+
{
|
|
43
|
+
placeholder: t.nextra.searchPlaceholder,
|
|
44
|
+
emptyResult: t.nextra.searchEmpty,
|
|
45
|
+
errorText: t.nextra.searchError,
|
|
46
|
+
loading: t.nextra.searchLoading
|
|
47
|
+
}
|
|
48
|
+
),
|
|
49
|
+
children
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function DocsLayout({ initialLocale, ...props }) {
|
|
54
|
+
return /* @__PURE__ */ jsx(DocsI18nProvider, { initialLocale, children: /* @__PURE__ */ jsx(LocalizedLayout, { ...props }) });
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
DocsLayout
|
|
58
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
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 LocalizedPage_exports = {};
|
|
21
|
+
__export(LocalizedPage_exports, {
|
|
22
|
+
LocalizedPage: () => LocalizedPage
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(LocalizedPage_exports);
|
|
25
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
26
|
+
var import_components = require("nextra/components");
|
|
27
|
+
var import_nextra_theme_docs = require("nextra-theme-docs");
|
|
28
|
+
var import_docsI18n = require("../docsI18n");
|
|
29
|
+
function LocalizedPage({ variants }) {
|
|
30
|
+
const { locale, t } = (0, import_docsI18n.useDocsI18n)();
|
|
31
|
+
const Wrapper = (0, import_nextra_theme_docs.useMDXComponents)().wrapper;
|
|
32
|
+
const translated = variants[locale];
|
|
33
|
+
const page = translated ?? variants.en;
|
|
34
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrapper, { toc: page.toc, metadata: page.metadata, sourceCode: page.sourceCode, children: [
|
|
35
|
+
!translated && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "tuwadocs:clear-both", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_components.Callout, { type: "info", children: t.englishOnly }) }),
|
|
36
|
+
page.content
|
|
37
|
+
] });
|
|
38
|
+
}
|
|
39
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
40
|
+
0 && (module.exports = {
|
|
41
|
+
LocalizedPage
|
|
42
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { Heading, $NextraMetadata } from 'nextra';
|
|
4
|
+
import { TuwaLocale } from '../locale.cjs';
|
|
5
|
+
|
|
6
|
+
/** One language of a page: the rendered MDX and what the Nextra page wrapper needs. */
|
|
7
|
+
interface LocalizedPageVariant {
|
|
8
|
+
/** The rendered MDX content. */
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
/** The table of contents of the MDX file (its `toc` export). */
|
|
11
|
+
toc: Heading[];
|
|
12
|
+
/** The metadata of the MDX file (its `metadata` export): the title, the file of the edit link, the last change. */
|
|
13
|
+
metadata: $NextraMetadata;
|
|
14
|
+
/** The source of the MDX file (its `sourceCode` export), for the copy-page button. */
|
|
15
|
+
sourceCode?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Props of {@link LocalizedPage}. */
|
|
18
|
+
interface LocalizedPageProps {
|
|
19
|
+
/** The page in English (the server renders it) and in the languages it is translated into. */
|
|
20
|
+
variants: Partial<Record<TuwaLocale, LocalizedPageVariant>> & {
|
|
21
|
+
en: LocalizedPageVariant;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A page of a TUWA documentation site in the language of the reader, inside the Nextra page wrapper (sidebar, table of
|
|
26
|
+
* contents, breadcrumbs, pagination). All variants come prerendered from the server, so switching the language makes no
|
|
27
|
+
* request and the URL stays the same; the server HTML and the search index hold English. A page without a translation
|
|
28
|
+
* in the active language (package READMEs and the generated API reference) shows English with a short note in that
|
|
29
|
+
* language. Must be rendered inside {@link DocsLayout}; `createDocsPage` (`@tuwaio/docs-ui/nextra/page`) renders it.
|
|
30
|
+
*
|
|
31
|
+
* @param props - See {@link LocalizedPageProps}.
|
|
32
|
+
* @returns The page.
|
|
33
|
+
*/
|
|
34
|
+
declare function LocalizedPage({ variants }: LocalizedPageProps): react.JSX.Element;
|
|
35
|
+
|
|
36
|
+
export { LocalizedPage };
|
|
37
|
+
export type { LocalizedPageProps, LocalizedPageVariant };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { Heading, $NextraMetadata } from 'nextra';
|
|
4
|
+
import { TuwaLocale } from '../locale.js';
|
|
5
|
+
|
|
6
|
+
/** One language of a page: the rendered MDX and what the Nextra page wrapper needs. */
|
|
7
|
+
interface LocalizedPageVariant {
|
|
8
|
+
/** The rendered MDX content. */
|
|
9
|
+
content: ReactNode;
|
|
10
|
+
/** The table of contents of the MDX file (its `toc` export). */
|
|
11
|
+
toc: Heading[];
|
|
12
|
+
/** The metadata of the MDX file (its `metadata` export): the title, the file of the edit link, the last change. */
|
|
13
|
+
metadata: $NextraMetadata;
|
|
14
|
+
/** The source of the MDX file (its `sourceCode` export), for the copy-page button. */
|
|
15
|
+
sourceCode?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Props of {@link LocalizedPage}. */
|
|
18
|
+
interface LocalizedPageProps {
|
|
19
|
+
/** The page in English (the server renders it) and in the languages it is translated into. */
|
|
20
|
+
variants: Partial<Record<TuwaLocale, LocalizedPageVariant>> & {
|
|
21
|
+
en: LocalizedPageVariant;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A page of a TUWA documentation site in the language of the reader, inside the Nextra page wrapper (sidebar, table of
|
|
26
|
+
* contents, breadcrumbs, pagination). All variants come prerendered from the server, so switching the language makes no
|
|
27
|
+
* request and the URL stays the same; the server HTML and the search index hold English. A page without a translation
|
|
28
|
+
* in the active language (package READMEs and the generated API reference) shows English with a short note in that
|
|
29
|
+
* language. Must be rendered inside {@link DocsLayout}; `createDocsPage` (`@tuwaio/docs-ui/nextra/page`) renders it.
|
|
30
|
+
*
|
|
31
|
+
* @param props - See {@link LocalizedPageProps}.
|
|
32
|
+
* @returns The page.
|
|
33
|
+
*/
|
|
34
|
+
declare function LocalizedPage({ variants }: LocalizedPageProps): react.JSX.Element;
|
|
35
|
+
|
|
36
|
+
export { LocalizedPage };
|
|
37
|
+
export type { LocalizedPageProps, LocalizedPageVariant };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Callout } from "nextra/components";
|
|
4
|
+
import { useMDXComponents } from "nextra-theme-docs";
|
|
5
|
+
import { useDocsI18n } from "../docsI18n";
|
|
6
|
+
function LocalizedPage({ variants }) {
|
|
7
|
+
const { locale, t } = useDocsI18n();
|
|
8
|
+
const Wrapper = useMDXComponents().wrapper;
|
|
9
|
+
const translated = variants[locale];
|
|
10
|
+
const page = translated ?? variants.en;
|
|
11
|
+
return /* @__PURE__ */ jsxs(Wrapper, { toc: page.toc, metadata: page.metadata, sourceCode: page.sourceCode, children: [
|
|
12
|
+
!translated && /* @__PURE__ */ jsx("div", { className: "tuwadocs:clear-both", children: /* @__PURE__ */ jsx(Callout, { type: "info", children: t.englishOnly }) }),
|
|
13
|
+
page.content
|
|
14
|
+
] });
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
LocalizedPage
|
|
18
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
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 docsI18n_exports = {};
|
|
21
|
+
__export(docsI18n_exports, {
|
|
22
|
+
DocsI18nProvider: () => DocsI18nProvider,
|
|
23
|
+
useDocsI18n: () => useDocsI18n
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(docsI18n_exports);
|
|
26
|
+
var import_docsLabels = require("./docsLabels");
|
|
27
|
+
var import_i18n = require("./i18n");
|
|
28
|
+
const { I18nProvider: DocsI18nProvider, useI18n: useDocsI18n } = (0, import_i18n.createI18n)(import_docsLabels.docsLabels);
|
|
29
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
30
|
+
0 && (module.exports = {
|
|
31
|
+
DocsI18nProvider,
|
|
32
|
+
useDocsI18n
|
|
33
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DocsLabels } from './docsLabels.cjs';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { I18nProviderProps, I18nContextValue } from './i18n.cjs';
|
|
4
|
+
import './locale.cjs';
|
|
5
|
+
import './policies.cjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The i18n kit of the TUWA documentation sites, on {@link docsLabels}: `DocsI18nProvider` provides the language of the
|
|
9
|
+
* reader and the interface texts, `useDocsI18n` reads them (outside the provider: English and a no-op setter).
|
|
10
|
+
* `DocsLayout` renders the provider, so a site's client components inside the layout call `useDocsI18n` directly.
|
|
11
|
+
* The language is detected after mount and kept under `tuwa-locale` in Local Storage (list it in `PreferenceExpiry`).
|
|
12
|
+
*/
|
|
13
|
+
declare const DocsI18nProvider: react.ComponentType<I18nProviderProps>;
|
|
14
|
+
declare const useDocsI18n: () => I18nContextValue<DocsLabels>;
|
|
15
|
+
|
|
16
|
+
export { DocsI18nProvider, useDocsI18n };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DocsLabels } from './docsLabels.js';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { I18nProviderProps, I18nContextValue } from './i18n.js';
|
|
4
|
+
import './locale.js';
|
|
5
|
+
import './policies.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The i18n kit of the TUWA documentation sites, on {@link docsLabels}: `DocsI18nProvider` provides the language of the
|
|
9
|
+
* reader and the interface texts, `useDocsI18n` reads them (outside the provider: English and a no-op setter).
|
|
10
|
+
* `DocsLayout` renders the provider, so a site's client components inside the layout call `useDocsI18n` directly.
|
|
11
|
+
* The language is detected after mount and kept under `tuwa-locale` in Local Storage (list it in `PreferenceExpiry`).
|
|
12
|
+
*/
|
|
13
|
+
declare const DocsI18nProvider: react.ComponentType<I18nProviderProps>;
|
|
14
|
+
declare const useDocsI18n: () => I18nContextValue<DocsLabels>;
|
|
15
|
+
|
|
16
|
+
export { DocsI18nProvider, useDocsI18n };
|
package/dist/docsI18n.js
ADDED