@astrojs/starlight 0.23.4 → 0.24.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/404.astro +3 -1
  2. package/CHANGELOG.md +15 -0
  3. package/components/Footer.astro +31 -2
  4. package/components/SidebarSublist.astro +11 -12
  5. package/components.ts +1 -0
  6. package/index.ts +12 -3
  7. package/integrations/shared/localeToLang.ts +2 -1
  8. package/package.json +1 -1
  9. package/schemas/badge.ts +9 -0
  10. package/schemas/i18n.ts +6 -0
  11. package/translations/ar.json +2 -1
  12. package/translations/cs.json +2 -1
  13. package/translations/da.json +2 -1
  14. package/translations/de.json +2 -1
  15. package/translations/en.json +2 -1
  16. package/translations/es.json +2 -1
  17. package/translations/fa.json +2 -1
  18. package/translations/fr.json +2 -1
  19. package/translations/gl.json +2 -1
  20. package/translations/he.json +2 -1
  21. package/translations/hi.json +2 -1
  22. package/translations/id.json +2 -1
  23. package/translations/it.json +2 -1
  24. package/translations/ja.json +2 -1
  25. package/translations/ko.json +2 -1
  26. package/translations/nb.json +2 -1
  27. package/translations/nl.json +2 -1
  28. package/translations/pl.json +2 -1
  29. package/translations/pt.json +2 -1
  30. package/translations/ro.json +2 -1
  31. package/translations/ru.json +2 -1
  32. package/translations/sv.json +2 -1
  33. package/translations/tr.json +2 -1
  34. package/translations/uk.json +2 -1
  35. package/translations/vi.json +2 -1
  36. package/translations/zh-CN.json +2 -1
  37. package/translations/zh-TW.json +2 -1
  38. package/user-components/Badge.astro +141 -0
  39. package/utils/createTranslationSystem.ts +2 -1
  40. package/utils/i18n.ts +166 -0
  41. package/utils/routing.ts +2 -1
  42. package/utils/slugs.ts +2 -1
  43. package/utils/user-config.ts +14 -3
  44. package/components/Badge.astro +0 -87
package/404.astro CHANGED
@@ -6,10 +6,12 @@ import Page from './components/Page.astro';
6
6
  import { generateRouteData } from './utils/route-data';
7
7
  import type { StarlightDocsEntry } from './utils/routing';
8
8
  import { useTranslations } from './utils/translations';
9
+ import { BuiltInDefaultLocale } from './utils/i18n';
9
10
 
10
11
  export const prerender = true;
11
12
 
12
- const { lang = 'en', dir = 'ltr' } = config.defaultLocale || {};
13
+ const { lang = BuiltInDefaultLocale.lang, dir = BuiltInDefaultLocale.dir } =
14
+ config.defaultLocale || {};
13
15
  let locale = config.defaultLocale?.locale;
14
16
  if (locale === 'root') locale = undefined;
15
17
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.24.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1841](https://github.com/withastro/starlight/pull/1841) [`ee0cd38a`](https://github.com/withastro/starlight/commit/ee0cd38a1fae31717fe820e779baeabe693cd67a) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds support for `Astro.currentLocale` and Astro’s i18n routing.
8
+
9
+ ⚠️ **Potentially breaking change:** Starlight now configures Astro’s `i18n` option for you based on its `locales` config.
10
+
11
+ If you are currently using Astro’s `i18n` option as well as Starlight’s `locales` option, you will need to remove one of these.
12
+ In general we recommend using Starlight’s `locales`, but if you have a more advanced configuration you may choose to keep Astro’s `i18n` config instead.
13
+
14
+ - [#1958](https://github.com/withastro/starlight/pull/1958) [`081d1a96`](https://github.com/withastro/starlight/commit/081d1a969462633e41ca95a18a1ec121cb4af5d2) Thanks [@delucis](https://github.com/delucis)! - Allows users to opt into displaying a “Built with Starlight” link in the site footer
15
+
16
+ - [#1530](https://github.com/withastro/starlight/pull/1530) [`dd64836a`](https://github.com/withastro/starlight/commit/dd64836af45f33df4a99ab864eabb91fc9b8e204) Thanks [@kevinzunigacuellar](https://github.com/kevinzunigacuellar)! - Adds a new `<Badge>` component
17
+
3
18
  ## 0.23.4
4
19
 
5
20
  ### Patch Changes
@@ -4,26 +4,55 @@ import type { Props } from '../props';
4
4
  import EditLink from 'virtual:starlight/components/EditLink';
5
5
  import LastUpdated from 'virtual:starlight/components/LastUpdated';
6
6
  import Pagination from 'virtual:starlight/components/Pagination';
7
+ import config from 'virtual:starlight/user-config';
8
+ import { Icon } from '../components';
7
9
  ---
8
10
 
9
- <footer>
11
+ <footer class="sl-flex">
10
12
  <div class="meta sl-flex">
11
13
  <EditLink {...Astro.props} />
12
14
  <LastUpdated {...Astro.props} />
13
15
  </div>
14
16
  <Pagination {...Astro.props} />
17
+
18
+ {
19
+ config.credits && (
20
+ <a class="kudos sl-flex" href="https://starlight.astro.build">
21
+ <Icon name={'starlight'} /> {Astro.props.labels['builtWithStarlight.label']}
22
+ </a>
23
+ )
24
+ }
15
25
  </footer>
16
26
 
17
27
  <style>
28
+ footer {
29
+ flex-direction: column;
30
+ gap: 1.5rem;
31
+ }
18
32
  .meta {
19
33
  gap: 0.75rem 3rem;
20
34
  justify-content: space-between;
21
35
  flex-wrap: wrap;
22
- margin-block: 3rem 1.5rem;
36
+ margin-top: 3rem;
23
37
  font-size: var(--sl-text-sm);
24
38
  color: var(--sl-color-gray-3);
25
39
  }
26
40
  .meta > :global(p:only-child) {
27
41
  margin-inline-start: auto;
28
42
  }
43
+
44
+ .kudos {
45
+ align-items: center;
46
+ gap: 0.5em;
47
+ margin: 1.5rem auto;
48
+ font-size: var(--sl-text-xs);
49
+ text-decoration: none;
50
+ color: var(--sl-color-gray-3);
51
+ }
52
+ .kudos :global(svg) {
53
+ color: var(--sl-color-orange);
54
+ }
55
+ .kudos:hover {
56
+ color: var(--sl-color-white);
57
+ }
29
58
  </style>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import { flattenSidebar, type SidebarEntry } from '../utils/navigation';
3
3
  import Icon from '../user-components/Icon.astro';
4
- import Badge from './Badge.astro';
4
+ import Badge from '../user-components/Badge.astro';
5
5
 
6
6
  interface Props {
7
7
  sublist: SidebarEntry[];
@@ -24,13 +24,11 @@ const { sublist, nested } = Astro.props;
24
24
  >
25
25
  <span>{entry.label}</span>
26
26
  {entry.badge && (
27
- <>
28
- {' '}
29
- <Badge
30
- text={entry.badge.text}
31
- variant={entry.isCurrent ? 'outline' : entry.badge.variant}
32
- />
33
- </>
27
+ <Badge
28
+ variant={entry.badge.variant}
29
+ class={entry.badge.class}
30
+ text={entry.badge.text}
31
+ />
34
32
  )}
35
33
  </a>
36
34
  ) : (
@@ -41,10 +39,11 @@ const { sublist, nested } = Astro.props;
41
39
  <div class="group-label">
42
40
  <span class="large">{entry.label}</span>
43
41
  {entry.badge && (
44
- <>
45
- {' '}
46
- <Badge text={entry.badge.text} variant={entry.badge.variant} />
47
- </>
42
+ <Badge
43
+ variant={entry.badge.variant}
44
+ class={entry.badge.class}
45
+ text={entry.badge.text}
46
+ />
48
47
  )}
49
48
  </div>
50
49
  <Icon name="right-caret" class="caret" size="1.25rem" />
package/components.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as Aside } from './user-components/Aside.astro';
2
+ export { default as Badge } from './user-components/Badge.astro';
2
3
  export { default as Card } from './user-components/Card.astro';
3
4
  export { default as CardGrid } from './user-components/CardGrid.astro';
4
5
  export { default as Icon } from './user-components/Icon.astro';
package/index.ts CHANGED
@@ -10,6 +10,7 @@ import { vitePluginStarlightUserConfig } from './integrations/virtual-user-confi
10
10
  import { rehypeRtlCodeSupport } from './integrations/code-rtl-support';
11
11
  import { createTranslationSystemFromFs } from './utils/translations-fs';
12
12
  import { runPlugins, type StarlightUserConfigWithPlugins } from './utils/plugins';
13
+ import { processI18nConfig } from './utils/i18n';
13
14
  import type { StarlightConfig } from './types';
14
15
 
15
16
  export default function StarlightIntegration({
@@ -28,18 +29,25 @@ export default function StarlightIntegration({
28
29
  logger,
29
30
  updateConfig,
30
31
  }) => {
31
- // Run plugins to get the final configuration and any extra Astro integrations to load.
32
- const { integrations, starlightConfig } = await runPlugins(opts, plugins, {
32
+ // Run plugins to get the updated configuration and any extra Astro integrations to load.
33
+ const pluginResult = await runPlugins(opts, plugins, {
33
34
  command,
34
35
  config,
35
36
  isRestart,
36
37
  logger,
37
38
  });
39
+ // Process the Astro and Starlight configurations for i18n and translations.
40
+ const { astroI18nConfig, starlightConfig } = processI18nConfig(
41
+ pluginResult.starlightConfig,
42
+ config.i18n
43
+ );
44
+
45
+ const { integrations } = pluginResult;
38
46
  userConfig = starlightConfig;
39
47
 
40
48
  const useTranslations = createTranslationSystemFromFs(starlightConfig, config);
41
49
 
42
- if (!userConfig.disable404Route) {
50
+ if (!starlightConfig.disable404Route) {
43
51
  injectRoute({
44
52
  pattern: '404',
45
53
  entrypoint: '@astrojs/starlight/404.astro',
@@ -92,6 +100,7 @@ export default function StarlightIntegration({
92
100
  experimental: {
93
101
  globalRoutePriority: true,
94
102
  },
103
+ i18n: astroI18nConfig,
95
104
  });
96
105
  },
97
106
 
@@ -1,4 +1,5 @@
1
1
  import type { StarlightConfig } from '../../types';
2
+ import { BuiltInDefaultLocale } from '../../utils/i18n';
2
3
 
3
4
  /**
4
5
  * Get the BCP-47 language tag for the given locale.
@@ -7,5 +8,5 @@ import type { StarlightConfig } from '../../types';
7
8
  export function localeToLang(config: StarlightConfig, locale: string | undefined): string {
8
9
  const lang = locale ? config.locales?.[locale]?.lang : config.locales?.root?.lang;
9
10
  const defaultLang = config.defaultLocale?.lang || config.defaultLocale?.locale;
10
- return lang || defaultLang || 'en';
11
+ return lang || defaultLang || BuiltInDefaultLocale.lang;
11
12
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.23.4",
3
+ "version": "0.24.0",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
package/schemas/badge.ts CHANGED
@@ -4,8 +4,17 @@ const badgeSchema = () =>
4
4
  z.object({
5
5
  variant: z.enum(['note', 'danger', 'success', 'caution', 'tip', 'default']).default('default'),
6
6
  text: z.string(),
7
+ class: z.string().optional(),
7
8
  });
8
9
 
10
+ export const BadgeComponentSchema = badgeSchema()
11
+ .extend({
12
+ size: z.enum(['small', 'medium', 'large']).default('small'),
13
+ })
14
+ .passthrough();
15
+
16
+ export type BadgeComponentProps = z.input<typeof BadgeComponentSchema>;
17
+
9
18
  export const BadgeConfigSchema = () =>
10
19
  z
11
20
  .union([z.string(), badgeSchema()])
package/schemas/i18n.ts CHANGED
@@ -138,6 +138,12 @@ function starlightI18nSchema() {
138
138
  'fileTree.directory': z
139
139
  .string()
140
140
  .describe('Label for the directory icon in the file tree component.'),
141
+
142
+ 'builtWithStarlight.label': z
143
+ .string()
144
+ .describe(
145
+ 'Label for the “Built with Starlight” badge optionally displayed in the site footer.'
146
+ ),
141
147
  })
142
148
  .partial();
143
149
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "نصيحة",
25
25
  "aside.caution": "تنبيه",
26
26
  "aside.danger": "تحذير",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tipp",
25
25
  "aside.caution": "Achtung",
26
26
  "aside.danger": "Gefahr",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Erstellt mit Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -27,5 +27,6 @@
27
27
  "expressiveCode.copyButtonCopied": "¡Copiado!",
28
28
  "expressiveCode.copyButtonTooltip": "Copiar al portapapeles",
29
29
  "expressiveCode.terminalWindowFallbackTitle": "Ventana de terminal",
30
- "fileTree.directory": "Directory"
30
+ "fileTree.directory": "Directory",
31
+ "builtWithStarlight.label": "Hecho con Starlight"
31
32
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "نکته",
25
25
  "aside.caution": "احتیاط",
26
26
  "aside.danger": "خطر",
27
- "fileTree.directory": "فهرست"
27
+ "fileTree.directory": "فهرست",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -27,5 +27,6 @@
27
27
  "expressiveCode.copyButtonCopied": "Copié !",
28
28
  "expressiveCode.copyButtonTooltip": "Copier dans le presse-papiers",
29
29
  "expressiveCode.terminalWindowFallbackTitle": "Fenêtre de terminal",
30
- "fileTree.directory": "Répertoire"
30
+ "fileTree.directory": "Répertoire",
31
+ "builtWithStarlight.label": "Créé avec Starlight"
31
32
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "संकेत",
25
25
  "aside.caution": "सावधानी",
26
26
  "aside.danger": "खतरा",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Starlight द्वारा निर्मित"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tips",
25
25
  "aside.caution": "Perhatian",
26
26
  "aside.danger": "Bahaya",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Consiglio",
25
25
  "aside.caution": "Attenzione",
26
26
  "aside.danger": "Pericolo",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "ヒント",
25
25
  "aside.caution": "注意",
26
26
  "aside.danger": "危険",
27
- "fileTree.directory": "ディレクトリ"
27
+ "fileTree.directory": "ディレクトリ",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "팁",
25
25
  "aside.caution": "주의",
26
26
  "aside.danger": "위험",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Laget med Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Opgepast",
26
26
  "aside.danger": "Gevaar",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -27,5 +27,6 @@
27
27
  "fileTree.directory": "Folder",
28
28
  "expressiveCode.copyButtonCopied": "Skopiowane!",
29
29
  "expressiveCode.copyButtonTooltip": "Skopiuj do schowka",
30
- "expressiveCode.terminalWindowFallbackTitle": "Okno terminala"
30
+ "expressiveCode.terminalWindowFallbackTitle": "Okno terminala",
31
+ "builtWithStarlight.label": "Built with Starlight"
31
32
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Dica",
25
25
  "aside.caution": "Cuidado",
26
26
  "aside.danger": "Perigo",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Feito com Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Sfat",
25
25
  "aside.caution": "Atenție",
26
26
  "aside.danger": "Pericol",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -27,5 +27,6 @@
27
27
  "fileTree.directory": "Директория",
28
28
  "expressiveCode.copyButtonCopied": "Скопировано!",
29
29
  "expressiveCode.copyButtonTooltip": "Копировать",
30
- "expressiveCode.terminalWindowFallbackTitle": "Окно терминала"
30
+ "expressiveCode.terminalWindowFallbackTitle": "Окно терминала",
31
+ "builtWithStarlight.label": "Built with Starlight"
31
32
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Tip",
25
25
  "aside.caution": "Caution",
26
26
  "aside.danger": "Danger",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "İpucu",
25
25
  "aside.caution": "Dikkat",
26
26
  "aside.danger": "Tehlike",
27
- "fileTree.directory": "Dizin"
27
+ "fileTree.directory": "Dizin",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Порада",
25
25
  "aside.caution": "Обережно",
26
26
  "aside.danger": "Небезпечно",
27
- "fileTree.directory": "Directory"
27
+ "fileTree.directory": "Directory",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "Mẹo",
25
25
  "aside.caution": "Thận trọng",
26
26
  "aside.danger": "Nguy hiểm",
27
- "fileTree.directory": "Danh mục"
27
+ "fileTree.directory": "Danh mục",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "提示",
25
25
  "aside.caution": "警告",
26
26
  "aside.danger": "危险",
27
- "fileTree.directory": "文件夹"
27
+ "fileTree.directory": "文件夹",
28
+ "builtWithStarlight.label": "基于 Starlight 构建"
28
29
  }
@@ -24,5 +24,6 @@
24
24
  "aside.tip": "提示",
25
25
  "aside.caution": "警告",
26
26
  "aside.danger": "危險",
27
- "fileTree.directory": "目錄"
27
+ "fileTree.directory": "目錄",
28
+ "builtWithStarlight.label": "Built with Starlight"
28
29
  }
@@ -0,0 +1,141 @@
1
+ ---
2
+ import { BadgeComponentSchema, type BadgeComponentProps } from '../schemas/badge';
3
+ import { parseWithFriendlyErrors } from '../utils/error-map';
4
+ import type { HTMLAttributes } from 'astro/types';
5
+
6
+ type Props = BadgeComponentProps & HTMLAttributes<'span'>;
7
+
8
+ const {
9
+ text,
10
+ variant,
11
+ size,
12
+ class: customClass,
13
+ ...attrs
14
+ } = parseWithFriendlyErrors(
15
+ BadgeComponentSchema,
16
+ Astro.props,
17
+ 'Invalid prop passed to the `<Badge/>` component.'
18
+ );
19
+ ---
20
+
21
+ <span class:list={['sl-badge', variant, size, customClass]} {...attrs}>{text}</span>
22
+
23
+ <style>
24
+ :global(:root) {
25
+ --sl-badge-default-border: var(--sl-color-accent);
26
+ --sl-badge-default-bg: var(--sl-color-accent-low);
27
+ --sl-badge-default-text: #fff;
28
+
29
+ --sl-badge-note-border: var(--sl-color-blue);
30
+ --sl-badge-note-bg: var(--sl-color-blue-low);
31
+ --sl-badge-note-text: #fff;
32
+
33
+ --sl-badge-danger-border: var(--sl-color-red);
34
+ --sl-badge-danger-bg: var(--sl-color-red-low);
35
+ --sl-badge-danger-text: #fff;
36
+
37
+ --sl-badge-success-border: var(--sl-color-green);
38
+ --sl-badge-success-bg: var(--sl-color-green-low);
39
+ --sl-badge-success-text: #fff;
40
+
41
+ --sl-badge-caution-border: var(--sl-color-orange);
42
+ --sl-badge-caution-bg: var(--sl-color-orange-low);
43
+ --sl-badge-caution-text: #fff;
44
+
45
+ --sl-badge-tip-border: var(--sl-color-purple);
46
+ --sl-badge-tip-bg: var(--sl-color-purple-low);
47
+ --sl-badge-tip-text: #fff;
48
+ }
49
+
50
+ :global([data-theme='light']:root) {
51
+ --sl-badge-default-bg: var(--sl-color-accent-high);
52
+ --sl-badge-note-bg: var(--sl-color-blue-high);
53
+ --sl-badge-danger-bg: var(--sl-color-red-high);
54
+ --sl-badge-success-bg: var(--sl-color-green-high);
55
+ --sl-badge-caution-bg: var(--sl-color-orange-high);
56
+ --sl-badge-tip-bg: var(--sl-color-purple-high);
57
+ }
58
+
59
+ .sl-badge {
60
+ display: inline-block;
61
+ border: 1px solid var(--sl-color-border-badge);
62
+ border-radius: 0.25rem;
63
+ font-family: var(--sl-font-system-mono);
64
+ line-height: normal;
65
+ color: var(--sl-color-text-badge);
66
+ background-color: var(--sl-color-bg-badge);
67
+ overflow-wrap: anywhere;
68
+ }
69
+
70
+ /* Sidebar overrides */
71
+ :global(.sidebar-content) .sl-badge {
72
+ line-height: 1;
73
+ font-size: var(--sl-text-xs);
74
+ padding: 0.125rem 0.375rem;
75
+ }
76
+
77
+ /* outline variant */
78
+ :global(.sidebar-content a[aria-current='page']) > .sl-badge {
79
+ --sl-color-bg-badge: transparent;
80
+ --sl-color-border-badge: currentColor;
81
+ color: inherit;
82
+ }
83
+
84
+ /* Color variants */
85
+ .default {
86
+ --sl-color-bg-badge: var(--sl-badge-default-bg);
87
+ --sl-color-border-badge: var(--sl-badge-default-border);
88
+ --sl-color-text-badge: var(--sl-badge-default-text);
89
+ }
90
+
91
+ .note {
92
+ --sl-color-bg-badge: var(--sl-badge-note-bg);
93
+ --sl-color-border-badge: var(--sl-badge-note-border);
94
+ --sl-color-text-badge: var(--sl-badge-note-text);
95
+ }
96
+
97
+ .danger {
98
+ --sl-color-bg-badge: var(--sl-badge-danger-bg);
99
+ --sl-color-border-badge: var(--sl-badge-danger-border);
100
+ --sl-color-text-badge: var(--sl-badge-danger-text);
101
+ }
102
+
103
+ .success {
104
+ --sl-color-bg-badge: var(--sl-badge-success-bg);
105
+ --sl-color-border-badge: var(--sl-badge-success-border);
106
+ --sl-color-text-badge: var(--sl-badge-success-text);
107
+ }
108
+
109
+ .tip {
110
+ --sl-color-bg-badge: var(--sl-badge-tip-bg);
111
+ --sl-color-border-badge: var(--sl-badge-tip-border);
112
+ --sl-color-text-badge: var(--sl-badge-tip-text);
113
+ }
114
+
115
+ .caution {
116
+ --sl-color-bg-badge: var(--sl-badge-caution-bg);
117
+ --sl-color-border-badge: var(--sl-badge-caution-border);
118
+ --sl-color-text-badge: var(--sl-badge-caution-text);
119
+ }
120
+
121
+ /* Size variants */
122
+ .small {
123
+ font-size: var(--sl-text-xs);
124
+ padding: 0.125rem 0.25rem;
125
+ }
126
+
127
+ .medium {
128
+ font-size: var(--sl-text-sm);
129
+ padding: 0.175rem 0.35rem;
130
+ }
131
+
132
+ .large {
133
+ font-size: var(--sl-text-base);
134
+ padding: 0.225rem 0.45rem;
135
+ }
136
+
137
+ /* Badge in headings */
138
+ :global(.sl-markdown-content :is(h1, h2, h3, h4, h5, h6)) .sl-badge {
139
+ vertical-align: middle;
140
+ }
141
+ </style>
@@ -1,5 +1,6 @@
1
1
  import type { i18nSchemaOutput } from '../schemas/i18n';
2
2
  import builtinTranslations from '../translations/index';
3
+ import { BuiltInDefaultLocale } from './i18n';
3
4
  import type { StarlightConfig } from './user-config';
4
5
 
5
6
  export function createTranslationSystem<T extends i18nSchemaOutput>(
@@ -64,7 +65,7 @@ function localeToLang(
64
65
  ): string {
65
66
  const lang = locale ? locales?.[locale]?.lang : locales?.root?.lang;
66
67
  const defaultLang = defaultLocale?.lang || defaultLocale?.locale;
67
- return lang || defaultLang || 'en';
68
+ return lang || defaultLang || BuiltInDefaultLocale.lang;
68
69
  }
69
70
 
70
71
  type BuiltInStrings = (typeof builtinTranslations)['en'];
package/utils/i18n.ts CHANGED
@@ -1,3 +1,166 @@
1
+ import type { AstroConfig } from 'astro';
2
+ import { AstroError } from 'astro/errors';
3
+ import type { StarlightConfig } from './user-config';
4
+
5
+ /** Informations about the built-in default locale used as a fallback when no locales are defined. */
6
+ export const BuiltInDefaultLocale = { ...getLocaleInfo('en'), lang: 'en' };
7
+
8
+ /**
9
+ * Processes the Astro and Starlight i18n configurations to generate/update them accordingly:
10
+ *
11
+ * - If no Astro and Starlight i18n configurations are provided, the built-in default locale is
12
+ * used in Starlight and the generated Astro i18n configuration will match it.
13
+ * - If only a Starlight i18n configuration is provided, an equivalent Astro i18n configuration is
14
+ * generated.
15
+ * - If only an Astro i18n configuration is provided, an equivalent Starlight i18n configuration is
16
+ * used.
17
+ * - If both an Astro and Starlight i18n configurations are provided, an error is thrown.
18
+ */
19
+ export function processI18nConfig(
20
+ starlightConfig: StarlightConfig,
21
+ astroI18nConfig: AstroConfig['i18n']
22
+ ) {
23
+ // We don't know what to do if both an Astro and Starlight i18n configuration are provided.
24
+ if (astroI18nConfig && !starlightConfig.isUsingBuiltInDefaultLocale) {
25
+ throw new AstroError(
26
+ 'Cannot provide both an Astro `i18n` configuration and a Starlight `locales` configuration.',
27
+ 'Remove one of the two configurations.\nSee more at https://starlight.astro.build/guides/i18n/'
28
+ );
29
+ } else if (astroI18nConfig) {
30
+ // If a Starlight compatible Astro i18n configuration is provided, we generate the matching
31
+ // Starlight configuration.
32
+ return {
33
+ astroI18nConfig,
34
+ starlightConfig: {
35
+ ...starlightConfig,
36
+ ...getStarlightI18nConfig(astroI18nConfig),
37
+ } as StarlightConfig,
38
+ };
39
+ }
40
+ // Otherwise, we generate the Astro i18n configuration based on the Starlight configuration.
41
+ return { astroI18nConfig: getAstroI18nConfig(starlightConfig), starlightConfig: starlightConfig };
42
+ }
43
+
44
+ /** Generate an Astro i18n configuration based on a Starlight configuration. */
45
+ function getAstroI18nConfig(config: StarlightConfig): NonNullable<AstroConfig['i18n']> {
46
+ return {
47
+ defaultLocale:
48
+ config.defaultLocale.lang ?? config.defaultLocale.locale ?? BuiltInDefaultLocale.lang,
49
+ locales: config.locales
50
+ ? Object.entries(config.locales).map(([locale, localeConfig]) => {
51
+ return {
52
+ codes: [localeConfig?.lang ?? locale],
53
+ path: locale === 'root' ? localeConfig?.lang ?? BuiltInDefaultLocale.lang : locale,
54
+ };
55
+ })
56
+ : [BuiltInDefaultLocale.lang],
57
+ routing: {
58
+ prefixDefaultLocale:
59
+ // Sites with multiple languages without a root locale.
60
+ (config.isMultilingual && config.locales?.root === undefined) ||
61
+ // Sites with a single non-root language different from the built-in default locale.
62
+ (!config.isMultilingual && config.locales !== undefined),
63
+ redirectToDefaultLocale: false,
64
+ },
65
+ };
66
+ }
67
+
68
+ /** Generate a Starlight i18n configuration based on an Astro configuration. */
69
+ function getStarlightI18nConfig(
70
+ astroI18nConfig: NonNullable<AstroConfig['i18n']>
71
+ ): Pick<StarlightConfig, 'isMultilingual' | 'locales' | 'defaultLocale'> {
72
+ if (astroI18nConfig.routing === 'manual') {
73
+ throw new AstroError(
74
+ 'Starlight is not compatible with the `manual` routing option in the Astro i18n configuration.'
75
+ );
76
+ }
77
+
78
+ const prefixDefaultLocale = astroI18nConfig.routing.prefixDefaultLocale;
79
+ const isMultilingual = astroI18nConfig.locales.length > 1;
80
+ const isMonolingualWithRootLocale = !isMultilingual && !prefixDefaultLocale;
81
+
82
+ const locales = isMonolingualWithRootLocale
83
+ ? undefined
84
+ : Object.fromEntries(
85
+ astroI18nConfig.locales.map((locale) => [
86
+ isDefaultAstroLocale(astroI18nConfig, locale) && !prefixDefaultLocale
87
+ ? 'root'
88
+ : isAstroLocaleExtendedConfig(locale)
89
+ ? locale.path
90
+ : locale,
91
+ inferStarlightLocaleFromAstroLocale(locale),
92
+ ])
93
+ );
94
+
95
+ const defaultAstroLocale = astroI18nConfig.locales.find((locale) =>
96
+ isDefaultAstroLocale(astroI18nConfig, locale)
97
+ );
98
+
99
+ // This should never happen as Astro validation should prevent this case.
100
+ if (!defaultAstroLocale) {
101
+ throw new AstroError(
102
+ 'Astro default locale not found.',
103
+ 'This should never happen. Please open a new issue: https://github.com/withastro/starlight/issues/new?template=---01-bug-report.yml'
104
+ );
105
+ }
106
+
107
+ return {
108
+ isMultilingual,
109
+ locales,
110
+ defaultLocale: {
111
+ ...inferStarlightLocaleFromAstroLocale(defaultAstroLocale),
112
+ locale: isMonolingualWithRootLocale
113
+ ? undefined
114
+ : isAstroLocaleExtendedConfig(defaultAstroLocale)
115
+ ? defaultAstroLocale.codes[0]
116
+ : defaultAstroLocale,
117
+ },
118
+ };
119
+ }
120
+
121
+ /** Infer Starlight locale informations based on a locale from an Astro i18n configuration. */
122
+ function inferStarlightLocaleFromAstroLocale(astroLocale: AstroLocale) {
123
+ const lang = isAstroLocaleExtendedConfig(astroLocale) ? astroLocale.codes[0] : astroLocale;
124
+ return { ...getLocaleInfo(lang), lang };
125
+ }
126
+
127
+ /** Check if the passed locale is the default locale in an Astro i18n configuration. */
128
+ function isDefaultAstroLocale(
129
+ astroI18nConfig: NonNullable<AstroConfig['i18n']>,
130
+ locale: AstroLocale
131
+ ) {
132
+ return (
133
+ (isAstroLocaleExtendedConfig(locale) ? locale.path : locale) === astroI18nConfig.defaultLocale
134
+ );
135
+ }
136
+
137
+ /**
138
+ * Check if the passed Astro locale is using the object variant.
139
+ * @see AstroLocaleExtendedConfig
140
+ */
141
+ function isAstroLocaleExtendedConfig(locale: AstroLocale): locale is AstroLocaleExtendedConfig {
142
+ return typeof locale !== 'string';
143
+ }
144
+
145
+ /** Returns the locale informations such as a label and a direction based on a BCP-47 tag. */
146
+ function getLocaleInfo(lang: string) {
147
+ try {
148
+ const locale = new Intl.Locale(lang);
149
+ const label = new Intl.DisplayNames(locale, { type: 'language' }).of(lang);
150
+ if (!label || lang === label) throw new Error('Label not found.');
151
+ return {
152
+ label: label[0]?.toLocaleUpperCase(locale) + label.slice(1),
153
+ // @ts-expect-error - `textInfo` is not part of the `Intl.Locale` type but is available in Node.js 18.0.0+.
154
+ dir: locale.textInfo.direction as 'ltr' | 'rtl',
155
+ };
156
+ } catch (error) {
157
+ throw new AstroError(
158
+ `Failed to get locale informations for the '${lang}' locale.`,
159
+ 'Make sure to provide a valid BCP-47 tags (e.g. en, ar, or zh-CN).'
160
+ );
161
+ }
162
+ }
163
+
1
164
  /**
2
165
  * Get the string for the passed language from a dictionary object.
3
166
  *
@@ -14,3 +177,6 @@ export function pickLang<T extends Record<string, string>>(
14
177
  ): string | undefined {
15
178
  return dictionary[lang];
16
179
  }
180
+
181
+ type AstroLocale = NonNullable<AstroConfig['i18n']>['locales'][number];
182
+ type AstroLocaleExtendedConfig = Exclude<AstroLocale, string>;
package/utils/routing.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  slugToParam,
10
10
  } from './slugs';
11
11
  import { validateLogoImports } from './validateLogoImports';
12
+ import { BuiltInDefaultLocale } from './i18n';
12
13
 
13
14
  // Validate any user-provided logos imported correctly.
14
15
  // We do this here so all pages trigger it and at the top level so it runs just once.
@@ -86,7 +87,7 @@ function getRoutes(): Route[] {
86
87
  slug,
87
88
  id,
88
89
  isFallback: true,
89
- lang: localeConfig.lang || 'en',
90
+ lang: localeConfig.lang || BuiltInDefaultLocale.lang,
90
91
  locale,
91
92
  dir: localeConfig.dir,
92
93
  entryMeta: slugToLocaleData(fallback.slug),
package/utils/slugs.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import config from 'virtual:starlight/user-config';
2
+ import { BuiltInDefaultLocale } from './i18n';
2
3
 
3
4
  export interface LocaleData {
4
5
  /** Writing direction. */
@@ -35,7 +36,7 @@ export function slugToLocaleData(slug: string): LocaleData {
35
36
  export function localeToLang(locale: string | undefined): string {
36
37
  const lang = locale ? config.locales?.[locale]?.lang : config.locales?.root?.lang;
37
38
  const defaultLang = config.defaultLocale?.lang || config.defaultLocale?.locale;
38
- return lang || defaultLang || 'en';
39
+ return lang || defaultLang || BuiltInDefaultLocale.lang;
39
40
  }
40
41
 
41
42
  /**
@@ -9,6 +9,7 @@ import { SidebarItemSchema } from '../schemas/sidebar';
9
9
  import { SocialLinksSchema } from '../schemas/social';
10
10
  import { TableOfContentsSchema } from '../schemas/tableOfContents';
11
11
  import { TitleConfigSchema, TitleTransformConfigSchema } from '../schemas/site-title';
12
+ import { BuiltInDefaultLocale } from './i18n';
12
13
 
13
14
  const LocaleSchema = z.object({
14
15
  /** The label for this language to show in UI, e.g. `"English"`, `"العربية"`, or `"简体中文"`. */
@@ -207,6 +208,12 @@ const UserConfigSchema = z.object({
207
208
 
208
209
  /** Disable Starlight's default 404 page. */
209
210
  disable404Route: z.boolean().default(false).describe("Disable Starlight's default 404 page."),
211
+
212
+ /** Enable displaying a “Built with Starlight” link in your site’s footer. */
213
+ credits: z
214
+ .boolean()
215
+ .default(false)
216
+ .describe('Enable displaying a “Built with Starlight” link in your site’s footer.'),
210
217
  });
211
218
 
212
219
  export const StarlightConfigSchema = UserConfigSchema.strict().transform(
@@ -244,6 +251,8 @@ export const StarlightConfigSchema = UserConfigSchema.strict().transform(
244
251
  title: parsedTitle,
245
252
  /** Flag indicating if this site has multiple locales set up. */
246
253
  isMultilingual: configuredLocales.length > 1,
254
+ /** Flag indicating if the Starlight built-in default locale is used. */
255
+ isUsingBuiltInDefaultLocale: false,
247
256
  /** Full locale object for this site’s default language. */
248
257
  defaultLocale: { ...defaultLocaleConfig, locale: defaultLocale },
249
258
  locales,
@@ -254,9 +263,9 @@ export const StarlightConfigSchema = UserConfigSchema.strict().transform(
254
263
  // pretty simple.
255
264
  /** Full locale object for this site’s default language. */
256
265
  const defaultLocaleConfig = {
257
- label: 'English',
258
- lang: 'en',
259
- dir: 'ltr' as const,
266
+ label: BuiltInDefaultLocale.label,
267
+ lang: BuiltInDefaultLocale.lang,
268
+ dir: BuiltInDefaultLocale.dir,
260
269
  locale: undefined,
261
270
  ...locales?.root,
262
271
  };
@@ -268,6 +277,8 @@ export const StarlightConfigSchema = UserConfigSchema.strict().transform(
268
277
  title: parsedTitle,
269
278
  /** Flag indicating if this site has multiple locales set up. */
270
279
  isMultilingual: false,
280
+ /** Flag indicating if the Starlight built-in default locale is used. */
281
+ isUsingBuiltInDefaultLocale: locales?.root === undefined,
271
282
  defaultLocale: defaultLocaleConfig,
272
283
  locales: undefined,
273
284
  } as const;
@@ -1,87 +0,0 @@
1
- ---
2
- import type { Badge } from '../schemas/badge';
3
-
4
- interface Props {
5
- variant?: Badge['variant'] | 'outline';
6
- text?: string;
7
- }
8
- const { variant = 'default', text } = Astro.props;
9
- ---
10
-
11
- <span class:list={['sl-badge', variant]} set:html={text} />
12
-
13
- <style>
14
- .sl-badge {
15
- display: inline-block;
16
- border: 1px solid var(--sl-color-border-badge);
17
- border-radius: 0.25rem;
18
- font-family: var(--sl-font-system-mono);
19
- font-size: var(--sl-text-xs);
20
- font-weight: 400;
21
- padding: 0.125rem 0.375rem;
22
- line-height: 1;
23
- color: #fff;
24
- background-color: var(--sl-color-bg-badge);
25
- overflow-wrap: anywhere;
26
- }
27
-
28
- .outline {
29
- --sl-color-bg-badge: transparent;
30
- --sl-color-border-badge: currentColor;
31
- color: inherit;
32
- }
33
-
34
- .default {
35
- --sl-color-bg-badge: var(--sl-color-accent-low);
36
- --sl-color-border-badge: var(--sl-color-accent);
37
- }
38
-
39
- .note {
40
- --sl-color-bg-badge: var(--sl-color-blue-low);
41
- --sl-color-border-badge: var(--sl-color-blue);
42
- }
43
-
44
- .danger {
45
- --sl-color-bg-badge: var(--sl-color-red-low);
46
- --sl-color-border-badge: var(--sl-color-red);
47
- }
48
-
49
- .success {
50
- --sl-color-bg-badge: var(--sl-color-green-low);
51
- --sl-color-border-badge: var(--sl-color-green);
52
- }
53
-
54
- .caution {
55
- --sl-color-bg-badge: var(--sl-color-orange-low);
56
- --sl-color-border-badge: var(--sl-color-orange);
57
- }
58
-
59
- .tip {
60
- --sl-color-bg-badge: var(--sl-color-purple-low);
61
- --sl-color-border-badge: var(--sl-color-purple);
62
- }
63
-
64
- :global([data-theme='light']) .default {
65
- --sl-color-bg-badge: var(--sl-color-accent-high);
66
- }
67
-
68
- :global([data-theme='light']) .note {
69
- --sl-color-bg-badge: var(--sl-color-blue-high);
70
- }
71
-
72
- :global([data-theme='light']) .danger {
73
- --sl-color-bg-badge: var(--sl-color-red-high);
74
- }
75
-
76
- :global([data-theme='light']) .success {
77
- --sl-color-bg-badge: var(--sl-color-green-high);
78
- }
79
-
80
- :global([data-theme='light']) .caution {
81
- --sl-color-bg-badge: var(--sl-color-orange-high);
82
- }
83
-
84
- :global([data-theme='light']) .tip {
85
- --sl-color-bg-badge: var(--sl-color-purple-high);
86
- }
87
- </style>