@astrojs/starlight 0.27.0 → 0.28.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/CHANGELOG.md +40 -0
- package/components/DraftContentNotice.astro +1 -3
- package/components/EditLink.astro +2 -2
- package/components/FallbackContentNotice.astro +1 -3
- package/components/Footer.astro +1 -1
- package/components/LanguageSelect.astro +1 -3
- package/components/LastUpdated.astro +2 -2
- package/components/MobileMenuToggle.astro +1 -3
- package/components/MobileTableOfContents.astro +2 -2
- package/components/PageFrame.astro +2 -2
- package/components/Pagination.astro +3 -3
- package/components/Search.astro +8 -10
- package/components/SidebarRestorePoint.astro +1 -1
- package/components/SkipLink.astro +1 -3
- package/components/TableOfContents.astro +2 -2
- package/components/ThemeSelect.astro +4 -6
- package/i18n.d.ts +18 -0
- package/index.ts +32 -4
- package/integrations/asides.ts +3 -2
- package/integrations/virtual-user-config.ts +15 -3
- package/locals.d.ts +17 -0
- package/locals.ts +8 -0
- package/package.json +4 -2
- package/schemas/badge.ts +20 -10
- package/schemas/sidebar.ts +3 -3
- package/utils/createTranslationSystem.ts +68 -25
- package/utils/i18n.ts +20 -0
- package/utils/navigation.ts +57 -8
- package/utils/plugins.ts +67 -2
- package/utils/route-data.ts +5 -4
- package/utils/starlight-page.ts +3 -18
- package/utils/translations-fs.ts +4 -3
- package/utils/translations.ts +10 -3
- package/utils/types.ts +15 -0
- package/virtual.d.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#1923](https://github.com/withastro/starlight/pull/1923) [`5269aad`](https://github.com/withastro/starlight/commit/5269aad928773ae08b35ba8e19c0f2832d0d2c89) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Overhauls the built-in localization system which is now powered by the [`i18next`](https://www.i18next.com/) library and available to use anywhere in your documentation website.
|
|
8
|
+
|
|
9
|
+
See the [“Using UI translations”](https://starlight.astro.build/guides/i18n/#using-ui-translations) guide to learn more about how to access built-in UI labels or your own custom strings in your project. Plugin authors can also use the new [`injectTranslations()`](https://starlight.astro.build/reference/plugins/#injecttranslations) helper to add or update translation strings.
|
|
10
|
+
|
|
11
|
+
⚠️ **BREAKING CHANGE:** The `Astro.props.labels` props has been removed from the props passed down to custom component overrides.
|
|
12
|
+
|
|
13
|
+
If you are relying on `Astro.props.labels` (for example to read a built-in UI label), you will need to update your code to use the new [`Astro.locals.t()`](https://starlight.astro.build/guides/i18n/#using-ui-translations) helper instead.
|
|
14
|
+
|
|
15
|
+
```astro
|
|
16
|
+
---
|
|
17
|
+
import type { Props } from '@astrojs/starlight/props';
|
|
18
|
+
// The `search.label` UI label for this page’s language:
|
|
19
|
+
const searchLabel = Astro.locals.t('search.label');
|
|
20
|
+
---
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- [#2285](https://github.com/withastro/starlight/pull/2285) [`7286220`](https://github.com/withastro/starlight/commit/728622037602999ed67dedc2757ca5654236feb8) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds support for translating sidebar badges.
|
|
24
|
+
|
|
25
|
+
- [#1923](https://github.com/withastro/starlight/pull/1923) [`5269aad`](https://github.com/withastro/starlight/commit/5269aad928773ae08b35ba8e19c0f2832d0d2c89) Thanks [@HiDeoo](https://github.com/HiDeoo)! - ⚠️ **BREAKING CHANGE:** The minimum supported version of Astro is now 4.14.0
|
|
26
|
+
|
|
27
|
+
Please update Astro and Starlight together:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npx @astrojs/upgrade
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- [#2327](https://github.com/withastro/starlight/pull/2327) [`d7a295e`](https://github.com/withastro/starlight/commit/d7a295e5f63171c7eee9fc11333157d8c7e6c803) Thanks [@tritao](https://github.com/tritao)! - Fixes restoration of remark directives for nodes with custom data attached.
|
|
36
|
+
|
|
37
|
+
## 0.27.1
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- [#2303](https://github.com/withastro/starlight/pull/2303) [`f92791a`](https://github.com/withastro/starlight/commit/f92791aa1d1ec3d5498e445a078f7143fef60553) Thanks [@delucis](https://github.com/delucis)! - Fixes resolution for the internal module Git virtual module in projects with special characters in the file path
|
|
42
|
+
|
|
3
43
|
## 0.27.0
|
|
4
44
|
|
|
5
45
|
### Minor Changes
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import ContentNotice from './ContentNotice.astro';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
|
-
|
|
5
|
-
const { labels } = Astro.props;
|
|
6
4
|
---
|
|
7
5
|
|
|
8
|
-
<ContentNotice icon="warning" label={
|
|
6
|
+
<ContentNotice icon="warning" label={Astro.locals.t('page.draft')} />
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import Icon from '../user-components/Icon.astro';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
4
|
|
|
5
|
-
const { editUrl
|
|
5
|
+
const { editUrl } = Astro.props;
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
{
|
|
9
9
|
editUrl && (
|
|
10
10
|
<a href={editUrl} class="sl-flex">
|
|
11
11
|
<Icon name="pencil" size="1.2em" />
|
|
12
|
-
{
|
|
12
|
+
{Astro.locals.t('page.editLink')}
|
|
13
13
|
</a>
|
|
14
14
|
)
|
|
15
15
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import ContentNotice from './ContentNotice.astro';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
|
-
|
|
5
|
-
const { labels } = Astro.props;
|
|
6
4
|
---
|
|
7
5
|
|
|
8
|
-
<ContentNotice icon="warning" label={
|
|
6
|
+
<ContentNotice icon="warning" label={Astro.locals.t('i18n.untranslatedContent')} />
|
package/components/Footer.astro
CHANGED
|
@@ -18,7 +18,7 @@ import { Icon } from '../components';
|
|
|
18
18
|
{
|
|
19
19
|
config.credits && (
|
|
20
20
|
<a class="kudos sl-flex" href="https://starlight.astro.build">
|
|
21
|
-
<Icon name={'starlight'} /> {Astro.
|
|
21
|
+
<Icon name={'starlight'} /> {Astro.locals.t('builtWithStarlight.label')}
|
|
22
22
|
</a>
|
|
23
23
|
)
|
|
24
24
|
}
|
|
@@ -10,8 +10,6 @@ import type { Props } from '../props';
|
|
|
10
10
|
function localizedPathname(locale: string | undefined): string {
|
|
11
11
|
return localizedUrl(Astro.url, locale).pathname;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
const { labels } = Astro.props;
|
|
15
13
|
---
|
|
16
14
|
|
|
17
15
|
{
|
|
@@ -19,7 +17,7 @@ const { labels } = Astro.props;
|
|
|
19
17
|
<starlight-lang-select>
|
|
20
18
|
<Select
|
|
21
19
|
icon="translate"
|
|
22
|
-
label={
|
|
20
|
+
label={Astro.locals.t('languageSelect.accessibleLabel')}
|
|
23
21
|
value={localizedPathname(Astro.props.locale)}
|
|
24
22
|
options={Object.entries(config.locales).map(([code, locale]) => ({
|
|
25
23
|
value: localizedPathname(code),
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { Props } from '../props';
|
|
3
3
|
|
|
4
|
-
const {
|
|
4
|
+
const { lang, lastUpdated } = Astro.props;
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
{
|
|
8
8
|
lastUpdated && (
|
|
9
9
|
<p>
|
|
10
|
-
{
|
|
10
|
+
{Astro.locals.t('page.lastUpdated')}{' '}
|
|
11
11
|
<time datetime={lastUpdated.toISOString()}>
|
|
12
12
|
{lastUpdated.toLocaleDateString(lang, { dateStyle: 'medium', timeZone: 'UTC' })}
|
|
13
13
|
</time>
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { Props } from '../props';
|
|
3
3
|
import Icon from '../user-components/Icon.astro';
|
|
4
|
-
|
|
5
|
-
const { labels } = Astro.props;
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
<starlight-menu-button>
|
|
9
7
|
<button
|
|
10
8
|
aria-expanded="false"
|
|
11
|
-
aria-label={
|
|
9
|
+
aria-label={Astro.locals.t('menuButton.accessibleLabel')}
|
|
12
10
|
aria-controls="starlight__sidebar"
|
|
13
11
|
class="sl-flex md:sl-hidden"
|
|
14
12
|
>
|
|
@@ -3,7 +3,7 @@ import Icon from '../user-components/Icon.astro';
|
|
|
3
3
|
import TableOfContentsList from './TableOfContents/TableOfContentsList.astro';
|
|
4
4
|
import type { Props } from '../props';
|
|
5
5
|
|
|
6
|
-
const {
|
|
6
|
+
const { toc } = Astro.props;
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
{
|
|
@@ -13,7 +13,7 @@ const { labels, toc } = Astro.props;
|
|
|
13
13
|
<details id="starlight__mobile-toc">
|
|
14
14
|
<summary id="starlight__on-this-page--mobile" class="sl-flex">
|
|
15
15
|
<div class="toggle sl-flex">
|
|
16
|
-
{
|
|
16
|
+
{Astro.locals.t('tableOfContents.onThisPage')}
|
|
17
17
|
<Icon name={'right-caret'} class="caret" size="1rem" />
|
|
18
18
|
</div>
|
|
19
19
|
<span class="display-current" />
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import MobileMenuToggle from 'virtual:starlight/components/MobileMenuToggle';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
4
|
|
|
5
|
-
const { hasSidebar
|
|
5
|
+
const { hasSidebar } = Astro.props;
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
<div class="page sl-flex">
|
|
9
9
|
<header class="header"><slot name="header" /></header>
|
|
10
10
|
{
|
|
11
11
|
hasSidebar && (
|
|
12
|
-
<nav class="sidebar" aria-label={
|
|
12
|
+
<nav class="sidebar" aria-label={Astro.locals.t('sidebarNav.accessibleLabel')}>
|
|
13
13
|
<MobileMenuToggle {...Astro.props} />
|
|
14
14
|
<div id="starlight__sidebar" class="sidebar-pane">
|
|
15
15
|
<div class="sidebar-content sl-flex">
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import Icon from '../user-components/Icon.astro';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
4
|
|
|
5
|
-
const { dir,
|
|
5
|
+
const { dir, pagination } = Astro.props;
|
|
6
6
|
const { prev, next } = pagination;
|
|
7
7
|
const isRtl = dir === 'rtl';
|
|
8
8
|
---
|
|
@@ -13,7 +13,7 @@ const isRtl = dir === 'rtl';
|
|
|
13
13
|
<a href={prev.href} rel="prev">
|
|
14
14
|
<Icon name={isRtl ? 'right-arrow' : 'left-arrow'} size="1.5rem" />
|
|
15
15
|
<span>
|
|
16
|
-
{
|
|
16
|
+
{Astro.locals.t('page.previousLink')}
|
|
17
17
|
<br />
|
|
18
18
|
<span class="link-title">{prev.label}</span>
|
|
19
19
|
</span>
|
|
@@ -25,7 +25,7 @@ const isRtl = dir === 'rtl';
|
|
|
25
25
|
<a href={next.href} rel="next">
|
|
26
26
|
<Icon name={isRtl ? 'left-arrow' : 'right-arrow'} size="1.5rem" />
|
|
27
27
|
<span>
|
|
28
|
-
{
|
|
28
|
+
{Astro.locals.t('page.nextLink')}
|
|
29
29
|
<br />
|
|
30
30
|
<span class="link-title">{next.label}</span>
|
|
31
31
|
</span>
|
package/components/Search.astro
CHANGED
|
@@ -4,12 +4,10 @@ import Icon from '../user-components/Icon.astro';
|
|
|
4
4
|
import project from 'virtual:starlight/project-context';
|
|
5
5
|
import type { Props } from '../props';
|
|
6
6
|
|
|
7
|
-
const { labels } = Astro.props;
|
|
8
|
-
|
|
9
7
|
const pagefindTranslations = {
|
|
10
|
-
placeholder:
|
|
8
|
+
placeholder: Astro.locals.t('search.label'),
|
|
11
9
|
...Object.fromEntries(
|
|
12
|
-
Object.entries(
|
|
10
|
+
Object.entries(Astro.locals.t.all())
|
|
13
11
|
.filter(([key]) => key.startsWith('pagefind.'))
|
|
14
12
|
.map(([key, value]) => [key.replace('pagefind.', ''), value])
|
|
15
13
|
),
|
|
@@ -23,28 +21,28 @@ const pagefindTranslations = {
|
|
|
23
21
|
<button
|
|
24
22
|
data-open-modal
|
|
25
23
|
disabled
|
|
26
|
-
aria-label={
|
|
24
|
+
aria-label={Astro.locals.t('search.label')}
|
|
27
25
|
aria-keyshortcuts="Control+K"
|
|
28
26
|
>
|
|
29
27
|
<Icon name="magnifier" />
|
|
30
|
-
<span class="sl-hidden md:sl-block" aria-hidden="true">{
|
|
28
|
+
<span class="sl-hidden md:sl-block" aria-hidden="true">{Astro.locals.t('search.label')}</span>
|
|
31
29
|
<kbd class="sl-hidden md:sl-flex" style="display: none;">
|
|
32
|
-
<kbd>{
|
|
30
|
+
<kbd>{Astro.locals.t('search.ctrlKey')}</kbd><kbd>K</kbd>
|
|
33
31
|
</kbd>
|
|
34
32
|
</button>
|
|
35
33
|
|
|
36
|
-
<dialog style="padding:0" aria-label={
|
|
34
|
+
<dialog style="padding:0" aria-label={Astro.locals.t('search.label')}>
|
|
37
35
|
<div class="dialog-frame sl-flex">
|
|
38
36
|
{
|
|
39
37
|
/* TODO: Make the layout of this button flexible to accommodate different word lengths. Currently hard-coded for English: “Cancel” */
|
|
40
38
|
}
|
|
41
39
|
<button data-close-modal class="sl-flex md:sl-hidden">
|
|
42
|
-
{
|
|
40
|
+
{Astro.locals.t('search.cancelLabel')}
|
|
43
41
|
</button>
|
|
44
42
|
{
|
|
45
43
|
import.meta.env.DEV ? (
|
|
46
44
|
<div style="margin: auto; text-align: center; white-space: pre-line;" dir="ltr">
|
|
47
|
-
<p>{
|
|
45
|
+
<p>{Astro.locals.t('search.devWarning')}</p>
|
|
48
46
|
</div>
|
|
49
47
|
) : (
|
|
50
48
|
<div class="search-container">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
/** Unique symbol for storing a running index in `locals`. */
|
|
3
3
|
const currentGroupIndexSymbol = Symbol.for('starlight-sidebar-group-index');
|
|
4
|
-
const locals = Astro.locals as
|
|
4
|
+
const locals = Astro.locals as App.Locals & { [currentGroupIndexSymbol]: number };
|
|
5
5
|
|
|
6
6
|
/** The current sidebar group’s index retrieved from `locals` if set, starting at `0`. */
|
|
7
7
|
const index = locals[currentGroupIndexSymbol] || 0;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { PAGE_TITLE_ID } from '../constants';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
|
-
|
|
5
|
-
const { labels } = Astro.props;
|
|
6
4
|
---
|
|
7
5
|
|
|
8
|
-
<a href={`#${PAGE_TITLE_ID}`}>{
|
|
6
|
+
<a href={`#${PAGE_TITLE_ID}`}>{Astro.locals.t('skipLink.label')}</a>
|
|
9
7
|
|
|
10
8
|
<style>
|
|
11
9
|
a {
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import TableOfContentsList from './TableOfContents/TableOfContentsList.astro';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const { toc } = Astro.props;
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
{
|
|
9
9
|
toc && (
|
|
10
10
|
<starlight-toc data-min-h={toc.minHeadingLevel} data-max-h={toc.maxHeadingLevel}>
|
|
11
11
|
<nav aria-labelledby="starlight__on-this-page">
|
|
12
|
-
<h2 id="starlight__on-this-page">{
|
|
12
|
+
<h2 id="starlight__on-this-page">{Astro.locals.t('tableOfContents.onThisPage')}</h2>
|
|
13
13
|
<TableOfContentsList toc={toc.items} />
|
|
14
14
|
</nav>
|
|
15
15
|
</starlight-toc>
|
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Select from './Select.astro';
|
|
3
3
|
import type { Props } from '../props';
|
|
4
|
-
|
|
5
|
-
const { labels } = Astro.props;
|
|
6
4
|
---
|
|
7
5
|
|
|
8
6
|
<starlight-theme-select>
|
|
9
7
|
{/* TODO: Can we give this select a width that works well for each language’s strings? */}
|
|
10
8
|
<Select
|
|
11
9
|
icon="laptop"
|
|
12
|
-
label={
|
|
10
|
+
label={Astro.locals.t('themeSelect.accessibleLabel')}
|
|
13
11
|
value="auto"
|
|
14
12
|
options={[
|
|
15
|
-
{ label:
|
|
16
|
-
{ label:
|
|
17
|
-
{ label:
|
|
13
|
+
{ label: Astro.locals.t('themeSelect.dark'), selected: false, value: 'dark' },
|
|
14
|
+
{ label: Astro.locals.t('themeSelect.light'), selected: false, value: 'light' },
|
|
15
|
+
{ label: Astro.locals.t('themeSelect.auto'), selected: true, value: 'auto' },
|
|
18
16
|
]}
|
|
19
17
|
width="6.25em"
|
|
20
18
|
/>
|
package/i18n.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file imports the original `i18next` types and extends them to configure the
|
|
3
|
+
* Starlight namespace.
|
|
4
|
+
*
|
|
5
|
+
* Note that the top-level `import` makes this module non-ambient, so can’t be
|
|
6
|
+
* combined with other `.d.ts` files such as `locals.d.ts`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import 'i18next';
|
|
10
|
+
|
|
11
|
+
declare module 'i18next' {
|
|
12
|
+
interface CustomTypeOptions {
|
|
13
|
+
defaultNS: typeof import('./utils/createTranslationSystem').I18nextNamespace;
|
|
14
|
+
resources: {
|
|
15
|
+
starlight: Record<import('./utils/createTranslationSystem').I18nKeys, string>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These triple-slash directives defines dependencies to various declaration files that will be
|
|
3
|
+
* loaded when a user imports the Starlight integration in their Astro configuration file. These
|
|
4
|
+
* directives must be first at the top of the file and can only be preceded by this comment.
|
|
5
|
+
*/
|
|
6
|
+
/// <reference path="./locals.d.ts" />
|
|
7
|
+
/// <reference path="./i18n.d.ts" />
|
|
8
|
+
|
|
1
9
|
import mdx from '@astrojs/mdx';
|
|
2
10
|
import type { AstroIntegration } from 'astro';
|
|
3
11
|
import { spawn } from 'node:child_process';
|
|
@@ -9,7 +17,12 @@ import { starlightSitemap } from './integrations/sitemap';
|
|
|
9
17
|
import { vitePluginStarlightUserConfig } from './integrations/virtual-user-config';
|
|
10
18
|
import { rehypeRtlCodeSupport } from './integrations/code-rtl-support';
|
|
11
19
|
import { createTranslationSystemFromFs } from './utils/translations-fs';
|
|
12
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
injectPluginTranslationsTypes,
|
|
22
|
+
runPlugins,
|
|
23
|
+
type PluginTranslations,
|
|
24
|
+
type StarlightUserConfigWithPlugins,
|
|
25
|
+
} from './utils/plugins';
|
|
13
26
|
import { processI18nConfig } from './utils/i18n';
|
|
14
27
|
import type { StarlightConfig } from './types';
|
|
15
28
|
|
|
@@ -18,10 +31,12 @@ export default function StarlightIntegration({
|
|
|
18
31
|
...opts
|
|
19
32
|
}: StarlightUserConfigWithPlugins): AstroIntegration {
|
|
20
33
|
let userConfig: StarlightConfig;
|
|
34
|
+
let pluginTranslations: PluginTranslations = {};
|
|
21
35
|
return {
|
|
22
36
|
name: '@astrojs/starlight',
|
|
23
37
|
hooks: {
|
|
24
38
|
'astro:config:setup': async ({
|
|
39
|
+
addMiddleware,
|
|
25
40
|
command,
|
|
26
41
|
config,
|
|
27
42
|
injectRoute,
|
|
@@ -42,10 +57,17 @@ export default function StarlightIntegration({
|
|
|
42
57
|
config.i18n
|
|
43
58
|
);
|
|
44
59
|
|
|
45
|
-
const
|
|
60
|
+
const integrations = pluginResult.integrations;
|
|
61
|
+
pluginTranslations = pluginResult.pluginTranslations;
|
|
46
62
|
userConfig = starlightConfig;
|
|
47
63
|
|
|
48
|
-
const useTranslations = createTranslationSystemFromFs(
|
|
64
|
+
const useTranslations = createTranslationSystemFromFs(
|
|
65
|
+
starlightConfig,
|
|
66
|
+
config,
|
|
67
|
+
pluginTranslations
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
addMiddleware({ entrypoint: '@astrojs/starlight/locals', order: 'pre' });
|
|
49
71
|
|
|
50
72
|
if (!starlightConfig.disable404Route) {
|
|
51
73
|
injectRoute({
|
|
@@ -91,7 +113,9 @@ export default function StarlightIntegration({
|
|
|
91
113
|
|
|
92
114
|
updateConfig({
|
|
93
115
|
vite: {
|
|
94
|
-
plugins: [
|
|
116
|
+
plugins: [
|
|
117
|
+
vitePluginStarlightUserConfig(command, starlightConfig, config, pluginTranslations),
|
|
118
|
+
],
|
|
95
119
|
},
|
|
96
120
|
markdown: {
|
|
97
121
|
remarkPlugins: [
|
|
@@ -112,6 +136,10 @@ export default function StarlightIntegration({
|
|
|
112
136
|
});
|
|
113
137
|
},
|
|
114
138
|
|
|
139
|
+
'astro:config:done': ({ injectTypes }) => {
|
|
140
|
+
injectPluginTranslationsTypes(pluginTranslations, injectTypes);
|
|
141
|
+
},
|
|
142
|
+
|
|
115
143
|
'astro:build:done': ({ dir }) => {
|
|
116
144
|
if (!userConfig.pagefind) return;
|
|
117
145
|
const targetDir = fileURLToPath(dir);
|
package/integrations/asides.ts
CHANGED
|
@@ -166,7 +166,7 @@ function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
|
|
|
166
166
|
// children with the `directiveLabel` property set to true. We want to pass it as the title
|
|
167
167
|
// prop to <Aside>, so when we find a directive label, we store it for the title prop and
|
|
168
168
|
// remove the paragraph from the container’s children.
|
|
169
|
-
let title = t(`aside.${variant}`);
|
|
169
|
+
let title: string = t(`aside.${variant}`);
|
|
170
170
|
let titleNode: PhrasingContent[] = [{ type: 'text', value: title }];
|
|
171
171
|
const firstChild = node.children[0];
|
|
172
172
|
if (
|
|
@@ -227,7 +227,8 @@ export function remarkDirectivesRestoration() {
|
|
|
227
227
|
if (
|
|
228
228
|
index !== undefined &&
|
|
229
229
|
parent &&
|
|
230
|
-
(node.type === 'textDirective' || node.type === 'leafDirective')
|
|
230
|
+
(node.type === 'textDirective' || node.type === 'leafDirective') &&
|
|
231
|
+
node.data === undefined
|
|
231
232
|
) {
|
|
232
233
|
transformUnhandledDirective(node, index, parent);
|
|
233
234
|
return;
|
|
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import type { StarlightConfig } from '../utils/user-config';
|
|
5
5
|
import { getAllNewestCommitDate } from '../utils/git';
|
|
6
|
+
import type { PluginTranslations } from '../utils/plugins';
|
|
6
7
|
|
|
7
8
|
function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
|
|
8
9
|
return `\0${id}`;
|
|
@@ -19,7 +20,8 @@ export function vitePluginStarlightUserConfig(
|
|
|
19
20
|
trailingSlash,
|
|
20
21
|
}: Pick<AstroConfig, 'root' | 'srcDir' | 'trailingSlash'> & {
|
|
21
22
|
build: Pick<AstroConfig['build'], 'format'>;
|
|
22
|
-
}
|
|
23
|
+
},
|
|
24
|
+
pluginTranslations: PluginTranslations
|
|
23
25
|
): NonNullable<ViteUserConfig['plugins']>[number] {
|
|
24
26
|
/**
|
|
25
27
|
* Resolves module IDs to a usable format:
|
|
@@ -31,6 +33,15 @@ export function vitePluginStarlightUserConfig(
|
|
|
31
33
|
const resolveId = (id: string, base = root) =>
|
|
32
34
|
JSON.stringify(id.startsWith('.') ? resolve(fileURLToPath(base), id) : id);
|
|
33
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a path to a Starlight file relative to this file.
|
|
38
|
+
* @example
|
|
39
|
+
* resolveLocalPath('../utils/git.ts');
|
|
40
|
+
* // => '"/users/houston/docs/node_modules/@astrojs/starlight/utils/git.ts"'
|
|
41
|
+
*/
|
|
42
|
+
const resolveLocalPath = (path: string) =>
|
|
43
|
+
JSON.stringify(fileURLToPath(new URL(path, import.meta.url)));
|
|
44
|
+
|
|
34
45
|
const docsPath = resolve(fileURLToPath(srcDir), 'content/docs');
|
|
35
46
|
|
|
36
47
|
const virtualComponentModules = Object.fromEntries(
|
|
@@ -51,9 +62,9 @@ export function vitePluginStarlightUserConfig(
|
|
|
51
62
|
})}`,
|
|
52
63
|
'virtual:starlight/git-info':
|
|
53
64
|
(command !== 'build'
|
|
54
|
-
? `import { makeAPI } from
|
|
65
|
+
? `import { makeAPI } from ${resolveLocalPath('../utils/git.ts')};` +
|
|
55
66
|
`const api = makeAPI(${JSON.stringify(docsPath)});`
|
|
56
|
-
: `import { makeAPI } from
|
|
67
|
+
: `import { makeAPI } from ${resolveLocalPath('../utils/gitInlined.ts')};` +
|
|
57
68
|
`const api = makeAPI(${JSON.stringify(getAllNewestCommitDate(docsPath))});`) +
|
|
58
69
|
'export const getNewestCommitDate = api.getNewestCommitDate;',
|
|
59
70
|
'virtual:starlight/user-css': opts.customCss.map((id) => `import ${resolveId(id)};`).join(''),
|
|
@@ -71,6 +82,7 @@ export function vitePluginStarlightUserConfig(
|
|
|
71
82
|
userCollections = (await import(${resolveId('./content/config.ts', srcDir)})).collections;
|
|
72
83
|
} catch {}
|
|
73
84
|
export const collections = userCollections;`,
|
|
85
|
+
'virtual:starlight/plugin-translations': `export default ${JSON.stringify(pluginTranslations)}`,
|
|
74
86
|
...virtualComponentModules,
|
|
75
87
|
} satisfies Record<string, string>;
|
|
76
88
|
|
package/locals.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This namespace is reserved for Starlight (only used for i18n at the moment).
|
|
3
|
+
* It can be extended by plugins using module augmentation and interface merging.
|
|
4
|
+
* For an example, see: https://starlight.astro.build/reference/plugins/#injecttranslations
|
|
5
|
+
*/
|
|
6
|
+
declare namespace StarlightApp {
|
|
7
|
+
interface I18n {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extending Astro’s `App.Locals` interface registers types for the middleware added by Starlight.
|
|
12
|
+
*/
|
|
13
|
+
declare namespace App {
|
|
14
|
+
interface Locals {
|
|
15
|
+
t: import('./utils/createTranslationSystem').I18nT;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/locals.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astrojs/starlight",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Build beautiful, high-performance documentation websites with Astro",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"type": "module",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": "./index.ts",
|
|
24
|
+
"./locals": "./locals.ts",
|
|
24
25
|
"./components": "./components.ts",
|
|
25
26
|
"./components/LanguageSelect.astro": {
|
|
26
27
|
"types": "./components/LanguageSelect.astro.tsx",
|
|
@@ -166,7 +167,7 @@
|
|
|
166
167
|
"./style/markdown.css": "./style/markdown.css"
|
|
167
168
|
},
|
|
168
169
|
"peerDependencies": {
|
|
169
|
-
"astro": "^4.
|
|
170
|
+
"astro": "^4.14.0"
|
|
170
171
|
},
|
|
171
172
|
"devDependencies": {
|
|
172
173
|
"@astrojs/markdown-remark": "^5.1.0",
|
|
@@ -189,6 +190,7 @@
|
|
|
189
190
|
"hast-util-select": "^6.0.2",
|
|
190
191
|
"hast-util-to-string": "^3.0.0",
|
|
191
192
|
"hastscript": "^9.0.0",
|
|
193
|
+
"i18next": "^23.11.5",
|
|
192
194
|
"mdast-util-directive": "^3.0.0",
|
|
193
195
|
"mdast-util-to-markdown": "^2.1.0",
|
|
194
196
|
"mdast-util-to-string": "^4.0.0",
|
package/schemas/badge.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { z } from 'astro/zod';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
z.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
const badgeBaseSchema = z.object({
|
|
4
|
+
variant: z.enum(['note', 'danger', 'success', 'caution', 'tip', 'default']).default('default'),
|
|
5
|
+
class: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const badgeSchema = badgeBaseSchema.extend({
|
|
9
|
+
text: z.string(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const i18nBadgeSchema = badgeBaseSchema.extend({
|
|
13
|
+
text: z.union([z.string(), z.record(z.string())]),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const BadgeComponentSchema = badgeSchema
|
|
11
17
|
.extend({
|
|
12
18
|
size: z.enum(['small', 'medium', 'large']).default('small'),
|
|
13
19
|
})
|
|
@@ -17,7 +23,7 @@ export type BadgeComponentProps = z.input<typeof BadgeComponentSchema>;
|
|
|
17
23
|
|
|
18
24
|
export const BadgeConfigSchema = () =>
|
|
19
25
|
z
|
|
20
|
-
.union([z.string(), badgeSchema
|
|
26
|
+
.union([z.string(), badgeSchema])
|
|
21
27
|
.transform((badge) => {
|
|
22
28
|
if (typeof badge === 'string') {
|
|
23
29
|
return { variant: 'default' as const, text: badge };
|
|
@@ -26,4 +32,8 @@ export const BadgeConfigSchema = () =>
|
|
|
26
32
|
})
|
|
27
33
|
.optional();
|
|
28
34
|
|
|
29
|
-
export
|
|
35
|
+
export const I18nBadgeConfigSchema = () => z.union([z.string(), i18nBadgeSchema]).optional();
|
|
36
|
+
|
|
37
|
+
export type Badge = z.output<typeof badgeSchema>;
|
|
38
|
+
export type I18nBadge = z.output<typeof i18nBadgeSchema>;
|
|
39
|
+
export type I18nBadgeConfig = z.output<ReturnType<typeof I18nBadgeConfigSchema>>;
|
package/schemas/sidebar.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AstroBuiltinAttributes } from 'astro';
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
3
|
import { z } from 'astro/zod';
|
|
4
|
-
import {
|
|
4
|
+
import { I18nBadgeConfigSchema } from './badge';
|
|
5
5
|
import { stripLeadingAndTrailingSlashes } from '../utils/path';
|
|
6
6
|
|
|
7
7
|
const SidebarBaseSchema = z.object({
|
|
@@ -9,8 +9,8 @@ const SidebarBaseSchema = z.object({
|
|
|
9
9
|
label: z.string(),
|
|
10
10
|
/** Translations of the `label` for each supported language. */
|
|
11
11
|
translations: z.record(z.string()).default({}),
|
|
12
|
-
/** Adds a badge to the
|
|
13
|
-
badge:
|
|
12
|
+
/** Adds a badge to the item */
|
|
13
|
+
badge: I18nBadgeConfigSchema(),
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
const SidebarGroupSchema = SidebarBaseSchema.extend({
|
|
@@ -1,27 +1,60 @@
|
|
|
1
|
+
import i18next, { type ExistsFunction, type TFunction } from 'i18next';
|
|
1
2
|
import type { i18nSchemaOutput } from '../schemas/i18n';
|
|
2
3
|
import builtinTranslations from '../translations/index';
|
|
3
4
|
import { BuiltInDefaultLocale } from './i18n';
|
|
4
5
|
import type { StarlightConfig } from './user-config';
|
|
6
|
+
import type { UserI18nKeys, UserI18nSchema } from './translations';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The namespace for i18next resources used by Starlight.
|
|
10
|
+
* All translations handled by Starlight are stored in the same namespace and Starlight always use
|
|
11
|
+
* a new instance of i18next configured for this namespace.
|
|
12
|
+
*/
|
|
13
|
+
export const I18nextNamespace = 'starlight' as const;
|
|
5
14
|
|
|
6
15
|
export function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
16
|
+
config: Pick<StarlightConfig, 'defaultLocale' | 'locales'>,
|
|
7
17
|
userTranslations: Record<string, T>,
|
|
8
|
-
|
|
18
|
+
pluginTranslations: Record<string, T> = {}
|
|
9
19
|
) {
|
|
10
|
-
|
|
11
|
-
|
|
20
|
+
const defaultLocale =
|
|
21
|
+
config.defaultLocale.lang || config.defaultLocale?.locale || BuiltInDefaultLocale.lang;
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
const translations = {
|
|
24
|
+
[defaultLocale]: buildResources(
|
|
25
|
+
builtinTranslations[defaultLocale],
|
|
26
|
+
builtinTranslations[stripLangRegion(defaultLocale)],
|
|
27
|
+
pluginTranslations[defaultLocale],
|
|
28
|
+
userTranslations[defaultLocale]
|
|
29
|
+
),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (config.locales) {
|
|
33
|
+
for (const locale in config.locales) {
|
|
34
|
+
const lang = localeToLang(locale, config.locales, config.defaultLocale);
|
|
35
|
+
|
|
36
|
+
translations[lang] = buildResources(
|
|
37
|
+
builtinTranslations[lang] || builtinTranslations[stripLangRegion(lang)],
|
|
38
|
+
pluginTranslations[lang],
|
|
39
|
+
userTranslations[lang]
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const i18n = i18next.createInstance();
|
|
45
|
+
i18n.init({
|
|
46
|
+
resources: translations,
|
|
47
|
+
fallbackLng:
|
|
48
|
+
config.defaultLocale.lang || config.defaultLocale?.locale || BuiltInDefaultLocale.lang,
|
|
49
|
+
});
|
|
20
50
|
|
|
21
51
|
/**
|
|
22
52
|
* Generate a utility function that returns UI strings for the given `locale`.
|
|
23
53
|
*
|
|
24
|
-
* Also includes
|
|
54
|
+
* Also includes a few utility methods:
|
|
55
|
+
* - `all()` method for getting the entire dictionary.
|
|
56
|
+
* - `exists()` method for checking if a key exists in the dictionary.
|
|
57
|
+
* - `dir()` method for getting the text direction of the locale.
|
|
25
58
|
*
|
|
26
59
|
* @param {string | undefined} [locale]
|
|
27
60
|
* @example
|
|
@@ -30,16 +63,19 @@ export function createTranslationSystem<T extends i18nSchemaOutput>(
|
|
|
30
63
|
* // => 'Search'
|
|
31
64
|
* const dictionary = t.all();
|
|
32
65
|
* // => { 'skipLink.label': 'Skip to content', 'search.label': 'Search', ... }
|
|
66
|
+
* const exists = t.exists('search.label');
|
|
67
|
+
* // => true
|
|
68
|
+
* const dir = t.dir();
|
|
69
|
+
* // => 'ltr'
|
|
33
70
|
*/
|
|
34
|
-
return
|
|
71
|
+
return (locale: string | undefined) => {
|
|
35
72
|
const lang = localeToLang(locale, config.locales, config.defaultLocale);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
t.all = () => dictionary;
|
|
73
|
+
|
|
74
|
+
const t = i18n.getFixedT(lang, I18nextNamespace) as I18nT;
|
|
75
|
+
t.all = () => i18n.getResourceBundle(lang, I18nextNamespace);
|
|
76
|
+
t.exists = (key, options) => i18n.exists(key, { lng: lang, ns: I18nextNamespace, ...options });
|
|
77
|
+
t.dir = (dirLang = lang) => i18n.dir(dirLang);
|
|
78
|
+
|
|
43
79
|
return t;
|
|
44
80
|
};
|
|
45
81
|
}
|
|
@@ -70,12 +106,11 @@ function localeToLang(
|
|
|
70
106
|
|
|
71
107
|
type BuiltInStrings = (typeof builtinTranslations)['en'];
|
|
72
108
|
|
|
73
|
-
/** Build
|
|
74
|
-
function
|
|
75
|
-
base: BuiltInStrings,
|
|
109
|
+
/** Build an i18next resources dictionary by layering preferred translation sources. */
|
|
110
|
+
function buildResources<T extends Record<string, string | undefined>>(
|
|
76
111
|
...dictionaries: (T | BuiltInStrings | undefined)[]
|
|
77
|
-
): BuiltInStrings & T {
|
|
78
|
-
const dictionary = {
|
|
112
|
+
): { [I18nextNamespace]: BuiltInStrings & T } {
|
|
113
|
+
const dictionary: Partial<BuiltInStrings> = {};
|
|
79
114
|
// Iterate over alternate dictionaries to avoid overwriting preceding values with `undefined`.
|
|
80
115
|
for (const dict of dictionaries) {
|
|
81
116
|
for (const key in dict) {
|
|
@@ -83,5 +118,13 @@ function buildDictionary<T extends Record<string, string | undefined>>(
|
|
|
83
118
|
if (value) dictionary[key as keyof typeof dictionary] = value;
|
|
84
119
|
}
|
|
85
120
|
}
|
|
86
|
-
return dictionary as BuiltInStrings & T;
|
|
121
|
+
return { [I18nextNamespace]: dictionary as BuiltInStrings & T };
|
|
87
122
|
}
|
|
123
|
+
|
|
124
|
+
export type I18nKeys = UserI18nKeys | keyof StarlightApp.I18n;
|
|
125
|
+
|
|
126
|
+
export type I18nT = TFunction<'starlight', undefined> & {
|
|
127
|
+
all: () => UserI18nSchema;
|
|
128
|
+
exists: ExistsFunction;
|
|
129
|
+
dir: (lang?: string) => 'ltr' | 'rtl';
|
|
130
|
+
};
|
package/utils/i18n.ts
CHANGED
|
@@ -2,6 +2,26 @@ import type { AstroConfig } from 'astro';
|
|
|
2
2
|
import { AstroError } from 'astro/errors';
|
|
3
3
|
import type { StarlightConfig } from './user-config';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A proxy object that throws an error when a user tries to access the deprecated `labels` prop in
|
|
7
|
+
* a component override.
|
|
8
|
+
*
|
|
9
|
+
* @todo Remove in a future release once people have updated — no later than v1.
|
|
10
|
+
*/
|
|
11
|
+
export const DeprecatedLabelsPropProxy = new Proxy<Record<string, never>>(
|
|
12
|
+
{},
|
|
13
|
+
{
|
|
14
|
+
get(_, key) {
|
|
15
|
+
const label = String(key);
|
|
16
|
+
throw new AstroError(
|
|
17
|
+
`The \`labels\` prop in component overrides has been removed.`,
|
|
18
|
+
`Replace \`Astro.props.labels["${label}"]\` with \`Astro.locals.t("${label}")\` instead.\n` +
|
|
19
|
+
'For more information see https://starlight.astro.build/guides/i18n/#using-ui-translations'
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
5
25
|
/**
|
|
6
26
|
* A list of well-known right-to-left languages used as a fallback when determining the text
|
|
7
27
|
* direction of a locale is not supported by the `Intl.Locale` API in the current environment.
|
package/utils/navigation.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AstroError } from 'astro/errors';
|
|
2
2
|
import config from 'virtual:starlight/user-config';
|
|
3
|
-
import type { Badge } from '../schemas/badge';
|
|
3
|
+
import type { Badge, I18nBadge, I18nBadgeConfig } from '../schemas/badge';
|
|
4
4
|
import type { PrevNextLinkConfig } from '../schemas/prevNextLink';
|
|
5
5
|
import type {
|
|
6
6
|
AutoSidebarGroup,
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
} from '../schemas/sidebar';
|
|
12
12
|
import { createPathFormatter } from './createPathFormatter';
|
|
13
13
|
import { formatPath } from './format-path';
|
|
14
|
-
import { pickLang } from './i18n';
|
|
14
|
+
import { BuiltInDefaultLocale, pickLang } from './i18n';
|
|
15
15
|
import { ensureLeadingSlash, ensureTrailingSlash, stripLeadingAndTrailingSlashes } from './path';
|
|
16
16
|
import { getLocaleRoutes, routes, type Route } from './routing';
|
|
17
17
|
import { localeToLang, slugToPathname } from './slugs';
|
|
@@ -79,12 +79,13 @@ function configItemToEntry(
|
|
|
79
79
|
} else if ('slug' in item) {
|
|
80
80
|
return linkFromInternalSidebarLinkItem(item, locale, currentPathname);
|
|
81
81
|
} else {
|
|
82
|
+
const label = pickLang(item.translations, localeToLang(locale)) || item.label;
|
|
82
83
|
return {
|
|
83
84
|
type: 'group',
|
|
84
|
-
label
|
|
85
|
+
label,
|
|
85
86
|
entries: item.items.map((i) => configItemToEntry(i, currentPathname, locale, routes)),
|
|
86
87
|
collapsed: item.collapsed,
|
|
87
|
-
badge: item.badge,
|
|
88
|
+
badge: getSidebarBadge(item.badge, locale, label),
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -106,12 +107,13 @@ function groupFromAutogenerateConfig(
|
|
|
106
107
|
doc.id.startsWith(localeDir + '/')
|
|
107
108
|
);
|
|
108
109
|
const tree = treeify(dirDocs, localeDir);
|
|
110
|
+
const label = pickLang(item.translations, localeToLang(locale)) || item.label;
|
|
109
111
|
return {
|
|
110
112
|
type: 'group',
|
|
111
|
-
label
|
|
113
|
+
label,
|
|
112
114
|
entries: sidebarFromDir(tree, currentPathname, locale, subgroupCollapsed ?? item.collapsed),
|
|
113
115
|
collapsed: item.collapsed,
|
|
114
|
-
badge: item.badge,
|
|
116
|
+
badge: getSidebarBadge(item.badge, locale, label),
|
|
115
117
|
};
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -131,7 +133,13 @@ function linkFromSidebarLinkItem(
|
|
|
131
133
|
if (locale) href = '/' + locale + href;
|
|
132
134
|
}
|
|
133
135
|
const label = pickLang(item.translations, localeToLang(locale)) || item.label;
|
|
134
|
-
return makeSidebarLink(
|
|
136
|
+
return makeSidebarLink(
|
|
137
|
+
href,
|
|
138
|
+
label,
|
|
139
|
+
currentPathname,
|
|
140
|
+
getSidebarBadge(item.badge, locale, label),
|
|
141
|
+
item.attrs
|
|
142
|
+
);
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
/** Create a link entry from an automatic internal link item in user config. */
|
|
@@ -161,7 +169,13 @@ function linkFromInternalSidebarLinkItem(
|
|
|
161
169
|
}
|
|
162
170
|
const label =
|
|
163
171
|
pickLang(item.translations, localeToLang(locale)) || item.label || entry.entry.data.title;
|
|
164
|
-
return makeSidebarLink(
|
|
172
|
+
return makeSidebarLink(
|
|
173
|
+
entry.slug,
|
|
174
|
+
label,
|
|
175
|
+
currentPathname,
|
|
176
|
+
getSidebarBadge(item.badge, locale, label),
|
|
177
|
+
item.attrs
|
|
178
|
+
);
|
|
165
179
|
}
|
|
166
180
|
|
|
167
181
|
/** Process sidebar link options to create a link entry. */
|
|
@@ -446,3 +460,38 @@ function stripExtension(path: string) {
|
|
|
446
460
|
const periodIndex = path.lastIndexOf('.');
|
|
447
461
|
return path.slice(0, periodIndex > -1 ? periodIndex : undefined);
|
|
448
462
|
}
|
|
463
|
+
|
|
464
|
+
/** Get a sidebar badge for a given item. */
|
|
465
|
+
function getSidebarBadge(
|
|
466
|
+
config: I18nBadgeConfig,
|
|
467
|
+
locale: string | undefined,
|
|
468
|
+
itemLabel: string
|
|
469
|
+
): Badge | undefined {
|
|
470
|
+
if (!config) return;
|
|
471
|
+
if (typeof config === 'string') {
|
|
472
|
+
return { variant: 'default', text: config };
|
|
473
|
+
}
|
|
474
|
+
return { ...config, text: getSidebarBadgeText(config.text, locale, itemLabel) };
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** Get the badge text for a sidebar item. */
|
|
478
|
+
function getSidebarBadgeText(
|
|
479
|
+
text: I18nBadge['text'],
|
|
480
|
+
locale: string | undefined,
|
|
481
|
+
itemLabel: string
|
|
482
|
+
): string {
|
|
483
|
+
if (typeof text === 'string') return text;
|
|
484
|
+
const defaultLang =
|
|
485
|
+
config.defaultLocale?.lang || config.defaultLocale?.locale || BuiltInDefaultLocale.lang;
|
|
486
|
+
const defaultText = text[defaultLang];
|
|
487
|
+
|
|
488
|
+
if (!defaultText) {
|
|
489
|
+
throw new AstroError(
|
|
490
|
+
`The badge text for "${itemLabel}" must have a key for the default language "${defaultLang}".`,
|
|
491
|
+
'Update the Starlight config to include a badge text for the default language.\n' +
|
|
492
|
+
'Learn more about sidebar badges internationalization at https://starlight.astro.build/guides/sidebar/#internationalization-with-badges'
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
return pickLang(text, localeToLang(locale)) || defaultText;
|
|
497
|
+
}
|
package/utils/plugins.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { AstroIntegration } from 'astro';
|
|
1
|
+
import type { AstroIntegration, HookParameters } from 'astro';
|
|
2
2
|
import { z } from 'astro/zod';
|
|
3
3
|
import { StarlightConfigSchema, type StarlightUserConfig } from '../utils/user-config';
|
|
4
4
|
import { parseWithFriendlyErrors } from '../utils/error-map';
|
|
5
5
|
import { AstroError } from 'astro/errors';
|
|
6
|
+
import type { UserI18nSchema } from './translations';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Runs Starlight plugins in the order that they are configured after validating the user-provided
|
|
@@ -32,6 +33,8 @@ export async function runPlugins(
|
|
|
32
33
|
|
|
33
34
|
// A list of Astro integrations added by the various plugins.
|
|
34
35
|
const integrations: AstroIntegration[] = [];
|
|
36
|
+
// A list of translations injected by the various plugins keyed by locale.
|
|
37
|
+
const pluginTranslations: PluginTranslations = {};
|
|
35
38
|
|
|
36
39
|
for (const {
|
|
37
40
|
name,
|
|
@@ -70,6 +73,13 @@ export async function runPlugins(
|
|
|
70
73
|
command: context.command,
|
|
71
74
|
isRestart: context.isRestart,
|
|
72
75
|
logger: context.logger.fork(name),
|
|
76
|
+
injectTranslations(translations) {
|
|
77
|
+
// Merge the translations injected by the plugin.
|
|
78
|
+
for (const [locale, localeTranslations] of Object.entries(translations)) {
|
|
79
|
+
pluginTranslations[locale] ??= {};
|
|
80
|
+
Object.assign(pluginTranslations[locale]!, localeTranslations);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
73
83
|
});
|
|
74
84
|
}
|
|
75
85
|
|
|
@@ -81,7 +91,34 @@ export async function runPlugins(
|
|
|
81
91
|
);
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
return { integrations, starlightConfig };
|
|
94
|
+
return { integrations, starlightConfig, pluginTranslations };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function injectPluginTranslationsTypes(
|
|
98
|
+
translations: PluginTranslations,
|
|
99
|
+
injectTypes: HookParameters<'astro:config:done'>['injectTypes']
|
|
100
|
+
) {
|
|
101
|
+
const allKeys = new Set<string>();
|
|
102
|
+
|
|
103
|
+
for (const localeTranslations of Object.values(translations)) {
|
|
104
|
+
for (const key of Object.keys(localeTranslations)) {
|
|
105
|
+
allKeys.add(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// If there are no translations to inject, we don't need to generate any types or cleanup
|
|
110
|
+
// previous ones as they will not be referenced anymore.
|
|
111
|
+
if (allKeys.size === 0) return;
|
|
112
|
+
|
|
113
|
+
injectTypes({
|
|
114
|
+
filename: 'i18n-plugins.d.ts',
|
|
115
|
+
content: `declare namespace StarlightApp {
|
|
116
|
+
type PluginUIStringKeys = {
|
|
117
|
+
${[...allKeys].map((key) => `'${key}': string;`).join('\n\t\t')}
|
|
118
|
+
};
|
|
119
|
+
interface I18n extends PluginUIStringKeys {}
|
|
120
|
+
}`,
|
|
121
|
+
});
|
|
85
122
|
}
|
|
86
123
|
|
|
87
124
|
// https://github.com/withastro/astro/blob/910eb00fe0b70ca80bd09520ae100e8c78b675b5/packages/astro/src/core/config/schema.ts#L113
|
|
@@ -192,6 +229,32 @@ const starlightPluginSchema = baseStarlightPluginSchema.extend({
|
|
|
192
229
|
* @see https://docs.astro.build/en/reference/integrations-reference/#astrointegrationlogger
|
|
193
230
|
*/
|
|
194
231
|
logger: z.any() as z.Schema<StarlightPluginContext['logger']>,
|
|
232
|
+
/**
|
|
233
|
+
* A callback function to add or update translations strings.
|
|
234
|
+
*
|
|
235
|
+
* @see https://starlight.astro.build/guides/i18n/#extend-translation-schema
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* {
|
|
239
|
+
* name: 'My Starlight Plugin',
|
|
240
|
+
* hooks: {
|
|
241
|
+
* setup({ injectTranslations }) {
|
|
242
|
+
* injectTranslations({
|
|
243
|
+
* en: {
|
|
244
|
+
* 'myPlugin.doThing': 'Do the thing',
|
|
245
|
+
* },
|
|
246
|
+
* fr: {
|
|
247
|
+
* 'myPlugin.doThing': 'Faire le truc',
|
|
248
|
+
* },
|
|
249
|
+
* });
|
|
250
|
+
* }
|
|
251
|
+
* }
|
|
252
|
+
* }
|
|
253
|
+
*/
|
|
254
|
+
injectTranslations: z.function(
|
|
255
|
+
z.tuple([z.record(z.string(), z.record(z.string(), z.string()))]),
|
|
256
|
+
z.void()
|
|
257
|
+
),
|
|
195
258
|
}),
|
|
196
259
|
]),
|
|
197
260
|
z.union([z.void(), z.promise(z.void())])
|
|
@@ -222,3 +285,5 @@ export type StarlightPluginContext = Pick<
|
|
|
222
285
|
Parameters<NonNullable<AstroIntegration['hooks']['astro:config:setup']>>[0],
|
|
223
286
|
'command' | 'config' | 'isRestart' | 'logger'
|
|
224
287
|
>;
|
|
288
|
+
|
|
289
|
+
export type PluginTranslations = Record<string, UserI18nSchema & Record<string, string>>;
|
package/utils/route-data.ts
CHANGED
|
@@ -7,8 +7,9 @@ import { getPrevNextLinks, getSidebar, type SidebarEntry } from './navigation';
|
|
|
7
7
|
import { ensureTrailingSlash } from './path';
|
|
8
8
|
import type { Route } from './routing';
|
|
9
9
|
import { localizedId } from './slugs';
|
|
10
|
-
import { useTranslations } from './translations';
|
|
11
10
|
import { formatPath } from './format-path';
|
|
11
|
+
import { useTranslations } from './translations';
|
|
12
|
+
import { DeprecatedLabelsPropProxy } from './i18n';
|
|
12
13
|
|
|
13
14
|
export interface PageProps extends Route {
|
|
14
15
|
headings: MarkdownHeading[];
|
|
@@ -33,8 +34,8 @@ export interface StarlightRouteData extends Route {
|
|
|
33
34
|
lastUpdated: Date | undefined;
|
|
34
35
|
/** URL object for the address where this page can be edited if enabled. */
|
|
35
36
|
editUrl: URL | undefined;
|
|
36
|
-
/**
|
|
37
|
-
labels:
|
|
37
|
+
/** @deprecated Use `Astro.locals.t()` instead. */
|
|
38
|
+
labels: Record<string, never>;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export function generateRouteData({
|
|
@@ -57,7 +58,7 @@ export function generateRouteData({
|
|
|
57
58
|
toc: getToC(props),
|
|
58
59
|
lastUpdated: getLastUpdated(props),
|
|
59
60
|
editUrl: getEditUrl(props),
|
|
60
|
-
labels:
|
|
61
|
+
labels: DeprecatedLabelsPropProxy,
|
|
61
62
|
};
|
|
62
63
|
}
|
|
63
64
|
|
package/utils/starlight-page.ts
CHANGED
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
import type { StarlightDocsEntry } from './routing';
|
|
14
14
|
import { slugToLocaleData, urlToSlug } from './slugs';
|
|
15
15
|
import { getPrevNextLinks, getSidebarFromConfig } from './navigation';
|
|
16
|
-
import { useTranslations } from './translations';
|
|
17
16
|
import { docsSchema } from '../schema';
|
|
17
|
+
import type { Prettify, RemoveIndexSignature } from './types';
|
|
18
|
+
import { DeprecatedLabelsPropProxy } from './i18n';
|
|
18
19
|
import { SidebarItemSchema } from '../schemas/sidebar';
|
|
19
20
|
import type { StarlightConfig, StarlightUserConfig } from './user-config';
|
|
20
21
|
|
|
@@ -151,7 +152,7 @@ export async function generateStarlightPageRouteData({
|
|
|
151
152
|
entryMeta,
|
|
152
153
|
hasSidebar: props.hasSidebar ?? entry.data.template !== 'splash',
|
|
153
154
|
headings,
|
|
154
|
-
labels:
|
|
155
|
+
labels: DeprecatedLabelsPropProxy,
|
|
155
156
|
lastUpdated,
|
|
156
157
|
pagination: getPrevNextLinks(sidebar, config.pagination, entry.data),
|
|
157
158
|
sidebar,
|
|
@@ -214,19 +215,3 @@ async function getUserDocsSchema(): Promise<
|
|
|
214
215
|
const userCollections = (await import('virtual:starlight/collection-config')).collections;
|
|
215
216
|
return userCollections?.docs.schema ?? docsSchema();
|
|
216
217
|
}
|
|
217
|
-
|
|
218
|
-
// https://stackoverflow.com/a/66252656/1945960
|
|
219
|
-
type RemoveIndexSignature<T> = {
|
|
220
|
-
[K in keyof T as string extends K
|
|
221
|
-
? never
|
|
222
|
-
: number extends K
|
|
223
|
-
? never
|
|
224
|
-
: symbol extends K
|
|
225
|
-
? never
|
|
226
|
-
: K]: T[K];
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// https://www.totaltypescript.com/concepts/the-prettify-helper
|
|
230
|
-
type Prettify<T> = {
|
|
231
|
-
[K in keyof T]: T[K];
|
|
232
|
-
} & {};
|
package/utils/translations-fs.ts
CHANGED
|
@@ -11,9 +11,10 @@ import type { AstroConfig } from 'astro';
|
|
|
11
11
|
*
|
|
12
12
|
* @see [`./translations.ts`](./translations.ts)
|
|
13
13
|
*/
|
|
14
|
-
export function createTranslationSystemFromFs(
|
|
14
|
+
export function createTranslationSystemFromFs<T extends i18nSchemaOutput>(
|
|
15
15
|
opts: Pick<StarlightConfig, 'defaultLocale' | 'locales'>,
|
|
16
|
-
{ srcDir }: Pick<AstroConfig, 'srcDir'
|
|
16
|
+
{ srcDir }: Pick<AstroConfig, 'srcDir'>,
|
|
17
|
+
pluginTranslations: Record<string, T> = {}
|
|
17
18
|
) {
|
|
18
19
|
/** All translation data from the i18n collection, keyed by `id`, which matches locale. */
|
|
19
20
|
let userTranslations: Record<string, i18nSchemaOutput> = {};
|
|
@@ -40,5 +41,5 @@ export function createTranslationSystemFromFs(
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
return createTranslationSystem(userTranslations,
|
|
44
|
+
return createTranslationSystem(opts, userTranslations, pluginTranslations);
|
|
44
45
|
}
|
package/utils/translations.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { getCollection, type CollectionEntry, type DataCollectionKey } from 'astro:content';
|
|
2
2
|
import config from 'virtual:starlight/user-config';
|
|
3
|
+
import pluginTranslations from 'virtual:starlight/plugin-translations';
|
|
3
4
|
import type { i18nSchemaOutput } from '../schemas/i18n';
|
|
4
5
|
import { createTranslationSystem } from './createTranslationSystem';
|
|
6
|
+
import type { RemoveIndexSignature } from './types';
|
|
5
7
|
|
|
6
|
-
type UserI18nSchema = 'i18n' extends DataCollectionKey
|
|
8
|
+
export type UserI18nSchema = 'i18n' extends DataCollectionKey
|
|
7
9
|
? CollectionEntry<'i18n'> extends { data: infer T }
|
|
8
|
-
? T
|
|
10
|
+
? i18nSchemaOutput & T
|
|
9
11
|
: i18nSchemaOutput
|
|
10
12
|
: i18nSchemaOutput;
|
|
13
|
+
export type UserI18nKeys = keyof RemoveIndexSignature<UserI18nSchema>;
|
|
11
14
|
|
|
12
15
|
/** Get all translation data from the i18n collection, keyed by `id`, which matches locale. */
|
|
13
16
|
async function loadTranslations() {
|
|
@@ -34,4 +37,8 @@ async function loadTranslations() {
|
|
|
34
37
|
* const t = useTranslations('en');
|
|
35
38
|
* const label = t('search.label'); // => 'Search'
|
|
36
39
|
*/
|
|
37
|
-
export const useTranslations = createTranslationSystem(
|
|
40
|
+
export const useTranslations = createTranslationSystem(
|
|
41
|
+
config,
|
|
42
|
+
await loadTranslations(),
|
|
43
|
+
pluginTranslations
|
|
44
|
+
);
|
package/utils/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// https://stackoverflow.com/a/66252656/1945960
|
|
2
|
+
export type RemoveIndexSignature<T> = {
|
|
3
|
+
[K in keyof T as string extends K
|
|
4
|
+
? never
|
|
5
|
+
: number extends K
|
|
6
|
+
? never
|
|
7
|
+
: symbol extends K
|
|
8
|
+
? never
|
|
9
|
+
: K]: T[K];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// https://www.totaltypescript.com/concepts/the-prettify-helper
|
|
13
|
+
export type Prettify<T> = {
|
|
14
|
+
[K in keyof T]: T[K];
|
|
15
|
+
} & {};
|
package/virtual.d.ts
CHANGED
|
@@ -28,6 +28,11 @@ declare module 'virtual:starlight/user-images' {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
declare module 'virtual:starlight/plugin-translations' {
|
|
32
|
+
const PluginTranslations: import('./utils/plugins').PluginTranslations;
|
|
33
|
+
export default PluginTranslations;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
declare module 'virtual:starlight/collection-config' {
|
|
32
37
|
export const collections: import('astro:content').ContentConfig['collections'] | undefined;
|
|
33
38
|
}
|