@lexibase/rdx-ui 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/package.json +3 -2
- package/src/components/button.tsx +20 -8
- package/src/components/code-block.tsx +4 -1
- package/src/components/copy-code-button.tsx +9 -6
- package/src/components/dialog.tsx +4 -1
- package/src/components/docs-search-dialog.tsx +23 -13
- package/src/components/docs-search.tsx +9 -5
- package/src/components/docs-sidebar.tsx +9 -7
- package/src/components/docs-toc.tsx +6 -3
- package/src/components/footer-credit.tsx +45 -0
- package/src/components/home.tsx +3 -3
- package/src/components/kbd.tsx +29 -0
- package/src/components/loading-button.tsx +5 -1
- package/src/components/mdx-components.tsx +59 -35
- package/src/components/mode-toggle.tsx +5 -2
- package/src/components/navbar.tsx +13 -9
- package/src/components/not-found.tsx +36 -0
- package/src/components/table.tsx +122 -0
- package/src/components/tabs.tsx +10 -7
- package/src/components/version-dropdown.tsx +9 -2
- package/src/components/version-notice.tsx +7 -5
- package/src/lib/i18n.ts +144 -0
- package/src/providers/locale-provider.tsx +29 -0
- package/src/components/button-link.tsx +0 -27
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getMessages } from '../lib/i18n'
|
|
1
2
|
import type { ComponentProps } from 'react'
|
|
2
3
|
|
|
3
4
|
import { cn } from '../lib/utils'
|
|
@@ -10,9 +11,37 @@ import {
|
|
|
10
11
|
import { Blockquote } from './blockquote'
|
|
11
12
|
import { Callout } from './callout'
|
|
12
13
|
import { CodeBlock } from './code-block'
|
|
14
|
+
import { Kbd, KbdGroup } from './kbd'
|
|
15
|
+
import {
|
|
16
|
+
Table,
|
|
17
|
+
TableBody,
|
|
18
|
+
TableCaption,
|
|
19
|
+
TableCell,
|
|
20
|
+
TableFooter,
|
|
21
|
+
TableHead,
|
|
22
|
+
TableHeader,
|
|
23
|
+
TableRow,
|
|
24
|
+
} from './table'
|
|
13
25
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs'
|
|
14
26
|
import { Separator } from '@lexibase/rdx-ui/components/separator'
|
|
15
27
|
|
|
28
|
+
function MdxTable({ lang, className, ...props }: ComponentProps<'table'>) {
|
|
29
|
+
return (
|
|
30
|
+
<Table
|
|
31
|
+
containerProps={{
|
|
32
|
+
'data-slot': 'mdx-table-scroll',
|
|
33
|
+
role: 'region',
|
|
34
|
+
'aria-label': getMessages(lang).table,
|
|
35
|
+
tabIndex: 0,
|
|
36
|
+
className:
|
|
37
|
+
'my-6 min-w-0 max-w-full focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
|
|
38
|
+
}}
|
|
39
|
+
className={className}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
export const mdxComponents = {
|
|
17
46
|
h1: ({ className, ...props }: ComponentProps<'h1'>) => (
|
|
18
47
|
<h1 className={cn('text-4xl font-bold my-6', className)} {...props} />
|
|
@@ -84,42 +113,26 @@ export const mdxComponents = {
|
|
|
84
113
|
{...props}
|
|
85
114
|
/>
|
|
86
115
|
),
|
|
87
|
-
table:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
className="my-6 min-w-0 max-w-full overflow-x-auto rounded-lg border border-border bg-background focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
94
|
-
>
|
|
95
|
-
<table
|
|
96
|
-
className={cn(
|
|
97
|
-
'w-full border-separate border-spacing-0 text-sm [&_tbody>tr]:transition-colors motion-reduce:[&_tbody>tr]:transition-none [&_tbody>tr:nth-child(even)]:bg-muted/20 [&_tbody>tr:hover]:bg-muted/40 [&_tbody>tr:last-child>td]:border-b-0',
|
|
98
|
-
className
|
|
99
|
-
)}
|
|
100
|
-
{...props}
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
),
|
|
116
|
+
table: MdxTable,
|
|
117
|
+
thead: TableHeader,
|
|
118
|
+
tbody: TableBody,
|
|
119
|
+
tfoot: TableFooter,
|
|
120
|
+
tr: TableRow,
|
|
121
|
+
caption: TableCaption,
|
|
104
122
|
th: ({ className, scope = 'col', ...props }: ComponentProps<'th'>) => (
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
className
|
|
119
|
-
)}
|
|
120
|
-
{...props}
|
|
121
|
-
/>
|
|
122
|
-
),
|
|
123
|
+
<TableHead scope={scope} className={className} {...props} />
|
|
124
|
+
),
|
|
125
|
+
td: TableCell,
|
|
126
|
+
Kbd,
|
|
127
|
+
KbdGroup,
|
|
128
|
+
Table,
|
|
129
|
+
TableHeader,
|
|
130
|
+
TableBody,
|
|
131
|
+
TableFooter,
|
|
132
|
+
TableRow,
|
|
133
|
+
TableHead,
|
|
134
|
+
TableCell,
|
|
135
|
+
TableCaption,
|
|
123
136
|
Callout,
|
|
124
137
|
Accordion,
|
|
125
138
|
AccordionItem,
|
|
@@ -140,3 +153,14 @@ export const mdxComponents = {
|
|
|
140
153
|
/>
|
|
141
154
|
),
|
|
142
155
|
}
|
|
156
|
+
|
|
157
|
+
/** Bind server-rendered MDX labels to the same locale as interactive components. */
|
|
158
|
+
export function getMdxComponents(lang?: string) {
|
|
159
|
+
return {
|
|
160
|
+
...mdxComponents,
|
|
161
|
+
pre: (props: ComponentProps<'pre'>) => <CodeBlock {...props} lang={lang} />,
|
|
162
|
+
table: (props: ComponentProps<'table'>) => (
|
|
163
|
+
<MdxTable {...props} lang={lang} />
|
|
164
|
+
),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import { SunMoon } from 'lucide-react'
|
|
4
6
|
import { useTheme } from 'next-themes'
|
|
5
7
|
import { Button } from '@lexibase/rdx-ui/components/button'
|
|
6
8
|
|
|
7
9
|
export function ModeToggle() {
|
|
10
|
+
const messages = useMessages()
|
|
8
11
|
const { theme, setTheme } = useTheme()
|
|
9
12
|
|
|
10
13
|
const toggleTheme = () => {
|
|
@@ -17,10 +20,10 @@ export function ModeToggle() {
|
|
|
17
20
|
size="icon"
|
|
18
21
|
variant="ghost"
|
|
19
22
|
onClick={toggleTheme}
|
|
20
|
-
aria-label=
|
|
23
|
+
aria-label={messages.modeToggle}
|
|
21
24
|
>
|
|
22
25
|
<SunMoon className="h-[1.2rem] w-[1.2rem]" />
|
|
23
|
-
<span className="sr-only">
|
|
26
|
+
<span className="sr-only">{messages.modeToggle}</span>
|
|
24
27
|
</Button>
|
|
25
28
|
)
|
|
26
29
|
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import { getMessages } from '../lib/i18n'
|
|
1
2
|
import type { ReactNode } from 'react'
|
|
2
3
|
import { Github } from 'lucide-react'
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import { Button } from './button'
|
|
5
6
|
import { Logo } from './logo'
|
|
6
7
|
import { Separator } from './separator'
|
|
7
8
|
import { ModeToggle } from './mode-toggle'
|
|
8
9
|
|
|
9
10
|
/** Shared composition extracted from apps/web/components/layout/Navbar. */
|
|
10
11
|
export function Navbar({
|
|
12
|
+
lang,
|
|
11
13
|
title = '',
|
|
12
14
|
siteTitle = 'RDX',
|
|
13
15
|
logo = <Logo className="size-5" />,
|
|
@@ -19,6 +21,7 @@ export function Navbar({
|
|
|
19
21
|
github,
|
|
20
22
|
showToggleMode = false,
|
|
21
23
|
}: {
|
|
24
|
+
lang?: string
|
|
22
25
|
title?: string
|
|
23
26
|
siteTitle?: string
|
|
24
27
|
logo?: ReactNode
|
|
@@ -30,14 +33,15 @@ export function Navbar({
|
|
|
30
33
|
github?: { visible: boolean; url: string }
|
|
31
34
|
showToggleMode?: boolean
|
|
32
35
|
}) {
|
|
36
|
+
const messages = getMessages(lang)
|
|
33
37
|
return (
|
|
34
38
|
<nav
|
|
35
39
|
data-rdx-navbar
|
|
36
|
-
aria-label=
|
|
40
|
+
aria-label={messages.mainNavigation}
|
|
37
41
|
className="w-full flex flex-wrap shrink-0 items-center justify-between px-6 py-2 border-b border-border gap-y-2"
|
|
38
42
|
>
|
|
39
43
|
<div className="flex items-center gap-1">
|
|
40
|
-
<
|
|
44
|
+
<Button
|
|
41
45
|
variant="ghost"
|
|
42
46
|
size={title === '' ? 'icon' : 'default'}
|
|
43
47
|
aria-label={siteTitle}
|
|
@@ -46,20 +50,20 @@ export function Navbar({
|
|
|
46
50
|
>
|
|
47
51
|
{logo}
|
|
48
52
|
{title !== '' && <span>{title}</span>}
|
|
49
|
-
</
|
|
53
|
+
</Button>
|
|
50
54
|
</div>
|
|
51
55
|
<div className="flex items-center gap-x-1 md:gap-x-2 h-4">
|
|
52
56
|
{search}
|
|
53
57
|
{versionMenu}
|
|
54
58
|
{docsLink ?? (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
</
|
|
59
|
+
<Button variant="link" size="sm" href={docsHref}>
|
|
60
|
+
{messages.docs}
|
|
61
|
+
</Button>
|
|
58
62
|
)}
|
|
59
63
|
{github?.visible && github.url && (
|
|
60
64
|
<>
|
|
61
65
|
<Separator orientation="vertical" />
|
|
62
|
-
<
|
|
66
|
+
<Button
|
|
63
67
|
variant="ghost"
|
|
64
68
|
size="icon"
|
|
65
69
|
title="GitHub"
|
|
@@ -71,7 +75,7 @@ export function Navbar({
|
|
|
71
75
|
>
|
|
72
76
|
<Github className="h-[1.2rem] w-[1.2rem]" />
|
|
73
77
|
<span className="sr-only">GitHub</span>
|
|
74
|
-
</
|
|
78
|
+
</Button>
|
|
75
79
|
</>
|
|
76
80
|
)}
|
|
77
81
|
{showToggleMode && (
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getMessages } from '../lib/i18n'
|
|
2
|
+
import { Button } from './button'
|
|
3
|
+
|
|
4
|
+
/** The development website's 404 layout, shared with generated projects. */
|
|
5
|
+
export function NotFound({
|
|
6
|
+
href = '/docs',
|
|
7
|
+
lang,
|
|
8
|
+
}: {
|
|
9
|
+
href?: string
|
|
10
|
+
lang?: string
|
|
11
|
+
}) {
|
|
12
|
+
const messages = getMessages(lang)
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<main
|
|
16
|
+
id="rdx-main"
|
|
17
|
+
data-rdx-not-found
|
|
18
|
+
tabIndex={-1}
|
|
19
|
+
className="bg-transparent text-primary h-full min-h-0 w-full flex flex-1 items-center justify-center cursor-default p-7 outline-none"
|
|
20
|
+
>
|
|
21
|
+
<div className="w-full md:w-1/2">
|
|
22
|
+
<h1 className="text-9xl font-bold font-mono text-center">404</h1>
|
|
23
|
+
<article className="flex flex-col gap-y-5">
|
|
24
|
+
<p className="text-3xl font-bold font-mono text-center">
|
|
25
|
+
{messages.notFound}
|
|
26
|
+
</p>
|
|
27
|
+
<p className="text-sm">{messages.notFoundDescription}</p>
|
|
28
|
+
<p className="text-sm">{messages.brokenLink}</p>
|
|
29
|
+
<Button variant="primary" href={href}>
|
|
30
|
+
{messages.backToDocs}
|
|
31
|
+
</Button>
|
|
32
|
+
</article>
|
|
33
|
+
</div>
|
|
34
|
+
</main>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from 'react'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../lib/utils'
|
|
6
|
+
|
|
7
|
+
// shadcn/ui base-nova Table; see THIRD_PARTY_NOTICES.md for the upstream MIT license.
|
|
8
|
+
type TableProps = ComponentProps<'table'> & {
|
|
9
|
+
/** Configure the scroll container for documentation layout and keyboard access. */
|
|
10
|
+
containerProps?: ComponentProps<'div'> & { 'data-slot'?: string }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function Table({ className, containerProps, ...props }: TableProps) {
|
|
14
|
+
const { className: containerClassName, ...containerAttributes } =
|
|
15
|
+
containerProps ?? {}
|
|
16
|
+
return (
|
|
17
|
+
<div
|
|
18
|
+
data-slot="table-container"
|
|
19
|
+
{...containerAttributes}
|
|
20
|
+
className={cn('relative w-full overflow-x-auto', containerClassName)}
|
|
21
|
+
>
|
|
22
|
+
<table
|
|
23
|
+
data-slot="table"
|
|
24
|
+
className={cn('w-full caption-bottom text-sm', className)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TableHeader({ className, ...props }: ComponentProps<'thead'>) {
|
|
32
|
+
return (
|
|
33
|
+
<thead
|
|
34
|
+
data-slot="table-header"
|
|
35
|
+
className={cn('[&_tr]:border-b', className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function TableBody({ className, ...props }: ComponentProps<'tbody'>) {
|
|
42
|
+
return (
|
|
43
|
+
<tbody
|
|
44
|
+
data-slot="table-body"
|
|
45
|
+
className={cn('[&_tr:last-child]:border-0', className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function TableFooter({ className, ...props }: ComponentProps<'tfoot'>) {
|
|
52
|
+
return (
|
|
53
|
+
<tfoot
|
|
54
|
+
data-slot="table-footer"
|
|
55
|
+
className={cn(
|
|
56
|
+
'border-t bg-muted/50 font-medium [&>tr]:last:border-b-0',
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function TableRow({ className, ...props }: ComponentProps<'tr'>) {
|
|
65
|
+
return (
|
|
66
|
+
<tr
|
|
67
|
+
data-slot="table-row"
|
|
68
|
+
className={cn(
|
|
69
|
+
'border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted',
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function TableHead({ className, ...props }: ComponentProps<'th'>) {
|
|
78
|
+
return (
|
|
79
|
+
<th
|
|
80
|
+
data-slot="table-head"
|
|
81
|
+
className={cn(
|
|
82
|
+
'h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0',
|
|
83
|
+
className
|
|
84
|
+
)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function TableCell({ className, ...props }: ComponentProps<'td'>) {
|
|
91
|
+
return (
|
|
92
|
+
<td
|
|
93
|
+
data-slot="table-cell"
|
|
94
|
+
className={cn(
|
|
95
|
+
'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0',
|
|
96
|
+
className
|
|
97
|
+
)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function TableCaption({ className, ...props }: ComponentProps<'caption'>) {
|
|
104
|
+
return (
|
|
105
|
+
<caption
|
|
106
|
+
data-slot="table-caption"
|
|
107
|
+
className={cn('mt-4 text-sm text-muted-foreground', className)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
Table,
|
|
115
|
+
TableHeader,
|
|
116
|
+
TableBody,
|
|
117
|
+
TableFooter,
|
|
118
|
+
TableHead,
|
|
119
|
+
TableRow,
|
|
120
|
+
TableCell,
|
|
121
|
+
TableCaption,
|
|
122
|
+
}
|
package/src/components/tabs.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
|
|
4
6
|
import { Check, Copy } from 'lucide-react'
|
|
5
7
|
import { useEffect, useRef, useState } from 'react'
|
|
@@ -43,6 +45,7 @@ function panelText(node: Node): string {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
function CopyTabButton() {
|
|
48
|
+
const messages = useMessages()
|
|
46
49
|
const [status, setStatus] = useState<'idle' | 'copied' | 'error'>('idle')
|
|
47
50
|
const pending = useRef(false)
|
|
48
51
|
const mounted = useRef(false)
|
|
@@ -92,13 +95,13 @@ function CopyTabButton() {
|
|
|
92
95
|
type="button"
|
|
93
96
|
variant="ghost"
|
|
94
97
|
size="icon"
|
|
95
|
-
aria-label=
|
|
98
|
+
aria-label={messages.copyTab}
|
|
96
99
|
title={
|
|
97
100
|
status === 'error'
|
|
98
|
-
?
|
|
101
|
+
? messages.copyTabError
|
|
99
102
|
: status === 'copied'
|
|
100
|
-
?
|
|
101
|
-
:
|
|
103
|
+
? messages.copied
|
|
104
|
+
: messages.copyTab
|
|
102
105
|
}
|
|
103
106
|
data-status={status}
|
|
104
107
|
className="mr-1.5 size-7 shrink-0 text-muted-foreground hover:text-foreground data-[status=copied]:text-emerald-700 dark:data-[status=copied]:text-emerald-400 data-[status=error]:text-amber-600 [&>svg]:size-3.5"
|
|
@@ -113,9 +116,9 @@ function CopyTabButton() {
|
|
|
113
116
|
className="sr-only"
|
|
114
117
|
>
|
|
115
118
|
{status === 'copied'
|
|
116
|
-
?
|
|
119
|
+
? messages.tabCopied
|
|
117
120
|
: status === 'error'
|
|
118
|
-
?
|
|
121
|
+
? messages.copyTabError
|
|
119
122
|
: ''}
|
|
120
123
|
</span>
|
|
121
124
|
</>
|
|
@@ -172,7 +175,7 @@ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
|
172
175
|
<TabsPrimitive.Panel
|
|
173
176
|
data-slot="tabs-content"
|
|
174
177
|
className={mergeStateClassName(
|
|
175
|
-
'min-w-0 rounded-b-lg p-3 outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&>[data-rdx-code-block]]:my-3 [&_[data-rdx-code-block]>pre]:p-3',
|
|
178
|
+
'min-w-0 rounded-b-lg p-3 outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&>[data-rdx-code-block]]:my-3 [&_[data-rdx-code-block]>pre]:p-3 font-mono',
|
|
176
179
|
className
|
|
177
180
|
)}
|
|
178
181
|
{...props}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useMessages } from '../providers/locale-provider'
|
|
4
|
+
|
|
3
5
|
import { Archive, Bird, PackageOpen } from 'lucide-react'
|
|
4
6
|
import Link from 'next/link'
|
|
5
7
|
import type { ReactNode } from 'react'
|
|
6
8
|
|
|
9
|
+
import { formatMessage } from '../lib/i18n'
|
|
10
|
+
|
|
7
11
|
import { Button } from './button'
|
|
8
12
|
import {
|
|
9
13
|
DropdownMenu,
|
|
@@ -42,6 +46,7 @@ export function VersionDropdown({
|
|
|
42
46
|
onSelect?: (id: string) => void
|
|
43
47
|
footer?: ReactNode
|
|
44
48
|
}) {
|
|
49
|
+
const messages = useMessages()
|
|
45
50
|
const current =
|
|
46
51
|
options.find((option) => option.id === currentId) ?? options[0]
|
|
47
52
|
if (!current) return null
|
|
@@ -87,7 +92,9 @@ export function VersionDropdown({
|
|
|
87
92
|
variant="outline"
|
|
88
93
|
size="sm"
|
|
89
94
|
className="min-w-0 capitalize sm:min-w-[134px]"
|
|
90
|
-
aria-label={
|
|
95
|
+
aria-label={formatMessage(messages.documentationVersion, {
|
|
96
|
+
version: current.label,
|
|
97
|
+
})}
|
|
91
98
|
/>
|
|
92
99
|
}
|
|
93
100
|
>
|
|
@@ -119,7 +126,7 @@ export function VersionDropdown({
|
|
|
119
126
|
{active.length > 0 && <DropdownMenuSeparator />}
|
|
120
127
|
<DropdownMenuGroup>
|
|
121
128
|
<DropdownMenuLabel className="text-center py-px cursor-default">
|
|
122
|
-
|
|
129
|
+
{messages.archived}
|
|
123
130
|
</DropdownMenuLabel>
|
|
124
131
|
<DropdownMenuSeparator />
|
|
125
132
|
{archived.map(item)}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getMessages } from '../lib/i18n'
|
|
1
2
|
import Link from 'next/link'
|
|
2
3
|
import { ArrowRight } from 'lucide-react'
|
|
3
4
|
|
|
@@ -5,29 +6,30 @@ import { Callout } from './callout'
|
|
|
5
6
|
|
|
6
7
|
/** Presentation only: version selection remains the responsibility of the caller. */
|
|
7
8
|
export function VersionNotice({
|
|
9
|
+
lang,
|
|
8
10
|
kind,
|
|
9
11
|
currentHref,
|
|
10
12
|
}: {
|
|
13
|
+
lang?: string
|
|
11
14
|
kind: 'canary' | 'outdated'
|
|
12
15
|
currentHref: string
|
|
13
16
|
}) {
|
|
17
|
+
const messages = getMessages(lang)
|
|
14
18
|
const isCanary = kind === 'canary'
|
|
15
19
|
return (
|
|
16
20
|
<Callout
|
|
17
21
|
data-rdx-version-notice={kind}
|
|
18
22
|
variant={isCanary ? 'tip' : 'warning'}
|
|
19
|
-
title={isCanary ?
|
|
23
|
+
title={isCanary ? messages.preReleaseTitle : messages.outdatedTitle}
|
|
20
24
|
className="mt-0 mb-5"
|
|
21
25
|
>
|
|
22
26
|
<p>
|
|
23
|
-
{isCanary
|
|
24
|
-
? 'This is a pre-release version of the documentation.'
|
|
25
|
-
: 'This is an outdated version of the documentation.'}{' '}
|
|
27
|
+
{isCanary ? messages.preReleaseMessage : messages.outdatedMessage}{' '}
|
|
26
28
|
<Link
|
|
27
29
|
href={currentHref}
|
|
28
30
|
className="font-medium text-foreground underline decoration-current/40 underline-offset-4 transition-colors hover:decoration-current focus-visible:rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
29
31
|
>
|
|
30
|
-
|
|
32
|
+
{messages.backToCurrent}
|
|
31
33
|
<ArrowRight
|
|
32
34
|
aria-hidden="true"
|
|
33
35
|
className="ml-1 inline size-3.5 align-text-bottom"
|
package/src/lib/i18n.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** Shared labels only: document content and author-provided names stay untouched. */
|
|
2
|
+
const en = {
|
|
3
|
+
skipContent: 'Skip to content',
|
|
4
|
+
mainNavigation: 'Main navigation',
|
|
5
|
+
docs: 'Docs',
|
|
6
|
+
documentationPages: 'Documentation pages',
|
|
7
|
+
documentationNavigation: 'Documentation navigation',
|
|
8
|
+
openSidebar: 'Open sidebar',
|
|
9
|
+
closeSidebar: 'Close sidebar',
|
|
10
|
+
previous: 'Previous',
|
|
11
|
+
next: 'Next',
|
|
12
|
+
previousPage: 'Previous page',
|
|
13
|
+
nextPage: 'Next page',
|
|
14
|
+
onThisPage: 'On this page',
|
|
15
|
+
search: 'Search…',
|
|
16
|
+
searchDocumentation: 'Search documentation',
|
|
17
|
+
searchPlaceholder: 'Search documentation…',
|
|
18
|
+
searchDescription:
|
|
19
|
+
'Search pages and sections in {version}. Use arrow keys to choose a result and Enter to open it.',
|
|
20
|
+
loadingSearch: 'Loading search…',
|
|
21
|
+
searchUnavailable: 'Search is unavailable.',
|
|
22
|
+
tryAgain: 'Try again',
|
|
23
|
+
typeToSearch: 'Type to search this documentation.',
|
|
24
|
+
noResults: 'No results found.',
|
|
25
|
+
navigate: 'Navigate',
|
|
26
|
+
open: 'Open',
|
|
27
|
+
close: 'Close',
|
|
28
|
+
closeSearch: 'Close search',
|
|
29
|
+
copyCode: 'Copy code',
|
|
30
|
+
codeCopied: 'Code copied to clipboard.',
|
|
31
|
+
copyCodeError: 'Could not copy. Select the code and copy it manually.',
|
|
32
|
+
copyRetry: 'Could not copy. Try again or copy manually.',
|
|
33
|
+
copied: 'Copied!',
|
|
34
|
+
copyTab: 'Copy active tab',
|
|
35
|
+
tabCopied: 'Tab content copied to clipboard.',
|
|
36
|
+
copyTabError: 'Could not copy. Select the tab content and copy it manually.',
|
|
37
|
+
code: 'Code',
|
|
38
|
+
table: 'Table',
|
|
39
|
+
current: 'Current',
|
|
40
|
+
version: 'Version {version}',
|
|
41
|
+
documentationVersion: 'Documentation version: {version}',
|
|
42
|
+
archived: 'Archived',
|
|
43
|
+
archivedVersion: 'Archived',
|
|
44
|
+
loadingVersions: 'Loading versions',
|
|
45
|
+
preReleaseTitle: 'Pre-release documentation',
|
|
46
|
+
outdatedTitle: 'Outdated documentation',
|
|
47
|
+
preReleaseMessage: 'This is a pre-release version of the documentation.',
|
|
48
|
+
outdatedMessage: 'This is an outdated version of the documentation.',
|
|
49
|
+
backToCurrent: 'Go back to the current version',
|
|
50
|
+
poweredBy: 'Powered by',
|
|
51
|
+
developedBy: 'Developed by',
|
|
52
|
+
modeToggle: 'Mode Toggle',
|
|
53
|
+
documentationReady: 'Your documentation is ready.',
|
|
54
|
+
addDocument: 'Add an .mdx file inside docs/.',
|
|
55
|
+
notFound: 'Page not found',
|
|
56
|
+
notFoundDescription: "We couldn't find what you were looking for.",
|
|
57
|
+
brokenLink:
|
|
58
|
+
'Please contact the owner of the site that led you to this link and let them know the link is broken.',
|
|
59
|
+
backToDocs: 'Go back to documentation',
|
|
60
|
+
} satisfies Record<string, string>
|
|
61
|
+
|
|
62
|
+
export type InterfaceMessages = { [Key in keyof typeof en]: string }
|
|
63
|
+
export type InterfaceLocale = 'en' | 'pt-BR'
|
|
64
|
+
|
|
65
|
+
const ptBR = {
|
|
66
|
+
skipContent: 'Ir para o conteúdo',
|
|
67
|
+
mainNavigation: 'Navegação principal',
|
|
68
|
+
docs: 'Docs',
|
|
69
|
+
documentationPages: 'Páginas da documentação',
|
|
70
|
+
documentationNavigation: 'Navegação da documentação',
|
|
71
|
+
openSidebar: 'Abrir menu lateral',
|
|
72
|
+
closeSidebar: 'Fechar menu lateral',
|
|
73
|
+
previous: 'Anterior',
|
|
74
|
+
next: 'Próxima',
|
|
75
|
+
previousPage: 'Página anterior',
|
|
76
|
+
nextPage: 'Próxima página',
|
|
77
|
+
onThisPage: 'Nesta página',
|
|
78
|
+
search: 'Buscar…',
|
|
79
|
+
searchDocumentation: 'Buscar na documentação',
|
|
80
|
+
searchPlaceholder: 'Buscar na documentação…',
|
|
81
|
+
searchDescription:
|
|
82
|
+
'Busque páginas e seções em {version}. Use as setas para escolher um resultado e Enter para abri-lo.',
|
|
83
|
+
loadingSearch: 'Carregando busca…',
|
|
84
|
+
searchUnavailable: 'A busca está indisponível.',
|
|
85
|
+
tryAgain: 'Tentar novamente',
|
|
86
|
+
typeToSearch: 'Digite para buscar nesta documentação.',
|
|
87
|
+
noResults: 'Nenhum resultado encontrado.',
|
|
88
|
+
navigate: 'Navegar',
|
|
89
|
+
open: 'Abrir',
|
|
90
|
+
close: 'Fechar',
|
|
91
|
+
closeSearch: 'Fechar busca',
|
|
92
|
+
copyCode: 'Copiar código',
|
|
93
|
+
codeCopied: 'Código copiado para a área de transferência.',
|
|
94
|
+
copyCodeError:
|
|
95
|
+
'Não foi possível copiar. Selecione o código e copie manualmente.',
|
|
96
|
+
copyRetry: 'Não foi possível copiar. Tente novamente ou copie manualmente.',
|
|
97
|
+
copied: 'Copiado!',
|
|
98
|
+
copyTab: 'Copiar aba ativa',
|
|
99
|
+
tabCopied: 'Conteúdo da aba copiado para a área de transferência.',
|
|
100
|
+
copyTabError:
|
|
101
|
+
'Não foi possível copiar. Selecione o conteúdo da aba e copie manualmente.',
|
|
102
|
+
code: 'Código',
|
|
103
|
+
table: 'Tabela',
|
|
104
|
+
current: 'Atual',
|
|
105
|
+
version: 'Versão {version}',
|
|
106
|
+
documentationVersion: 'Versão da documentação: {version}',
|
|
107
|
+
archived: 'Arquivadas',
|
|
108
|
+
archivedVersion: 'Arquivada',
|
|
109
|
+
loadingVersions: 'Carregando versões',
|
|
110
|
+
preReleaseTitle: 'Documentação de pré-lançamento',
|
|
111
|
+
outdatedTitle: 'Documentação desatualizada',
|
|
112
|
+
preReleaseMessage: 'Esta é uma versão de pré-lançamento da documentação.',
|
|
113
|
+
outdatedMessage: 'Esta é uma versão desatualizada da documentação.',
|
|
114
|
+
backToCurrent: 'Voltar para a versão atual',
|
|
115
|
+
poweredBy: 'Feito com',
|
|
116
|
+
developedBy: 'Desenvolvido por',
|
|
117
|
+
modeToggle: 'Alternar tema',
|
|
118
|
+
documentationReady: 'Sua documentação está pronta.',
|
|
119
|
+
addDocument: 'Adicione um arquivo .mdx dentro de docs/.',
|
|
120
|
+
notFound: 'Página não encontrada',
|
|
121
|
+
notFoundDescription: 'Não encontramos o que você estava procurando.',
|
|
122
|
+
brokenLink:
|
|
123
|
+
'Entre em contato com o responsável pelo site que indicou este link e informe que ele não está funcionando.',
|
|
124
|
+
backToDocs: 'Voltar para a documentação',
|
|
125
|
+
} satisfies InterfaceMessages
|
|
126
|
+
|
|
127
|
+
/** Match the locale, then its base language; unsupported languages use English. */
|
|
128
|
+
export function resolveInterfaceLocale(lang = 'en'): InterfaceLocale {
|
|
129
|
+
const normalized = lang.trim().replaceAll('_', '-').toLowerCase()
|
|
130
|
+
if (normalized === 'pt-br' || normalized.split('-')[0] === 'pt')
|
|
131
|
+
return 'pt-BR'
|
|
132
|
+
return 'en'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getMessages(lang?: string): InterfaceMessages {
|
|
136
|
+
return resolveInterfaceLocale(lang) === 'pt-BR' ? ptBR : en
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function formatMessage(message: string, values: Record<string, string>) {
|
|
140
|
+
return message.replace(
|
|
141
|
+
/\{(\w+)\}/g,
|
|
142
|
+
(placeholder, key: string) => values[key] ?? placeholder
|
|
143
|
+
)
|
|
144
|
+
}
|