Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for translating sidebar badges #2285

Merged
merged 9 commits into from
Sep 18, 2024
5 changes: 5 additions & 0 deletions .changeset/slow-flowers-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': minor
---

Adds support for translating sidebar badges.
5 changes: 4 additions & 1 deletion docs/src/components/sidebar-preview.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import type {
InternalSidebarLinkItem,
} from '../../../packages/starlight/schemas/sidebar';
import SidebarSublist from '../../../packages/starlight/components/SidebarSublist.astro';
import type { Badge } from '../../../packages/starlight/schemas/badge';
import type { SidebarEntry } from '../../../packages/starlight/utils/navigation';

interface Props {
config: SidebarConfig;
}

type SidebarConfig = Exclude<SidebarItem, AutoSidebarGroup | InternalSidebarLinkItem>[];
type SidebarConfig = (Exclude<SidebarItem, AutoSidebarGroup | InternalSidebarLinkItem> & {
badge?: Badge;
})[];

const { config } = Astro.props;

Expand Down
46 changes: 46 additions & 0 deletions docs/src/content/docs/guides/sidebar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,52 @@ Browsing the documentation in Brazilian Portuguese will generate the following s
In multilingual sites, the value of `slug` does not include the language portion of the URL.
For example, if you have pages at `en/intro` and `pt-br/intro`, the slug is `intro` when configuring the sidebar.

### Internationalization with badges

For [badges](#badges), the `text` property can be a string, or for multilingual sites, an object with values for each different locale.
When using the object form, the keys must be [BCP-47](https://www.w3.org/International/questions/qa-choosing-language-tags) tags (e.g. `en`, `ar`, or `zh-CN`):

```js {11-16}
starlight({
sidebar: [
{
label: 'Constellations',
translations: {
'pt-BR': 'Constelações',
},
items: [
{
slug: 'constellations/andromeda',
badge: {
text: {
en: 'New',
'pt-BR': 'Novo',
},
},
},
],
},
],
});
```

Browsing the documentation in Brazilian Portuguese will generate the following sidebar:

<SidebarPreview
config={[
{
label: 'Constelações',
items: [
{
label: 'Andrômeda',
link: '',
badge: { text: 'Novo', variant: 'default' },
},
],
},
]}
/>

## Collapsing groups

Groups of links can be collapsed by default by setting the `collapsed` property to `true`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, test, vi } from 'vitest';
import { getSidebar } from '../../utils/navigation';

vi.mock('astro:content', async () =>
(await import('../test-utils')).mockedAstroContent({
docs: [['getting-started.mdx', { title: 'Getting Started' }]],
})
);

describe('getSidebar', () => {
test('throws an error if an i18n badge doesn’t have a key for the default language', () => {
expect(() => getSidebar('/', undefined)).toThrowErrorMatchingInlineSnapshot(`
"[AstroUserError]:
The badge text for "Getting Started" must have a key for the default language "en-US".
Hint:
Update the Starlight config to include a badge text for the default language.
Learn more about sidebar badges internationalization at https://starlight.astro.build/guides/sidebar/#internationalization-with-badges"
`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineVitestConfig } from '../test-config';

export default defineVitestConfig({
title: 'i18n sidebar badge error',
locales: {
fr: { label: 'French' },
root: { label: 'English', lang: 'en-US' },
},
sidebar: [
{
slug: 'getting-started',
badge: { text: { fr: 'Nouveau' } },
},
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,21 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "New",
"variant": "default",
},
"href": "/manual-setup",
"isCurrent": false,
"label": "Do it yourself",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Eco-friendly",
"variant": "success",
},
"href": "/environmental-impact",
"isCurrent": false,
"label": "Eco-friendly docs",
Expand All @@ -65,7 +71,10 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Deprecated",
"variant": "default",
},
"href": "/guides/authoring-content",
"isCurrent": false,
"label": "Authoring Content in Markdown",
Expand Down Expand Up @@ -107,15 +116,21 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Nouveau",
"variant": "default",
},
"href": "/fr/manual-setup",
"isCurrent": false,
"label": "Fait maison",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Écologique",
"variant": "success",
},
"href": "/fr/environmental-impact",
"isCurrent": false,
"label": "Eco-friendly docs",
Expand All @@ -135,7 +150,10 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Deprecated",
"variant": "default",
},
"href": "/fr/guides/authoring-content",
"isCurrent": false,
"label": "Authoring Content in Markdown",
Expand Down
45 changes: 36 additions & 9 deletions packages/starlight/__tests__/i18n-sidebar/i18n-sidebar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,21 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "New",
"variant": "default",
},
"href": "/manual-setup",
"isCurrent": false,
"label": "Do it yourself",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Eco-friendly",
"variant": "success",
},
"href": "/environmental-impact",
"isCurrent": false,
"label": "Eco-friendly docs",
Expand All @@ -72,7 +78,10 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Deprecated",
"variant": "default",
},
"href": "/guides/authoring-content",
"isCurrent": false,
"label": "Authoring Content in Markdown",
Expand Down Expand Up @@ -114,15 +123,21 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Nouveau",
"variant": "default",
},
"href": "/fr/manual-setup",
"isCurrent": false,
"label": "Fait maison",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Écologique",
"variant": "success",
},
"href": "/fr/environmental-impact",
"isCurrent": false,
"label": "Documents écologiques",
Expand All @@ -142,7 +157,10 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Deprecated",
"variant": "default",
},
"href": "/fr/guides/authoring-content",
"isCurrent": false,
"label": "Création de contenu en Markdown",
Expand Down Expand Up @@ -184,15 +202,21 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Nouveau",
"variant": "default",
},
"href": "/fr/manual-setup",
"isCurrent": false,
"label": "Fait maison",
"type": "link",
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Écologique",
"variant": "success",
},
"href": "/fr/environmental-impact",
"isCurrent": false,
"label": "Documents écologiques",
Expand All @@ -212,7 +236,10 @@ describe('getSidebar', () => {
},
{
"attrs": {},
"badge": undefined,
"badge": {
"text": "Deprecated",
"variant": "default",
},
"href": "/fr/guides/authoring-content",
"isCurrent": false,
"label": "Création de contenu en Markdown",
Expand Down
17 changes: 14 additions & 3 deletions packages/starlight/__tests__/i18n-sidebar/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,22 @@ export default defineVitestConfig({
sidebar: [
{ slug: 'index' },
'getting-started',
{ slug: 'manual-setup', label: 'Do it yourself', translations: { fr: 'Fait maison' } },
{ slug: 'environmental-impact' },
{
slug: 'manual-setup',
label: 'Do it yourself',
translations: { fr: 'Fait maison' },
badge: { text: { 'en-US': 'New', fr: 'Nouveau' } },
},
{
slug: 'environmental-impact',
badge: {
variant: 'success',
text: { 'en-US': 'Eco-friendly', fr: 'Écologique' },
},
},
{
label: 'Guides',
items: [{ slug: 'guides/pages' }, { slug: 'guides/authoring-content' }],
items: [{ slug: 'guides/pages' }, { slug: 'guides/authoring-content', badge: 'Deprecated' }],
},
'resources/plugins',
],
Expand Down
30 changes: 20 additions & 10 deletions packages/starlight/schemas/badge.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { z } from 'astro/zod';

const badgeSchema = () =>
z.object({
variant: z.enum(['note', 'danger', 'success', 'caution', 'tip', 'default']).default('default'),
text: z.string(),
class: z.string().optional(),
});

export const BadgeComponentSchema = badgeSchema()
const badgeBaseSchema = z.object({
variant: z.enum(['note', 'danger', 'success', 'caution', 'tip', 'default']).default('default'),
class: z.string().optional(),
});

const badgeSchema = badgeBaseSchema.extend({
text: z.string(),
});

const i18nBadgeSchema = badgeBaseSchema.extend({
text: z.union([z.string(), z.record(z.string())]),
});

export const BadgeComponentSchema = badgeSchema
.extend({
size: z.enum(['small', 'medium', 'large']).default('small'),
})
Expand All @@ -17,7 +23,7 @@ export type BadgeComponentProps = z.input<typeof BadgeComponentSchema>;

export const BadgeConfigSchema = () =>
z
.union([z.string(), badgeSchema()])
.union([z.string(), badgeSchema])
.transform((badge) => {
if (typeof badge === 'string') {
return { variant: 'default' as const, text: badge };
Expand All @@ -26,4 +32,8 @@ export const BadgeConfigSchema = () =>
})
.optional();

export type Badge = z.output<ReturnType<typeof badgeSchema>>;
export const I18nBadgeConfigSchema = () => z.union([z.string(), i18nBadgeSchema]).optional();

export type Badge = z.output<typeof badgeSchema>;
export type I18nBadge = z.output<typeof i18nBadgeSchema>;
export type I18nBadgeConfig = z.output<ReturnType<typeof I18nBadgeConfigSchema>>;
6 changes: 3 additions & 3 deletions packages/starlight/schemas/sidebar.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { AstroBuiltinAttributes } from 'astro';
import type { HTMLAttributes } from 'astro/types';
import { z } from 'astro/zod';
import { BadgeConfigSchema } from './badge';
import { I18nBadgeConfigSchema } from './badge';
import { stripLeadingAndTrailingSlashes } from '../utils/path';

const SidebarBaseSchema = z.object({
/** The visible label for this item in the sidebar. */
label: z.string(),
/** Translations of the `label` for each supported language. */
translations: z.record(z.string()).default({}),
/** Adds a badge to the link item */
badge: BadgeConfigSchema(),
/** Adds a badge to the item */
badge: I18nBadgeConfigSchema(),
});

const SidebarGroupSchema = SidebarBaseSchema.extend({
Expand Down
Loading
Loading