@tuwaio/docs-ui 0.4.1 → 0.5.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 +6 -6
- package/dist/components/PolicyDocument.cjs +50 -18
- package/dist/components/PolicyDocument.d.cts +15 -13
- package/dist/components/PolicyDocument.d.ts +15 -13
- package/dist/components/PolicyDocument.js +56 -19
- package/dist/index.css +6 -0
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/policies.cjs +35 -6
- package/dist/policies.d.cts +53 -13
- package/dist/policies.d.ts +53 -13
- package/dist/policies.js +31 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- **Theme switch:** `ThemeSwitcher` is a light/dark switch for any theme state (for example `next-themes`).
|
|
15
15
|
- **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).
|
|
16
16
|
- **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).
|
|
17
|
-
- **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, and `PolicyDocument` renders it with the date of the version, a Download PDF button, anchored sections 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.
|
|
17
|
+
- **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.
|
|
18
18
|
- **Search index:** the `tuwa-docs-search-index` command builds the [Pagefind](https://pagefind.app) index that the Nextra search loads from `/_pagefind`, from the pages `next build` prerendered. It reads both places Next.js keeps them: `.next/server/app` and, in builds with a deployment adapter (Next.js 16.3.8 and later on Vercel), `.next/server/route-cache`, where `pagefind --site .next/server/app` finds no pages.
|
|
19
19
|
- **Stylesheet:** `dist/index.css` contains the `--tuwa-*` theme variables of [`@tuwaio/nova-core`](https://stories.tuwa.io/?path=/docs/packages-nova-core-overview--docs) (light and `.dark`), the styles of the Nextra parts and the Tailwind CSS v4 utilities used by the components, prefixed with `tuwadocs:`.
|
|
20
20
|
|
|
@@ -85,13 +85,13 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|
|
85
85
|
|
|
86
86
|
### Policy pages
|
|
87
87
|
|
|
88
|
-
A policy page in the Next.js App Router (a Server Component; the content is cached for an hour)
|
|
88
|
+
A policy page in the Next.js App Router (a Server Component; the content is cached for an hour). The language comes from `?lang=` (`en` by default, `uk` for Ukrainian); a policy without a translation loads in English, and the switch links each language with `?lang=<language>`:
|
|
89
89
|
|
|
90
90
|
```tsx
|
|
91
|
-
import { loadPolicy, PolicyDocument } from '@tuwaio/docs-ui';
|
|
91
|
+
import { loadPolicy, PolicyDocument, policyLanguageFrom } from '@tuwaio/docs-ui';
|
|
92
92
|
|
|
93
|
-
export default async function RefundPage() {
|
|
94
|
-
const content = await loadPolicy('refund');
|
|
93
|
+
export default async function RefundPage({ searchParams }: { searchParams: Promise<{ lang?: string | string[] }> }) {
|
|
94
|
+
const content = await loadPolicy('refund', { language: policyLanguageFrom((await searchParams).lang) });
|
|
95
95
|
|
|
96
96
|
return (
|
|
97
97
|
<main className="mx-auto max-w-6xl px-4 py-12">
|
|
@@ -147,7 +147,7 @@ It writes the index to `public/_pagefind` (keep that directory in `.gitignore`)
|
|
|
147
147
|
|
|
148
148
|
## 🌐 External Services
|
|
149
149
|
|
|
150
|
-
`RemoteLogo` requests its `url` on the server (by default `cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg`), cached by Next.js for 24 hours. `loadPolicy` requests `raw.githubusercontent.com/TuwaIO/workflows/main/docs/<slug>.json` 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.
|
|
150
|
+
`RemoteLogo` requests its `url` on the server (by default `cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/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.
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
@@ -73,13 +73,18 @@ function PolicyDocument({
|
|
|
73
73
|
content,
|
|
74
74
|
source = import_policies.POLICY_SOURCE,
|
|
75
75
|
labels,
|
|
76
|
+
languageHref = (language) => `?lang=${language}`,
|
|
76
77
|
className,
|
|
77
78
|
classNames
|
|
78
79
|
}) {
|
|
79
|
-
const
|
|
80
|
+
const language = content.language ?? "en";
|
|
81
|
+
const languages = content.languages ?? ["en"];
|
|
82
|
+
const texts = { ...import_policies.POLICY_DOCUMENT_LABELS[language], ...labels };
|
|
83
|
+
const tocLabel = texts.toc;
|
|
80
84
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
81
85
|
"div",
|
|
82
86
|
{
|
|
87
|
+
lang: language,
|
|
83
88
|
className: (0, import_utils.cn)(
|
|
84
89
|
"tuwadocs:grid tuwadocs:grid-cols-1 tuwadocs:gap-10 tuwadocs:lg:grid-cols-[minmax(0,1fr)_14rem]",
|
|
85
90
|
className
|
|
@@ -106,26 +111,53 @@ function PolicyDocument({
|
|
|
106
111
|
),
|
|
107
112
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-between tuwadocs:gap-3", children: [
|
|
108
113
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: (0, import_utils.cn)("tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)]", classNames?.updated), children: [
|
|
109
|
-
|
|
114
|
+
texts.updated,
|
|
110
115
|
" ",
|
|
111
116
|
content.updated
|
|
112
117
|
] }),
|
|
113
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:gap-2", children: [
|
|
119
|
+
languages.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
120
|
+
"nav",
|
|
121
|
+
{
|
|
122
|
+
"aria-label": texts.language,
|
|
123
|
+
className: (0, import_utils.cn)(
|
|
124
|
+
"tuwadocs:inline-flex tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:p-0.5",
|
|
125
|
+
classNames?.languages
|
|
126
|
+
),
|
|
127
|
+
children: languages.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
128
|
+
"a",
|
|
129
|
+
{
|
|
130
|
+
href: languageHref(item),
|
|
131
|
+
hrefLang: item,
|
|
132
|
+
lang: item,
|
|
133
|
+
"aria-current": item === language ? "page" : void 0,
|
|
134
|
+
className: (0, import_utils.cn)(
|
|
135
|
+
"tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:px-3 tuwadocs:py-1.5 tuwadocs:text-xs tuwadocs:font-bold tuwadocs:no-underline tuwadocs:transition-colors",
|
|
136
|
+
item === language ? "tuwadocs:bg-[var(--tuwa-bg-muted)] tuwadocs:text-[var(--tuwa-text-primary)]" : "tuwadocs:text-[var(--tuwa-text-secondary)] tuwadocs:hover:text-[var(--tuwa-text-primary)]"
|
|
137
|
+
),
|
|
138
|
+
children: import_policies.POLICY_LANGUAGE_NAMES[item]
|
|
139
|
+
},
|
|
140
|
+
item
|
|
141
|
+
))
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
145
|
+
"a",
|
|
146
|
+
{
|
|
147
|
+
href: (0, import_policies.getPolicyFileUrl)(slug, "pdf", source, language),
|
|
148
|
+
target: "_blank",
|
|
149
|
+
rel: "noopener noreferrer",
|
|
150
|
+
className: (0, import_utils.cn)(
|
|
151
|
+
"tuwadocs-policy-title tuwadocs:inline-flex tuwadocs:h-10 tuwadocs:no-underline tuwadocs:items-center tuwadocs:gap-2 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:px-4 tuwadocs:text-sm tuwadocs:font-bold tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:transition-colors tuwadocs:hover:bg-[var(--tuwa-bg-muted)]",
|
|
152
|
+
classNames?.download
|
|
153
|
+
),
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_outline.ArrowDownTrayIcon, { className: "tuwadocs:h-4 tuwadocs:w-4", "aria-hidden": "true" }),
|
|
156
|
+
texts.download
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
] })
|
|
129
161
|
] })
|
|
130
162
|
]
|
|
131
163
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { PolicySlug, PolicyContent } from '../policies.cjs';
|
|
2
|
+
import { PolicySlug, PolicyContent, PolicyDocumentLabels, PolicyLanguage } from '../policies.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Props of {@link PolicyDocument}.
|
|
@@ -11,15 +11,13 @@ interface PolicyDocumentProps {
|
|
|
11
11
|
content: PolicyContent;
|
|
12
12
|
/** Base URL of the policy files for the PDF link (default: `POLICY_SOURCE`). */
|
|
13
13
|
source?: string;
|
|
14
|
-
/** Texts of the page. */
|
|
15
|
-
labels?:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
toc?: string;
|
|
22
|
-
};
|
|
14
|
+
/** Texts of the page; the defaults are those of the language of `content` ({@link POLICY_DOCUMENT_LABELS}). */
|
|
15
|
+
labels?: PolicyDocumentLabels;
|
|
16
|
+
/**
|
|
17
|
+
* Link of each choice of the language switch (default: `?lang=<language>`, the page itself in that language). The
|
|
18
|
+
* switch shows when `content.languages` has more than one language.
|
|
19
|
+
*/
|
|
20
|
+
languageHref?: (language: PolicyLanguage) => string;
|
|
23
21
|
/** Classes of the root element. */
|
|
24
22
|
className?: string;
|
|
25
23
|
/** Classes of the parts of the page. */
|
|
@@ -36,12 +34,16 @@ interface PolicyDocumentProps {
|
|
|
36
34
|
body?: string;
|
|
37
35
|
/** The list of sections (shown from the `lg` breakpoint). */
|
|
38
36
|
toc?: string;
|
|
37
|
+
/** The language switch. */
|
|
38
|
+
languages?: string;
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* A TUWA policy page (Terms, Privacy, Cookie, Refund, Security): the title, the date of the version, a Download PDF
|
|
43
|
-
* button with the official document
|
|
44
|
-
*
|
|
43
|
+
* button with the official document in the language of the text, a language switch when the policy exists in more
|
|
44
|
+
* than one language, the text with anchored sections and, from the `lg` breakpoint, a sticky list of the sections.
|
|
45
|
+
* The sections keep their anchors in every language, and the page marks its language (`lang`) for screen readers and
|
|
46
|
+
* search engines. The text is selectable and readable on phones, by search engines and by screen readers; the PDF is
|
|
45
47
|
* generated from the same source.
|
|
46
48
|
*
|
|
47
49
|
* Load the content on the server with `loadPolicy` and render the page inside the layout of the site. The body is
|
|
@@ -52,7 +54,7 @@ interface PolicyDocumentProps {
|
|
|
52
54
|
* @param props - See {@link PolicyDocumentProps}.
|
|
53
55
|
* @returns The policy page.
|
|
54
56
|
*/
|
|
55
|
-
declare function PolicyDocument({ slug, content, source, labels, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
57
|
+
declare function PolicyDocument({ slug, content, source, labels, languageHref, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
56
58
|
|
|
57
59
|
export { PolicyDocument };
|
|
58
60
|
export type { PolicyDocumentProps };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { PolicySlug, PolicyContent } from '../policies.js';
|
|
2
|
+
import { PolicySlug, PolicyContent, PolicyDocumentLabels, PolicyLanguage } from '../policies.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Props of {@link PolicyDocument}.
|
|
@@ -11,15 +11,13 @@ interface PolicyDocumentProps {
|
|
|
11
11
|
content: PolicyContent;
|
|
12
12
|
/** Base URL of the policy files for the PDF link (default: `POLICY_SOURCE`). */
|
|
13
13
|
source?: string;
|
|
14
|
-
/** Texts of the page. */
|
|
15
|
-
labels?:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
toc?: string;
|
|
22
|
-
};
|
|
14
|
+
/** Texts of the page; the defaults are those of the language of `content` ({@link POLICY_DOCUMENT_LABELS}). */
|
|
15
|
+
labels?: PolicyDocumentLabels;
|
|
16
|
+
/**
|
|
17
|
+
* Link of each choice of the language switch (default: `?lang=<language>`, the page itself in that language). The
|
|
18
|
+
* switch shows when `content.languages` has more than one language.
|
|
19
|
+
*/
|
|
20
|
+
languageHref?: (language: PolicyLanguage) => string;
|
|
23
21
|
/** Classes of the root element. */
|
|
24
22
|
className?: string;
|
|
25
23
|
/** Classes of the parts of the page. */
|
|
@@ -36,12 +34,16 @@ interface PolicyDocumentProps {
|
|
|
36
34
|
body?: string;
|
|
37
35
|
/** The list of sections (shown from the `lg` breakpoint). */
|
|
38
36
|
toc?: string;
|
|
37
|
+
/** The language switch. */
|
|
38
|
+
languages?: string;
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* A TUWA policy page (Terms, Privacy, Cookie, Refund, Security): the title, the date of the version, a Download PDF
|
|
43
|
-
* button with the official document
|
|
44
|
-
*
|
|
43
|
+
* button with the official document in the language of the text, a language switch when the policy exists in more
|
|
44
|
+
* than one language, the text with anchored sections and, from the `lg` breakpoint, a sticky list of the sections.
|
|
45
|
+
* The sections keep their anchors in every language, and the page marks its language (`lang`) for screen readers and
|
|
46
|
+
* search engines. The text is selectable and readable on phones, by search engines and by screen readers; the PDF is
|
|
45
47
|
* generated from the same source.
|
|
46
48
|
*
|
|
47
49
|
* Load the content on the server with `loadPolicy` and render the page inside the layout of the site. The body is
|
|
@@ -52,7 +54,7 @@ interface PolicyDocumentProps {
|
|
|
52
54
|
* @param props - See {@link PolicyDocumentProps}.
|
|
53
55
|
* @returns The policy page.
|
|
54
56
|
*/
|
|
55
|
-
declare function PolicyDocument({ slug, content, source, labels, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
57
|
+
declare function PolicyDocument({ slug, content, source, labels, languageHref, className, classNames, }: PolicyDocumentProps): React.JSX.Element;
|
|
56
58
|
|
|
57
59
|
export { PolicyDocument };
|
|
58
60
|
export type { PolicyDocumentProps };
|
|
@@ -2,7 +2,12 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
|
3
3
|
import { cn } from "@tuwaio/nova-core/utils";
|
|
4
4
|
import parse, { Element } from "html-react-parser";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getPolicyFileUrl,
|
|
7
|
+
POLICY_DOCUMENT_LABELS,
|
|
8
|
+
POLICY_LANGUAGE_NAMES,
|
|
9
|
+
POLICY_SOURCE
|
|
10
|
+
} from "../policies";
|
|
6
11
|
const BLOCKED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
7
12
|
"script",
|
|
8
13
|
"style",
|
|
@@ -40,13 +45,18 @@ function PolicyDocument({
|
|
|
40
45
|
content,
|
|
41
46
|
source = POLICY_SOURCE,
|
|
42
47
|
labels,
|
|
48
|
+
languageHref = (language) => `?lang=${language}`,
|
|
43
49
|
className,
|
|
44
50
|
classNames
|
|
45
51
|
}) {
|
|
46
|
-
const
|
|
52
|
+
const language = content.language ?? "en";
|
|
53
|
+
const languages = content.languages ?? ["en"];
|
|
54
|
+
const texts = { ...POLICY_DOCUMENT_LABELS[language], ...labels };
|
|
55
|
+
const tocLabel = texts.toc;
|
|
47
56
|
return /* @__PURE__ */ jsxs(
|
|
48
57
|
"div",
|
|
49
58
|
{
|
|
59
|
+
lang: language,
|
|
50
60
|
className: cn(
|
|
51
61
|
"tuwadocs:grid tuwadocs:grid-cols-1 tuwadocs:gap-10 tuwadocs:lg:grid-cols-[minmax(0,1fr)_14rem]",
|
|
52
62
|
className
|
|
@@ -73,26 +83,53 @@ function PolicyDocument({
|
|
|
73
83
|
),
|
|
74
84
|
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:justify-between tuwadocs:gap-3", children: [
|
|
75
85
|
/* @__PURE__ */ jsxs("p", { className: cn("tuwadocs:text-sm tuwadocs:text-[var(--tuwa-text-secondary)]", classNames?.updated), children: [
|
|
76
|
-
|
|
86
|
+
texts.updated,
|
|
77
87
|
" ",
|
|
78
88
|
content.updated
|
|
79
89
|
] }),
|
|
80
|
-
/* @__PURE__ */ jsxs(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
90
|
+
/* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:flex-wrap tuwadocs:items-center tuwadocs:gap-2", children: [
|
|
91
|
+
languages.length > 1 && /* @__PURE__ */ jsx(
|
|
92
|
+
"nav",
|
|
93
|
+
{
|
|
94
|
+
"aria-label": texts.language,
|
|
95
|
+
className: cn(
|
|
96
|
+
"tuwadocs:inline-flex tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:p-0.5",
|
|
97
|
+
classNames?.languages
|
|
98
|
+
),
|
|
99
|
+
children: languages.map((item) => /* @__PURE__ */ jsx(
|
|
100
|
+
"a",
|
|
101
|
+
{
|
|
102
|
+
href: languageHref(item),
|
|
103
|
+
hrefLang: item,
|
|
104
|
+
lang: item,
|
|
105
|
+
"aria-current": item === language ? "page" : void 0,
|
|
106
|
+
className: cn(
|
|
107
|
+
"tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:px-3 tuwadocs:py-1.5 tuwadocs:text-xs tuwadocs:font-bold tuwadocs:no-underline tuwadocs:transition-colors",
|
|
108
|
+
item === language ? "tuwadocs:bg-[var(--tuwa-bg-muted)] tuwadocs:text-[var(--tuwa-text-primary)]" : "tuwadocs:text-[var(--tuwa-text-secondary)] tuwadocs:hover:text-[var(--tuwa-text-primary)]"
|
|
109
|
+
),
|
|
110
|
+
children: POLICY_LANGUAGE_NAMES[item]
|
|
111
|
+
},
|
|
112
|
+
item
|
|
113
|
+
))
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
/* @__PURE__ */ jsxs(
|
|
117
|
+
"a",
|
|
118
|
+
{
|
|
119
|
+
href: getPolicyFileUrl(slug, "pdf", source, language),
|
|
120
|
+
target: "_blank",
|
|
121
|
+
rel: "noopener noreferrer",
|
|
122
|
+
className: cn(
|
|
123
|
+
"tuwadocs-policy-title tuwadocs:inline-flex tuwadocs:h-10 tuwadocs:no-underline tuwadocs:items-center tuwadocs:gap-2 tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:bg-[var(--tuwa-bg-primary)] tuwadocs:px-4 tuwadocs:text-sm tuwadocs:font-bold tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:transition-colors tuwadocs:hover:bg-[var(--tuwa-bg-muted)]",
|
|
124
|
+
classNames?.download
|
|
125
|
+
),
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ jsx(ArrowDownTrayIcon, { className: "tuwadocs:h-4 tuwadocs:w-4", "aria-hidden": "true" }),
|
|
128
|
+
texts.download
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] })
|
|
96
133
|
] })
|
|
97
134
|
]
|
|
98
135
|
}
|
package/dist/index.css
CHANGED
|
@@ -749,6 +749,9 @@ html {
|
|
|
749
749
|
border-color: color-mix(in oklab, var(--tuwadocs-color-black) 5%, transparent);
|
|
750
750
|
}
|
|
751
751
|
}
|
|
752
|
+
.tuwadocs\:bg-\[var\(--tuwa-bg-muted\)\] {
|
|
753
|
+
background-color: var(--tuwa-bg-muted);
|
|
754
|
+
}
|
|
752
755
|
.tuwadocs\:bg-\[var\(--tuwa-bg-primary\)\] {
|
|
753
756
|
background-color: var(--tuwa-bg-primary);
|
|
754
757
|
}
|
|
@@ -766,6 +769,9 @@ html {
|
|
|
766
769
|
--tw-gradient-from: var(--tuwadocs-color-slate-200);
|
|
767
770
|
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
|
768
771
|
}
|
|
772
|
+
.tuwadocs\:p-0\.5 {
|
|
773
|
+
padding: calc(var(--tuwadocs-spacing) * 0.5);
|
|
774
|
+
}
|
|
769
775
|
.tuwadocs\:p-1 {
|
|
770
776
|
padding: var(--tuwadocs-spacing);
|
|
771
777
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ export { Orb, OrbProps, PackageType } from './components/Orb.cjs';
|
|
|
6
6
|
export { PolicyDocument, PolicyDocumentProps } from './components/PolicyDocument.cjs';
|
|
7
7
|
export { default as StarryBackground } from './components/StarryBackground.cjs';
|
|
8
8
|
export { ThemeSwitcher, ThemeSwitcherProps } from './components/ThemeSwitcher.cjs';
|
|
9
|
-
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicySection, PolicySlug, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies } from './policies.cjs';
|
|
9
|
+
export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies } from './policies.cjs';
|
|
10
10
|
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences } from './preferences.cjs';
|
|
11
11
|
export { baseFooterLinks, baseNavLinks } from './utils.cjs';
|
|
12
12
|
import 'react';
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { Orb, OrbProps, PackageType } from './components/Orb.js';
|
|
|
6
6
|
export { PolicyDocument, PolicyDocumentProps } from './components/PolicyDocument.js';
|
|
7
7
|
export { default as StarryBackground } from './components/StarryBackground.js';
|
|
8
8
|
export { ThemeSwitcher, ThemeSwitcherProps } from './components/ThemeSwitcher.js';
|
|
9
|
-
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicySection, PolicySlug, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies } from './policies.js';
|
|
9
|
+
export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies } from './policies.js';
|
|
10
10
|
export { PREFERENCE_DAYS, PreferenceExpiry, expireStoredPreferences } from './preferences.js';
|
|
11
11
|
export { baseFooterLinks, baseNavLinks } from './utils.js';
|
|
12
12
|
import 'react';
|
package/dist/policies.cjs
CHANGED
|
@@ -19,6 +19,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var policies_exports = {};
|
|
20
20
|
__export(policies_exports, {
|
|
21
21
|
POLICIES_INDEX_URL: () => POLICIES_INDEX_URL,
|
|
22
|
+
POLICY_DOCUMENT_LABELS: () => POLICY_DOCUMENT_LABELS,
|
|
23
|
+
POLICY_LANGUAGES: () => POLICY_LANGUAGES,
|
|
24
|
+
POLICY_LANGUAGE_NAMES: () => POLICY_LANGUAGE_NAMES,
|
|
22
25
|
POLICY_SITE_URL: () => POLICY_SITE_URL,
|
|
23
26
|
POLICY_SOURCE: () => POLICY_SOURCE,
|
|
24
27
|
footerPolicies: () => footerPolicies,
|
|
@@ -26,9 +29,23 @@ __export(policies_exports, {
|
|
|
26
29
|
getPolicyPageUrl: () => getPolicyPageUrl,
|
|
27
30
|
loadPolicy: () => loadPolicy,
|
|
28
31
|
policies: () => policies,
|
|
32
|
+
policyLanguageFrom: () => policyLanguageFrom,
|
|
29
33
|
publishedPolicies: () => publishedPolicies
|
|
30
34
|
});
|
|
31
35
|
module.exports = __toCommonJS(policies_exports);
|
|
36
|
+
const POLICY_LANGUAGES = ["en", "uk"];
|
|
37
|
+
const POLICY_LANGUAGE_NAMES = {
|
|
38
|
+
en: "English",
|
|
39
|
+
uk: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"
|
|
40
|
+
};
|
|
41
|
+
function policyLanguageFrom(value) {
|
|
42
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
43
|
+
return POLICY_LANGUAGES.find((language) => language === first) ?? "en";
|
|
44
|
+
}
|
|
45
|
+
const POLICY_DOCUMENT_LABELS = {
|
|
46
|
+
en: { updated: "Last updated", download: "Download PDF", toc: "On this page", language: "Language" },
|
|
47
|
+
uk: { updated: "\u041E\u0441\u0442\u0430\u043D\u043D\u0454 \u043E\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044F", download: "\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 PDF", toc: "\u041D\u0430 \u0446\u0456\u0439 \u0441\u0442\u043E\u0440\u0456\u043D\u0446\u0456", language: "\u041C\u043E\u0432\u0430" }
|
|
48
|
+
};
|
|
32
49
|
const POLICY_SOURCE = "https://raw.githubusercontent.com/TuwaIO/workflows/main/docs";
|
|
33
50
|
const POLICY_SITE_URL = "https://tuwa.io";
|
|
34
51
|
const POLICIES_INDEX_URL = "https://docs.tuwa.io/policies";
|
|
@@ -79,11 +96,11 @@ const policies = [
|
|
|
79
96
|
];
|
|
80
97
|
const publishedPolicies = policies.filter((policy) => policy.published);
|
|
81
98
|
const footerPolicies = publishedPolicies.filter((policy) => policy.footer !== false);
|
|
82
|
-
function getPolicyPageUrl(slug, site = POLICY_SITE_URL) {
|
|
83
|
-
return `${site.replace(/\/+$/, "")}/${slug}`;
|
|
99
|
+
function getPolicyPageUrl(slug, site = POLICY_SITE_URL, language = "en") {
|
|
100
|
+
return `${site.replace(/\/+$/, "")}/${slug}${language === "en" ? "" : `?lang=${language}`}`;
|
|
84
101
|
}
|
|
85
|
-
function getPolicyFileUrl(slug, format, source = POLICY_SOURCE) {
|
|
86
|
-
return `${source.replace(/\/+$/, "")}/${slug}.${format}`;
|
|
102
|
+
function getPolicyFileUrl(slug, format, source = POLICY_SOURCE, language = "en") {
|
|
103
|
+
return `${source.replace(/\/+$/, "")}${language === "en" ? "" : `/${language}`}/${slug}.${format}`;
|
|
87
104
|
}
|
|
88
105
|
function isPolicyContent(value) {
|
|
89
106
|
if (!value || typeof value !== "object") return false;
|
|
@@ -93,8 +110,12 @@ function isPolicyContent(value) {
|
|
|
93
110
|
);
|
|
94
111
|
}
|
|
95
112
|
async function loadPolicy(slug, options = {}) {
|
|
96
|
-
const
|
|
113
|
+
const language = options.language ?? "en";
|
|
114
|
+
const url = getPolicyFileUrl(slug, "json", options.source, language);
|
|
97
115
|
const response = await fetch(url, { next: { revalidate: options.revalidate ?? 3600 } });
|
|
116
|
+
if (response.status === 404 && language !== "en") {
|
|
117
|
+
return loadPolicy(slug, { ...options, language: "en" });
|
|
118
|
+
}
|
|
98
119
|
if (!response.ok) {
|
|
99
120
|
throw new Error(`loadPolicy: ${url} answered ${response.status}`);
|
|
100
121
|
}
|
|
@@ -102,16 +123,23 @@ async function loadPolicy(slug, options = {}) {
|
|
|
102
123
|
if (!isPolicyContent(content)) {
|
|
103
124
|
throw new Error(`loadPolicy: ${url} is not a policy page`);
|
|
104
125
|
}
|
|
126
|
+
const raw = content;
|
|
127
|
+
const languages = Array.isArray(raw.languages) ? POLICY_LANGUAGES.filter((item) => raw.languages.includes(item)) : [];
|
|
105
128
|
return {
|
|
106
129
|
title: content.title,
|
|
107
130
|
updated: content.updated,
|
|
108
131
|
sections: content.sections.map(({ id, title }) => ({ id, title })),
|
|
109
|
-
html: content.html
|
|
132
|
+
html: content.html,
|
|
133
|
+
language: policyLanguageFrom(raw.language ?? language),
|
|
134
|
+
languages: languages.length > 0 ? languages : ["en"]
|
|
110
135
|
};
|
|
111
136
|
}
|
|
112
137
|
// Annotate the CommonJS export names for ESM import in node:
|
|
113
138
|
0 && (module.exports = {
|
|
114
139
|
POLICIES_INDEX_URL,
|
|
140
|
+
POLICY_DOCUMENT_LABELS,
|
|
141
|
+
POLICY_LANGUAGES,
|
|
142
|
+
POLICY_LANGUAGE_NAMES,
|
|
115
143
|
POLICY_SITE_URL,
|
|
116
144
|
POLICY_SOURCE,
|
|
117
145
|
footerPolicies,
|
|
@@ -119,5 +147,6 @@ async function loadPolicy(slug, options = {}) {
|
|
|
119
147
|
getPolicyPageUrl,
|
|
120
148
|
loadPolicy,
|
|
121
149
|
policies,
|
|
150
|
+
policyLanguageFrom,
|
|
122
151
|
publishedPolicies
|
|
123
152
|
});
|
package/dist/policies.d.cts
CHANGED
|
@@ -15,6 +15,35 @@ interface Policy {
|
|
|
15
15
|
/** Whether footers link the policy (default: `true`); documents linked from other policies set `false`. */
|
|
16
16
|
footer?: boolean;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A language of the TUWA policies: English, the original, and the translations generated next to it in
|
|
20
|
+
* `TuwaIO/workflows/docs/<language>/`.
|
|
21
|
+
*/
|
|
22
|
+
type PolicyLanguage = 'en' | 'uk';
|
|
23
|
+
/** The languages of the policies, the original first. */
|
|
24
|
+
declare const POLICY_LANGUAGES: readonly PolicyLanguage[];
|
|
25
|
+
/** The name of each language in that language, for language switches. */
|
|
26
|
+
declare const POLICY_LANGUAGE_NAMES: Readonly<Record<PolicyLanguage, string>>;
|
|
27
|
+
/**
|
|
28
|
+
* Reads the language of a policy page from a value such as the `lang` search parameter.
|
|
29
|
+
*
|
|
30
|
+
* @param value - The value: a string, an array of strings (the first counts) or nothing.
|
|
31
|
+
* @returns The language it names, or `en` when it names none of {@link POLICY_LANGUAGES}.
|
|
32
|
+
*/
|
|
33
|
+
declare function policyLanguageFrom(value: unknown): PolicyLanguage;
|
|
34
|
+
/** Texts of a policy page. */
|
|
35
|
+
interface PolicyDocumentLabels {
|
|
36
|
+
/** Text before the date of the version (default in English: `Last updated`). */
|
|
37
|
+
updated?: string;
|
|
38
|
+
/** Text of the button that opens the PDF (default in English: `Download PDF`). */
|
|
39
|
+
download?: string;
|
|
40
|
+
/** Heading and accessible name of the list of sections (default in English: `On this page`). */
|
|
41
|
+
toc?: string;
|
|
42
|
+
/** Accessible name of the language switch (default in English: `Language`). */
|
|
43
|
+
language?: string;
|
|
44
|
+
}
|
|
45
|
+
/** The texts of `PolicyDocument` in each language of the policies; its `labels` override them. */
|
|
46
|
+
declare const POLICY_DOCUMENT_LABELS: Readonly<Record<PolicyLanguage, Required<PolicyDocumentLabels>>>;
|
|
18
47
|
/** A section of a policy: a level-2 heading with its anchor. */
|
|
19
48
|
interface PolicySection {
|
|
20
49
|
/** Anchor of the heading, without `#` (stable when sections are renumbered). */
|
|
@@ -28,10 +57,14 @@ interface PolicyContent {
|
|
|
28
57
|
title: string;
|
|
29
58
|
/** Date of the current version as written in the document (`October 8, 2026`). */
|
|
30
59
|
updated: string;
|
|
31
|
-
/** Level-2 sections in document order. */
|
|
60
|
+
/** Level-2 sections in document order; a translation keeps the anchors of the English sections. */
|
|
32
61
|
sections: PolicySection[];
|
|
33
62
|
/** Body of the document as HTML, without the title and the date line; headings carry their anchors as `id`. */
|
|
34
63
|
html: string;
|
|
64
|
+
/** Language of this text. */
|
|
65
|
+
language: PolicyLanguage;
|
|
66
|
+
/** Languages the policy exists in, the original first: the choices of the language switch. */
|
|
67
|
+
languages: PolicyLanguage[];
|
|
35
68
|
}
|
|
36
69
|
/** Where the policy files live: the `docs` folder of `TuwaIO/workflows` on GitHub, branch `main`. */
|
|
37
70
|
declare const POLICY_SOURCE = "https://raw.githubusercontent.com/TuwaIO/workflows/main/docs";
|
|
@@ -53,37 +86,44 @@ declare const footerPolicies: readonly Policy[];
|
|
|
53
86
|
*
|
|
54
87
|
* @param slug - Slug of the policy.
|
|
55
88
|
* @param site - Origin of the site with the policy pages (default: {@link POLICY_SITE_URL}).
|
|
56
|
-
* @
|
|
89
|
+
* @param language - Language of the page (default: `en`); a translation adds `?lang=<language>`.
|
|
90
|
+
* @returns The URL of the page, for example `https://tuwa.io/terms` or `https://tuwa.io/terms?lang=uk`.
|
|
57
91
|
*/
|
|
58
|
-
declare function getPolicyPageUrl(slug: PolicySlug, site?: string): string;
|
|
92
|
+
declare function getPolicyPageUrl(slug: PolicySlug, site?: string, language?: PolicyLanguage): string;
|
|
59
93
|
/**
|
|
60
94
|
* Returns the URL of a policy file.
|
|
61
95
|
*
|
|
62
96
|
* @param slug - Slug of the policy.
|
|
63
97
|
* @param format - `json` for the page content, `pdf` for the official document, `md` for the source text.
|
|
64
98
|
* @param source - Base URL of the policy files (default: {@link POLICY_SOURCE}).
|
|
65
|
-
* @
|
|
99
|
+
* @param language - Language of the file (default: `en`); a translation lives in the folder of its language.
|
|
100
|
+
* @returns The URL of the file, for example `…/docs/terms.pdf` or `…/docs/uk/terms.pdf`.
|
|
66
101
|
*/
|
|
67
|
-
declare function getPolicyFileUrl(slug: PolicySlug, format: 'json' | 'pdf' | 'md', source?: string): string;
|
|
102
|
+
declare function getPolicyFileUrl(slug: PolicySlug, format: 'json' | 'pdf' | 'md', source?: string, language?: PolicyLanguage): string;
|
|
68
103
|
/**
|
|
69
|
-
* Loads the content of a policy page (its `json` file). Call it on the server, for example in a Server
|
|
70
|
-
* the Next.js App Router, and pass the result to `PolicyDocument`.
|
|
104
|
+
* Loads the content of a policy page (its `json` file) in a language. Call it on the server, for example in a Server
|
|
105
|
+
* Component of the Next.js App Router, and pass the result to `PolicyDocument`. A policy not translated into the
|
|
106
|
+
* language loads in English, so a page asked for in that language still shows the policy.
|
|
71
107
|
*
|
|
72
|
-
* Network: one request to `getPolicyFileUrl(slug, 'json', source)` per revalidation,
|
|
73
|
-
*
|
|
108
|
+
* Network: one request to `getPolicyFileUrl(slug, 'json', source, language)` per revalidation, and one more for the
|
|
109
|
+
* English file when the translation does not exist; cached by the Next.js `fetch` cache for `revalidate` seconds
|
|
110
|
+
* (outside Next.js, plain requests).
|
|
74
111
|
*
|
|
75
112
|
* @param slug - Slug of the policy.
|
|
76
113
|
* @param options - Optional settings.
|
|
77
114
|
* @param options.source - Base URL of the policy files (default: {@link POLICY_SOURCE}).
|
|
78
115
|
* @param options.revalidate - Seconds the Next.js `fetch` cache keeps the content, or `false` to cache it until the
|
|
79
116
|
* next deployment (default: 3600).
|
|
80
|
-
* @
|
|
81
|
-
* @
|
|
117
|
+
* @param options.language - Language of the text (default: `en`).
|
|
118
|
+
* @returns The content of the policy, with its language and the languages it exists in.
|
|
119
|
+
* @throws Error when the request fails, the response is not `2xx` (other than a missing translation), or the file is
|
|
120
|
+
* not a policy page.
|
|
82
121
|
*/
|
|
83
122
|
declare function loadPolicy(slug: PolicySlug, options?: {
|
|
84
123
|
source?: string;
|
|
85
124
|
revalidate?: number | false;
|
|
125
|
+
language?: PolicyLanguage;
|
|
86
126
|
}): Promise<PolicyContent>;
|
|
87
127
|
|
|
88
|
-
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies };
|
|
89
|
-
export type { Policy, PolicyContent, PolicySection, PolicySlug };
|
|
128
|
+
export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies };
|
|
129
|
+
export type { Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug };
|
package/dist/policies.d.ts
CHANGED
|
@@ -15,6 +15,35 @@ interface Policy {
|
|
|
15
15
|
/** Whether footers link the policy (default: `true`); documents linked from other policies set `false`. */
|
|
16
16
|
footer?: boolean;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A language of the TUWA policies: English, the original, and the translations generated next to it in
|
|
20
|
+
* `TuwaIO/workflows/docs/<language>/`.
|
|
21
|
+
*/
|
|
22
|
+
type PolicyLanguage = 'en' | 'uk';
|
|
23
|
+
/** The languages of the policies, the original first. */
|
|
24
|
+
declare const POLICY_LANGUAGES: readonly PolicyLanguage[];
|
|
25
|
+
/** The name of each language in that language, for language switches. */
|
|
26
|
+
declare const POLICY_LANGUAGE_NAMES: Readonly<Record<PolicyLanguage, string>>;
|
|
27
|
+
/**
|
|
28
|
+
* Reads the language of a policy page from a value such as the `lang` search parameter.
|
|
29
|
+
*
|
|
30
|
+
* @param value - The value: a string, an array of strings (the first counts) or nothing.
|
|
31
|
+
* @returns The language it names, or `en` when it names none of {@link POLICY_LANGUAGES}.
|
|
32
|
+
*/
|
|
33
|
+
declare function policyLanguageFrom(value: unknown): PolicyLanguage;
|
|
34
|
+
/** Texts of a policy page. */
|
|
35
|
+
interface PolicyDocumentLabels {
|
|
36
|
+
/** Text before the date of the version (default in English: `Last updated`). */
|
|
37
|
+
updated?: string;
|
|
38
|
+
/** Text of the button that opens the PDF (default in English: `Download PDF`). */
|
|
39
|
+
download?: string;
|
|
40
|
+
/** Heading and accessible name of the list of sections (default in English: `On this page`). */
|
|
41
|
+
toc?: string;
|
|
42
|
+
/** Accessible name of the language switch (default in English: `Language`). */
|
|
43
|
+
language?: string;
|
|
44
|
+
}
|
|
45
|
+
/** The texts of `PolicyDocument` in each language of the policies; its `labels` override them. */
|
|
46
|
+
declare const POLICY_DOCUMENT_LABELS: Readonly<Record<PolicyLanguage, Required<PolicyDocumentLabels>>>;
|
|
18
47
|
/** A section of a policy: a level-2 heading with its anchor. */
|
|
19
48
|
interface PolicySection {
|
|
20
49
|
/** Anchor of the heading, without `#` (stable when sections are renumbered). */
|
|
@@ -28,10 +57,14 @@ interface PolicyContent {
|
|
|
28
57
|
title: string;
|
|
29
58
|
/** Date of the current version as written in the document (`October 8, 2026`). */
|
|
30
59
|
updated: string;
|
|
31
|
-
/** Level-2 sections in document order. */
|
|
60
|
+
/** Level-2 sections in document order; a translation keeps the anchors of the English sections. */
|
|
32
61
|
sections: PolicySection[];
|
|
33
62
|
/** Body of the document as HTML, without the title and the date line; headings carry their anchors as `id`. */
|
|
34
63
|
html: string;
|
|
64
|
+
/** Language of this text. */
|
|
65
|
+
language: PolicyLanguage;
|
|
66
|
+
/** Languages the policy exists in, the original first: the choices of the language switch. */
|
|
67
|
+
languages: PolicyLanguage[];
|
|
35
68
|
}
|
|
36
69
|
/** Where the policy files live: the `docs` folder of `TuwaIO/workflows` on GitHub, branch `main`. */
|
|
37
70
|
declare const POLICY_SOURCE = "https://raw.githubusercontent.com/TuwaIO/workflows/main/docs";
|
|
@@ -53,37 +86,44 @@ declare const footerPolicies: readonly Policy[];
|
|
|
53
86
|
*
|
|
54
87
|
* @param slug - Slug of the policy.
|
|
55
88
|
* @param site - Origin of the site with the policy pages (default: {@link POLICY_SITE_URL}).
|
|
56
|
-
* @
|
|
89
|
+
* @param language - Language of the page (default: `en`); a translation adds `?lang=<language>`.
|
|
90
|
+
* @returns The URL of the page, for example `https://tuwa.io/terms` or `https://tuwa.io/terms?lang=uk`.
|
|
57
91
|
*/
|
|
58
|
-
declare function getPolicyPageUrl(slug: PolicySlug, site?: string): string;
|
|
92
|
+
declare function getPolicyPageUrl(slug: PolicySlug, site?: string, language?: PolicyLanguage): string;
|
|
59
93
|
/**
|
|
60
94
|
* Returns the URL of a policy file.
|
|
61
95
|
*
|
|
62
96
|
* @param slug - Slug of the policy.
|
|
63
97
|
* @param format - `json` for the page content, `pdf` for the official document, `md` for the source text.
|
|
64
98
|
* @param source - Base URL of the policy files (default: {@link POLICY_SOURCE}).
|
|
65
|
-
* @
|
|
99
|
+
* @param language - Language of the file (default: `en`); a translation lives in the folder of its language.
|
|
100
|
+
* @returns The URL of the file, for example `…/docs/terms.pdf` or `…/docs/uk/terms.pdf`.
|
|
66
101
|
*/
|
|
67
|
-
declare function getPolicyFileUrl(slug: PolicySlug, format: 'json' | 'pdf' | 'md', source?: string): string;
|
|
102
|
+
declare function getPolicyFileUrl(slug: PolicySlug, format: 'json' | 'pdf' | 'md', source?: string, language?: PolicyLanguage): string;
|
|
68
103
|
/**
|
|
69
|
-
* Loads the content of a policy page (its `json` file). Call it on the server, for example in a Server
|
|
70
|
-
* the Next.js App Router, and pass the result to `PolicyDocument`.
|
|
104
|
+
* Loads the content of a policy page (its `json` file) in a language. Call it on the server, for example in a Server
|
|
105
|
+
* Component of the Next.js App Router, and pass the result to `PolicyDocument`. A policy not translated into the
|
|
106
|
+
* language loads in English, so a page asked for in that language still shows the policy.
|
|
71
107
|
*
|
|
72
|
-
* Network: one request to `getPolicyFileUrl(slug, 'json', source)` per revalidation,
|
|
73
|
-
*
|
|
108
|
+
* Network: one request to `getPolicyFileUrl(slug, 'json', source, language)` per revalidation, and one more for the
|
|
109
|
+
* English file when the translation does not exist; cached by the Next.js `fetch` cache for `revalidate` seconds
|
|
110
|
+
* (outside Next.js, plain requests).
|
|
74
111
|
*
|
|
75
112
|
* @param slug - Slug of the policy.
|
|
76
113
|
* @param options - Optional settings.
|
|
77
114
|
* @param options.source - Base URL of the policy files (default: {@link POLICY_SOURCE}).
|
|
78
115
|
* @param options.revalidate - Seconds the Next.js `fetch` cache keeps the content, or `false` to cache it until the
|
|
79
116
|
* next deployment (default: 3600).
|
|
80
|
-
* @
|
|
81
|
-
* @
|
|
117
|
+
* @param options.language - Language of the text (default: `en`).
|
|
118
|
+
* @returns The content of the policy, with its language and the languages it exists in.
|
|
119
|
+
* @throws Error when the request fails, the response is not `2xx` (other than a missing translation), or the file is
|
|
120
|
+
* not a policy page.
|
|
82
121
|
*/
|
|
83
122
|
declare function loadPolicy(slug: PolicySlug, options?: {
|
|
84
123
|
source?: string;
|
|
85
124
|
revalidate?: number | false;
|
|
125
|
+
language?: PolicyLanguage;
|
|
86
126
|
}): Promise<PolicyContent>;
|
|
87
127
|
|
|
88
|
-
export { POLICIES_INDEX_URL, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, publishedPolicies };
|
|
89
|
-
export type { Policy, PolicyContent, PolicySection, PolicySlug };
|
|
128
|
+
export { POLICIES_INDEX_URL, POLICY_DOCUMENT_LABELS, POLICY_LANGUAGES, POLICY_LANGUAGE_NAMES, POLICY_SITE_URL, POLICY_SOURCE, footerPolicies, getPolicyFileUrl, getPolicyPageUrl, loadPolicy, policies, policyLanguageFrom, publishedPolicies };
|
|
129
|
+
export type { Policy, PolicyContent, PolicyDocumentLabels, PolicyLanguage, PolicySection, PolicySlug };
|
package/dist/policies.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
const POLICY_LANGUAGES = ["en", "uk"];
|
|
2
|
+
const POLICY_LANGUAGE_NAMES = {
|
|
3
|
+
en: "English",
|
|
4
|
+
uk: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"
|
|
5
|
+
};
|
|
6
|
+
function policyLanguageFrom(value) {
|
|
7
|
+
const first = Array.isArray(value) ? value[0] : value;
|
|
8
|
+
return POLICY_LANGUAGES.find((language) => language === first) ?? "en";
|
|
9
|
+
}
|
|
10
|
+
const POLICY_DOCUMENT_LABELS = {
|
|
11
|
+
en: { updated: "Last updated", download: "Download PDF", toc: "On this page", language: "Language" },
|
|
12
|
+
uk: { updated: "\u041E\u0441\u0442\u0430\u043D\u043D\u0454 \u043E\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044F", download: "\u0417\u0430\u0432\u0430\u043D\u0442\u0430\u0436\u0438\u0442\u0438 PDF", toc: "\u041D\u0430 \u0446\u0456\u0439 \u0441\u0442\u043E\u0440\u0456\u043D\u0446\u0456", language: "\u041C\u043E\u0432\u0430" }
|
|
13
|
+
};
|
|
1
14
|
const POLICY_SOURCE = "https://raw.githubusercontent.com/TuwaIO/workflows/main/docs";
|
|
2
15
|
const POLICY_SITE_URL = "https://tuwa.io";
|
|
3
16
|
const POLICIES_INDEX_URL = "https://docs.tuwa.io/policies";
|
|
@@ -48,11 +61,11 @@ const policies = [
|
|
|
48
61
|
];
|
|
49
62
|
const publishedPolicies = policies.filter((policy) => policy.published);
|
|
50
63
|
const footerPolicies = publishedPolicies.filter((policy) => policy.footer !== false);
|
|
51
|
-
function getPolicyPageUrl(slug, site = POLICY_SITE_URL) {
|
|
52
|
-
return `${site.replace(/\/+$/, "")}/${slug}`;
|
|
64
|
+
function getPolicyPageUrl(slug, site = POLICY_SITE_URL, language = "en") {
|
|
65
|
+
return `${site.replace(/\/+$/, "")}/${slug}${language === "en" ? "" : `?lang=${language}`}`;
|
|
53
66
|
}
|
|
54
|
-
function getPolicyFileUrl(slug, format, source = POLICY_SOURCE) {
|
|
55
|
-
return `${source.replace(/\/+$/, "")}/${slug}.${format}`;
|
|
67
|
+
function getPolicyFileUrl(slug, format, source = POLICY_SOURCE, language = "en") {
|
|
68
|
+
return `${source.replace(/\/+$/, "")}${language === "en" ? "" : `/${language}`}/${slug}.${format}`;
|
|
56
69
|
}
|
|
57
70
|
function isPolicyContent(value) {
|
|
58
71
|
if (!value || typeof value !== "object") return false;
|
|
@@ -62,8 +75,12 @@ function isPolicyContent(value) {
|
|
|
62
75
|
);
|
|
63
76
|
}
|
|
64
77
|
async function loadPolicy(slug, options = {}) {
|
|
65
|
-
const
|
|
78
|
+
const language = options.language ?? "en";
|
|
79
|
+
const url = getPolicyFileUrl(slug, "json", options.source, language);
|
|
66
80
|
const response = await fetch(url, { next: { revalidate: options.revalidate ?? 3600 } });
|
|
81
|
+
if (response.status === 404 && language !== "en") {
|
|
82
|
+
return loadPolicy(slug, { ...options, language: "en" });
|
|
83
|
+
}
|
|
67
84
|
if (!response.ok) {
|
|
68
85
|
throw new Error(`loadPolicy: ${url} answered ${response.status}`);
|
|
69
86
|
}
|
|
@@ -71,15 +88,22 @@ async function loadPolicy(slug, options = {}) {
|
|
|
71
88
|
if (!isPolicyContent(content)) {
|
|
72
89
|
throw new Error(`loadPolicy: ${url} is not a policy page`);
|
|
73
90
|
}
|
|
91
|
+
const raw = content;
|
|
92
|
+
const languages = Array.isArray(raw.languages) ? POLICY_LANGUAGES.filter((item) => raw.languages.includes(item)) : [];
|
|
74
93
|
return {
|
|
75
94
|
title: content.title,
|
|
76
95
|
updated: content.updated,
|
|
77
96
|
sections: content.sections.map(({ id, title }) => ({ id, title })),
|
|
78
|
-
html: content.html
|
|
97
|
+
html: content.html,
|
|
98
|
+
language: policyLanguageFrom(raw.language ?? language),
|
|
99
|
+
languages: languages.length > 0 ? languages : ["en"]
|
|
79
100
|
};
|
|
80
101
|
}
|
|
81
102
|
export {
|
|
82
103
|
POLICIES_INDEX_URL,
|
|
104
|
+
POLICY_DOCUMENT_LABELS,
|
|
105
|
+
POLICY_LANGUAGES,
|
|
106
|
+
POLICY_LANGUAGE_NAMES,
|
|
83
107
|
POLICY_SITE_URL,
|
|
84
108
|
POLICY_SOURCE,
|
|
85
109
|
footerPolicies,
|
|
@@ -87,5 +111,6 @@ export {
|
|
|
87
111
|
getPolicyPageUrl,
|
|
88
112
|
loadPolicy,
|
|
89
113
|
policies,
|
|
114
|
+
policyLanguageFrom,
|
|
90
115
|
publishedPolicies
|
|
91
116
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuwaio/docs-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"author": "Oleksandr Tkach",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
}
|
|
62
62
|
],
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@tuwaio/nova-core": "^0.10.
|
|
64
|
+
"@tuwaio/nova-core": "^0.10.1",
|
|
65
65
|
"clsx": "^2.1.1",
|
|
66
66
|
"html-react-parser": "^6.1.8",
|
|
67
67
|
"tailwind-merge": "^3.7.0"
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"next": "16.4.1",
|
|
83
83
|
"nextra": "^4.6.1",
|
|
84
84
|
"nextra-theme-docs": "^4.6.1",
|
|
85
|
-
"postcss": "^8.5.
|
|
85
|
+
"postcss": "^8.5.31",
|
|
86
86
|
"postcss-cli": "^12.0.0",
|
|
87
87
|
"react": "^19.3.1",
|
|
88
88
|
"tailwindcss": "^4.3.3",
|