@umami/shiso 1.17.0 → 1.18.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 +147 -160
- 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 +668 -22
- package/dist/chunks/ganttDiagram-EL5Y4UJY.js +1 -1
- package/dist/chunks/src.js +1 -1
- package/dist/entry-client.js +1 -1
- package/dist/entry-server.js +1 -1
- package/docs.schema.json +4 -0
- package/package.json +1 -1
- package/scripts/check-content.mjs +2 -0
- package/scripts/load-shiso-config.mjs +30 -1
- package/src/App.tsx +32 -22
- package/src/components/CodeBlock.tsx +3 -1
- package/src/components/Docs.tsx +3 -1
- package/src/components/Footer.tsx +3 -1
- package/src/components/Header.tsx +4 -3
- package/src/components/LanguageSwitcher.tsx +17 -4
- package/src/components/OpenApiOperation.tsx +22 -15
- package/src/components/PageActions.tsx +8 -7
- 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.ts +21 -8
- package/src/lib/site-config.ts +66 -2
- package/src/lib/site-model.ts +2 -32
- package/src/lib/standalone-pages.ts +15 -1
- package/src/lib/translations/de.json +74 -0
- package/src/lib/translations/en.json +74 -0
- package/src/lib/translations/es.json +74 -0
- package/src/lib/translations/fr.json +74 -0
- package/src/lib/translations/ja.json +74 -0
- package/src/lib/translations/zh-Hans.json +74 -0
- package/src/lib/translations/zh-Hant.json +74 -0
- package/src/lib/types.ts +20 -32
- package/types/config.d.ts +7 -1
- package/types/labels.d.ts +76 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rr as __commonJSMin, zr as __toESM } from "./docs.js";
|
|
2
2
|
import { n as __name } from "./chunk-Y2CYZVJY.js";
|
|
3
3
|
import { a as nogamma, c as Rgb, d as define_default, f as extend, g as require_dayjs_min, i as hue, m as log, p as select_default, s as Color, u as rgbConvert } from "./src.js";
|
|
4
4
|
import { H as setAccDescription, K as setDiagramTitle, U as setAccTitle, a as clear, c as configureSvgSize, s as common_default, v as getAccDescription, w as getDiagramTitle, x as getConfig2, y as getAccTitle } from "./chunk-DU6HZSFF.js";
|
package/dist/chunks/src.js
CHANGED
package/dist/entry-client.js
CHANGED
package/dist/entry-server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fr as createPath, Ir as parsePath, Lr as ABSOLUTE_URL_REGEX, jr as Router } from "./chunks/docs.js";
|
|
2
2
|
import { a as docsSite, c as getSeo, d as standalonePages, f as getDocModule, g as toAbsoluteUrl, h as BASE_URL, i as docsHomeUrl, l as siteConfig, m as getScopeForPage, n as buildHead, o as getLocaleByPathname, p as getLastModified, r as renderHeadToString, s as getRedirects, t as App, u as siteName } from "./chunks/App.js";
|
|
3
3
|
import * as React$1 from "react";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
package/docs.schema.json
CHANGED
|
@@ -232,6 +232,10 @@
|
|
|
232
232
|
"title": {
|
|
233
233
|
"allOf": [{ "$ref": "#/definitions/stringValue" }],
|
|
234
234
|
"description": "Page title used in the document head. Frontmatter title wins."
|
|
235
|
+
},
|
|
236
|
+
"language": {
|
|
237
|
+
"allOf": [{ "$ref": "#/definitions/nonEmptyStringValue" }],
|
|
238
|
+
"description": "Navigation language this page belongs to, e.g. \"ja\". Must match a language in navigation.languages. The language selector links pages whose file slugs match once the language folder is removed, e.g. \"ja/home\" and \"home\"."
|
|
235
239
|
}
|
|
236
240
|
}
|
|
237
241
|
}
|
package/package.json
CHANGED
|
@@ -87,6 +87,8 @@ function collectPageReferences(navigation) {
|
|
|
87
87
|
|
|
88
88
|
function visitContainer(container) {
|
|
89
89
|
if (!container || typeof container !== 'object') return;
|
|
90
|
+
// Groups reached through tabs/dropdowns/languages carry their own landing page.
|
|
91
|
+
if (typeof container.root === 'string') references.push(container.root);
|
|
90
92
|
if (Array.isArray(container.pages)) visitItems(container.pages);
|
|
91
93
|
for (const key of ['tabs', 'dropdowns', 'groups', 'versions', 'languages']) {
|
|
92
94
|
if (Array.isArray(container[key])) container[key].forEach(visitContainer);
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
4
|
import { createJiti } from 'jiti';
|
|
5
|
+
import englishLabels from '../src/lib/translations/en.json' with { type: 'json' };
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Loads the optional project code config (shiso.config.ts/.mjs/.js).
|
|
@@ -16,7 +17,7 @@ import { createJiti } from 'jiti';
|
|
|
16
17
|
export const SHISO_CONFIG_FILES = ['shiso.config.ts', 'shiso.config.mjs', 'shiso.config.js'];
|
|
17
18
|
|
|
18
19
|
const STRING_KEYS = ['docsPrefix', 'contentDir', 'siteUrl', 'locale'];
|
|
19
|
-
const KNOWN_KEYS = [...STRING_KEYS, 'mdx'];
|
|
20
|
+
const KNOWN_KEYS = [...STRING_KEYS, 'mdx', 'translations'];
|
|
20
21
|
const MDX_KEYS = ['remarkPlugins', 'rehypePlugins'];
|
|
21
22
|
|
|
22
23
|
let importGeneration = 0;
|
|
@@ -88,6 +89,31 @@ function resolveMdxConfig(value, sourcePath) {
|
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
function resolveTranslations(value, sourcePath) {
|
|
93
|
+
if (value === undefined) return undefined;
|
|
94
|
+
const invalid = message => {
|
|
95
|
+
throw new ShisoConfigLoadError(message, { code: 'INVALID_OPTION', sourcePath });
|
|
96
|
+
};
|
|
97
|
+
if (!isPlainObject(value)) invalid('Shiso config option "translations" must be an object.');
|
|
98
|
+
const result = {};
|
|
99
|
+
for (const [locale, labels] of Object.entries(value)) {
|
|
100
|
+
let canonical;
|
|
101
|
+
try {
|
|
102
|
+
canonical = Intl.getCanonicalLocales(locale)[0];
|
|
103
|
+
} catch {
|
|
104
|
+
invalid(`Invalid translations locale "${locale}". Use a BCP 47 tag such as "fr" or "pt-BR".`);
|
|
105
|
+
}
|
|
106
|
+
if (!isPlainObject(labels)) invalid(`translations.${locale} must be an object.`);
|
|
107
|
+
for (const [key, label] of Object.entries(labels)) {
|
|
108
|
+
if (!Object.hasOwn(englishLabels, key))
|
|
109
|
+
invalid(`Unknown UI label "translations.${locale}.${key}".`);
|
|
110
|
+
if (typeof label !== 'string') invalid(`translations.${locale}.${key} must be a string.`);
|
|
111
|
+
}
|
|
112
|
+
result[canonical] = { ...result[canonical], ...labels };
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
91
117
|
/**
|
|
92
118
|
* Applies defaults and normalization. Single source of truth for resolved
|
|
93
119
|
* values, so runtime and build-time consumers never re-implement defaulting.
|
|
@@ -103,6 +129,9 @@ export function resolveShisoConfig(raw = {}, sourcePath = null) {
|
|
|
103
129
|
siteUrl: raw.siteUrl?.trim().replace(/\/+$/, '') || undefined,
|
|
104
130
|
locale: raw.locale?.trim() || 'en-US',
|
|
105
131
|
mdx: resolveMdxConfig(raw.mdx, sourcePath),
|
|
132
|
+
...(raw.translations === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { translations: resolveTranslations(raw.translations, sourcePath) }),
|
|
106
135
|
};
|
|
107
136
|
}
|
|
108
137
|
|
package/src/App.tsx
CHANGED
|
@@ -3,12 +3,18 @@ import '@fontsource/jetbrains-mono/400.css';
|
|
|
3
3
|
import '@umami/shiso/styles.css';
|
|
4
4
|
|
|
5
5
|
import { MDXProvider } from '@mdx-js/react';
|
|
6
|
-
import { Navigate, Route, Routes } from 'react-router';
|
|
6
|
+
import { Navigate, Route, Routes, useLocation } from 'react-router';
|
|
7
7
|
import { CodeBlock } from '@/components/CodeBlock';
|
|
8
8
|
import * as docsComponents from '@/components/docs/index';
|
|
9
9
|
import { Layout } from '@/components/Layout';
|
|
10
10
|
import { TooltipProvider } from '@/components/ui/tooltip';
|
|
11
|
-
import {
|
|
11
|
+
import { LabelContext } from '@/lib/label-context';
|
|
12
|
+
import {
|
|
13
|
+
docsHomeUrl,
|
|
14
|
+
getSiteModelByPathname,
|
|
15
|
+
hasRootStandalonePage,
|
|
16
|
+
standalonePages,
|
|
17
|
+
} from '@/lib/site-config';
|
|
12
18
|
import { DocPage } from '@/pages/DocPage';
|
|
13
19
|
import { StandalonePageView } from '@/pages/StandalonePage';
|
|
14
20
|
|
|
@@ -27,27 +33,31 @@ const mdxComponents = {
|
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
export function App() {
|
|
36
|
+
const { pathname } = useLocation();
|
|
37
|
+
const siteModel = getSiteModelByPathname(pathname);
|
|
30
38
|
return (
|
|
31
|
-
<
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
<LabelContext.Provider value={siteModel.labels}>
|
|
40
|
+
<TooltipProvider>
|
|
41
|
+
<MDXProvider components={mdxComponents}>
|
|
42
|
+
<Layout site={siteModel}>
|
|
43
|
+
<Routes>
|
|
44
|
+
{standalonePages.map(page => (
|
|
45
|
+
<Route
|
|
46
|
+
key={page.path}
|
|
47
|
+
path={page.path}
|
|
48
|
+
element={<StandalonePageView page={page} site={siteModel} />}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
{/* When the default scope's landing page is the root, or a
|
|
43
52
|
standalone page owns "/", there is nothing to redirect. */}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
{docsHomeUrl !== '/' && !hasRootStandalonePage ? (
|
|
54
|
+
<Route path="/" element={<Navigate to={docsHomeUrl} replace />} />
|
|
55
|
+
) : null}
|
|
56
|
+
<Route path="*" element={<DocPage site={siteModel} />} />
|
|
57
|
+
</Routes>
|
|
58
|
+
</Layout>
|
|
59
|
+
</MDXProvider>
|
|
60
|
+
</TooltipProvider>
|
|
61
|
+
</LabelContext.Provider>
|
|
52
62
|
);
|
|
53
63
|
}
|
|
@@ -3,6 +3,7 @@ import { type ComponentProps, type CSSProperties, useRef, useState } from 'react
|
|
|
3
3
|
import { CheckIcon, Copy } from '@/components/icons';
|
|
4
4
|
import { Button } from '@/components/ui/button';
|
|
5
5
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
6
|
+
import { useLabels } from '@/lib/label-context';
|
|
6
7
|
import { cn } from '@/lib/utils';
|
|
7
8
|
import { Mermaid, type MermaidPlacement } from './docs/Mermaid';
|
|
8
9
|
|
|
@@ -60,6 +61,7 @@ function copyText(pre: HTMLPreElement | null, language?: string): string {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
export function CodeBlock({ children, className, style, ...rest }: CodeBlockProps) {
|
|
64
|
+
const labels = useLabels();
|
|
63
65
|
const {
|
|
64
66
|
'data-title': title,
|
|
65
67
|
'data-language': language,
|
|
@@ -137,7 +139,7 @@ export function CodeBlock({ children, className, style, ...rest }: CodeBlockProp
|
|
|
137
139
|
title ? 'top-1' : 'top-2.5',
|
|
138
140
|
)}
|
|
139
141
|
onClick={handleCopy}
|
|
140
|
-
aria-label=
|
|
142
|
+
aria-label={copied ? labels.copied : labels.copyCode}
|
|
141
143
|
>
|
|
142
144
|
{copied ? <CheckIcon className="size-3.5 text-primary" /> : <Copy className="size-3.5" />}
|
|
143
145
|
</Button>
|
package/src/components/Docs.tsx
CHANGED
|
@@ -60,7 +60,9 @@ export function Docs({ page, doc, site }: DocsProps) {
|
|
|
60
60
|
|
|
61
61
|
// API reference pages append their generated section anchors to the TOC.
|
|
62
62
|
const operation = getOperation(doc.frontmatter?.openapi);
|
|
63
|
-
const toc = operation
|
|
63
|
+
const toc = operation
|
|
64
|
+
? [...(doc.toc || []), ...operationSections(operation, site.labels)]
|
|
65
|
+
: doc.toc;
|
|
64
66
|
|
|
65
67
|
return (
|
|
66
68
|
<PanelProvider>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ConfiguredIcon } from '@/components/ConfiguredIcon';
|
|
2
|
+
import { useLabels } from '@/lib/label-context';
|
|
2
3
|
import type { NormalizedFooter } from '@/lib/types';
|
|
3
4
|
|
|
4
5
|
export function Footer({
|
|
@@ -8,6 +9,7 @@ export function Footer({
|
|
|
8
9
|
footer: NormalizedFooter | null;
|
|
9
10
|
className?: string;
|
|
10
11
|
}) {
|
|
12
|
+
const labels = useLabels();
|
|
11
13
|
if (!footer) {
|
|
12
14
|
return null;
|
|
13
15
|
}
|
|
@@ -48,7 +50,7 @@ export function Footer({
|
|
|
48
50
|
href="https://shiso.umami.is?ref=docs-footer"
|
|
49
51
|
className="text-sm hover:text-foreground"
|
|
50
52
|
>
|
|
51
|
-
|
|
53
|
+
{labels.poweredBy} <span className="font-bold">shiso</span>
|
|
52
54
|
</a>
|
|
53
55
|
) : (
|
|
54
56
|
<span />
|
|
@@ -6,7 +6,7 @@ import { ThemeToggle } from '@/components/ThemeToggle';
|
|
|
6
6
|
import { TopNav } from '@/components/TopNav';
|
|
7
7
|
import { VersionSwitcher } from '@/components/VersionSwitcher';
|
|
8
8
|
import { isExternalHref } from '@/lib/paths';
|
|
9
|
-
import {
|
|
9
|
+
import { getHomeHref, getScopeByPathname } from '@/lib/site-config';
|
|
10
10
|
import type { NormalizedLink, SiteModel } from '@/lib/types';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -62,8 +62,9 @@ export function Header({ site }: { site: SiteModel }) {
|
|
|
62
62
|
const { pathname } = useLocation();
|
|
63
63
|
// The header renders the navigation of whichever scope owns the current page.
|
|
64
64
|
const docs = getScopeByPathname(pathname).docs;
|
|
65
|
-
// The brand links to the standalone home page
|
|
66
|
-
|
|
65
|
+
// The brand links to the standalone home page in the current language when
|
|
66
|
+
// one owns "/", else the docs home.
|
|
67
|
+
const brandHref = logo?.href || getHomeHref(pathname);
|
|
67
68
|
const hasBrand = !!name || !!logo?.light || !!logo?.dark;
|
|
68
69
|
const brandClassName =
|
|
69
70
|
'inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]';
|
|
@@ -9,12 +9,19 @@ import {
|
|
|
9
9
|
} from '@/components/ui/dropdown-menu';
|
|
10
10
|
import { getLanguageScopes } from '@/lib/docs-config';
|
|
11
11
|
import { getLanguageName, isValidLocale } from '@/lib/locale';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
docsSite,
|
|
14
|
+
getScopeByPathname,
|
|
15
|
+
getStandaloneCounterpart,
|
|
16
|
+
getStandalonePage,
|
|
17
|
+
} from '@/lib/site-config';
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* Language selector for multi-language sites. Each option is a language's
|
|
16
|
-
* landing scope — its default version — so switching languages
|
|
17
|
-
*
|
|
21
|
+
* landing scope — its default version — so switching languages lands on that
|
|
22
|
+
* language's default-version first page. On a standalone page (e.g. a home
|
|
23
|
+
* page) it lands on the same page in the chosen language when one exists.
|
|
24
|
+
* Hidden languages never
|
|
18
25
|
* appear as options. Each language is shown by its native name, e.g. "ja"
|
|
19
26
|
* as "日本語".
|
|
20
27
|
*/
|
|
@@ -23,6 +30,7 @@ export function LanguageSwitcher() {
|
|
|
23
30
|
const navigate = useNavigate();
|
|
24
31
|
const current = getScopeByPathname(pathname);
|
|
25
32
|
const options = getLanguageScopes(docsSite);
|
|
33
|
+
const standalone = getStandalonePage(pathname);
|
|
26
34
|
|
|
27
35
|
if (!current.language || (options.length < 2 && !current.hidden)) {
|
|
28
36
|
return null;
|
|
@@ -47,7 +55,12 @@ export function LanguageSwitcher() {
|
|
|
47
55
|
key={scope.id}
|
|
48
56
|
onClick={() => {
|
|
49
57
|
if (scope.language !== current.language) {
|
|
50
|
-
|
|
58
|
+
const counterpart =
|
|
59
|
+
standalone && scope.language
|
|
60
|
+
? getStandaloneCounterpart(standalone, scope.language)
|
|
61
|
+
: null;
|
|
62
|
+
|
|
63
|
+
navigate(counterpart?.path || scope.firstPageUrl);
|
|
51
64
|
}
|
|
52
65
|
}}
|
|
53
66
|
>
|
|
@@ -3,16 +3,18 @@ import { Badge } from '@/components/docs/Badge';
|
|
|
3
3
|
import { CodeGroup } from '@/components/docs/CodeGroup';
|
|
4
4
|
import { Expandable } from '@/components/docs/Expandable';
|
|
5
5
|
import { PropertiesTable } from '@/components/docs/PropertiesTable';
|
|
6
|
+
import { useLabels } from '@/lib/label-context';
|
|
6
7
|
import { operationParameterSections, operationSections, statusColor } from '@/lib/openapi';
|
|
7
|
-
import type { NormalizedOperation, SchemaNode } from '@/lib/types';
|
|
8
|
+
import type { NormalizedOperation, SchemaNode, ThemeLabels } from '@/lib/types';
|
|
8
9
|
|
|
9
10
|
function FieldChildren({ node }: { node: SchemaNode }) {
|
|
11
|
+
const labels = useLabels();
|
|
10
12
|
return (
|
|
11
13
|
<>
|
|
12
14
|
{node.description}
|
|
13
15
|
{node.enum && node.enum.length > 0 && (
|
|
14
16
|
<div className="mt-1">
|
|
15
|
-
|
|
17
|
+
{labels.fieldOptions}{' '}
|
|
16
18
|
{node.enum.map((value, index) => (
|
|
17
19
|
<span key={value}>
|
|
18
20
|
{index > 0 && ', '}
|
|
@@ -25,12 +27,14 @@ function FieldChildren({ node }: { node: SchemaNode }) {
|
|
|
25
27
|
);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
function schemaDetails(node: SchemaNode) {
|
|
30
|
+
function schemaDetails(node: SchemaNode, labels: ThemeLabels) {
|
|
29
31
|
if (!node.children?.length) return undefined;
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<Expandable
|
|
33
|
-
title={
|
|
35
|
+
title={
|
|
36
|
+
node.type === 'oneOf' || node.type === 'anyOf' ? labels.allowedTypes : labels.properties
|
|
37
|
+
}
|
|
34
38
|
>
|
|
35
39
|
<SchemaTable nodes={node.children} />
|
|
36
40
|
</Expandable>
|
|
@@ -38,6 +42,7 @@ function schemaDetails(node: SchemaNode) {
|
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
function SchemaTable({ nodes }: { nodes: SchemaNode[] }) {
|
|
45
|
+
const labels = useLabels();
|
|
41
46
|
return (
|
|
42
47
|
<PropertiesTable>
|
|
43
48
|
{nodes.map(node => (
|
|
@@ -48,7 +53,7 @@ function SchemaTable({ nodes }: { nodes: SchemaNode[] }) {
|
|
|
48
53
|
required={node.required}
|
|
49
54
|
deprecated={node.deprecated}
|
|
50
55
|
default={node.default}
|
|
51
|
-
details={schemaDetails(node)}
|
|
56
|
+
details={schemaDetails(node, labels)}
|
|
52
57
|
>
|
|
53
58
|
<FieldChildren node={node} />
|
|
54
59
|
</PropertiesTable.Row>
|
|
@@ -105,17 +110,19 @@ export interface OpenApiOperationProps {
|
|
|
105
110
|
|
|
106
111
|
/** The generated reference for one API operation, rendered under the page body. */
|
|
107
112
|
export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
108
|
-
const
|
|
113
|
+
const labels = useLabels();
|
|
114
|
+
const sectionEntries = operationSections(operation);
|
|
115
|
+
const sections = new Map(sectionEntries.map(entry => [entry.name, entry.id]));
|
|
109
116
|
|
|
110
117
|
return (
|
|
111
118
|
<div className="docs-markdown">
|
|
112
|
-
{operationParameterSections(operation).map(({ location, name }) => (
|
|
119
|
+
{operationParameterSections(operation, labels).map(({ location, name }, index) => (
|
|
113
120
|
<section key={location}>
|
|
114
|
-
<h2 id={
|
|
121
|
+
<h2 id={sectionEntries[index].id}>{name}</h2>
|
|
115
122
|
<PropertiesTable>
|
|
116
123
|
{location === 'header' && operation.security.length > 0 && (
|
|
117
124
|
<PropertiesTable.Row name="Authorization" type="string" required>
|
|
118
|
-
|
|
125
|
+
{labels.apiCredentials} <code>Bearer <token></code>.
|
|
119
126
|
</PropertiesTable.Row>
|
|
120
127
|
)}
|
|
121
128
|
{operation.parameters[location].map(parameter => (
|
|
@@ -126,7 +133,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
126
133
|
required={parameter.required}
|
|
127
134
|
deprecated={parameter.deprecated}
|
|
128
135
|
default={parameter.default}
|
|
129
|
-
details={schemaDetails(parameter)}
|
|
136
|
+
details={schemaDetails(parameter, labels)}
|
|
130
137
|
>
|
|
131
138
|
<FieldChildren node={parameter} />
|
|
132
139
|
</PropertiesTable.Row>
|
|
@@ -136,12 +143,12 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
136
143
|
))}
|
|
137
144
|
{operation.requestBody && (
|
|
138
145
|
<section>
|
|
139
|
-
<h2 id={sections.get('Request body')}>
|
|
146
|
+
<h2 id={sections.get('Request body')}>{labels.apiRequestBody}</h2>
|
|
140
147
|
<SchemaFields node={operation.requestBody.schema} />
|
|
141
148
|
{operation.requestBody.example && (
|
|
142
149
|
<HighlightedCode
|
|
143
150
|
language="json"
|
|
144
|
-
title=
|
|
151
|
+
title={labels.apiExampleRequest}
|
|
145
152
|
html={operation.requestBody.exampleHtml}
|
|
146
153
|
source={operation.requestBody.example}
|
|
147
154
|
/>
|
|
@@ -150,7 +157,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
150
157
|
)}
|
|
151
158
|
{operation.responses.length > 0 && (
|
|
152
159
|
<section>
|
|
153
|
-
<h2 id={sections.get('Responses')}>
|
|
160
|
+
<h2 id={sections.get('Responses')}>{labels.apiResponses}</h2>
|
|
154
161
|
{operation.responses.map(response => (
|
|
155
162
|
<div key={response.status} className="mt-6 first:mt-0">
|
|
156
163
|
<div className="flex items-center gap-2">
|
|
@@ -165,7 +172,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
165
172
|
{response.example && (
|
|
166
173
|
<HighlightedCode
|
|
167
174
|
language="json"
|
|
168
|
-
title={
|
|
175
|
+
title={labels.apiExampleResponse.replace('{status}', () => response.status)}
|
|
169
176
|
html={response.exampleHtml}
|
|
170
177
|
source={response.example}
|
|
171
178
|
/>
|
|
@@ -176,7 +183,7 @@ export function OpenApiOperation({ operation }: OpenApiOperationProps) {
|
|
|
176
183
|
)}
|
|
177
184
|
{operation.samples.length > 0 && (
|
|
178
185
|
<section>
|
|
179
|
-
<h2 id={sections.get('Code samples')}>
|
|
186
|
+
<h2 id={sections.get('Code samples')}>{labels.apiCodeSamples}</h2>
|
|
180
187
|
<CodeGroup>
|
|
181
188
|
{operation.samples.map(sample => (
|
|
182
189
|
<HighlightedCode
|
|
@@ -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
|
)}
|
|
@@ -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
|
+
|