diff --git a/.changeset/thick-boxes-fail.md b/.changeset/thick-boxes-fail.md
new file mode 100644
index 00000000000..18b7aa870b6
--- /dev/null
+++ b/.changeset/thick-boxes-fail.md
@@ -0,0 +1,5 @@
+---
+"@astrojs/starlight": minor
+---
+
+Add i18n support for default aside labels
diff --git a/packages/starlight/__tests__/remark-rehype/asides.test.ts b/packages/starlight/__tests__/remark-rehype/asides.test.ts
index 7d1da5a6781..b3f792166ba 100644
--- a/packages/starlight/__tests__/remark-rehype/asides.test.ts
+++ b/packages/starlight/__tests__/remark-rehype/asides.test.ts
@@ -1,9 +1,29 @@
import { createMarkdownProcessor } from '@astrojs/markdown-remark';
import { describe, expect, test } from 'vitest';
import { starlightAsides } from '../../integrations/asides';
+import { createTranslationSystemFromFs } from '../../utils/translations-fs';
+import { StarlightConfigSchema, type StarlightUserConfig } from '../../utils/user-config';
+
+const starlightConfig = StarlightConfigSchema.parse({
+ title: 'Asides Tests',
+ locales: { en: { label: 'English' }, fr: { label: 'French' } },
+ defaultLocale: 'en',
+} satisfies StarlightUserConfig);
+
+const useTranslations = createTranslationSystemFromFs(
+ starlightConfig,
+ // Using non-existent `_src/` to ignore custom files in this test fixture.
+ { srcDir: new URL('./_src/', import.meta.url) }
+);
const processor = await createMarkdownProcessor({
- remarkPlugins: [...starlightAsides()],
+ remarkPlugins: [
+ ...starlightAsides({
+ starlightConfig,
+ astroConfig: { root: new URL(import.meta.url), srcDir: new URL('./_src/', import.meta.url) },
+ useTranslations,
+ }),
+ ],
});
test('generates
`);
+ });
+});
+
+test('runs without locales config', async () => {
+ const processor = await createMarkdownProcessor({
+ remarkPlugins: [
+ ...starlightAsides({
+ starlightConfig: { locales: undefined },
+ astroConfig: {
+ root: new URL(import.meta.url),
+ srcDir: new URL('./_src/', import.meta.url),
+ },
+ useTranslations,
+ }),
+ ],
+ });
+ const res = await processor.render(':::note\nTest\n::');
+ expect(res.code.includes('aria-label=Note"'));
+});
diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts
index 53ffb2e20cc..93a1e9c743f 100644
--- a/packages/starlight/index.ts
+++ b/packages/starlight/index.ts
@@ -49,7 +49,7 @@ export default function StarlightIntegration(opts: StarlightUserConfig): AstroIn
plugins: [vitePluginStarlightUserConfig(userConfig, config)],
},
markdown: {
- remarkPlugins: [...starlightAsides()],
+ remarkPlugins: [...starlightAsides({ starlightConfig: userConfig, astroConfig: config, useTranslations })],
rehypePlugins: [rehypeRtlCodeSupport()],
shikiConfig:
// Configure Shiki theme if the user is using the default github-dark theme.
diff --git a/packages/starlight/integrations/asides.ts b/packages/starlight/integrations/asides.ts
index 5d2107ad79e..a97a610ae0a 100644
--- a/packages/starlight/integrations/asides.ts
+++ b/packages/starlight/integrations/asides.ts
@@ -1,10 +1,45 @@
-import type { AstroUserConfig } from 'astro';
+import type { AstroConfig, AstroUserConfig } from 'astro';
import { h as _h, s as _s, type Properties } from 'hastscript';
import type { Paragraph as P, Root } from 'mdast';
import remarkDirective from 'remark-directive';
import type { Plugin, Transformer } from 'unified';
import { remove } from 'unist-util-remove';
import { visit } from 'unist-util-visit';
+import type { StarlightConfig } from '../types';
+import type { createTranslationSystemFromFs } from '../utils/translations-fs';
+
+interface AsidesOptions {
+ starlightConfig: { locales: StarlightConfig['locales'] };
+ astroConfig: { root: AstroConfig['root']; srcDir: AstroConfig['srcDir'] };
+ useTranslations: ReturnType;
+}
+
+function pathToLocale(
+ slug: string | undefined,
+ config: AsidesOptions['starlightConfig']
+): string | undefined {
+ const locales = Object.keys(config.locales || {});
+ const baseSegment = slug?.split('/')[0];
+ if (baseSegment && locales.includes(baseSegment)) return baseSegment;
+ return undefined;
+}
+
+/** get current lang from file full path */
+function getLocaleFromPath(
+ unformattedPath: string | undefined,
+ { starlightConfig, astroConfig }: AsidesOptions
+): string | undefined {
+ const srcDir = new URL(astroConfig.srcDir, astroConfig.root);
+ const docsDir = new URL('content/docs/', srcDir);
+ const path = unformattedPath
+ // Format path to unix style path.
+ ?.replace(/\\/g, '/')
+ // Strip docs path leaving only content collection file ID.
+ // Example: /Users/houston/repo/src/content/docs/en/guide.md => en/guide.md
+ .replace(docsDir.pathname, '');
+ const locale = pathToLocale(path, starlightConfig);
+ return locale;
+}
/** Hacky function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
function h(el: string, attrs: Properties = {}, children: any[] = []): P {
@@ -50,19 +85,11 @@ function s(el: string, attrs: Properties = {}, children: any[] = []): P {
*
* ```
*/
-function remarkAsides(): Plugin<[], Root> {
+function remarkAsides(options: AsidesOptions): Plugin<[], Root> {
type Variant = 'note' | 'tip' | 'caution' | 'danger';
const variants = new Set(['note', 'tip', 'caution', 'danger']);
const isAsideVariant = (s: string): s is Variant => variants.has(s);
- // TODO: hook these up for i18n once the design for translating strings is ready
- const defaultTitles = {
- note: 'Note',
- tip: 'Tip',
- caution: 'Caution',
- danger: 'Danger',
- };
-
const iconPaths = {
// Information icon
note: [
@@ -95,7 +122,9 @@ function remarkAsides(): Plugin<[], Root> {
],
};
- const transformer: Transformer = (tree) => {
+ const transformer: Transformer = (tree, file) => {
+ const locale = getLocaleFromPath(file.history[0], options);
+ const t = options.useTranslations(locale);
visit(tree, (node, index, parent) => {
if (!parent || index === null || node.type !== 'containerDirective') {
return;
@@ -107,7 +136,7 @@ function remarkAsides(): Plugin<[], Root> {
// its children, but we want to pass it as the title prop to