Skip to content

Commit 6420bc5

Browse files
committed
fix: add yaml support to the fs translation system
1 parent 88f6510 commit 6420bc5

8 files changed

Lines changed: 58 additions & 13 deletions

File tree

.changeset/thick-houses-pull.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@astrojs/starlight': patch
3+
---
4+
5+
Fixes an issue with custom UI strings defined in YAML files not being loaded in some contexts.

packages/starlight/__tests__/i18n/malformed-src/content/i18n/en.json renamed to packages/starlight/__tests__/i18n/malformed-json-src/content/i18n/en.json

File renamed without changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test: 'Malformed YAML file with dangling trailing comma',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
page.editLink: Rendre cette page différente

packages/starlight/__tests__/i18n/translations-fs.test.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, test } from 'vitest';
22
import { createTranslationSystemFromFs } from '../../utils/translations-fs';
3+
import { YAMLException } from 'js-yaml';
34

45
describe('createTranslationSystemFromFs', () => {
56
test('creates a translation system that returns default strings', () => {
@@ -18,14 +19,21 @@ describe('createTranslationSystemFromFs', () => {
1819
test('creates a translation system that uses custom strings', () => {
1920
const useTranslations = createTranslationSystemFromFs(
2021
{
21-
locales: { en: { label: 'English', dir: 'ltr' } },
22+
locales: {
23+
en: { label: 'English', dir: 'ltr', lang: 'en' },
24+
fr: { label: 'Français', dir: 'ltr', lang: 'fr' },
25+
},
2226
defaultLocale: { label: 'English', locale: 'en', dir: 'ltr' },
2327
},
2428
// Using `src/` to load custom files in this test fixture.
2529
{ srcDir: new URL('./src/', import.meta.url) }
2630
);
27-
const t = useTranslations('en');
31+
// From an i18n JSON file
32+
let t = useTranslations('en');
2833
expect(t('page.editLink')).toMatchInlineSnapshot('"Make this page different"');
34+
// From an i18n YAML file
35+
t = useTranslations('fr');
36+
expect(t('page.editLink')).toMatchInlineSnapshot('"Rendre cette page différente"');
2937
});
3038

3139
test('supports root locale', () => {
@@ -68,12 +76,22 @@ describe('createTranslationSystemFromFs', () => {
6876
expect(() =>
6977
createTranslationSystemFromFs(
7078
{ locales: {}, defaultLocale: { label: 'English', locale: 'en', dir: 'ltr' } },
71-
// Using `malformed-src/` to trigger syntax error in bad JSON file.
72-
{ srcDir: new URL('./malformed-src/', import.meta.url) }
79+
// Using `malformed-json-src/` to trigger syntax error in bad JSON file.
80+
{ srcDir: new URL('./malformed-json-src/', import.meta.url) }
7381
)
7482
).toThrow(SyntaxError);
7583
});
7684

85+
test('throws on malformed i18n YAML', () => {
86+
expect(() =>
87+
createTranslationSystemFromFs(
88+
{ locales: {}, defaultLocale: { label: 'English', locale: 'en', dir: 'ltr' } },
89+
// Using `malformed-yaml-src/` to trigger syntax error in bad YAML file.
90+
{ srcDir: new URL('./malformed-yaml-src/', import.meta.url) }
91+
)
92+
).toThrow(YAMLException);
93+
});
94+
7795
test('creates a translation system that uses custom strings injected by plugins', () => {
7896
const useTranslations = createTranslationSystemFromFs(
7997
{

packages/starlight/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
"devDependencies": {
178178
"@astrojs/markdown-remark": "^5.1.0",
179179
"@playwright/test": "^1.45.0",
180+
"@types/js-yaml": "^4.0.9",
180181
"@types/node": "^18.16.19",
181182
"@vitest/coverage-v8": "^1.6.0",
182183
"astro": "^4.15.3",
@@ -196,6 +197,7 @@
196197
"hast-util-to-string": "^3.0.0",
197198
"hastscript": "^9.0.0",
198199
"i18next": "^23.11.5",
200+
"js-yaml": "^4.1.0",
199201
"mdast-util-directive": "^3.0.0",
200202
"mdast-util-to-markdown": "^2.1.0",
201203
"mdast-util-to-string": "^4.0.0",

packages/starlight/utils/translations-fs.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import fs from 'node:fs';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import yaml from 'js-yaml';
25
import type { i18nSchemaOutput } from '../schemas/i18n';
36
import { createTranslationSystem } from './createTranslationSystem';
47
import type { StarlightConfig } from './user-config';
58
import type { AstroConfig } from 'astro';
69

10+
const contentCollectionFileExtensions = ['.json', '.yaml', '.yml'];
11+
712
/**
813
* Loads and creates a translation system from the file system.
914
* Only for use in integration code.
@@ -23,15 +28,18 @@ export function createTranslationSystemFromFs<T extends i18nSchemaOutput>(
2328
// Load the user’s i18n directory
2429
const files = fs.readdirSync(i18nDir, 'utf-8');
2530
// Load the user’s i18n collection and ignore the error if it doesn’t exist.
26-
userTranslations = Object.fromEntries(
27-
files
28-
.filter((file) => file.endsWith('.json'))
29-
.map((file) => {
30-
const id = file.slice(0, -5);
31-
const data = JSON.parse(fs.readFileSync(new URL(file, i18nDir), 'utf-8'));
32-
return [id, data] as const;
33-
})
34-
);
31+
for (const file of files) {
32+
const filePath = path.parse(file);
33+
if (!contentCollectionFileExtensions.includes(filePath.ext)) continue;
34+
const id = filePath.name;
35+
const url = new URL(filePath.base, i18nDir);
36+
const content = fs.readFileSync(new URL(file, i18nDir), 'utf-8');
37+
const data =
38+
filePath.ext === '.json'
39+
? JSON.parse(content)
40+
: yaml.load(content, { filename: fileURLToPath(url) });
41+
userTranslations[id] = data as i18nSchemaOutput;
42+
}
3543
} catch (e: unknown) {
3644
if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {
3745
// i18nDir doesn’t exist, so we ignore the error.

pnpm-lock.yaml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)