@umami/shiso 1.17.0 → 1.19.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/dist/chunks/App.js +1217 -196
- package/dist/chunks/architectureDiagram-5GKGNRK7.js +1 -1
- package/dist/chunks/chunk-GMAD6QVW.js +1 -1
- package/dist/chunks/cose-bilkent-JH36ORCC.js +1 -1
- package/dist/chunks/dist.js +1 -1
- package/dist/chunks/docs.js +843 -22
- package/dist/chunks/ganttDiagram-EL5Y4UJY.js +1 -1
- package/dist/chunks/src.js +1 -1
- package/dist/components.js +1 -1
- package/dist/entry-client.js +1 -1
- package/dist/entry-server.js +1 -1
- package/docs.schema.json +1164 -1131
- package/package.json +1 -2
- package/scripts/check-content.mjs +44 -15
- package/scripts/expand-openapi-navigation.mjs +47 -21
- package/scripts/generate-openapi.mjs +36 -11
- package/scripts/generate-search-index.mjs +19 -18
- package/scripts/lib/openapi-project.mjs +197 -0
- package/scripts/lib/openapi.mjs +257 -111
- package/scripts/lib/request-samples.mjs +323 -0
- package/scripts/load-docs-config.mjs +23 -15
- package/scripts/load-shiso-config.mjs +30 -1
- package/scripts/prerender.mjs +17 -14
- package/scripts/vite-docs-config.mjs +1 -0
- package/src/App.tsx +32 -22
- package/src/components/ApiPlayground.tsx +522 -0
- package/src/components/CodeBlock.tsx +3 -1
- package/src/components/DocContent.tsx +15 -4
- package/src/components/Docs.tsx +19 -2
- package/src/components/Footer.tsx +3 -1
- package/src/components/Header.tsx +4 -3
- package/src/components/LanguageSwitcher.tsx +38 -29
- package/src/components/OpenApiOperation.tsx +73 -20
- package/src/components/OpenApiSchema.tsx +97 -0
- package/src/components/PageActions.tsx +8 -7
- package/src/components/SideNav.tsx +2 -2
- package/src/components/docs/Changelog.tsx +5 -5
- package/src/components/docs/CodeGroup.tsx +3 -1
- package/src/components/docs/Mermaid.tsx +8 -6
- package/src/components/docs/PropertiesTable.tsx +11 -6
- package/src/components/docs/Tabs.tsx +3 -1
- package/src/components/docs/Tree.tsx +3 -1
- package/src/components/docs/ZoomableImage.tsx +4 -2
- package/src/components/ui/dialog.tsx +7 -2
- package/src/components/ui/sheet.tsx +5 -2
- package/src/lib/label-context.tsx +7 -0
- package/src/lib/labels.ts +37 -0
- package/src/lib/openapi.generated.ts +2 -1
- package/src/lib/openapi.ts +123 -16
- package/src/lib/site-config.ts +66 -2
- package/src/lib/site-model.ts +14 -32
- package/src/lib/standalone-pages.ts +15 -1
- package/src/lib/translations/de.json +99 -0
- package/src/lib/translations/en.json +99 -0
- package/src/lib/translations/es.json +99 -0
- package/src/lib/translations/fr.json +99 -0
- package/src/lib/translations/ja.json +99 -0
- package/src/lib/translations/zh-Hans.json +99 -0
- package/src/lib/translations/zh-Hant.json +99 -0
- package/src/lib/types.ts +109 -37
- package/types/config.d.ts +7 -1
- package/types/labels.d.ts +101 -0
- package/vite.config.ts +3 -4
- package/CHANGELOG.md +0 -8
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import { useLocation, useNavigate } from 'react-router';
|
|
2
|
-
import {
|
|
2
|
+
import { ChevronRight } from '@/components/icons';
|
|
3
3
|
import { Button } from '@/components/ui/button';
|
|
4
4
|
import {
|
|
5
5
|
DropdownMenu,
|
|
6
6
|
DropdownMenuContent,
|
|
7
|
-
|
|
7
|
+
DropdownMenuRadioGroup,
|
|
8
|
+
DropdownMenuRadioItem,
|
|
8
9
|
DropdownMenuTrigger,
|
|
9
10
|
} from '@/components/ui/dropdown-menu';
|
|
10
11
|
import { getLanguageScopes } from '@/lib/docs-config';
|
|
11
12
|
import { getLanguageName, isValidLocale } from '@/lib/locale';
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
docsSite,
|
|
15
|
+
getScopeByPathname,
|
|
16
|
+
getStandaloneCounterpart,
|
|
17
|
+
getStandalonePage,
|
|
18
|
+
} from '@/lib/site-config';
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Language selector for multi-language sites. Each option is a language's
|
|
16
|
-
* landing scope — its default version — so switching languages
|
|
17
|
-
*
|
|
22
|
+
* landing scope — its default version — so switching languages lands on that
|
|
23
|
+
* language's default-version first page. On a standalone page (e.g. a home
|
|
24
|
+
* page) it lands on the same page in the chosen language when one exists.
|
|
25
|
+
* Hidden languages never
|
|
18
26
|
* appear as options. Each language is shown by its native name, e.g. "ja"
|
|
19
27
|
* as "日本語".
|
|
20
28
|
*/
|
|
@@ -23,6 +31,7 @@ export function LanguageSwitcher() {
|
|
|
23
31
|
const navigate = useNavigate();
|
|
24
32
|
const current = getScopeByPathname(pathname);
|
|
25
33
|
const options = getLanguageScopes(docsSite);
|
|
34
|
+
const standalone = getStandalonePage(pathname);
|
|
26
35
|
|
|
27
36
|
if (!current.language || (options.length < 2 && !current.hidden)) {
|
|
28
37
|
return null;
|
|
@@ -31,35 +40,35 @@ export function LanguageSwitcher() {
|
|
|
31
40
|
return (
|
|
32
41
|
<DropdownMenu>
|
|
33
42
|
<DropdownMenuTrigger
|
|
34
|
-
render={
|
|
35
|
-
|
|
36
|
-
variant="outline"
|
|
37
|
-
className="h-auto gap-1.5 rounded-md bg-card px-2.5 py-1.5 text-sm font-medium text-foreground"
|
|
38
|
-
/>
|
|
39
|
-
}
|
|
43
|
+
render={<Button variant="outline" />}
|
|
44
|
+
aria-label={`Language: ${getLanguageName(current.language)}`}
|
|
40
45
|
>
|
|
41
46
|
{getLanguageName(current.language)}
|
|
42
47
|
<ChevronRight className="size-3.5 rotate-90 text-muted-foreground" />
|
|
43
48
|
</DropdownMenuTrigger>
|
|
44
49
|
<DropdownMenuContent align="start" className="min-w-32">
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{scope.
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
<DropdownMenuRadioGroup
|
|
51
|
+
value={current.language}
|
|
52
|
+
onValueChange={language => {
|
|
53
|
+
const scope = options.find(option => option.language === language);
|
|
54
|
+
if (scope && scope.language !== current.language) {
|
|
55
|
+
const counterpart =
|
|
56
|
+
standalone && scope.language
|
|
57
|
+
? getStandaloneCounterpart(standalone, scope.language)
|
|
58
|
+
: null;
|
|
59
|
+
|
|
60
|
+
navigate(counterpart?.path || scope.firstPageUrl);
|
|
61
|
+
}
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{options.map(scope => (
|
|
65
|
+
<DropdownMenuRadioItem key={scope.id} value={scope.language}>
|
|
66
|
+
<span lang={isValidLocale(scope.language) ? scope.language : undefined}>
|
|
67
|
+
{scope.language ? getLanguageName(scope.language) : null}
|
|
68
|
+
</span>
|
|
69
|
+
</DropdownMenuRadioItem>
|
|
70
|
+
))}
|
|
71
|
+
</DropdownMenuRadioGroup>
|
|
63
72
|
</DropdownMenuContent>
|
|
64
73
|
</DropdownMenu>
|
|
65
74
|
);
|
|
@@ -1,18 +1,28 @@
|
|
|
1
|
+
import { ApiPlayground } from '@/components/ApiPlayground';
|
|
1
2
|
import { CodeBlock } from '@/components/CodeBlock';
|
|
2
3
|
import { Badge } from '@/components/docs/Badge';
|
|
3
4
|
import { CodeGroup } from '@/components/docs/CodeGroup';
|
|
4
5
|
import { Expandable } from '@/components/docs/Expandable';
|
|
5
6
|
import { PropertiesTable } from '@/components/docs/PropertiesTable';
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
7
|
+
import { useLabels } from '@/lib/label-context';
|
|
8
|
+
import {
|
|
9
|
+
operationParameterSections,
|
|
10
|
+
operationSections,
|
|
11
|
+
securityFieldName,
|
|
12
|
+
securityForLocation,
|
|
13
|
+
statusColor,
|
|
14
|
+
} from '@/lib/openapi';
|
|
15
|
+
import type { NormalizedOperation, SchemaNode, SecurityScheme, ThemeLabels } from '@/lib/types';
|
|
16
|
+
import { securityPlaceholder } from '../../scripts/lib/request-samples.mjs';
|
|
8
17
|
|
|
9
18
|
function FieldChildren({ node }: { node: SchemaNode }) {
|
|
19
|
+
const labels = useLabels();
|
|
10
20
|
return (
|
|
11
21
|
<>
|
|
12
22
|
{node.description}
|
|
13
23
|
{node.enum && node.enum.length > 0 && (
|
|
14
24
|
<div className="mt-1">
|
|
15
|
-
|
|
25
|
+
{labels.fieldOptions}{' '}
|
|
16
26
|
{node.enum.map((value, index) => (
|
|
17
27
|
<span key={value}>
|
|
18
28
|
{index > 0 && ', '}
|
|
@@ -25,12 +35,14 @@ function FieldChildren({ node }: { node: SchemaNode }) {
|
|
|
25
35
|
);
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
function schemaDetails(node: SchemaNode) {
|
|
38
|
+
function schemaDetails(node: SchemaNode, labels: ThemeLabels) {
|
|
29
39
|
if (!node.children?.length) return undefined;
|
|
30
40
|
|
|
31
41
|
return (
|
|
32
42
|
<Expandable
|
|
33
|
-
title={
|
|
43
|
+
title={
|
|
44
|
+
node.type === 'oneOf' || node.type === 'anyOf' ? labels.allowedTypes : labels.properties
|
|
45
|
+
}
|
|
34
46
|
>
|
|
35
47
|
<SchemaTable nodes={node.children} />
|
|
36
48
|
</Expandable>
|
|
@@ -38,6 +50,7 @@ function schemaDetails(node: SchemaNode) {
|
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
function SchemaTable({ nodes }: { nodes: SchemaNode[] }) {
|
|
53
|
+
const labels = useLabels();
|
|
41
54
|
return (
|
|
42
55
|
<PropertiesTable>
|
|
43
56
|
{nodes.map(node => (
|
|
@@ -48,7 +61,7 @@ function SchemaTable({ nodes }: { nodes: SchemaNode[] }) {
|
|
|
48
61
|
required={node.required}
|
|
49
62
|
deprecated={node.deprecated}
|
|
50
63
|
default={node.default}
|
|
51
|
-
details={schemaDetails(node)}
|
|
64
|
+
details={schemaDetails(node, labels)}
|
|
52
65
|
>
|
|
53
66
|
<FieldChildren node={node} />
|
|
54
67
|
</PropertiesTable.Row>
|
|
@@ -99,25 +112,63 @@ function HighlightedCode({
|
|
|
99
112
|
);
|
|
100
113
|
}
|
|
101
114
|
|
|
115
|
+
/** How a credential is sent, e.g. "Bearer <token>" or "<api-key>". */
|
|
116
|
+
function credentialExample(scheme: SecurityScheme): string {
|
|
117
|
+
const placeholder = securityPlaceholder(scheme);
|
|
118
|
+
if (scheme.type === 'apiKey') return placeholder;
|
|
119
|
+
const prefix =
|
|
120
|
+
scheme.type === 'http' && scheme.scheme && scheme.scheme !== 'bearer'
|
|
121
|
+
? scheme.scheme.charAt(0).toUpperCase() + scheme.scheme.slice(1)
|
|
122
|
+
: 'Bearer';
|
|
123
|
+
return `${prefix} ${placeholder}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
102
126
|
export interface OpenApiOperationProps {
|
|
103
127
|
operation: NormalizedOperation;
|
|
128
|
+
/** Renders the "Try it" panel ahead of the reference; carries the proxy setting. */
|
|
129
|
+
playground?: { proxy?: string } | false;
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
/** The generated reference for one API operation, rendered under the page body. */
|
|
107
|
-
export function OpenApiOperation({
|
|
108
|
-
|
|
133
|
+
export function OpenApiOperation({
|
|
134
|
+
operation,
|
|
135
|
+
playground: requested = false,
|
|
136
|
+
}: OpenApiOperationProps) {
|
|
137
|
+
const labels = useLabels();
|
|
138
|
+
// Webhooks describe requests the API sends, so there is nothing to try.
|
|
139
|
+
const playground = operation.webhook ? false : requested;
|
|
140
|
+
const options = { playground: !!playground };
|
|
141
|
+
const sectionEntries = operationSections(operation, labels, options);
|
|
142
|
+
// Ids come from the untranslated names so anchors are stable across locales.
|
|
143
|
+
const sections = new Map(
|
|
144
|
+
operationSections(operation, undefined, options).map(entry => [entry.name, entry.id]),
|
|
145
|
+
);
|
|
146
|
+
const parameterSections = operationParameterSections(operation, labels);
|
|
147
|
+
const parameterOffset = playground ? 1 : 0;
|
|
109
148
|
|
|
110
149
|
return (
|
|
111
150
|
<div className="docs-markdown">
|
|
112
|
-
{
|
|
151
|
+
{playground && (
|
|
152
|
+
<section>
|
|
153
|
+
<h2 id={sections.get('Try it')}>{labels.apiPlayground}</h2>
|
|
154
|
+
<ApiPlayground operation={operation} proxy={playground.proxy} />
|
|
155
|
+
</section>
|
|
156
|
+
)}
|
|
157
|
+
{parameterSections.map(({ location, name }, index) => (
|
|
113
158
|
<section key={location}>
|
|
114
|
-
<h2 id={
|
|
159
|
+
<h2 id={sectionEntries[index + parameterOffset].id}>{name}</h2>
|
|
115
160
|
<PropertiesTable>
|
|
116
|
-
{location
|
|
117
|
-
<PropertiesTable.Row
|
|
118
|
-
|
|
161
|
+
{securityForLocation(operation, location).map(scheme => (
|
|
162
|
+
<PropertiesTable.Row
|
|
163
|
+
key={`security:${scheme.name}`}
|
|
164
|
+
name={securityFieldName(scheme)}
|
|
165
|
+
type="string"
|
|
166
|
+
required
|
|
167
|
+
>
|
|
168
|
+
{scheme.description || labels.apiCredentials}{' '}
|
|
169
|
+
<code>{credentialExample(scheme)}</code>.
|
|
119
170
|
</PropertiesTable.Row>
|
|
120
|
-
)}
|
|
171
|
+
))}
|
|
121
172
|
{operation.parameters[location].map(parameter => (
|
|
122
173
|
<PropertiesTable.Row
|
|
123
174
|
key={parameter.name}
|
|
@@ -126,7 +177,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
126
177
|
required={parameter.required}
|
|
127
178
|
deprecated={parameter.deprecated}
|
|
128
179
|
default={parameter.default}
|
|
129
|
-
details={schemaDetails(parameter)}
|
|
180
|
+
details={schemaDetails(parameter, labels)}
|
|
130
181
|
>
|
|
131
182
|
<FieldChildren node={parameter} />
|
|
132
183
|
</PropertiesTable.Row>
|
|
@@ -136,12 +187,14 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
136
187
|
))}
|
|
137
188
|
{operation.requestBody && (
|
|
138
189
|
<section>
|
|
139
|
-
<h2 id={sections.get('Request body')}>
|
|
190
|
+
<h2 id={sections.get(operation.webhook ? 'Payload' : 'Request body')}>
|
|
191
|
+
{operation.webhook ? labels.apiPayload : labels.apiRequestBody}
|
|
192
|
+
</h2>
|
|
140
193
|
<SchemaFields node={operation.requestBody.schema} />
|
|
141
194
|
{operation.requestBody.example && (
|
|
142
195
|
<HighlightedCode
|
|
143
196
|
language="json"
|
|
144
|
-
title=
|
|
197
|
+
title={labels.apiExampleRequest}
|
|
145
198
|
html={operation.requestBody.exampleHtml}
|
|
146
199
|
source={operation.requestBody.example}
|
|
147
200
|
/>
|
|
@@ -150,7 +203,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
150
203
|
)}
|
|
151
204
|
{operation.responses.length > 0 && (
|
|
152
205
|
<section>
|
|
153
|
-
<h2 id={sections.get('Responses')}>
|
|
206
|
+
<h2 id={sections.get('Responses')}>{labels.apiResponses}</h2>
|
|
154
207
|
{operation.responses.map(response => (
|
|
155
208
|
<div key={response.status} className="mt-6 first:mt-0">
|
|
156
209
|
<div className="flex items-center gap-2">
|
|
@@ -165,7 +218,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
165
218
|
{response.example && (
|
|
166
219
|
<HighlightedCode
|
|
167
220
|
language="json"
|
|
168
|
-
title={
|
|
221
|
+
title={labels.apiExampleResponse.replace('{status}', () => response.status)}
|
|
169
222
|
html={response.exampleHtml}
|
|
170
223
|
source={response.example}
|
|
171
224
|
/>
|
|
@@ -176,7 +229,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
176
229
|
)}
|
|
177
230
|
{operation.samples.length > 0 && (
|
|
178
231
|
<section>
|
|
179
|
-
<h2 id={sections.get('Code samples')}>
|
|
232
|
+
<h2 id={sections.get('Code samples')}>{labels.apiCodeSamples}</h2>
|
|
180
233
|
<CodeGroup>
|
|
181
234
|
{operation.samples.map(sample => (
|
|
182
235
|
<HighlightedCode
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { CodeBlock } from '@/components/CodeBlock';
|
|
2
|
+
import { Expandable } from '@/components/docs/Expandable';
|
|
3
|
+
import { PropertiesTable } from '@/components/docs/PropertiesTable';
|
|
4
|
+
import { useLabels } from '@/lib/label-context';
|
|
5
|
+
import { schemaSections } from '@/lib/openapi';
|
|
6
|
+
import type { SchemaNode, SchemaPage, ThemeLabels } from '@/lib/types';
|
|
7
|
+
|
|
8
|
+
function FieldChildren({ node }: { node: SchemaNode }) {
|
|
9
|
+
const labels = useLabels();
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
{node.description}
|
|
13
|
+
{node.enum && node.enum.length > 0 && (
|
|
14
|
+
<div className="mt-1">
|
|
15
|
+
{labels.fieldOptions}{' '}
|
|
16
|
+
{node.enum.map((value, index) => (
|
|
17
|
+
<span key={value}>
|
|
18
|
+
{index > 0 && ', '}
|
|
19
|
+
<code>{value}</code>
|
|
20
|
+
</span>
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
)}
|
|
24
|
+
</>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function SchemaTable({ nodes, labels }: { nodes: SchemaNode[]; labels: ThemeLabels }) {
|
|
29
|
+
return (
|
|
30
|
+
<PropertiesTable>
|
|
31
|
+
{nodes.map(node => (
|
|
32
|
+
<PropertiesTable.Row
|
|
33
|
+
key={node.name || node.type}
|
|
34
|
+
name={node.name || node.type}
|
|
35
|
+
type={node.type}
|
|
36
|
+
required={node.required}
|
|
37
|
+
deprecated={node.deprecated}
|
|
38
|
+
default={node.default}
|
|
39
|
+
details={
|
|
40
|
+
node.children?.length ? (
|
|
41
|
+
<Expandable
|
|
42
|
+
title={
|
|
43
|
+
node.type === 'oneOf' || node.type === 'anyOf'
|
|
44
|
+
? labels.allowedTypes
|
|
45
|
+
: labels.properties
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
<SchemaTable nodes={node.children} labels={labels} />
|
|
49
|
+
</Expandable>
|
|
50
|
+
) : undefined
|
|
51
|
+
}
|
|
52
|
+
>
|
|
53
|
+
<FieldChildren node={node} />
|
|
54
|
+
</PropertiesTable.Row>
|
|
55
|
+
))}
|
|
56
|
+
</PropertiesTable>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface OpenApiSchemaProps {
|
|
61
|
+
page: SchemaPage;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The generated reference for one named component schema. */
|
|
65
|
+
export function OpenApiSchema({ page }: OpenApiSchemaProps) {
|
|
66
|
+
const labels = useLabels();
|
|
67
|
+
const sections = new Map(schemaSections(page, labels).map(entry => [entry.id, entry.name]));
|
|
68
|
+
const properties = sections.get('properties');
|
|
69
|
+
const example = sections.get('example');
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className="docs-markdown">
|
|
73
|
+
{properties && (
|
|
74
|
+
<section>
|
|
75
|
+
<h2 id="properties">{properties}</h2>
|
|
76
|
+
<SchemaTable nodes={page.schema.children ?? []} labels={labels} />
|
|
77
|
+
</section>
|
|
78
|
+
)}
|
|
79
|
+
{example && page.example && (
|
|
80
|
+
<section>
|
|
81
|
+
<h2 id="example">{example}</h2>
|
|
82
|
+
<CodeBlock data-language="json" data-title={page.name}>
|
|
83
|
+
{page.exampleHtml ? (
|
|
84
|
+
<code
|
|
85
|
+
className="language-json"
|
|
86
|
+
// biome-ignore lint/security/noDangerouslySetInnerHtml: build-time generated markup from the project's own OpenAPI spec.
|
|
87
|
+
dangerouslySetInnerHTML={{ __html: page.exampleHtml }}
|
|
88
|
+
/>
|
|
89
|
+
) : (
|
|
90
|
+
<code className="language-json">{page.example}</code>
|
|
91
|
+
)}
|
|
92
|
+
</CodeBlock>
|
|
93
|
+
</section>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -90,7 +90,7 @@ export function PageActions({
|
|
|
90
90
|
rel="noopener noreferrer"
|
|
91
91
|
className="ml-auto inline-flex shrink-0 items-center gap-1.5 text-right no-underline hover:text-primary"
|
|
92
92
|
>
|
|
93
|
-
{site.editLink?.label ||
|
|
93
|
+
{site.editLink?.label || site.labels.editPage}
|
|
94
94
|
<ExternalLink size={14} aria-hidden="true" />
|
|
95
95
|
</a>
|
|
96
96
|
)}
|
|
@@ -103,11 +103,11 @@ export function PageActions({
|
|
|
103
103
|
data-pagefind-ignore
|
|
104
104
|
>
|
|
105
105
|
<fieldset
|
|
106
|
-
aria-label={feedback.prompt ||
|
|
106
|
+
aria-label={feedback.prompt || site.labels.feedbackPrompt}
|
|
107
107
|
className="flex flex-wrap items-center gap-2"
|
|
108
108
|
>
|
|
109
109
|
<span className="mr-2 text-muted-foreground">
|
|
110
|
-
{feedback.prompt ||
|
|
110
|
+
{feedback.prompt || site.labels.feedbackPrompt}
|
|
111
111
|
</span>
|
|
112
112
|
{[true, false].map(helpful => (
|
|
113
113
|
<button
|
|
@@ -118,14 +118,15 @@ export function PageActions({
|
|
|
118
118
|
onClick={() => void submit(helpful)}
|
|
119
119
|
className="rounded-md border border-border px-3 py-1.5 text-foreground hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-default disabled:opacity-60 aria-pressed:border-primary aria-pressed:text-primary"
|
|
120
120
|
>
|
|
121
|
-
{helpful
|
|
121
|
+
{helpful
|
|
122
|
+
? feedback.helpfulLabel || site.labels.feedbackYes
|
|
123
|
+
: feedback.unhelpfulLabel || site.labels.feedbackNo}
|
|
122
124
|
</button>
|
|
123
125
|
))}
|
|
124
126
|
</fieldset>
|
|
125
127
|
<div role="status" aria-live="polite" className="text-muted-foreground">
|
|
126
|
-
{status === 'success' && (feedback.successMessage ||
|
|
127
|
-
{status === 'error' &&
|
|
128
|
-
(feedback.errorMessage || 'Could not send feedback. Please try again.')}
|
|
128
|
+
{status === 'success' && (feedback.successMessage || site.labels.feedbackSuccess)}
|
|
129
|
+
{status === 'error' && (feedback.errorMessage || site.labels.feedbackError)}
|
|
129
130
|
</div>
|
|
130
131
|
</div>
|
|
131
132
|
)}
|
|
@@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button';
|
|
|
8
8
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
9
9
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
10
10
|
import { flattenNav, isNodeHidden } from '@/lib/docs-config';
|
|
11
|
-
import { methodColor } from '@/lib/openapi';
|
|
11
|
+
import { methodBadgeText, methodColor } from '@/lib/openapi';
|
|
12
12
|
import type { DocsTab, NavGroupNode, NavNode } from '@/lib/types';
|
|
13
13
|
import { cn } from '@/lib/utils';
|
|
14
14
|
|
|
@@ -281,7 +281,7 @@ function NavNodes({
|
|
|
281
281
|
size="xs"
|
|
282
282
|
className="w-10 shrink-0 justify-center whitespace-nowrap font-mono"
|
|
283
283
|
>
|
|
284
|
-
{method
|
|
284
|
+
{methodBadgeText(method)}
|
|
285
285
|
</MethodBadge>
|
|
286
286
|
) : null}
|
|
287
287
|
{label}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
useMemo,
|
|
8
8
|
} from 'react';
|
|
9
9
|
import { useSearchParams } from 'react-router';
|
|
10
|
+
import { useLabels } from '@/lib/label-context';
|
|
10
11
|
import { Badge } from './Badge';
|
|
11
12
|
import { styles } from './styles';
|
|
12
13
|
import { Update } from './Update';
|
|
@@ -73,6 +74,7 @@ function updateInfo(children: React.ReactNode): Map<string, UpdateChildInfo> {
|
|
|
73
74
|
* Pages without any tagged updates render as a plain list.
|
|
74
75
|
*/
|
|
75
76
|
export function Changelog({ children }: ChangelogProps) {
|
|
77
|
+
const labels = useLabels();
|
|
76
78
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
77
79
|
|
|
78
80
|
const allTags = useMemo(() => {
|
|
@@ -144,7 +146,7 @@ export function Changelog({ children }: ChangelogProps) {
|
|
|
144
146
|
return (
|
|
145
147
|
<div className={styles.changelog}>
|
|
146
148
|
{/* biome-ignore lint/a11y/useSemanticElements: toggle-button group labelled for the filter */}
|
|
147
|
-
<div className={styles.changelogFilters} role="group" aria-label=
|
|
149
|
+
<div className={styles.changelogFilters} role="group" aria-label={labels.filterUpdates}>
|
|
148
150
|
{allTags.map(tag => {
|
|
149
151
|
const active = selectedSet.has(tag.toLowerCase());
|
|
150
152
|
return (
|
|
@@ -164,13 +166,11 @@ export function Changelog({ children }: ChangelogProps) {
|
|
|
164
166
|
})}
|
|
165
167
|
{selected.length > 0 ? (
|
|
166
168
|
<button type="button" onClick={clear} className={styles.changelogClear}>
|
|
167
|
-
|
|
169
|
+
{labels.clear}
|
|
168
170
|
</button>
|
|
169
171
|
) : null}
|
|
170
172
|
</div>
|
|
171
|
-
{visibleUpdates === 0 ?
|
|
172
|
-
<p className={styles.changelogEmpty}>No updates match the selected tags.</p>
|
|
173
|
-
) : null}
|
|
173
|
+
{visibleUpdates === 0 ? <p className={styles.changelogEmpty}>{labels.noUpdates}</p> : null}
|
|
174
174
|
{filtered}
|
|
175
175
|
</div>
|
|
176
176
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
3
|
import { TabsContent, TabsList, Tabs as TabsPrimitive, TabsTrigger } from '@/components/ui/tabs';
|
|
4
|
+
import { useLabels } from '@/lib/label-context';
|
|
4
5
|
import { styles } from './styles';
|
|
5
6
|
import { findCodeLanguage, findCodeTitle, slugify, toElementArray } from './utils';
|
|
6
7
|
|
|
@@ -9,6 +10,7 @@ export interface CodeGroupProps {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function CodeGroup({ children }: CodeGroupProps) {
|
|
13
|
+
const labels = useLabels();
|
|
12
14
|
const blocks = useMemo(() => {
|
|
13
15
|
const counts = new Map<string, number>();
|
|
14
16
|
|
|
@@ -55,7 +57,7 @@ export function CodeGroup({ children }: CodeGroupProps) {
|
|
|
55
57
|
<TabsList
|
|
56
58
|
variant="line"
|
|
57
59
|
className="h-9 w-full justify-start gap-5 overflow-x-auto overflow-y-hidden rounded-none px-3 py-0 shadow-[inset_0_-1px_0_var(--border)] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
58
|
-
aria-label=
|
|
60
|
+
aria-label={labels.codeSnippets}
|
|
59
61
|
>
|
|
60
62
|
{blocks.map(block => (
|
|
61
63
|
<TabsTrigger
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode, useEffect, useId, useRef, useState } from 'react';
|
|
2
|
+
import { useLabels } from '@/lib/label-context';
|
|
2
3
|
import { styles } from './styles';
|
|
3
4
|
|
|
4
5
|
export type MermaidPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
@@ -63,6 +64,7 @@ export function Mermaid({
|
|
|
63
64
|
placement = 'bottom-right',
|
|
64
65
|
children,
|
|
65
66
|
}: MermaidProps) {
|
|
67
|
+
const labels = useLabels();
|
|
66
68
|
const source = mermaidSource(chart, children);
|
|
67
69
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
68
70
|
const svgHostRef = useRef<HTMLDivElement>(null);
|
|
@@ -129,29 +131,29 @@ export function Mermaid({
|
|
|
129
131
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: mermaid generates this SVG from the page's own diagram source.
|
|
130
132
|
dangerouslySetInnerHTML={{ __html: svg }}
|
|
131
133
|
role="img"
|
|
132
|
-
aria-label={title ? String(title) :
|
|
134
|
+
aria-label={title ? String(title) : labels.diagram}
|
|
133
135
|
/>
|
|
134
136
|
) : (
|
|
135
|
-
<pre className={styles.mermaidFallback} title=
|
|
137
|
+
<pre className={styles.mermaidFallback} title={labels.diagramSource}>
|
|
136
138
|
{source}
|
|
137
139
|
</pre>
|
|
138
140
|
)}
|
|
139
|
-
{error ? <p className={styles.mermaidError}>
|
|
141
|
+
{error ? <p className={styles.mermaidError}>{labels.diagramError}</p> : null}
|
|
140
142
|
{showControls && svg && !error ? (
|
|
141
143
|
<div className={`${styles.mermaidControls} ${PLACEMENT_CLASS[placement]}`}>
|
|
142
144
|
<button
|
|
143
145
|
type="button"
|
|
144
|
-
aria-label=
|
|
146
|
+
aria-label={labels.zoomOut}
|
|
145
147
|
onClick={() => setZoom(z => Math.max(0.5, +(z - 0.25).toFixed(2)))}
|
|
146
148
|
>
|
|
147
149
|
−
|
|
148
150
|
</button>
|
|
149
|
-
<button type="button" aria-label=
|
|
151
|
+
<button type="button" aria-label={labels.resetView} onClick={() => setZoom(1)}>
|
|
150
152
|
⟳
|
|
151
153
|
</button>
|
|
152
154
|
<button
|
|
153
155
|
type="button"
|
|
154
|
-
aria-label=
|
|
156
|
+
aria-label={labels.zoomIn}
|
|
155
157
|
onClick={() => setZoom(z => Math.min(2.5, +(z + 0.25).toFixed(2)))}
|
|
156
158
|
>
|
|
157
159
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import { useLabels } from '@/lib/label-context';
|
|
2
3
|
import { Badge } from './Badge';
|
|
3
4
|
import { decodeHtmlEntities, toElementArray } from './utils';
|
|
4
5
|
|
|
@@ -27,6 +28,7 @@ function displayValue(value: PropertiesTableRowProps['type']): string | undefine
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export function PropertiesTable({ children }: PropertiesTableProps) {
|
|
31
|
+
const labels = useLabels();
|
|
30
32
|
const rows = toElementArray<PropertiesTableRowProps>(children);
|
|
31
33
|
|
|
32
34
|
if (!rows.length) {
|
|
@@ -40,9 +42,9 @@ export function PropertiesTable({ children }: PropertiesTableProps) {
|
|
|
40
42
|
<table className="m-0 table-fixed [overflow-wrap:anywhere]">
|
|
41
43
|
<thead>
|
|
42
44
|
<tr>
|
|
43
|
-
<th className="w-[30%]">
|
|
44
|
-
<th className="w-[15%]">
|
|
45
|
-
<th>
|
|
45
|
+
<th className="w-[30%]">{labels.fieldName}</th>
|
|
46
|
+
<th className="w-[15%]">{labels.fieldType}</th>
|
|
47
|
+
<th>{labels.fieldDescription}</th>
|
|
46
48
|
</tr>
|
|
47
49
|
</thead>
|
|
48
50
|
<tbody>{rows}</tbody>
|
|
@@ -60,6 +62,7 @@ function PropertiesTableRow({
|
|
|
60
62
|
children,
|
|
61
63
|
details,
|
|
62
64
|
}: PropertiesTableRowProps) {
|
|
65
|
+
const labels = useLabels();
|
|
63
66
|
const defaultValue = displayValue(value);
|
|
64
67
|
|
|
65
68
|
return (
|
|
@@ -70,17 +73,19 @@ function PropertiesTableRow({
|
|
|
70
73
|
{name}
|
|
71
74
|
{required ? (
|
|
72
75
|
<Badge size="sm" tone="primary">
|
|
73
|
-
|
|
76
|
+
{labels.fieldRequired}
|
|
74
77
|
</Badge>
|
|
75
78
|
) : null}
|
|
76
|
-
{deprecated ? <Badge size="sm">
|
|
79
|
+
{deprecated ? <Badge size="sm">{labels.fieldDeprecated}</Badge> : null}
|
|
77
80
|
</span>
|
|
78
81
|
</td>
|
|
79
82
|
<td>{displayValue(type)}</td>
|
|
80
83
|
<td className="[&_p]:m-0">
|
|
81
84
|
{children}
|
|
82
85
|
{defaultValue ? (
|
|
83
|
-
<span className="mt-1 block text-muted-foreground">
|
|
86
|
+
<span className="mt-1 block text-muted-foreground">
|
|
87
|
+
{labels.fieldDefault} {defaultValue}
|
|
88
|
+
</span>
|
|
84
89
|
) : null}
|
|
85
90
|
</td>
|
|
86
91
|
</tr>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import { useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { TabsContent, TabsList, Tabs as TabsPrimitive, TabsTrigger } from '@/components/ui/tabs';
|
|
4
|
+
import { useLabels } from '@/lib/label-context';
|
|
4
5
|
import { slugify, toElementArray } from './utils';
|
|
5
6
|
|
|
6
7
|
interface TabChildProps {
|
|
@@ -18,6 +19,7 @@ export interface TabsProps {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function Tabs({ children, group }: TabsProps) {
|
|
22
|
+
const labels = useLabels();
|
|
21
23
|
const tabs = useMemo(() => {
|
|
22
24
|
const counts = new Map<string, number>();
|
|
23
25
|
|
|
@@ -94,7 +96,7 @@ export function Tabs({ children, group }: TabsProps) {
|
|
|
94
96
|
<TabsList
|
|
95
97
|
variant="line"
|
|
96
98
|
className="w-full justify-start gap-5 overflow-x-auto overflow-y-hidden p-0 shadow-[inset_0_-1px_0_var(--border)] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
97
|
-
aria-label=
|
|
99
|
+
aria-label={labels.contentTabs}
|
|
98
100
|
>
|
|
99
101
|
{tabs.map(tab => (
|
|
100
102
|
<TabsTrigger
|