@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.
@@ -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: ({ className, ...props }: ComponentProps<'table'>) => (
88
- <div
89
- data-slot="mdx-table-scroll"
90
- role="region"
91
- aria-label="Table"
92
- tabIndex={0}
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
- <th
106
- scope={scope}
107
- className={cn(
108
- 'border-b border-r border-border bg-muted/40 px-4 py-3 text-left text-xs font-semibold tracking-wide text-foreground/90 last:border-r-0',
109
- className
110
- )}
111
- {...props}
112
- />
113
- ),
114
- td: ({ className, ...props }: ComponentProps<'td'>) => (
115
- <td
116
- className={cn(
117
- 'min-w-40 border-b border-r border-border/70 px-4 py-3 align-top leading-6 last:border-r-0',
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="Mode Toggle"
23
+ aria-label={messages.modeToggle}
21
24
  >
22
25
  <SunMoon className="h-[1.2rem] w-[1.2rem]" />
23
- <span className="sr-only">colorTheme</span>
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 { ButtonLink } from './button-link'
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="Main navigation"
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
- <ButtonLink
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
- </ButtonLink>
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
- <ButtonLink variant="link" size="sm" href={docsHref}>
56
- Docs
57
- </ButtonLink>
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
- <ButtonLink
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
- </ButtonLink>
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
+ }
@@ -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="Copy active tab"
98
+ aria-label={messages.copyTab}
96
99
  title={
97
100
  status === 'error'
98
- ? 'Could not copy. Select the tab content and copy it manually.'
101
+ ? messages.copyTabError
99
102
  : status === 'copied'
100
- ? 'Copied!'
101
- : 'Copy active tab'
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
- ? 'Tab content copied to clipboard.'
119
+ ? messages.tabCopied
117
120
  : status === 'error'
118
- ? 'Could not copy. Select the tab content and copy it manually.'
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={`Documentation version: ${current.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
- Archived
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 ? 'Pre-release documentation' : 'Outdated documentation'}
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
- Go back to the current version
32
+ {messages.backToCurrent}
31
33
  <ArrowRight
32
34
  aria-hidden="true"
33
35
  className="ml-1 inline size-3.5 align-text-bottom"
@@ -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
+ }