diff --git a/.changeset/long-parrots-give.md b/.changeset/long-parrots-give.md new file mode 100644 index 00000000000..269c5130bba --- /dev/null +++ b/.changeset/long-parrots-give.md @@ -0,0 +1,5 @@ +--- +'@astrojs/starlight': patch +--- + +Improve inline code and code block support in RTL languages diff --git a/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts b/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts new file mode 100644 index 00000000000..69aa85ad8e2 --- /dev/null +++ b/packages/starlight/__tests__/remark-rehype/code-rtl-support.test.ts @@ -0,0 +1,25 @@ +import { rehype } from 'rehype'; +import { expect, test } from 'vitest'; +import { rehypeRtlCodeSupport } from '../../integrations/code-rtl-support'; + +const processor = rehype().data('settings', { fragment: true }).use(rehypeRtlCodeSupport()); + +test('applies `dir="auto"` to inline code', async () => { + const input = `

Some text with inline code.

`; + const output = String(await processor.process(input)); + expect(output).not.toEqual(input); + expect(output).includes('dir="auto"'); + expect(output).toMatchInlineSnapshot( + '"

Some text with inline code.

"' + ); +}); + +test('applies `dir="ltr"` to code blocks', async () => { + const input = `

Some text in a paragraph:

console.log('test')
`; + const output = String(await processor.process(input)); + expect(output).not.toEqual(input); + expect(output).includes('dir="ltr"'); + expect(output).toMatchInlineSnapshot( + '"

Some text in a paragraph:

console.log(\'test\')
"' + ); +}); diff --git a/packages/starlight/__tests__/remark-rehype/vitest.config.ts b/packages/starlight/__tests__/remark-rehype/vitest.config.ts new file mode 100644 index 00000000000..0b12dc3a687 --- /dev/null +++ b/packages/starlight/__tests__/remark-rehype/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineVitestConfig } from '../test-config'; + +export default defineVitestConfig({ title: 'Remark-Rehype' }); diff --git a/packages/starlight/index.ts b/packages/starlight/index.ts index dbd487bdded..5909777eb92 100644 --- a/packages/starlight/index.ts +++ b/packages/starlight/index.ts @@ -8,6 +8,7 @@ import { starlightSitemap } from './integrations/sitemap'; import { vitePluginStarlightUserConfig } from './integrations/virtual-user-config'; import { errorMap } from './utils/error-map'; import { StarlightConfigSchema, StarlightUserConfig } from './utils/user-config'; +import { rehypeRtlCodeSupport } from './integrations/code-rtl-support'; export default function StarlightIntegration(opts: StarlightUserConfig): AstroIntegration[] { const parsedConfig = StarlightConfigSchema.safeParse(opts, { errorMap }); @@ -39,6 +40,7 @@ export default function StarlightIntegration(opts: StarlightUserConfig): AstroIn }, markdown: { remarkPlugins: [...starlightAsides()], + rehypePlugins: [rehypeRtlCodeSupport()], shikiConfig: // Configure Shiki theme if the user is using the default github-dark theme. config.markdown.shikiConfig.theme !== 'github-dark' ? {} : { theme: 'css-variables' }, diff --git a/packages/starlight/integrations/code-rtl-support.ts b/packages/starlight/integrations/code-rtl-support.ts new file mode 100644 index 00000000000..b8f7c3a6660 --- /dev/null +++ b/packages/starlight/integrations/code-rtl-support.ts @@ -0,0 +1,31 @@ +import type { Root } from 'hastscript/lib/core'; +import { CONTINUE, SKIP, visit } from 'unist-util-visit'; + +/** + * rehype plugin that adds `dir` attributes to `` and `
`
+ * elements that don’t already have them.
+ *
+ * `` will become ``
+ * `
` will become `
`
+ *
+ * `` _inside_ `
` is skipped, so respects the `ltr` on its parent.
+ *
+ * Reasoning:
+ * - `
` is usually a code block and code should be LTR even in an RTL document
+ * - `` is often LTR, but could also be RTL. `dir="auto"` ensures the bidirectional
+ *   algorithm treats the contents of `` in isolation and gives its best guess.
+ */
+export function rehypeRtlCodeSupport() {
+	return () => (root: Root) => {
+		visit(root, 'element', (el) => {
+			if (el.tagName === 'pre' || el.tagName === 'code') {
+				el.properties ||= {};
+				if (!('dir' in el.properties)) {
+					el.properties.dir = { pre: 'ltr', code: 'auto' }[el.tagName];
+				}
+				return SKIP;
+			}
+			return CONTINUE;
+		});
+	};
+}