@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
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
Folder as FolderIcon,
|
|
15
15
|
FolderOpen,
|
|
16
16
|
} from '@/components/icons';
|
|
17
|
+
import { useLabels } from '@/lib/label-context';
|
|
17
18
|
import { styles } from './styles';
|
|
18
19
|
|
|
19
20
|
export interface TreeFolderProps {
|
|
@@ -208,6 +209,7 @@ function visibleFocusables(root: HTMLElement): HTMLElement[] {
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
function TreeRoot({ children, style, className }: TreeProps) {
|
|
212
|
+
const labels = useLabels();
|
|
211
213
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
212
214
|
const searchRef = useRef({ text: '', timer: 0 });
|
|
213
215
|
|
|
@@ -334,7 +336,7 @@ function TreeRoot({ children, style, className }: TreeProps) {
|
|
|
334
336
|
onKeyDown={onKeyDown}
|
|
335
337
|
>
|
|
336
338
|
{/* biome-ignore lint/a11y/noNoninteractiveElementToInteractiveRole: tree is the required ARIA role for this widget */}
|
|
337
|
-
<ul role="tree" aria-label=
|
|
339
|
+
<ul role="tree" aria-label={labels.fileTree} className={styles.treeList}>
|
|
338
340
|
{content}
|
|
339
341
|
</ul>
|
|
340
342
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ImgHTMLAttributes } from 'react';
|
|
2
2
|
import { Dialog, DialogContent, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
|
|
3
|
+
import { useLabels } from '@/lib/label-context';
|
|
3
4
|
import { cn } from '@/lib/utils';
|
|
4
5
|
|
|
5
6
|
export interface ZoomableImageProps extends ImgHTMLAttributes<HTMLImageElement> {
|
|
@@ -13,6 +14,7 @@ export function ZoomableImage({
|
|
|
13
14
|
src,
|
|
14
15
|
...props
|
|
15
16
|
}: ZoomableImageProps) {
|
|
17
|
+
const labels = useLabels();
|
|
16
18
|
const image = (
|
|
17
19
|
<img
|
|
18
20
|
{...props}
|
|
@@ -26,7 +28,7 @@ export function ZoomableImage({
|
|
|
26
28
|
return image;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
const label = alt ?
|
|
31
|
+
const label = alt ? labels.viewNamedImage.replace('{alt}', () => alt) : labels.viewImage;
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Dialog>
|
|
@@ -47,7 +49,7 @@ export function ZoomableImage({
|
|
|
47
49
|
overlayClassName="bg-black/80 supports-backdrop-filter:backdrop-blur-sm"
|
|
48
50
|
className="flex max-h-[calc(100vh-2rem)] w-auto max-w-[calc(100vw-2rem)] items-center justify-center bg-transparent p-0 ring-0 shadow-none sm:max-w-[calc(100vw-2rem)]"
|
|
49
51
|
>
|
|
50
|
-
<DialogTitle className="sr-only">{alt ||
|
|
52
|
+
<DialogTitle className="sr-only">{alt || labels.fullSizeImage}</DialogTitle>
|
|
51
53
|
<img
|
|
52
54
|
src={src}
|
|
53
55
|
srcSet={props.srcSet}
|
|
@@ -2,6 +2,7 @@ import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
|
|
|
2
2
|
import { XIcon } from 'lucide-react';
|
|
3
3
|
import type * as React from 'react';
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { useLabels } from '@/lib/label-context';
|
|
5
6
|
import { cn } from '@/lib/utils';
|
|
6
7
|
|
|
7
8
|
function Dialog({ ...props }: DialogPrimitive.Root.Props) {
|
|
@@ -43,6 +44,7 @@ function DialogContent({
|
|
|
43
44
|
showCloseButton?: boolean;
|
|
44
45
|
overlayClassName?: string;
|
|
45
46
|
}) {
|
|
47
|
+
const labels = useLabels();
|
|
46
48
|
return (
|
|
47
49
|
<DialogPortal>
|
|
48
50
|
<DialogOverlay className={overlayClassName} />
|
|
@@ -61,7 +63,7 @@ function DialogContent({
|
|
|
61
63
|
render={<Button variant="ghost" className="absolute top-2 right-2" size="icon-sm" />}
|
|
62
64
|
>
|
|
63
65
|
<XIcon />
|
|
64
|
-
<span className="sr-only">
|
|
66
|
+
<span className="sr-only">{labels.close}</span>
|
|
65
67
|
</DialogPrimitive.Close>
|
|
66
68
|
)}
|
|
67
69
|
</DialogPrimitive.Popup>
|
|
@@ -83,6 +85,7 @@ function DialogFooter({
|
|
|
83
85
|
}: React.ComponentProps<'div'> & {
|
|
84
86
|
showCloseButton?: boolean;
|
|
85
87
|
}) {
|
|
88
|
+
const labels = useLabels();
|
|
86
89
|
return (
|
|
87
90
|
<div
|
|
88
91
|
data-slot="dialog-footer"
|
|
@@ -94,7 +97,9 @@ function DialogFooter({
|
|
|
94
97
|
>
|
|
95
98
|
{children}
|
|
96
99
|
{showCloseButton && (
|
|
97
|
-
<DialogPrimitive.Close render={<Button variant="outline" />}>
|
|
100
|
+
<DialogPrimitive.Close render={<Button variant="outline" />}>
|
|
101
|
+
{labels.close}
|
|
102
|
+
</DialogPrimitive.Close>
|
|
98
103
|
)}
|
|
99
104
|
</div>
|
|
100
105
|
);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import { useLabels } from '@/lib/label-context';
|
|
2
|
+
|
|
3
|
+
('use client');
|
|
2
4
|
|
|
3
5
|
import { Dialog as SheetPrimitive } from '@base-ui/react/dialog';
|
|
4
6
|
import { XIcon } from 'lucide-react';
|
|
@@ -45,6 +47,7 @@ function SheetContent({
|
|
|
45
47
|
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
46
48
|
showCloseButton?: boolean;
|
|
47
49
|
}) {
|
|
50
|
+
const labels = useLabels();
|
|
48
51
|
return (
|
|
49
52
|
<SheetPortal>
|
|
50
53
|
<SheetOverlay />
|
|
@@ -64,7 +67,7 @@ function SheetContent({
|
|
|
64
67
|
render={<Button variant="ghost" className="absolute top-3 right-3" size="icon-sm" />}
|
|
65
68
|
>
|
|
66
69
|
<XIcon />
|
|
67
|
-
<span className="sr-only">
|
|
70
|
+
<span className="sr-only">{labels.close}</span>
|
|
68
71
|
</SheetPrimitive.Close>
|
|
69
72
|
)}
|
|
70
73
|
</SheetPrimitive.Popup>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolveLocale } from '@/lib/locale';
|
|
2
|
+
import de from '@/lib/translations/de.json';
|
|
3
|
+
import en from '@/lib/translations/en.json';
|
|
4
|
+
import es from '@/lib/translations/es.json';
|
|
5
|
+
import fr from '@/lib/translations/fr.json';
|
|
6
|
+
import ja from '@/lib/translations/ja.json';
|
|
7
|
+
import zhHans from '@/lib/translations/zh-Hans.json';
|
|
8
|
+
import zhHant from '@/lib/translations/zh-Hant.json';
|
|
9
|
+
import type { ThemeLabels, Translations } from '@/lib/types';
|
|
10
|
+
|
|
11
|
+
export const englishLabels: ThemeLabels = en;
|
|
12
|
+
const dictionaries: Record<string, ThemeLabels> = {
|
|
13
|
+
en,
|
|
14
|
+
de,
|
|
15
|
+
es,
|
|
16
|
+
fr,
|
|
17
|
+
ja,
|
|
18
|
+
'zh-Hans': zhHans,
|
|
19
|
+
'zh-Hant': zhHant,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Merge from least to most specific; overrides win at each locale level. */
|
|
23
|
+
export function resolveLabels(locale: string, overrides: Translations = {}): ThemeLabels {
|
|
24
|
+
const tag = new Intl.Locale(resolveLocale(locale, 'en'));
|
|
25
|
+
const script = tag.script || (tag.language === 'zh' ? tag.maximize().script : undefined);
|
|
26
|
+
const candidates = new Set([
|
|
27
|
+
'en',
|
|
28
|
+
tag.language,
|
|
29
|
+
...(script ? [`${tag.language}-${script}`] : []),
|
|
30
|
+
tag.baseName,
|
|
31
|
+
]);
|
|
32
|
+
const labels = { ...englishLabels };
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
Object.assign(labels, dictionaries[candidate], overrides[candidate]);
|
|
35
|
+
}
|
|
36
|
+
return labels;
|
|
37
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Build-time alias target. Shiso replaces this module with the project's generated operations.
|
|
2
|
-
import type { NormalizedOperation } from '@/lib/types';
|
|
2
|
+
import type { NormalizedOperation, SchemaPage } from '@/lib/types';
|
|
3
3
|
|
|
4
4
|
export const OPENAPI_OPERATIONS: Record<string, NormalizedOperation> = {};
|
|
5
|
+
export const OPENAPI_SCHEMAS: Record<string, SchemaPage> = {};
|
package/src/lib/openapi.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { englishLabels } from '@/lib/labels';
|
|
1
2
|
/**
|
|
2
3
|
* Runtime helpers for OpenAPI reference pages. Operation data is produced at
|
|
3
4
|
* build time by scripts/generate-openapi.mjs and reaches the client through
|
|
@@ -5,9 +6,18 @@
|
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import type { BadgeColor } from '@/components/docs/Badge';
|
|
8
|
-
import { OPENAPI_OPERATIONS } from '@/lib/openapi.generated';
|
|
9
|
+
import { OPENAPI_OPERATIONS, OPENAPI_SCHEMAS } from '@/lib/openapi.generated';
|
|
9
10
|
import { createSlugger } from '@/lib/slug';
|
|
10
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ApiPlaygroundDisplay,
|
|
13
|
+
DocFrontmatter,
|
|
14
|
+
NormalizedOperation,
|
|
15
|
+
ResolvedApiPlayground,
|
|
16
|
+
SchemaPage,
|
|
17
|
+
SecurityScheme,
|
|
18
|
+
ThemeLabels,
|
|
19
|
+
TocEntry,
|
|
20
|
+
} from '@/lib/types';
|
|
11
21
|
|
|
12
22
|
export const METHOD_COLORS: Record<string, BadgeColor> = {
|
|
13
23
|
GET: 'green',
|
|
@@ -15,8 +25,33 @@ export const METHOD_COLORS: Record<string, BadgeColor> = {
|
|
|
15
25
|
PUT: 'orange',
|
|
16
26
|
PATCH: 'purple',
|
|
17
27
|
DELETE: 'red',
|
|
28
|
+
WEBHOOK: 'yellow',
|
|
18
29
|
};
|
|
19
30
|
|
|
31
|
+
const HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE']);
|
|
32
|
+
|
|
33
|
+
/** Short sidebar badge text for a method. */
|
|
34
|
+
export function methodBadgeText(method: string): string {
|
|
35
|
+
if (method === 'DELETE') return 'DEL';
|
|
36
|
+
if (method === 'WEBHOOK') return 'HOOK';
|
|
37
|
+
return method;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes an `openapi:` frontmatter value into a lookup key. Mirrors
|
|
42
|
+
* normalizeOperationKey in scripts/lib/openapi.mjs: the method (or "webhook")
|
|
43
|
+
* is uppercased and an optional leading spec qualifier is kept verbatim.
|
|
44
|
+
*/
|
|
45
|
+
export function normalizeOperationKey(value: unknown): string | undefined {
|
|
46
|
+
if (typeof value !== 'string' || !value.trim()) return undefined;
|
|
47
|
+
const [first, ...rest] = value.trim().split(/\s+/);
|
|
48
|
+
if (!rest.length) return undefined;
|
|
49
|
+
const upper = first.toUpperCase();
|
|
50
|
+
if (upper === 'WEBHOOK' || HTTP_METHODS.has(upper)) return `${upper} ${rest.join(' ')}`;
|
|
51
|
+
const inner = normalizeOperationKey(rest.join(' '));
|
|
52
|
+
return inner ? `${first} ${inner}` : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
20
55
|
export function methodColor(method?: string): BadgeColor {
|
|
21
56
|
return (method && METHOD_COLORS[method.toUpperCase()]) || 'gray';
|
|
22
57
|
}
|
|
@@ -28,14 +63,68 @@ export function statusColor(status: string): BadgeColor {
|
|
|
28
63
|
return 'gray';
|
|
29
64
|
}
|
|
30
65
|
|
|
66
|
+
/** Options that change which generated sections an operation page renders. */
|
|
67
|
+
export interface OperationSectionOptions {
|
|
68
|
+
/** Whether the "Try it" panel renders ahead of the reference sections. */
|
|
69
|
+
playground?: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolves the effective playground display for a page: the frontmatter
|
|
74
|
+
* `playground` value wins over `api.playground.display`.
|
|
75
|
+
*/
|
|
76
|
+
export function resolvePlaygroundDisplay(
|
|
77
|
+
playground: ResolvedApiPlayground,
|
|
78
|
+
frontmatter?: DocFrontmatter,
|
|
79
|
+
): ApiPlaygroundDisplay {
|
|
80
|
+
const override = frontmatter?.playground;
|
|
81
|
+
if (override === 'interactive' || override === 'simple' || override === 'none') {
|
|
82
|
+
return override;
|
|
83
|
+
}
|
|
84
|
+
return playground.display;
|
|
85
|
+
}
|
|
86
|
+
|
|
31
87
|
/** Looks up the operation bound by an `openapi:` frontmatter value. */
|
|
32
88
|
export function getOperation(key?: unknown): NormalizedOperation | undefined {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
89
|
+
const normalized = normalizeOperationKey(key);
|
|
90
|
+
return normalized ? OPENAPI_OPERATIONS[normalized] : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Looks up the schema bound by an `openapi-schema:` frontmatter value. */
|
|
94
|
+
export function getSchema(key?: unknown): SchemaPage | undefined {
|
|
95
|
+
if (typeof key !== 'string' || !key.trim()) return undefined;
|
|
96
|
+
return OPENAPI_SCHEMAS[key.trim().split(/\s+/).join(' ')];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Section headings for a schema page, in render order. Mirrors schemaAnchors
|
|
101
|
+
* in scripts/lib/openapi.mjs.
|
|
102
|
+
*/
|
|
103
|
+
export function schemaSections(page: SchemaPage, labels: ThemeLabels = englishLabels): TocEntry[] {
|
|
104
|
+
return [
|
|
105
|
+
...(page.schema.children?.length
|
|
106
|
+
? [{ name: labels.apiSchemaProperties, id: 'properties', size: 2 }]
|
|
107
|
+
: []),
|
|
108
|
+
...(page.example ? [{ name: labels.apiExample, id: 'example', size: 2 }] : []),
|
|
109
|
+
];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Where a security scheme's credential travels: a header unless it is a query or cookie API key. */
|
|
113
|
+
export function securityLocation(scheme: SecurityScheme): 'header' | 'query' | 'cookie' {
|
|
114
|
+
return scheme.type === 'apiKey' && scheme.in ? scheme.in : 'header';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Security schemes that are sent in the given parameter location. */
|
|
118
|
+
export function securityForLocation(
|
|
119
|
+
operation: NormalizedOperation,
|
|
120
|
+
location: 'header' | 'path' | 'query' | 'cookie',
|
|
121
|
+
): SecurityScheme[] {
|
|
122
|
+
return operation.security.filter(scheme => securityLocation(scheme) === location);
|
|
123
|
+
}
|
|
36
124
|
|
|
37
|
-
|
|
38
|
-
|
|
125
|
+
/** Display name of the header, query, or cookie entry a scheme adds. */
|
|
126
|
+
export function securityFieldName(scheme: SecurityScheme): string {
|
|
127
|
+
return scheme.type === 'apiKey' ? scheme.paramName || scheme.name : 'Authorization';
|
|
39
128
|
}
|
|
40
129
|
|
|
41
130
|
export function hasParameters(operation: NormalizedOperation): boolean {
|
|
@@ -46,18 +135,21 @@ export function hasParameters(operation: NormalizedOperation): boolean {
|
|
|
46
135
|
}
|
|
47
136
|
|
|
48
137
|
/** Parameter groups shared by the renderer and table of contents. */
|
|
49
|
-
export function operationParameterSections(
|
|
138
|
+
export function operationParameterSections(
|
|
139
|
+
operation: NormalizedOperation,
|
|
140
|
+
labels: ThemeLabels = englishLabels,
|
|
141
|
+
) {
|
|
50
142
|
const groups = [
|
|
51
|
-
{ location: 'header', name:
|
|
52
|
-
{ location: 'path', name:
|
|
53
|
-
{ location: 'query', name:
|
|
54
|
-
{ location: 'cookie', name:
|
|
143
|
+
{ location: 'header', name: labels.apiHeaders },
|
|
144
|
+
{ location: 'path', name: labels.apiPathParameters },
|
|
145
|
+
{ location: 'query', name: labels.apiQueryParameters },
|
|
146
|
+
{ location: 'cookie', name: labels.apiCookieParameters },
|
|
55
147
|
] as const;
|
|
56
148
|
|
|
57
149
|
return groups.filter(
|
|
58
150
|
({ location }) =>
|
|
59
151
|
operation.parameters[location].length > 0 ||
|
|
60
|
-
(location
|
|
152
|
+
securityForLocation(operation, location).length > 0,
|
|
61
153
|
);
|
|
62
154
|
}
|
|
63
155
|
|
|
@@ -66,14 +158,29 @@ export function operationParameterSections(operation: NormalizedOperation) {
|
|
|
66
158
|
* truth for section ids: the component, the table of contents, the content
|
|
67
159
|
* checker, and the search indexer all derive their anchors from these labels.
|
|
68
160
|
*/
|
|
69
|
-
export function operationSections(
|
|
161
|
+
export function operationSections(
|
|
162
|
+
operation: NormalizedOperation,
|
|
163
|
+
labels: ThemeLabels = englishLabels,
|
|
164
|
+
options: OperationSectionOptions = {},
|
|
165
|
+
): TocEntry[] {
|
|
70
166
|
const slugger = createSlugger();
|
|
167
|
+
const playground = options.playground && !operation.webhook;
|
|
71
168
|
const names = [
|
|
169
|
+
playground ? 'Try it' : undefined,
|
|
72
170
|
...operationParameterSections(operation).map(section => section.name),
|
|
73
|
-
operation.requestBody ? 'Request body' : undefined,
|
|
171
|
+
operation.requestBody ? (operation.webhook ? 'Payload' : 'Request body') : undefined,
|
|
74
172
|
operation.responses.length ? 'Responses' : undefined,
|
|
75
173
|
operation.samples.length ? 'Code samples' : undefined,
|
|
76
174
|
].filter((name): name is string => Boolean(name));
|
|
77
175
|
|
|
78
|
-
|
|
176
|
+
const translated = [
|
|
177
|
+
...(playground ? [labels.apiPlayground] : []),
|
|
178
|
+
...operationParameterSections(operation, labels).map(section => section.name),
|
|
179
|
+
...(operation.requestBody
|
|
180
|
+
? [operation.webhook ? labels.apiPayload : labels.apiRequestBody]
|
|
181
|
+
: []),
|
|
182
|
+
...(operation.responses.length ? [labels.apiResponses] : []),
|
|
183
|
+
...(operation.samples.length ? [labels.apiCodeSamples] : []),
|
|
184
|
+
];
|
|
185
|
+
return names.map((name, index) => ({ name: translated[index], id: slugger.slug(name), size: 2 }));
|
|
79
186
|
}
|
package/src/lib/site-config.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getPageByPathname as getSitePageByPathname,
|
|
9
9
|
normalizeDocsSite,
|
|
10
10
|
} from '@/lib/docs-config';
|
|
11
|
+
import { resolveLabels } from '@/lib/labels';
|
|
11
12
|
import { getTextDirection, resolveLocale } from '@/lib/locale';
|
|
12
13
|
import { DOCS_PREFIX, stripBase } from '@/lib/paths';
|
|
13
14
|
import { resolveSiteModel } from '@/lib/site-model';
|
|
@@ -53,10 +54,60 @@ export function getStandalonePage(pathname: string): StandalonePage | null {
|
|
|
53
54
|
return getStandalonePageByPathname(standalonePages, stripBase(pathname));
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
/**
|
|
57
|
+
/** Landing scope of a language: its default version. */
|
|
58
|
+
function getLanguageScope(language: string): DocsScope | undefined {
|
|
59
|
+
return docsSite.scopes.find(scope => scope.language === language && scope.isLanguageDefault);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Scope that owns the current pathname. A standalone page tagged with a
|
|
64
|
+
* `language` belongs to that language's landing scope; unknown paths and
|
|
65
|
+
* untagged standalone pages belong to the default scope.
|
|
66
|
+
*/
|
|
57
67
|
export function getScopeByPathname(pathname: string): DocsScope {
|
|
58
68
|
const page = getPageByPathname(pathname);
|
|
59
|
-
|
|
69
|
+
|
|
70
|
+
if (page) {
|
|
71
|
+
return getScopeForPage(docsSite, page);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const standalone = getStandalonePage(pathname);
|
|
75
|
+
const languageScope = standalone?.language ? getLanguageScope(standalone.language) : undefined;
|
|
76
|
+
|
|
77
|
+
return languageScope || getDefaultScope(docsSite);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The same standalone page in another language, matched by `key`. Untagged
|
|
82
|
+
* pages count as the default scope's language.
|
|
83
|
+
*/
|
|
84
|
+
export function getStandaloneCounterpart(
|
|
85
|
+
page: StandalonePage,
|
|
86
|
+
language: string,
|
|
87
|
+
): StandalonePage | null {
|
|
88
|
+
const defaultLanguage = getDefaultScope(docsSite).language;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
standalonePages.find(
|
|
92
|
+
candidate =>
|
|
93
|
+
candidate.key === page.key && (candidate.language || defaultLanguage) === language,
|
|
94
|
+
) || null
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Home link for the current pathname: the "/" standalone page in the current
|
|
100
|
+
* language when one exists, else "/", else the docs home when nothing owns "/".
|
|
101
|
+
*/
|
|
102
|
+
export function getHomeHref(pathname: string): string {
|
|
103
|
+
const root = standalonePages.find(page => page.path === '/');
|
|
104
|
+
|
|
105
|
+
if (!root) {
|
|
106
|
+
return docsHomeUrl;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const language = getScopeByPathname(pathname).language;
|
|
110
|
+
return (language && getStandaloneCounterpart(root, language)?.path) || '/';
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
/** Document language and direction for a pathname, from its scope's language. */
|
|
@@ -65,6 +116,19 @@ export function getLocaleByPathname(pathname: string): { lang: string; dir: 'ltr
|
|
|
65
116
|
return { lang, dir: getTextDirection(lang) };
|
|
66
117
|
}
|
|
67
118
|
|
|
119
|
+
/** Resolve UI text on every route, including standalone pages and language switches. */
|
|
120
|
+
export function getSiteModelByPathname(pathname: string) {
|
|
121
|
+
const { lang } = getLocaleByPathname(pathname);
|
|
122
|
+
const labels = resolveLabels(lang, shisoConfig.translations);
|
|
123
|
+
const customPrompt =
|
|
124
|
+
typeof siteConfig.search === 'object' ? siteConfig.search.prompt?.trim() : undefined;
|
|
125
|
+
return {
|
|
126
|
+
...siteModel,
|
|
127
|
+
labels,
|
|
128
|
+
search: { ...siteModel.search, prompt: customPrompt || labels.searchPlaceholder },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
68
132
|
export const siteName = siteModel.name;
|
|
69
133
|
|
|
70
134
|
/** Trailing-slash-insensitive route key for redirect matching. */
|
package/src/lib/site-model.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveCodeBlockConfig } from '@/lib/code-blocks';
|
|
2
|
+
import { resolveLabels, englishLabels as SHISO_THEME_LABELS } from '@/lib/labels';
|
|
2
3
|
import { toAbsoluteUrl, toHref } from '@/lib/paths';
|
|
3
4
|
import { resolveSearchConfig } from '@/lib/search/config';
|
|
4
5
|
import type {
|
|
@@ -18,37 +19,6 @@ import type {
|
|
|
18
19
|
ThemeLabels,
|
|
19
20
|
} from '@/lib/types';
|
|
20
21
|
|
|
21
|
-
const SHISO_THEME_LABELS: ThemeLabels = {
|
|
22
|
-
menu: 'Menu',
|
|
23
|
-
documentationNavigation: 'Documentation navigation',
|
|
24
|
-
sections: 'Sections',
|
|
25
|
-
tableOfContents: 'On this page',
|
|
26
|
-
tableOfContentsNavigation: 'Table of contents',
|
|
27
|
-
searchTitle: 'Search',
|
|
28
|
-
searching: 'Searching...',
|
|
29
|
-
searchUnavailable: 'Search unavailable',
|
|
30
|
-
noResults: 'No results',
|
|
31
|
-
lastUpdated: 'Last updated on',
|
|
32
|
-
relatedTopics: 'Related topics',
|
|
33
|
-
previousPage: 'Previous',
|
|
34
|
-
nextPage: 'Next',
|
|
35
|
-
notFound: 'Page not found',
|
|
36
|
-
dismissBanner: 'Dismiss banner',
|
|
37
|
-
toggleTheme: 'Toggle theme',
|
|
38
|
-
moreOptions: 'More options',
|
|
39
|
-
copied: 'Copied',
|
|
40
|
-
expand: 'Expand',
|
|
41
|
-
collapse: 'Collapse',
|
|
42
|
-
copyPage: 'Copy page',
|
|
43
|
-
copyPageDescription: 'Copy this page as Markdown',
|
|
44
|
-
viewMarkdown: 'View as Markdown',
|
|
45
|
-
viewMarkdownDescription: 'Open this page as plain Markdown',
|
|
46
|
-
openInChatGPT: 'Open in ChatGPT',
|
|
47
|
-
openInClaude: 'Open in Claude',
|
|
48
|
-
openInPerplexity: 'Open in Perplexity',
|
|
49
|
-
askQuestionsAboutPage: 'Ask questions about this page',
|
|
50
|
-
};
|
|
51
|
-
|
|
52
22
|
function isInternalHref(href: string): boolean {
|
|
53
23
|
return /^(?:#|\/|\.\.?\/)/.test(href);
|
|
54
24
|
}
|
|
@@ -78,6 +48,17 @@ function normalizeNavbar(config: DocsConfig['navbar']): NormalizedNavbar | null
|
|
|
78
48
|
return links.length || primary ? { links, primary } : null;
|
|
79
49
|
}
|
|
80
50
|
|
|
51
|
+
function normalizePlayground(config: DocsConfig['api']): SiteModel['api']['playground'] {
|
|
52
|
+
const playground = config?.playground;
|
|
53
|
+
const display =
|
|
54
|
+
playground?.display === 'simple' || playground?.display === 'none'
|
|
55
|
+
? playground.display
|
|
56
|
+
: 'interactive';
|
|
57
|
+
const proxy = playground?.proxy?.trim() || undefined;
|
|
58
|
+
|
|
59
|
+
return proxy ? { display, proxy } : { display };
|
|
60
|
+
}
|
|
61
|
+
|
|
81
62
|
function normalizeFooter(config: DocsConfig['footer']): NormalizedFooter | null {
|
|
82
63
|
const socials = (config?.socials || []).filter(link => !!link?.href).map(normalizeLink);
|
|
83
64
|
const links = (config?.links || [])
|
|
@@ -141,7 +122,8 @@ export function resolveSiteModel(
|
|
|
141
122
|
showTimestamp: config.metadata?.timestamp === true,
|
|
142
123
|
drilldown: config.interaction?.drilldown,
|
|
143
124
|
locale: shiso?.locale || 'en-US',
|
|
144
|
-
labels:
|
|
125
|
+
labels: resolveLabels(shiso?.locale || 'en-US', shiso?.translations),
|
|
126
|
+
api: { playground: normalizePlayground(config.api) },
|
|
145
127
|
docs,
|
|
146
128
|
};
|
|
147
129
|
}
|
|
@@ -108,7 +108,21 @@ export function normalizeStandalonePages(
|
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
const language = item?.language?.trim() || undefined;
|
|
112
|
+
|
|
113
|
+
if (language && !site.scopes.some(scope => scope.language === language)) {
|
|
114
|
+
throw invalid(
|
|
115
|
+
`standalone page "${path}" uses language "${language}", which is not declared in navigation.languages.`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Language-independent identity: "ja/home" in language "ja" is the "home" page.
|
|
120
|
+
const key =
|
|
121
|
+
language && fileSlug.startsWith(`${language}/`)
|
|
122
|
+
? fileSlug.slice(language.length + 1)
|
|
123
|
+
: fileSlug;
|
|
124
|
+
|
|
125
|
+
pages.push({ path, filePath, title: item?.title?.trim() || undefined, language, key });
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
return pages;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"menu": "Menü",
|
|
3
|
+
"documentationNavigation": "Dokumentationsnavigation",
|
|
4
|
+
"sections": "Abschnitte",
|
|
5
|
+
"tableOfContents": "Auf dieser Seite",
|
|
6
|
+
"tableOfContentsNavigation": "Inhaltsverzeichnis",
|
|
7
|
+
"searchTitle": "Suchen",
|
|
8
|
+
"searching": "Suche läuft...",
|
|
9
|
+
"searchUnavailable": "Suche nicht verfügbar",
|
|
10
|
+
"noResults": "Keine Ergebnisse",
|
|
11
|
+
"lastUpdated": "Zuletzt aktualisiert am",
|
|
12
|
+
"relatedTopics": "Verwandte Themen",
|
|
13
|
+
"previousPage": "Zurück",
|
|
14
|
+
"nextPage": "Weiter",
|
|
15
|
+
"notFound": "Seite nicht gefunden",
|
|
16
|
+
"dismissBanner": "Banner schließen",
|
|
17
|
+
"toggleTheme": "Design wechseln",
|
|
18
|
+
"moreOptions": "Weitere Optionen",
|
|
19
|
+
"copied": "Kopiert",
|
|
20
|
+
"expand": "Aufklappen",
|
|
21
|
+
"collapse": "Zuklappen",
|
|
22
|
+
"copyPage": "Seite kopieren",
|
|
23
|
+
"copyPageDescription": "Diese Seite als Markdown kopieren",
|
|
24
|
+
"viewMarkdown": "Als Markdown anzeigen",
|
|
25
|
+
"viewMarkdownDescription": "Diese Seite als reinen Markdown-Text öffnen",
|
|
26
|
+
"openInChatGPT": "In ChatGPT öffnen",
|
|
27
|
+
"openInClaude": "In Claude öffnen",
|
|
28
|
+
"openInPerplexity": "In Perplexity öffnen",
|
|
29
|
+
"askQuestionsAboutPage": "Fragen zu dieser Seite stellen",
|
|
30
|
+
"searchPlaceholder": "Suchen...",
|
|
31
|
+
"poweredBy": "Erstellt mit",
|
|
32
|
+
"copyCode": "Code kopieren",
|
|
33
|
+
"editPage": "Diese Seite bearbeiten",
|
|
34
|
+
"feedbackPrompt": "War diese Seite hilfreich?",
|
|
35
|
+
"feedbackYes": "Ja",
|
|
36
|
+
"feedbackNo": "Nein",
|
|
37
|
+
"feedbackSuccess": "Vielen Dank für dein Feedback!",
|
|
38
|
+
"feedbackError": "Feedback konnte nicht gesendet werden. Bitte versuche es erneut.",
|
|
39
|
+
"close": "Schließen",
|
|
40
|
+
"filterUpdates": "Aktualisierungen nach Tag filtern",
|
|
41
|
+
"clear": "Zurücksetzen",
|
|
42
|
+
"noUpdates": "Keine Aktualisierungen gefunden.",
|
|
43
|
+
"codeSnippets": "Codebeispiele",
|
|
44
|
+
"contentTabs": "Inhaltsregisterkarten",
|
|
45
|
+
"fileTree": "Dateibaum",
|
|
46
|
+
"diagram": "Diagramm",
|
|
47
|
+
"diagramError": "Dieses Diagramm konnte nicht angezeigt werden.",
|
|
48
|
+
"zoomOut": "Verkleinern",
|
|
49
|
+
"resetView": "Ansicht zurücksetzen",
|
|
50
|
+
"zoomIn": "Vergrößern",
|
|
51
|
+
"viewImage": "Bild in voller Größe anzeigen",
|
|
52
|
+
"viewNamedImage": "„{alt}“ in voller Größe anzeigen",
|
|
53
|
+
"fullSizeImage": "Bild in voller Größe",
|
|
54
|
+
"diagramSource": "Diagrammquelltext",
|
|
55
|
+
"fieldName": "Name",
|
|
56
|
+
"fieldType": "Typ",
|
|
57
|
+
"fieldDescription": "Beschreibung",
|
|
58
|
+
"fieldRequired": "erforderlich",
|
|
59
|
+
"fieldDeprecated": "veraltet",
|
|
60
|
+
"fieldDefault": "Standard:",
|
|
61
|
+
"fieldOptions": "Optionen:",
|
|
62
|
+
"allowedTypes": "Zulässige Typen",
|
|
63
|
+
"properties": "Eigenschaften",
|
|
64
|
+
"apiHeaders": "Header",
|
|
65
|
+
"apiPathParameters": "Pfadparameter",
|
|
66
|
+
"apiQueryParameters": "Abfrageparameter",
|
|
67
|
+
"apiCookieParameters": "Cookie-Parameter",
|
|
68
|
+
"apiRequestBody": "Anfragetext",
|
|
69
|
+
"apiResponses": "Antworten",
|
|
70
|
+
"apiCodeSamples": "Codebeispiele",
|
|
71
|
+
"apiExampleRequest": "Beispielanfrage",
|
|
72
|
+
"apiExampleResponse": "Beispiel für {status}",
|
|
73
|
+
"apiCredentials": "Anmeldedaten, z. B.",
|
|
74
|
+
"apiPlayground": "Ausprobieren",
|
|
75
|
+
"apiServer": "Server",
|
|
76
|
+
"apiAuthorization": "Autorisierung",
|
|
77
|
+
"apiUsername": "Benutzername",
|
|
78
|
+
"apiPassword": "Passwort",
|
|
79
|
+
"apiToken": "Token",
|
|
80
|
+
"apiApiKey": "API-Schlüssel",
|
|
81
|
+
"apiBody": "Body",
|
|
82
|
+
"apiSend": "Anfrage senden",
|
|
83
|
+
"apiSending": "Wird gesendet...",
|
|
84
|
+
"apiCancel": "Abbrechen",
|
|
85
|
+
"apiRequest": "Anfrage",
|
|
86
|
+
"apiResponse": "Antwort",
|
|
87
|
+
"apiResponseHeaders": "Antwort-Header",
|
|
88
|
+
"apiResponseBody": "Antwort-Body",
|
|
89
|
+
"apiNoResponse": "Senden Sie eine Anfrage, um die Antwort zu sehen.",
|
|
90
|
+
"apiRequestFailed": "Die Anfrage konnte nicht gesendet werden. Die API blockiert möglicherweise Browser-Anfragen (CORS) oder ist nicht erreichbar; konfigurieren Sie einen Playground-Proxy oder führen Sie das cURL-Beispiel im Terminal aus.",
|
|
91
|
+
"apiRequestTimedOut": "Zeitüberschreitung der Anfrage.",
|
|
92
|
+
"apiCookiesUnsupported": "Cookie-Parameter können im Browser nicht gesetzt werden und werden nicht gesendet.",
|
|
93
|
+
"apiOptional": "optional",
|
|
94
|
+
"apiElapsed": "{ms} ms",
|
|
95
|
+
"apiCredentialsStored": "Zugangsdaten bleiben in diesem Browser-Tab und werden nur an die API gesendet.",
|
|
96
|
+
"apiPayload": "Nutzdaten",
|
|
97
|
+
"apiSchemaProperties": "Eigenschaften",
|
|
98
|
+
"apiExample": "Beispiel"
|
|
99
|
+
}
|