Skip to content

Commit

Permalink
Fix various Expressive Code translation issues (#1708)
Browse files Browse the repository at this point in the history
Co-authored-by: Chris Swithinbank <357379+delucis@users.noreply.github.com>
Co-authored-by: Hippo <6137925+hippotastic@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 5, 2024
1 parent b26238f commit a72cb96
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/wet-lemons-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Fixes translation issues with Expressive Code when using a default language other than English
117 changes: 117 additions & 0 deletions packages/starlight/__tests__/i18n/translations-ec.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { pluginFramesTexts } from 'astro-expressive-code';
import { afterEach, expect, test, vi } from 'vitest';
import { addTranslations } from '../../integrations/expressive-code/translations';
import { StarlightConfigSchema, type StarlightUserConfig } from '../../utils/user-config';

vi.mock('astro-expressive-code', async () => {
const mod = await vi.importActual<typeof import('astro-expressive-code')>(
'astro-expressive-code'
);
return {
...mod,
pluginFramesTexts: {
...mod.pluginFramesTexts,
overrideTexts: vi.fn(),
},
};
});

afterEach(() => {
vi.clearAllMocks();
});

test('adds default english translations with no i18n config', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations(undefined);

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['en']);
});

test('adds translations in a monolingual site with english as root locale', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations({
root: { label: 'English', lang: 'en' },
});

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['en']);
});

test('adds translations in a monolingual site with french as root locale', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations({
root: { label: 'Français', lang: 'fr' },
});

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['fr']);
});

test('add translations in a multilingual site with english as root locale', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations({
root: { label: 'English', lang: 'en' },
fr: { label: 'French' },
});

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['en', 'fr']);
});

test('add translations in a multilingual site with french as root locale', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations({
root: { label: 'French', lang: 'fr' },
ru: { label: 'Русский', lang: 'ru' },
});

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['fr', 'ru']);
});

test('add translations in a multilingual site with english as default locale', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations(
{
en: { label: 'English', lang: 'en' },
fr: { label: 'French' },
},
'en'
);

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['en', 'fr']);
});

test('add translations in a multilingual site with french as default locale', async () => {
const [config, useTranslations] = getStarlightConfigAndUseTranslations(
{
fr: { label: 'French', lang: 'fr' },
ru: { label: 'Русский', lang: 'ru' },
},
'fr'
);

addTranslations(config, useTranslations);

expect(getExpressiveCodeOverridenLanguages()).toEqual(['fr', 'ru']);
});

function getStarlightConfigAndUseTranslations(
locales: StarlightUserConfig['locales'],
defaultLocale?: StarlightUserConfig['defaultLocale']
) {
return [
StarlightConfigSchema.parse({
title: 'Expressive Code Translations Test',
locales,
defaultLocale,
}),
vi.fn().mockReturnValue(() => 'test UI string'),
] as const;
}

function getExpressiveCodeOverridenLanguages() {
return [...new Set(vi.mocked(pluginFramesTexts.overrideTexts).mock.calls.map(([lang]) => lang))];
}
4 changes: 2 additions & 2 deletions packages/starlight/integrations/expressive-code/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export function getStarlightEcConfigPreprocessor({
return (input): AstroExpressiveCodeOptions => {
const astroConfig = input.astroConfig;
const ecConfig = input.ecConfig as StarlightExpressiveCodeOptions;
const { locales } = starlightConfig;

const {
themes: themesInput,
Expand Down Expand Up @@ -111,7 +110,7 @@ export function getStarlightEcConfigPreprocessor({
});

// Add Expressive Code UI translations (if any) for all defined locales
if (useTranslations) addTranslations(locales, useTranslations);
if (useTranslations) addTranslations(starlightConfig, useTranslations);

return {
themes,
Expand All @@ -124,6 +123,7 @@ export function getStarlightEcConfigPreprocessor({
}
return theme;
},
defaultLocale: starlightConfig.defaultLocale?.lang ?? starlightConfig.defaultLocale?.locale,
themeCssSelector: (theme, { styleVariants }) => {
// If one dark and one light theme are available, and the user has not disabled it,
// generate theme CSS selectors compatible with Starlight's dark mode switch
Expand Down
45 changes: 28 additions & 17 deletions packages/starlight/integrations/expressive-code/translations.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
import { pluginFramesTexts } from 'astro-expressive-code';
import type { StarlightConfig } from '../../types';
import type { createTranslationSystemFromFs } from '../../utils/translations-fs';
import { localeToLang } from '../shared/localeToLang';

export function addTranslations(
locales: StarlightConfig['locales'],
config: StarlightConfig,
useTranslations: ReturnType<typeof createTranslationSystemFromFs>
) {
for (const locale in locales) {
const lang = locales[locale]?.lang;
if (!lang) continue;

const t = useTranslations(locale);
const translationKeys = [
'expressiveCode.copyButtonCopied',
'expressiveCode.copyButtonTooltip',
'expressiveCode.terminalWindowFallbackTitle',
] as const;
translationKeys.forEach((key) => {
const translation = t(key);
if (!translation) return;
const ecId = key.replace(/^expressiveCode\./, '');
pluginFramesTexts.overrideTexts(lang, { [ecId]: translation });
});
addTranslationsForLocale(config.defaultLocale.locale, config, useTranslations);
if (config.isMultilingual) {
for (const locale in config.locales) {
if (locale === config.defaultLocale.locale || locale === 'root') continue;
addTranslationsForLocale(locale, config, useTranslations);
}
}
}

function addTranslationsForLocale(
locale: string | undefined,
config: StarlightConfig,
useTranslations: ReturnType<typeof createTranslationSystemFromFs>
) {
const lang = localeToLang(config, locale);
const t = useTranslations(locale);
const translationKeys = [
'expressiveCode.copyButtonCopied',
'expressiveCode.copyButtonTooltip',
'expressiveCode.terminalWindowFallbackTitle',
] as const;
translationKeys.forEach((key) => {
const translation = t(key);
if (!translation) return;
const ecId = key.replace(/^expressiveCode\./, '');
pluginFramesTexts.overrideTexts(lang, { [ecId]: translation });
});
}
11 changes: 11 additions & 0 deletions packages/starlight/integrations/shared/localeToLang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { StarlightConfig } from '../../types';

/**
* Get the BCP-47 language tag for the given locale.
* @param locale Locale string or `undefined` for the root locale.
*/
export function localeToLang(config: StarlightConfig, locale: string | undefined): string {
const lang = locale ? config.locales?.[locale]?.lang : config.locales?.root?.lang;
const defaultLang = config.defaultLocale?.lang || config.defaultLocale?.locale;
return lang || defaultLang || 'en';
}

0 comments on commit a72cb96

Please sign in to comment.