Skip to content

Commit

Permalink
Include built-in UI translation for regional docs (#475)
Browse files Browse the repository at this point in the history
Co-authored-by: Chris Swithinbank <[email protected]>
Co-authored-by: Chris Swithinbank <[email protected]>
  • Loading branch information
3 people authored Aug 10, 2023
1 parent 43c1481 commit 06a205e
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/two-experts-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@astrojs/starlight": patch
---

Locales whose language tag includes a regional subtag now use built-in UI translations for their base language. For example, a locale with a language of `pt-BR` will use our `pt` UI translations.
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ The label for this language to show to users, for example in the language switch

**type:** `string`

The BCP-47 tag for this language, e.g. `"en"`, `"ar"`, or `"zh-CN"`. If not set, the language’s directory name will be used by default.
The BCP-47 tag for this language, e.g. `"en"`, `"ar"`, or `"zh-CN"`. If not set, the language’s directory name will be used by default. Language tags with regional subtags (e.g. `"pt-BR"` or `"en-US"`) will use built-in UI translations for their base language if no region-specific translations are found.

##### `dir`

Expand Down
2 changes: 1 addition & 1 deletion packages/starlight/__tests__/i18n/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test('test suite is using correct env', () => {

test('config.isMultilingual is true with multiple locales', () => {
expect(config.isMultilingual).toBe(true);
expect(config.locales).keys('fr', 'en', 'ar');
expect(config.locales).keys('fr', 'en', 'ar', 'pt-br');
});

test('config.defaultLocale is populated from the user’s chosen default', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/starlight/__tests__/i18n/routing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ test('routes have locale data added', () => {
expect(lang).toBe('ar');
expect(dir).toBe('rtl');
expect(locale).toBe('ar');
} else {
} else if (id.startsWith('fr')) {
expect(lang).toBe('fr');
expect(dir).toBe('ltr');
expect(locale).toBe('fr');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { useTranslations } from '../../utils/translations';

vi.mock('astro:content', async () =>
(await import('../test-utils')).mockedAstroContent({
i18n: [['en', { 'page.editLink': 'Modify this doc!' }]],
i18n: [
['en-US', { 'page.editLink': 'Modify this doc!' }],
['pt-BR', { 'page.editLink': 'Modifique esse doc!' }],
],
})
);

Expand All @@ -14,4 +17,10 @@ describe('useTranslations()', () => {
expect(t('page.editLink')).toBe('Modify this doc!');
expect(t('page.editLink')).not.toBe(translations.en?.['page.editLink']);
});

test('uses user-defined regional translations when available', () => {
const t = useTranslations('pt-br');
expect(t('page.editLink')).toBe('Modifique esse doc!');
expect(t('page.editLink')).not.toBe(translations.pt?.['page.editLink']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,10 @@ describe('useTranslations()', () => {
'tableOfContents.overview': 'Overview',
});
});

test('uses built-in translations for regional variants', () => {
const t = useTranslations('pt-br');
expect(t('page.nextLink')).toBe(translations.pt?.['page.nextLink']);
expect(t('page.nextLink')).not.toBe(translations.en?.['page.nextLink']);
});
});
1 change: 1 addition & 0 deletions packages/starlight/__tests__/i18n/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export default defineVitestConfig({
fr: { label: 'French' },
en: { label: 'English', lang: 'en-US' },
ar: { label: 'Arabic', dir: 'rtl' },
'pt-br': { label: 'Brazilian Portuguese', lang: 'pt-BR' },
},
});
19 changes: 16 additions & 3 deletions packages/starlight/utils/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,20 @@ try {
const defaults = buildDictionary(
builtinTranslations.en!,
userTranslations.en,
builtinTranslations[defaultLocale],
builtinTranslations[defaultLocale] || builtinTranslations[stripLangRegion(defaultLocale)],
userTranslations[defaultLocale]
);

/**
* Strips the region subtag from a BCP-47 lang string.
* @param {string} [lang]
* @example
* const lang = stripLangRegion('en-GB'); // => 'en'
*/
export function stripLangRegion(lang: string) {
return lang.replace(/-[a-zA-Z]{2}/, '');
}

/**
* Generate a utility function that returns UI strings for the given `locale`.
* @param {string | undefined} [locale]
Expand All @@ -31,9 +41,12 @@ const defaults = buildDictionary(
* const label = t('search.label'); // => 'Search'
*/
export function useTranslations(locale: string | undefined) {
// TODO: Use better mapping, e.g. so that `en-GB` matches `en`.
const lang = localeToLang(locale);
const dictionary = buildDictionary(defaults, builtinTranslations[lang], userTranslations[lang]);
const dictionary = buildDictionary(
defaults,
builtinTranslations[lang] || builtinTranslations[stripLangRegion(lang)],
userTranslations[lang]
);
const t = <K extends keyof typeof dictionary>(key: K) => dictionary[key];
t.pick = (startOfKey: string) =>
Object.fromEntries(Object.entries(dictionary).filter(([k]) => k.startsWith(startOfKey)));
Expand Down

0 comments on commit 06a205e

Please sign in to comment.