Skip to content

Commit

Permalink
Merge branch 'main' into chris/hbdsl
Browse files Browse the repository at this point in the history
  • Loading branch information
delucis committed Jun 5, 2024
2 parents 94ba1c9 + ee0cd38 commit e8c3d38
Show file tree
Hide file tree
Showing 25 changed files with 825 additions and 117 deletions.
10 changes: 10 additions & 0 deletions .changeset/early-pots-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@astrojs/starlight": minor
---

Adds support for `Astro.currentLocale` and Astro’s i18n routing.

⚠️ **Potentially breaking change:** Starlight now configures Astro’s `i18n` option for you based on its `locales` config.

If you are currently using Astro’s `i18n` option as well as Starlight’s `locales` option, you will need to remove one of these.
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.
6 changes: 6 additions & 0 deletions .changeset/wicked-melons-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astrojs/starlight': minor
---

Adds a new `<Badge>` component

30 changes: 30 additions & 0 deletions docs/src/content/docs/guides/components.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,36 @@ import { Steps } from '@astrojs/starlight/components';

</Steps>

### Badges

import { Badge } from '@astrojs/starlight/components';

Use the `<Badge>` component to display small pieces of information, such as status or labels.

Pass the content you want to display to the `text` attribute of the `<Badge>` component.

By default, the badge will use the theme accent color of your site. To use a built-in badge color, set the `variant` attribute to one of the following values: `note` (blue), `tip` (purple), `danger` (red), `caution` (orange), or `success` (green).

The `size` attribute (default: `small`) controls the size of the badge text. `medium` and `large` are also available options for displaying a larger badge.

For further customization, use other `<span>` attributes such as `class` or `style` with custom CSS.

```mdx title="src/content/docs/example.mdx"
import { Badge } from '@astrojs/starlight/components';
<Badge text="New" variant="tip" size="small" />
<Badge text="Deprecated" variant="caution" size="medium" />
<Badge text="Starlight" variant="note" size="large" />
<Badge text="Custom" variant="success" style={{ fontStyle: 'italic' }} />
```

The code above generates the following on the page:

<Badge text="New" variant="tip" size="small" />
<Badge text="Deprecated" variant="caution" size="medium" />
<Badge text="Starlight" variant="note" size="large" />
<Badge text="Custom" variant="success" style={{ fontStyle: 'italic' }} />

### Icon

import { Icon } from '@astrojs/starlight/components';
Expand Down
16 changes: 16 additions & 0 deletions docs/src/content/docs/guides/i18n.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ Starlight provides built-in support for multilingual sites, including routing, f

</Steps>

For more advanced i18n scenarios, Starlight also supports configuring internationalization using the [Astro’s `i18n` config](https://docs.astro.build/en/guides/internationalization/#configure-i18n-routing) option.

### Use a root locale

You can use a “root” locale to serve a language without any i18n prefix in its path. For example, if English is your root locale, an English page path would look like `/about` instead of `/en/about`.
Expand Down Expand Up @@ -272,3 +274,17 @@ export const collections = {
```
Learn more about content collection schemas in [“Defining a collection schema”](https://docs.astro.build/en/guides/content-collections/#defining-a-collection-schema) in the Astro docs.
## Accessing the current locale
You can use [`Astro.currentLocale`](https://docs.astro.build/en/reference/api-reference/#astrocurrentlocale) to read the current locale in `.astro` components.
The following example reads the current locale and uses it to generate a link to an about page in the current language:
```astro
---
// src/components/AboutLink.astro
---
<a href={`/${Astro.currentLocale}/about`}>About</a>
```
8 changes: 5 additions & 3 deletions docs/src/content/docs/guides/sidebar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -294,12 +294,14 @@ The configuration above generates the following sidebar:
]}
/>

### Badge variants
### Badge variants and custom styling

Customize the badge styling using an object with `text` and `variant` properties.
Customize the badge styling using an object with `text`, `variant`, and `class` properties.

The `text` represents the content to display (e.g. "New").
Override the `default` styling, which uses the accent color of your site, by setting the `variant` property to one of the following values: `note`, `tip`, `danger`, `caution` or `success`.
By default, the badge will use the accent color of your site. To use a built-in badge style, set the `variant` property to one of the following values: `note`, `tip`, `danger`, `caution` or `success`.

Optionally, you can create a custom badge style by setting the `class` property to a CSS class name.

```js {10}
starlight({
Expand Down
3 changes: 2 additions & 1 deletion docs/src/content/docs/reference/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ type SidebarItem = {
```ts
interface BadgeConfig {
text: string;
variant: 'note' | 'tip' | 'caution' | 'danger' | 'success' | 'default';
variant?: 'note' | 'tip' | 'caution' | 'danger' | 'success' | 'default';
class?: string;
}
```

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ sidebar:

Add a badge to the page in the sidebar when displayed in an autogenerated group of links.
When using a string, the badge will be displayed with a default accent color.
Optionally, pass a [`BadgeConfig` object](/reference/configuration/#badgeconfig) with `text` and `variant` fields to customize the badge.
Optionally, pass a [`BadgeConfig` object](/reference/configuration/#badgeconfig) with `text`, `variant`, and `class` fields to customize the badge.

```md
---
Expand Down
4 changes: 3 additions & 1 deletion packages/starlight/404.astro
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import Page from './components/Page.astro';
import { generateRouteData } from './utils/route-data';
import type { StarlightDocsEntry } from './utils/routing';
import { useTranslations } from './utils/translations';
import { BuiltInDefaultLocale } from './utils/i18n';
export const prerender = true;
const { lang = 'en', dir = 'ltr' } = config.defaultLocale || {};
const { lang = BuiltInDefaultLocale.lang, dir = BuiltInDefaultLocale.dir } =
config.defaultLocale || {};
let locale = config.defaultLocale?.locale;
if (locale === 'root') locale = undefined;
Expand Down
1 change: 1 addition & 0 deletions packages/starlight/__tests__/basics/config-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ test('parses valid config successfully', () => {
},
"head": [],
"isMultilingual": false,
"isUsingBuiltInDefaultLocale": true,
"lastUpdated": false,
"locales": undefined,
"pagefind": true,
Expand Down
250 changes: 248 additions & 2 deletions packages/starlight/__tests__/basics/i18n.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, expect, test } from 'vitest';
import { pickLang } from '../../utils/i18n';
import { assert, describe, expect, test } from 'vitest';
import config from 'virtual:starlight/user-config';
import { processI18nConfig, pickLang } from '../../utils/i18n';
import type { AstroConfig } from 'astro';
import type { AstroUserConfig } from 'astro/config';

describe('pickLang', () => {
const dictionary = { en: 'Hello', fr: 'Bonjour' };
Expand All @@ -13,3 +16,246 @@ describe('pickLang', () => {
expect(pickLang(dictionary, 'ar' as any)).toBeUndefined();
});
});

describe('processI18nConfig', () => {
test('returns the Astro i18n config for an unconfigured monolingual site using the built-in default locale', () => {
const { astroI18nConfig, starlightConfig } = processI18nConfig(config, undefined);

expect(astroI18nConfig.defaultLocale).toBe('en');
expect(astroI18nConfig.locales).toEqual(['en']);
assert(typeof astroI18nConfig.routing !== 'string');
expect(astroI18nConfig.routing?.prefixDefaultLocale).toBe(false);

// The Starlight configuration should not be modified.
expect(config).toStrictEqual(starlightConfig);
});

describe('with a provided Astro i18n config', () => {
test('throws an error when an Astro i18n `manual` routing option is used', () => {
expect(() =>
processI18nConfig(
config,
getAstroI18nTestConfig({
defaultLocale: 'en',
locales: ['en', 'fr'],
routing: 'manual',
})
)
).toThrowErrorMatchingInlineSnapshot(`
"[AstroUserError]:
Starlight is not compatible with the \`manual\` routing option in the Astro i18n configuration.
Hint:
"
`);
});

test('throws an error when an Astro i18n config contains an invalid locale', () => {
expect(() =>
processI18nConfig(
config,
getAstroI18nTestConfig({
defaultLocale: 'en',
locales: ['en', 'foo'],
})
)
).toThrowErrorMatchingInlineSnapshot(`
"[AstroUserError]:
Failed to get locale informations for the 'foo' locale.
Hint:
Make sure to provide a valid BCP-47 tags (e.g. en, ar, or zh-CN)."
`);
});

test.each([
{
i18nConfig: { defaultLocale: 'en', locales: ['en'] },
expected: {
defaultLocale: { label: 'English', lang: 'en', dir: 'ltr', locale: undefined },
},
},
{
i18nConfig: { defaultLocale: 'fr', locales: [{ codes: ['fr'], path: 'fr' }] },
expected: {
defaultLocale: { label: 'Français', lang: 'fr', dir: 'ltr', locale: undefined },
},
},
{
i18nConfig: {
defaultLocale: 'fa',
locales: ['fa'],
routing: { prefixDefaultLocale: false },
},
expected: {
defaultLocale: { label: 'فارسی', lang: 'fa', dir: 'rtl', locale: undefined },
},
},
])(
'updates the Starlight i18n config for a monolingual site with a single root locale',
({ i18nConfig, expected }) => {
const astroI18nTestConfig = getAstroI18nTestConfig(i18nConfig);

const { astroI18nConfig, starlightConfig } = processI18nConfig(config, astroI18nTestConfig);

expect(starlightConfig.isMultilingual).toBe(false);
expect(starlightConfig.locales).not.toBeDefined();
expect(starlightConfig.defaultLocale).toStrictEqual(expected.defaultLocale);

// The Astro i18n configuration should not be modified.
expect(astroI18nConfig).toStrictEqual(astroI18nConfig);
}
);

test.each([
{
i18nConfig: {
defaultLocale: 'en',
locales: ['en'],
routing: { prefixDefaultLocale: true },
},
expected: {
defaultLocale: { label: 'English', lang: 'en', dir: 'ltr', locale: 'en' },
locales: { en: { label: 'English', lang: 'en', dir: 'ltr' } },
},
},
{
i18nConfig: {
defaultLocale: 'french',
locales: [{ codes: ['fr'], path: 'french' }],
routing: { prefixDefaultLocale: true },
},
expected: {
defaultLocale: { label: 'Français', lang: 'fr', dir: 'ltr', locale: 'fr' },
locales: { french: { label: 'Français', lang: 'fr', dir: 'ltr' } },
},
},
{
i18nConfig: {
defaultLocale: 'farsi',
locales: [{ codes: ['fa'], path: 'farsi' }],
routing: { prefixDefaultLocale: true },
},
expected: {
defaultLocale: { label: 'فارسی', lang: 'fa', dir: 'rtl', locale: 'fa' },
locales: { farsi: { label: 'فارسی', lang: 'fa', dir: 'rtl' } },
},
},
])(
'updates the Starlight i18n config for a monolingual site with a single non-root locale',
({ i18nConfig, expected }) => {
const astroI18nTestConfig = getAstroI18nTestConfig(i18nConfig);

const { astroI18nConfig, starlightConfig } = processI18nConfig(config, astroI18nTestConfig);

expect(starlightConfig.isMultilingual).toBe(false);
expect(starlightConfig.locales).toStrictEqual(expected.locales);
expect(starlightConfig.defaultLocale).toStrictEqual(expected.defaultLocale);

// The Astro i18n configuration should not be modified.
expect(astroI18nConfig).toStrictEqual(astroI18nConfig);
}
);

test.each([
{
i18nConfig: {
defaultLocale: 'en',
locales: ['en', { codes: ['fr'], path: 'french' }],
},
expected: {
defaultLocale: { label: 'English', lang: 'en', dir: 'ltr', locale: 'en' },
locales: {
root: { label: 'English', lang: 'en', dir: 'ltr' },
french: { label: 'Français', lang: 'fr', dir: 'ltr' },
},
},
},
{
i18nConfig: {
defaultLocale: 'farsi',
// This configuration is a bit confusing as `prefixDefaultLocale` is `false` but the
// default locale is defined with a custom path.
// In this case, the default locale is considered to be a root locale and the custom path
// is ignored.
locales: [{ codes: ['fa'], path: 'farsi' }, 'de'],
routing: { prefixDefaultLocale: false },
},
expected: {
defaultLocale: { label: 'فارسی', lang: 'fa', dir: 'rtl', locale: 'fa' },
locales: {
root: { label: 'فارسی', lang: 'fa', dir: 'rtl' },
de: { label: 'Deutsch', lang: 'de', dir: 'ltr' },
},
},
},
])(
'updates the Starlight i18n config for a multilingual site with a root locale',
({ i18nConfig, expected }) => {
const astroI18nTestConfig = getAstroI18nTestConfig(i18nConfig);

const { astroI18nConfig, starlightConfig } = processI18nConfig(config, astroI18nTestConfig);

expect(starlightConfig.isMultilingual).toBe(true);
expect(starlightConfig.locales).toEqual(expected.locales);
expect(starlightConfig.defaultLocale).toEqual(expected.defaultLocale);

// The Astro i18n configuration should not be modified.
expect(astroI18nConfig).toEqual(astroI18nConfig);
}
);

test.each([
{
i18nConfig: {
defaultLocale: 'en',
locales: ['en', { codes: ['fr'], path: 'french' }],
routing: { prefixDefaultLocale: true },
},
expected: {
defaultLocale: { label: 'English', lang: 'en', dir: 'ltr', locale: 'en' },
locales: {
en: { label: 'English', lang: 'en', dir: 'ltr' },
french: { label: 'Français', lang: 'fr', dir: 'ltr' },
},
},
},
{
i18nConfig: {
defaultLocale: 'farsi',
locales: [{ codes: ['fa'], path: 'farsi' }, 'de'],
routing: { prefixDefaultLocale: true },
},
expected: {
defaultLocale: { label: 'فارسی', lang: 'fa', dir: 'rtl', locale: 'fa' },
locales: {
farsi: { label: 'فارسی', lang: 'fa', dir: 'rtl' },
de: { label: 'Deutsch', lang: 'de', dir: 'ltr' },
},
},
},
])(
'updates the Starlight i18n config for a multilingual site with no root locale',
({ i18nConfig, expected }) => {
const astroI18nTestConfig = getAstroI18nTestConfig(i18nConfig);

const { astroI18nConfig, starlightConfig } = processI18nConfig(config, astroI18nTestConfig);

expect(starlightConfig.isMultilingual).toBe(true);
expect(starlightConfig.locales).toEqual(expected.locales);
expect(starlightConfig.defaultLocale).toEqual(expected.defaultLocale);

// The Astro i18n configuration should not be modified.
expect(astroI18nConfig).toEqual(astroI18nConfig);
}
);
});
});

function getAstroI18nTestConfig(i18nConfig: AstroUserConfig['i18n']): AstroConfig['i18n'] {
return {
...i18nConfig,
routing:
typeof i18nConfig?.routing !== 'string'
? { prefixDefaultLocale: false, ...i18nConfig?.routing }
: i18nConfig.routing,
} as AstroConfig['i18n'];
}
Loading

0 comments on commit e8c3d38

Please sign in to comment.