Skip to content

Commit

Permalink
Improve locale text direction detection mechanism (#1987)
Browse files Browse the repository at this point in the history
Co-authored-by: Chris Swithinbank <357379+delucis@users.noreply.github.com>
  • Loading branch information
HiDeoo and delucis committed Jun 9, 2024
1 parent e6c0467 commit 0b8a843
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/olive-oranges-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Fixes issues with the locale text direction detection mechanism in some environments like WebContainers or Bun.
50 changes: 49 additions & 1 deletion packages/starlight/__tests__/basics/i18n.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert, describe, expect, test } from 'vitest';
import { assert, describe, expect, test, vi } from 'vitest';
import config from 'virtual:starlight/user-config';
import { processI18nConfig, pickLang } from '../../utils/i18n';
import type { AstroConfig } from 'astro';
Expand Down Expand Up @@ -250,6 +250,54 @@ describe('processI18nConfig', () => {
});
});

describe('getLocaleDir', () => {
test('uses the Node.js implementation to figure out the text direction', () => {
const { starlightConfig } = processI18nConfig(
config,
getAstroI18nTestConfig({
defaultLocale: 'en',
locales: ['en'],
})
);

expect(starlightConfig.defaultLocale.dir).toBe('ltr');
});

test('uses `getTextInfo()` when `textInfo` is not available', async () => {
// @ts-expect-error - `getTextInfo` is not typed but is available in some non-v8 based environments.
vi.spyOn(global.Intl, 'Locale').mockImplementation(() => ({
getTextInfo: () => ({ direction: 'rtl' }),
}));

const { starlightConfig } = processI18nConfig(
config,
getAstroI18nTestConfig({
defaultLocale: 'en',
locales: ['en'],
})
);

expect(starlightConfig.defaultLocale.dir).toBe('rtl');
});

test('fallbacks to a list of well-known RTL languages when `textInfo` and `getTextInfo()` are not available', async () => {
// @ts-expect-error - We are simulating the absence of `textInfo` and `getTextInfo()`.
vi.spyOn(global.Intl, 'Locale').mockImplementation((tag) => ({ language: tag }));

const { starlightConfig } = processI18nConfig(
config,
getAstroI18nTestConfig({
defaultLocale: 'en',
locales: ['en', 'fa'],
})
);

expect(starlightConfig.defaultLocale.dir).toBe('ltr');
expect(starlightConfig.locales?.root?.dir).toBe('ltr');
expect(starlightConfig.locales?.fa?.dir).toBe('rtl');
});
});

function getAstroI18nTestConfig(i18nConfig: AstroUserConfig['i18n']): AstroConfig['i18n'] {
return {
...i18nConfig,
Expand Down
29 changes: 27 additions & 2 deletions packages/starlight/utils/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import type { StarlightConfig } from './user-config';
/** Informations about the built-in default locale used as a fallback when no locales are defined. */
export const BuiltInDefaultLocale = { ...getLocaleInfo('en'), lang: 'en' };

/**
* A list of well-known right-to-left languages used as a fallback when determining the text
* direction of a locale is not supported by the `Intl.Locale` API in the current environment.
*
* @see getLocaleDir()
* @see https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags
*/
const wellKnownRTL = ['ar', 'fa', 'he', 'prs', 'ps', 'syc', 'ug', 'ur'];

/**
* Processes the Astro and Starlight i18n configurations to generate/update them accordingly:
*
Expand Down Expand Up @@ -150,8 +159,7 @@ function getLocaleInfo(lang: string) {
if (!label || lang === label) throw new Error('Label not found.');
return {
label: label[0]?.toLocaleUpperCase(locale) + label.slice(1),
// @ts-expect-error - `textInfo` is not part of the `Intl.Locale` type but is available in Node.js 18.0.0+.
dir: locale.textInfo.direction as 'ltr' | 'rtl',
dir: getLocaleDir(locale),
};
} catch (error) {
throw new AstroError(
Expand All @@ -161,6 +169,23 @@ function getLocaleInfo(lang: string) {
}
}

/**
* Returns the direction of the passed locale.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo
*/
function getLocaleDir(locale: Intl.Locale): 'ltr' | 'rtl' {
if ('textInfo' in locale) {
// @ts-expect-error - `textInfo` is not typed but is available in v8 based environments.
return locale.textInfo.direction;
} else if ('getTextInfo' in locale) {
// @ts-expect-error - `getTextInfo` is not typed but is available in some non-v8 based environments.
return locale.getTextInfo().direction;
}
// Firefox does not support `textInfo` or `getTextInfo` yet so we fallback to a well-known list
// of right-to-left languages.
return wellKnownRTL.includes(locale.language) ? 'rtl' : 'ltr';
}

/**
* Get the string for the passed language from a dictionary object.
*
Expand Down

0 comments on commit 0b8a843

Please sign in to comment.