@grove-dev/starlight 0.6.1 → 0.8.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +77 -78
  3. package/components/custom/ContainerSection.astro +1 -1
  4. package/components/custom/LinkButton.astro +134 -89
  5. package/components/custom/dropdown/Dropdown.astro +5 -5
  6. package/components/custom/dropdown/DropdownContent.astro +66 -66
  7. package/components/custom/dropdown/DropdownItem.astro +14 -9
  8. package/components/custom/dropdown/DropdownLabel.astro +4 -4
  9. package/components/custom/dropdown/DropdownTrigger.astro +13 -13
  10. package/components/custom/dropdown/index.ts +14 -14
  11. package/components/overrides/Footer.astro +3 -3
  12. package/components/overrides/Header.astro +5 -6
  13. package/components/overrides/Hero.astro +20 -12
  14. package/components/overrides/PageFrame.astro +25 -9
  15. package/components/overrides/PageSidebar.astro +1 -0
  16. package/components/overrides/PageTitle.astro +3 -3
  17. package/components/overrides/Search.astro +6 -6
  18. package/components/overrides/Sidebar.astro +38 -1
  19. package/components/overrides/SocialIcons.astro +3 -1
  20. package/components/overrides/ThemeSelect.astro +4 -4
  21. package/components/overrides/TwoColumnContent.astro +1 -1
  22. package/components/overrides/parts/Drawer.astro +13 -7
  23. package/components/overrides/parts/NavBar.astro +12 -31
  24. package/components/overrides/parts/SidebarSublist.astro +75 -4
  25. package/components/overrides/parts/toc/TableOfContentsList.astro +4 -4
  26. package/components/overrides/parts/toc/starlight-toc.ts +96 -98
  27. package/core/config/docs-schema.ts +58 -0
  28. package/core/config/expresive-code.ts +49 -51
  29. package/core/config/override.ts +37 -33
  30. package/core/config/schemas.ts +62 -41
  31. package/core/config/vite.ts +14 -14
  32. package/core/i18n.ts +175 -0
  33. package/core/plugin.ts +45 -43
  34. package/core/sidebar.ts +22 -0
  35. package/global.d.ts +3 -3
  36. package/package.json +3 -19
  37. package/schema.ts +44 -13
  38. package/styles/base.css +883 -896
  39. package/styles/layers.css +1 -1
  40. package/styles/theme.css +62 -62
  41. package/user-components.ts +1 -2
  42. package/virtual.d.ts +28 -28
  43. package/THIRD_PARTY_LICENSES.md +0 -68
  44. package/components/custom/Card.astro +0 -118
@@ -2,35 +2,35 @@
2
2
  import type { HTMLAttributes } from 'astro/types';
3
3
 
4
4
  type Props = HTMLAttributes<'div'> & {
5
- /**
6
- * Side of the dropdown
7
- * @default bottom
8
- */
9
- side?: 'top' | 'bottom' | 'left' | 'right';
10
- /**
11
- * Alignment of the dropdown
12
- * @default start
13
- */
14
- align?: 'start' | 'center' | 'end';
15
- /**
16
- * Offset distance in pixels
17
- * @default 4
18
- */
19
- sideOffset?: number;
20
- /**
21
- * Open and close animation duration in milliseconds
22
- * @default 150
23
- */
24
- animationDuration?: number;
5
+ /**
6
+ * Side of the dropdown
7
+ * @default bottom
8
+ */
9
+ side?: 'top' | 'bottom' | 'left' | 'right';
10
+ /**
11
+ * Alignment of the dropdown
12
+ * @default start
13
+ */
14
+ align?: 'start' | 'center' | 'end';
15
+ /**
16
+ * Offset distance in pixels
17
+ * @default 4
18
+ */
19
+ sideOffset?: number;
20
+ /**
21
+ * Open and close animation duration in milliseconds
22
+ * @default 150
23
+ */
24
+ animationDuration?: number;
25
25
  };
26
26
 
27
27
  const {
28
- class: className,
29
- side = 'bottom',
30
- align = 'start',
31
- sideOffset = 4,
32
- animationDuration = 150,
33
- ...rest
28
+ class: className,
29
+ side = 'bottom',
30
+ align = 'start',
31
+ sideOffset = 4,
32
+ animationDuration = 150,
33
+ ...rest
34
34
  } = Astro.props;
35
35
  ---
36
36
 
@@ -59,7 +59,7 @@ const {
59
59
  <style>
60
60
  div[data-slot='dropdown-content'] {
61
61
  background-color: var(--popover-background, var(--muted));
62
- color: var(var(--popover-foreground), var(--foreground));
62
+ color: var(--popover-foreground, var(--foreground));
63
63
  z-index: 50;
64
64
  min-width: 9rem;
65
65
  overflow-x: hidden;
@@ -134,46 +134,46 @@ const {
134
134
  &[data-side='right'][data-align='end'] {
135
135
  bottom: 0;
136
136
  }
137
+ }
138
+
139
+ /* animations */
140
+ @keyframes fade-in {
141
+ from {
142
+ opacity: 0;
143
+ }
144
+ to {
145
+ opacity: 1;
146
+ }
147
+ }
148
+
149
+ @keyframes fade-out {
150
+ from {
151
+ opacity: 1;
152
+ }
153
+ to {
154
+ opacity: 0;
155
+ }
156
+ }
137
157
 
138
- /* animations */
139
- @keyframes fade-in {
140
- from {
141
- opacity: 0;
142
- }
143
- to {
144
- opacity: 1;
145
- }
146
- }
147
-
148
- @keyframes fade-out {
149
- from {
150
- opacity: 1;
151
- }
152
- to {
153
- opacity: 0;
154
- }
155
- }
156
-
157
- @keyframes zoom-in-95 {
158
- from {
159
- opacity: 0;
160
- transform: scale(0.95);
161
- }
162
- to {
163
- opacity: 1;
164
- transform: scale(1);
165
- }
166
- }
167
-
168
- @keyframes zoom-out-95 {
169
- from {
170
- opacity: 1;
171
- transform: scale(1);
172
- }
173
- to {
174
- opacity: 0;
175
- transform: scale(0.95);
176
- }
158
+ @keyframes zoom-in-95 {
159
+ from {
160
+ opacity: 0;
161
+ transform: scale(0.95);
162
+ }
163
+ to {
164
+ opacity: 1;
165
+ transform: scale(1);
166
+ }
167
+ }
168
+
169
+ @keyframes zoom-out-95 {
170
+ from {
171
+ opacity: 1;
172
+ transform: scale(1);
173
+ }
174
+ to {
175
+ opacity: 0;
176
+ transform: scale(0.95);
177
177
  }
178
178
  }
179
179
  </style>
@@ -2,14 +2,14 @@
2
2
  import type { HTMLTag, Polymorphic } from 'astro/types';
3
3
 
4
4
  type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
5
- /**
6
- * Whether the item is inset (has left padding)
7
- */
8
- inset?: boolean;
9
- /**
10
- * Whether the item is disabled
11
- */
12
- disabled?: boolean;
5
+ /**
6
+ * Whether the item is inset (has left padding)
7
+ */
8
+ inset?: boolean;
9
+ /**
10
+ * Whether the item is disabled
11
+ */
12
+ disabled?: boolean;
13
13
  };
14
14
 
15
15
  const { class: className, inset = false, disabled = false, as: Tag = 'div', ...rest } = Astro.props;
@@ -53,7 +53,12 @@ const { class: className, inset = false, disabled = false, as: Tag = 'div', ...r
53
53
  color: var(--accent-foreground);
54
54
  }
55
55
 
56
- > svg {
56
+ /*
57
+ * `:global` because the icon is slotted in by the consumer, so it carries that component's
58
+ * scope class rather than this one's. Astro 7 scopes descendant selectors that Astro 6 left
59
+ * alone, which would otherwise leave slotted icons unsized.
60
+ */
61
+ > :global(svg) {
57
62
  width: 1rem;
58
63
  height: 1rem;
59
64
  flex-shrink: 0;
@@ -2,10 +2,10 @@
2
2
  import type { HTMLAttributes } from 'astro/types';
3
3
 
4
4
  type Props = HTMLAttributes<'div'> & {
5
- /**
6
- * Whether the label is inset (has left padding)
7
- */
8
- inset?: boolean;
5
+ /**
6
+ * Whether the label is inset (has left padding)
7
+ */
8
+ inset?: boolean;
9
9
  };
10
10
 
11
11
  const { class: className, inset = false, ...rest } = Astro.props;
@@ -1,29 +1,29 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types';
3
- import LinkButton from '../LinkButton.astro';
3
+ import LinkButton, { type ButtonVariant, type LegacyButtonVariant } from '../LinkButton.astro';
4
4
 
5
5
  type Props = Omit<HTMLAttributes<'button'>, 'role' | 'type'> & {
6
- /**
7
- * When true, the component will render its child element with a simple wrapper instead of a button component
8
- */
9
- asChild?: boolean;
6
+ /**
7
+ * When true, the component will render its child element with a simple wrapper instead of a button component
8
+ */
9
+ asChild?: boolean;
10
10
 
11
- variant?: 'primary' | 'secondary' | 'minimal';
12
- size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'icon-sm' | 'icon-md' | 'icon-lg';
11
+ variant?: ButtonVariant | LegacyButtonVariant;
12
+ size?: '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'icon-sm' | 'icon-md' | 'icon-lg';
13
13
  };
14
14
 
15
15
  const {
16
- class: className,
17
- variant = 'secondary',
18
- size = 'sm',
19
- asChild = false,
20
- ...rest
16
+ class: className,
17
+ variant = 'secondary',
18
+ size = 'sm',
19
+ asChild = false,
20
+ ...rest
21
21
  } = Astro.props;
22
22
 
23
23
  // Get the first child element if asChild is true
24
24
  let hasChildren = false;
25
25
  if (Astro.slots.has('default')) {
26
- hasChildren = true;
26
+ hasChildren = true;
27
27
  }
28
28
  ---
29
29
 
@@ -9,21 +9,21 @@ import DropdownShortcut from './DropdownShortcut.astro';
9
9
  import DropdownTrigger from './DropdownTrigger.astro';
10
10
 
11
11
  export {
12
- Dropdown,
13
- DropdownContent,
14
- DropdownItem,
15
- DropdownLabel,
16
- DropdownSeparator,
17
- DropdownShortcut,
18
- DropdownTrigger,
12
+ Dropdown,
13
+ DropdownContent,
14
+ DropdownItem,
15
+ DropdownLabel,
16
+ DropdownSeparator,
17
+ DropdownShortcut,
18
+ DropdownTrigger,
19
19
  };
20
20
 
21
21
  export default {
22
- Root: Dropdown,
23
- Trigger: DropdownTrigger,
24
- Content: DropdownContent,
25
- Item: DropdownItem,
26
- Label: DropdownLabel,
27
- Separator: DropdownSeparator,
28
- Shortcut: DropdownShortcut,
22
+ Root: Dropdown,
23
+ Trigger: DropdownTrigger,
24
+ Content: DropdownContent,
25
+ Item: DropdownItem,
26
+ Label: DropdownLabel,
27
+ Separator: DropdownSeparator,
28
+ Shortcut: DropdownShortcut,
29
29
  };
@@ -5,9 +5,9 @@ import Pagination from 'virtual:starlight/components/Pagination';
5
5
  import config from 'virtual:starlight/user-config';
6
6
 
7
7
  const {
8
- entry: {
9
- data: { template },
10
- },
8
+ entry: {
9
+ data: { template },
10
+ },
11
11
  } = Astro.locals.starlightRoute;
12
12
 
13
13
  const { lastUpdated: showLastUpdated, editLink } = config;
@@ -1,17 +1,16 @@
1
1
  ---
2
2
  import config from 'virtual:starlight/user-config';
3
+ import LanguageSelect from '@astrojs/starlight/components/LanguageSelect.astro';
4
+ import MobileMenuToggle from './parts/MobileMenuToggle.astro';
5
+ import NavBar from './parts/NavBar.astro';
3
6
  import Search from './Search.astro';
7
+ import SiteTitle from './SiteTitle.astro';
4
8
  import SocialIcons from './SocialIcons.astro';
5
- import LanguageSelect from '@astrojs/starlight/components/LanguageSelect.astro';
6
-
7
9
  import ThemeSelect from './ThemeSelect.astro';
8
- import SiteTitle from './SiteTitle.astro';
9
- import NavBar from './parts/NavBar.astro';
10
- import MobileMenuToggle from './parts/MobileMenuToggle.astro';
11
10
 
12
11
  /** render the `Search` component if Pagefind is enabled or the default search component has been overridden. */
13
12
  const shouldRenderSearch =
14
- config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro';
13
+ config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro';
15
14
  ---
16
15
 
17
16
  <MobileMenuToggle />
@@ -1,19 +1,27 @@
1
1
  ---
2
- import { Icon } from '@astrojs/starlight/components';
3
2
  import { Image } from 'astro:assets';
3
+ import { Icon } from '@astrojs/starlight/components';
4
4
  import { PAGE_TITLE_ID } from '../../core/config/constants';
5
- import type { HeroLayout } from '../../schema';
5
+ import { warnAboutMissingDocsSchemaOnce } from '../../core/config/docs-schema';
6
6
  import ContainerSection from '../custom/ContainerSection.astro';
7
7
  import LinkButton from '../custom/LinkButton.astro';
8
8
 
9
9
  const { data } = Astro.locals.starlightRoute.entry;
10
- const { title = data.title, tagline: heroTagline, image, actions = [] } = data.hero || {};
11
- const heroLayout = ((data.hero as any)?.layout as HeroLayout) ?? 'centered';
12
- const announcement =
13
- ((data.hero as any)?.announcement as { text: string; link: string }) ?? undefined;
14
- const hasImage = !!image;
10
+
11
+ warnAboutMissingDocsSchemaOnce(data.hero);
12
+
13
+ const {
14
+ title = data.title,
15
+ tagline: heroTagline,
16
+ image,
17
+ announcement,
18
+ actions = [],
19
+ } = data.hero || {};
20
+
21
+ // Falls back on an empty tagline too, not just a missing one.
15
22
  const tagline = heroTagline || data.description;
16
23
 
24
+ const hasImage = !!image;
17
25
  const imageAttrs = {
18
26
  loading: 'eager' as const,
19
27
  decoding: 'async' as const,
@@ -37,7 +45,7 @@ if (image) {
37
45
  ---
38
46
 
39
47
  <ContainerSection>
40
- <div class="hero" data-layout={heroLayout} data-has-image={hasImage ? '' : undefined}>
48
+ <div class="hero" data-layout={data.hero?.layout} data-has-image={hasImage ? '' : undefined}>
41
49
  {
42
50
  hasImage && (
43
51
  <div class="hero-image">
@@ -67,12 +75,11 @@ if (image) {
67
75
  )
68
76
  }
69
77
 
70
- <h1 id={PAGE_TITLE_ID} data-page-title>{title}</h1>
78
+ <h1 id={PAGE_TITLE_ID} data-page-title set:html={title} />
71
79
  </div>
72
80
 
73
81
  <div class="hero-content-container">
74
- {tagline && <div class="tagline">{tagline}</div>}
75
-
82
+ {tagline && <div class="tagline" set:html={tagline} />}
76
83
  {
77
84
  actions.length > 0 && (
78
85
  <div class="actions">
@@ -91,7 +98,8 @@ if (image) {
91
98
  {...attrs}
92
99
  >
93
100
  {text}
94
- {icon?.html && <Fragment set:html={icon.html} />}
101
+ {icon?.type === 'icon' && <Icon name={icon.name} />}
102
+ {icon?.type === 'raw' && <Fragment set:html={icon.html} />}
95
103
  </LinkButton>
96
104
  )
97
105
  )}
@@ -1,14 +1,30 @@
1
1
  ---
2
- import userConfig from 'virtual:lucode-starlight-config';
2
+ import userConfig from 'virtual:grove-starlight-config';
3
+ import starlightConfig from 'virtual:starlight/user-config';
4
+ import { AstroError } from 'astro/errors';
5
+ import { marked } from 'marked';
6
+ import { createLocaleLookup, resolveLocalizedString } from '../../core/i18n';
3
7
  import Drawer from './parts/Drawer.astro';
4
8
 
5
- const { hasSidebar } = Astro.locals.starlightRoute;
6
-
7
- // `footerText` is a plain string from the consumer's `astro.config.mjs`.
8
- // Astro auto-escapes interpolated text, so emitting it directly is safe
9
- // and avoids the XSS sink that `marked.parseInline` + `set:html` would
10
- // create. Consumers who want rich formatting should render Markdown in
11
- // their own component instead of leaning on this slot.
9
+ const { hasSidebar, lang, locale } = Astro.locals.starlightRoute;
10
+
11
+ const localeKeys = createLocaleLookup({
12
+ lang,
13
+ locale: Astro.currentLocale ?? locale,
14
+ defaultLang: starlightConfig.defaultLocale?.lang,
15
+ defaultLocale: starlightConfig.defaultLocale?.locale,
16
+ });
17
+
18
+ let footerText: string;
19
+
20
+ try {
21
+ footerText = resolveLocalizedString(userConfig.footerText, localeKeys);
22
+ } catch (error) {
23
+ throw new AstroError(
24
+ error instanceof Error ? error.message : 'Invalid footerText localization.',
25
+ 'Update the Grove config so footerText includes a key for the default language.',
26
+ );
27
+ }
12
28
  ---
13
29
 
14
30
  <div data-slot="layout">
@@ -39,7 +55,7 @@ const { hasSidebar } = Astro.locals.starlightRoute;
39
55
  </main>
40
56
 
41
57
  <footer>
42
- <div data-slot="footer-text">{userConfig.footerText}</div>
58
+ <div data-slot="footer-text" set:html={marked.parseInline(footerText)} />
43
59
  </footer>
44
60
  </div>
45
61
 
@@ -16,5 +16,6 @@ import TableOfContents from './TableOfContents.astro';
16
16
  max-height: calc(100vh - var(--header-height) - 1px);
17
17
  top: calc(var(--header-height) + 1px);
18
18
  position: sticky;
19
+ overflow-y: auto;
19
20
  }
20
21
  </style>
@@ -1,13 +1,13 @@
1
1
  ---
2
+ import userConfig from 'virtual:grove-starlight-config';
2
3
  import { Icon } from '@astrojs/starlight/components';
3
4
  import { PAGE_TITLE_ID } from '../../core/config/constants';
4
- import userConfig from 'virtual:lucode-starlight-config';
5
5
  import { Dropdown, DropdownContent, DropdownItem, DropdownTrigger } from '../custom/dropdown';
6
6
  import LinkButton from '../custom/LinkButton.astro';
7
7
 
8
8
  const {
9
- entry: { data },
10
- pagination: { prev, next },
9
+ entry: { data },
10
+ pagination: { prev, next },
11
11
  } = Astro.locals.starlightRoute;
12
12
 
13
13
  const { title, description } = data;
@@ -3,12 +3,12 @@ import '@pagefind/default-ui/css/ui.css';
3
3
  import project from 'virtual:starlight/project-context';
4
4
 
5
5
  const pagefindTranslations = {
6
- placeholder: Astro.locals.t('search.label'),
7
- ...Object.fromEntries(
8
- Object.entries(Astro.locals.t.all())
9
- .filter(([key]) => key.startsWith('pagefind.'))
10
- .map(([key, value]) => [key.replace('pagefind.', ''), value])
11
- ),
6
+ placeholder: Astro.locals.t('search.label'),
7
+ ...Object.fromEntries(
8
+ Object.entries(Astro.locals.t.all())
9
+ .filter(([key]) => key.startsWith('pagefind.'))
10
+ .map(([key, value]) => [key.replace('pagefind.', ''), value]),
11
+ ),
12
12
  };
13
13
 
14
14
  const dataAttributes: DOMStringMap = { 'data-translations': JSON.stringify(pagefindTranslations) };
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
2
3
  import SidebarSublist from './parts/SidebarSublist.astro';
3
4
 
4
5
  const { sidebar } = Astro.locals.starlightRoute;
@@ -10,12 +11,48 @@ const { sidebar } = Astro.locals.starlightRoute;
10
11
  <div class="sidebar-fade-top"></div>
11
12
  <div class="container-sidebar">
12
13
  <div class="sidebar-entries">
13
- <SidebarSublist sublist={sidebar} />
14
+ {/* Keeps collapsed groups open and the scroll position across navigations. */}
15
+ <SidebarPersister>
16
+ <SidebarSublist sublist={sidebar} />
17
+ </SidebarPersister>
14
18
  </div>
15
19
  <div class="sidebar-fade-bottom"></div>
16
20
  </div>
17
21
  </div>
18
22
 
23
+ {/*
24
+ Bring the current page's entry into view inside the sidebar's own scroll
25
+ container. SidebarPersister keeps the scroll position across navigations, but
26
+ on a fresh load (or the first visit to a deep page) the sidebar starts at the
27
+ top, so an entry far down the list renders off-screen with no hint that it is
28
+ selected. Only the container is scrolled -- never the page -- and only when
29
+ the entry is actually outside the visible band.
30
+ */}
31
+ <script is:inline data-astro-rerun>
32
+ (() => {
33
+ const reveal = () => {
34
+ const container = document.querySelector('.container-sidebar');
35
+ const current = container?.querySelector('[aria-current="page"]');
36
+ if (!container || !current) return;
37
+
38
+ const view = container.getBoundingClientRect();
39
+ const entry = current.getBoundingClientRect();
40
+ if (entry.top >= view.top && entry.bottom <= view.bottom) return;
41
+
42
+ const offset =
43
+ entry.top - view.top - container.clientHeight / 2 + entry.height / 2;
44
+ // Instant, not smooth: this positions the sidebar for the page
45
+ // being loaded, so it should already be correct on first paint
46
+ // rather than animating into place while the reader looks at it.
47
+ container.scrollTop += offset;
48
+ };
49
+
50
+ // Runs after SidebarPersister has restored any stored scroll position.
51
+ requestAnimationFrame(reveal);
52
+ document.addEventListener('astro:page-load', () => requestAnimationFrame(reveal));
53
+ })();
54
+ </script>
55
+
19
56
  <style>
20
57
  .sidebar {
21
58
  scrollbar-width: none;
@@ -32,7 +32,9 @@ const links = config.social || [];
32
32
  cursor: pointer;
33
33
  color: color-mix(in oklab, var(--foreground) 76%, var(--muted-foreground));
34
34
  background-color: transparent;
35
- transition: color 0.15s, background-color 0.15s;
35
+ transition:
36
+ color 0.15s,
37
+ background-color 0.15s;
36
38
  text-decoration: none;
37
39
  }
38
40
  a:hover {
@@ -2,7 +2,7 @@
2
2
 
3
3
  ---
4
4
 
5
- <starlight-theme-lucode-select>
5
+ <starlight-theme-grove-select>
6
6
  <button
7
7
  aria-label={Astro.locals.t('themeSelect.accessibleLabel')}
8
8
  aria-live="polite"
@@ -49,7 +49,7 @@
49
49
 
50
50
  <span class="sr-only">Toggle theme</span>
51
51
  </button>
52
- </starlight-theme-lucode-select>
52
+ </starlight-theme-grove-select>
53
53
 
54
54
  <style>
55
55
  button {
@@ -137,8 +137,8 @@
137
137
  });
138
138
 
139
139
  customElements.define(
140
- 'starlight-theme-lucode-select',
141
- class StarlightThemeLucodeSelect extends HTMLElement {
140
+ 'starlight-theme-grove-select',
141
+ class StarlightThemeGroveSelect extends HTMLElement {
142
142
  constructor() {
143
143
  super();
144
144
  onThemeChange(loadTheme());
@@ -42,7 +42,7 @@ const { toc } = Astro.locals.starlightRoute;
42
42
  }
43
43
 
44
44
  .main {
45
- max-width: 40rem;
45
+ max-width: var(--content-max-width);
46
46
  min-width: 0;
47
47
  width: 100%;
48
48
  margin-inline: auto;
@@ -1,10 +1,19 @@
1
1
  ---
2
- import userConfig from 'virtual:lucode-starlight-config';
3
- import { getTranslation } from './NavBar.astro';
4
2
  import { getRelativeLocaleUrl } from 'astro:i18n';
5
- const { sidebar } = Astro.locals.starlightRoute;
3
+ import userConfig from 'virtual:grove-starlight-config';
4
+ import starlightConfig from 'virtual:starlight/user-config';
5
+ import { createLocaleLookup, resolveNavLabel } from '../../../core/i18n';
6
+
7
+ const { sidebar, lang, locale } = Astro.locals.starlightRoute;
6
8
 
7
9
  const currentPath = Astro.url.pathname;
10
+
11
+ const localeKeys = createLocaleLookup({
12
+ lang,
13
+ locale: Astro.currentLocale ?? locale,
14
+ defaultLang: starlightConfig.defaultLocale?.lang,
15
+ defaultLocale: starlightConfig.defaultLocale?.locale,
16
+ });
8
17
  ---
9
18
 
10
19
  <div popover="auto" id="drawer">
@@ -25,10 +34,7 @@ const currentPath = Astro.url.pathname;
25
34
  !absoluteLinkRegex.test(nav.link) && Astro.currentLocale
26
35
  ? getRelativeLocaleUrl(Astro.currentLocale, nav.link)
27
36
  : nav.link;
28
- const label =
29
- typeof nav.label === 'string'
30
- ? nav.label
31
- : getTranslation(nav.label, nav.link, 'label');
37
+ const label = resolveNavLabel(nav.label, nav.translations, localeKeys);
32
38
 
33
39
  return (
34
40
  <a href={link} {...nav.attrs}>