Use space.language as source of truth (#3606)

This commit is contained in:
Samy Pessé
2025-08-27 18:27:29 +02:00
committed by GitHub
parent 1edc5d622a
commit 193d591e9f
20 changed files with 83 additions and 68 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/colors": patch
---
Fix return type for `colorContrast`
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Use space language as source of truth for UI locale
+5 -2
View File
@@ -419,7 +419,10 @@ export function dpsContrast(a: RGBColor, b: RGBColor) {
return contrast < 7.5 ? 0 : contrast; return contrast < 7.5 ? 0 : contrast;
} }
export function colorContrast(background: string, foreground: string[] = [LIGHT_BASE, DARK_BASE]) { export function colorContrast(
background: string,
foreground: string[] = [LIGHT_BASE, DARK_BASE]
): string {
const bg = hexToRgbArray(background); const bg = hexToRgbArray(background);
const best: { color?: RGBColor; contrast: number } = { const best: { color?: RGBColor; contrast: number } = {
@@ -436,5 +439,5 @@ export function colorContrast(background: string, foreground: string[] = [LIGHT_
} }
} }
return best.color ? rgbArrayToHex(best.color) : foreground[0]; return best.color ? rgbArrayToHex(best.color) : foreground[0] || LIGHT_BASE;
} }
@@ -22,7 +22,7 @@ export default async function SiteDynamicLayout({
const withTracking = shouldTrackEvents(await headers()); const withTracking = shouldTrackEvents(await headers());
return ( return (
<CustomizationRootLayout forcedTheme={forcedTheme} customization={context.customization}> <CustomizationRootLayout forcedTheme={forcedTheme} context={context}>
<SiteLayout <SiteLayout
context={context} context={context}
forcedTheme={forcedTheme} forcedTheme={forcedTheme}
@@ -19,7 +19,7 @@ export default async function SiteStaticLayout({
const withTracking = shouldTrackEvents(); const withTracking = shouldTrackEvents();
return ( return (
<CustomizationRootLayout customization={context.customization}> <CustomizationRootLayout context={context}>
<SiteLayout <SiteLayout
context={context} context={context}
withTracking={withTracking} withTracking={withTracking}
@@ -72,7 +72,7 @@ function DescriptionForPageContentToolCall(props: {
}) { }) {
const { toolCall, context } = props; const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization); const language = getSpaceLanguage(context);
return ( return (
<p> <p>
@@ -102,7 +102,7 @@ function DescriptionForMCPToolCall(props: {
}) { }) {
const { toolCall, context } = props; const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization); const language = getSpaceLanguage(context);
return ( return (
<p> <p>
@@ -130,7 +130,7 @@ async function DescriptionForSearchToolCall(props: {
}) { }) {
const { toolCall, context } = props; const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization); const language = getSpaceLanguage(context);
// Resolve all hrefs for search results in parallel // Resolve all hrefs for search results in parallel
const searchResultsWithHrefs = await Promise.all( const searchResultsWithHrefs = await Promise.all(
@@ -241,7 +241,7 @@ function DescriptionForGetPagesToolCall(props: {
}) { }) {
const { toolCall, context } = props; const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization); const language = getSpaceLanguage(context);
return ( return (
<p> <p>
@@ -20,10 +20,7 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
: null; : null;
const { contentContext } = context; const { contentContext } = context;
const language = const language = contentContext ? getSpaceLanguage(contentContext) : languages.en;
contentContext && 'customization' in contentContext
? getSpaceLanguage(contentContext.customization)
: languages.en;
if (!contentContext || !resolved) { if (!contentContext || !resolved) {
return ( return (
@@ -11,7 +11,7 @@ import { Heading } from '../Heading';
import './scalar.css'; import './scalar.css';
import './style.css'; import './style.css';
import { DEFAULT_LOCALE, getCustomizationLocale } from '@/intl/server'; import { DEFAULT_LOCALE, getSpaceLocale } from '@/intl/server';
import type { GitBookAnyContext } from '@/lib/context'; import type { GitBookAnyContext } from '@/lib/context';
import type { import type {
AnyOpenAPIOperationsBlock, AnyOpenAPIOperationsBlock,
@@ -30,10 +30,7 @@ export function getOpenAPIContext(args: {
const { props, specUrl, context } = args; const { props, specUrl, context } = args;
const { block } = props; const { block } = props;
const customization = context && 'customization' in context ? context.customization : null; const customizationLocale = context ? getSpaceLocale(context) : DEFAULT_LOCALE;
const customizationLocale = customization
? getCustomizationLocale(customization)
: DEFAULT_LOCALE;
const locale = checkIsValidLocale(customizationLocale) ? customizationLocale : DEFAULT_LOCALE; const locale = checkIsValidLocale(customizationLocale) ? customizationLocale : DEFAULT_LOCALE;
return { return {
@@ -21,7 +21,7 @@ export async function EmbeddableRootLayout({
children, children,
}: React.PropsWithChildren<EmbeddableRootLayoutProps>) { }: React.PropsWithChildren<EmbeddableRootLayoutProps>) {
return ( return (
<CustomizationRootLayout customization={context.customization}> <CustomizationRootLayout context={context}>
<SiteLayoutClientContexts <SiteLayoutClientContexts
forcedTheme={context.customization.themes.default} forcedTheme={context.customization.themes.default}
externalLinksTarget={context.customization.externalLinks.target} externalLinksTarget={context.customization.externalLinks.target}
@@ -138,7 +138,7 @@ export function Header(props: {
); );
})} })}
<HeaderLinkMore <HeaderLinkMore
label={t(getSpaceLanguage(customization), 'more')} label={t(getSpaceLanguage(context), 'more')}
links={customization.header.links} links={customization.header.links}
context={context} context={context}
/> />
@@ -57,7 +57,7 @@ export async function PDFPage(props: {
const customization = const customization =
'customization' in baseContext ? baseContext.customization : defaultCustomization(); 'customization' in baseContext ? baseContext.customization : defaultCustomization();
const language = getSpaceLanguage(customization); const language = getSpaceLanguage(baseContext);
// Compute the pages to render // Compute the pages to render
const { pages, total } = selectPages(baseContext.revision.pages, pdfParams); const { pages, total } = selectPages(baseContext.revision.pages, pdfParams);
@@ -149,8 +149,7 @@ export async function PDFPage(props: {
trademark={ trademark={
customization.trademark.enabled ? ( customization.trademark.enabled ? (
<TrademarkLink <TrademarkLink
space={context.space} context={context}
customization={customization}
placement={SiteInsightsTrademarkPlacement.Pdf} placement={SiteInsightsTrademarkPlacement.Pdf}
/> />
) : null ) : null
@@ -1,6 +1,5 @@
import { CustomizationRootLayout } from '@/components/RootLayout'; import { CustomizationRootLayout } from '@/components/RootLayout';
import type { GitBookSiteContext, GitBookSpaceContext } from '@/lib/context'; import type { GitBookSiteContext, GitBookSpaceContext } from '@/lib/context';
import { defaultCustomization } from '@/lib/utils';
import { CustomizationThemeMode } from '@gitbook/api'; import { CustomizationThemeMode } from '@gitbook/api';
/** /**
@@ -13,12 +12,7 @@ export async function PDFRootLayout(props: {
const { context, children } = props; const { context, children } = props;
return ( return (
<CustomizationRootLayout <CustomizationRootLayout context={context} forcedTheme={CustomizationThemeMode.Light}>
customization={
'customization' in context ? context.customization : defaultCustomization()
}
forcedTheme={CustomizationThemeMode.Light}
>
{children} {children}
</CustomizationRootLayout> </CustomizationRootLayout>
); );
@@ -128,7 +128,7 @@ export function PageAside(props: {
function PageAsideHeader(props: { context: GitBookSiteContext }) { function PageAsideHeader(props: { context: GitBookSiteContext }) {
const { context } = props; const { context } = props;
const language = getSpaceLanguage(context.customization); const language = getSpaceLanguage(context);
return ( return (
<div <div
@@ -42,7 +42,7 @@ export function PageBody(props: {
: false; : false;
const pageWidthWide = page.layout.width === 'wide'; const pageWidthWide = page.layout.width === 'wide';
const siteWidthWide = pageWidthWide || contentFullWidth; const siteWidthWide = pageWidthWide || contentFullWidth;
const language = getSpaceLanguage(customization); const language = getSpaceLanguage(context);
const updatedAt = page.updatedAt ?? page.createdAt; const updatedAt = page.updatedAt ?? page.createdAt;
return ( return (
@@ -17,9 +17,9 @@ export async function PageFooterNavigation(props: {
page: RevisionPageDocument; page: RevisionPageDocument;
}) { }) {
const { context, page } = props; const { context, page } = props;
const { customization, revision, linker } = context; const { revision, linker } = context;
const { previous, next } = resolvePrevNextPages(revision.pages, page); const { previous, next } = resolvePrevNextPages(revision.pages, page);
const language = getSpaceLanguage(customization); const language = getSpaceLanguage(context);
const previousHref = previous const previousHref = previous
? linker.toPathForPage({ pages: revision.pages, page: previous }) ? linker.toPathForPage({ pages: revision.pages, page: previous })
: ''; : '';
@@ -31,7 +31,9 @@ import { RootLayoutClientContexts } from './RootLayoutClientContexts';
import '@gitbook/icons/style.css'; import '@gitbook/icons/style.css';
import './globals.css'; import './globals.css';
import type { GitBookAnyContext } from '@/lib/context';
import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@/lib/env'; import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@/lib/env';
import { defaultCustomization } from '@/lib/utils';
import { AnnouncementDismissedScript } from '../Announcement'; import { AnnouncementDismissedScript } from '../Announcement';
function preloadFont(fontData: FontData) { function preloadFont(fontData: FontData) {
@@ -56,12 +58,14 @@ function preloadFont(fontData: FontData) {
*/ */
export async function CustomizationRootLayout(props: { export async function CustomizationRootLayout(props: {
forcedTheme?: CustomizationThemeMode | null; forcedTheme?: CustomizationThemeMode | null;
customization: SiteCustomizationSettings; context: GitBookAnyContext;
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const { customization, forcedTheme, children } = props; const { context, forcedTheme, children } = props;
const customization =
'customization' in context ? context.customization : defaultCustomization();
const language = getSpaceLanguage(customization); const language = getSpaceLanguage(context);
const tintColor = getTintColor(customization); const tintColor = getTintColor(customization);
const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor); const mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
const sidebarStyles = getSidebarStyles(customization); const sidebarStyles = getSidebarStyles(customization);
@@ -141,7 +145,6 @@ export async function CustomizationRootLayout(props: {
) )
}; };
--header-link: ${hexToRgb( --header-link: ${hexToRgb(
// @ts-expect-error
customization.header.linkColor?.light ?? customization.header.linkColor?.light ??
colorContrast( colorContrast(
tintColor?.light ?? customization.styling.primaryColor.light tintColor?.light ?? customization.styling.primaryColor.light
@@ -161,7 +164,6 @@ export async function CustomizationRootLayout(props: {
--header-background: ${hexToRgb(customization.header.backgroundColor?.dark ?? tintColor?.dark ?? customization.styling.primaryColor.dark)}; --header-background: ${hexToRgb(customization.header.backgroundColor?.dark ?? tintColor?.dark ?? customization.styling.primaryColor.dark)};
--header-link: ${hexToRgb( --header-link: ${hexToRgb(
// @ts-expect-error
customization.header.linkColor?.dark ?? customization.header.linkColor?.dark ??
colorContrast( colorContrast(
tintColor?.dark ?? customization.styling.primaryColor.dark tintColor?.dark ?? customization.styling.primaryColor.dark
@@ -337,7 +339,6 @@ function generateColorVariable(
return Object.entries(shades) return Object.entries(shades)
.map(([key, value]) => { .map(([key, value]) => {
const rgbValue = hexToRgb(value); // Check the original hex value const rgbValue = hexToRgb(value); // Check the original hex value
// @ts-expect-error
const contrastValue = withContrast ? hexToRgb(colorContrast(value)) : undefined; // Add contrast if needed const contrastValue = withContrast ? hexToRgb(colorContrast(value)) : undefined; // Add contrast if needed
return `--${name}-${key}: ${rgbValue}; ${ return `--${name}-${key}: ${rgbValue}; ${
contrastValue ? `--contrast-${name}-${key}: ${contrastValue};` : '' contrastValue ? `--contrast-${name}-${key}: ${contrastValue};` : ''
@@ -16,7 +16,7 @@ export async function TableOfContents(props: {
innerHeader?: React.ReactNode; // Displayed outside the scrollable TOC, directly above the page list innerHeader?: React.ReactNode; // Displayed outside the scrollable TOC, directly above the page list
}) { }) {
const { innerHeader, context, header } = props; const { innerHeader, context, header } = props;
const { space, customization, revision } = context; const { customization, revision } = context;
const pages = await encodeClientTableOfContents(context, revision.pages, revision.pages); const pages = await encodeClientTableOfContents(context, revision.pages, revision.pages);
@@ -115,8 +115,7 @@ export async function TableOfContents(props: {
/> />
{customization.trademark.enabled ? ( {customization.trademark.enabled ? (
<Trademark <Trademark
space={space} context={context}
customization={customization}
placement={SiteInsightsTrademarkPlacement.Sidebar} placement={SiteInsightsTrademarkPlacement.Sidebar}
/> />
) : null} ) : null}
@@ -1,21 +1,17 @@
import type { import type { SiteInsightsTrademarkPlacement } from '@gitbook/api';
SiteCustomizationSettings,
SiteInsightsTrademarkPlacement,
Space,
} from '@gitbook/api';
import { Icon } from '@gitbook/icons'; import { Icon } from '@gitbook/icons';
import { getSpaceLanguage, t } from '@/intl/server'; import { getSpaceLanguage, t } from '@/intl/server';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import type { GitBookSpaceContext } from '@/lib/context';
import { Link } from '../primitives'; import { Link } from '../primitives';
/** /**
* Trademark link to the GitBook. * Trademark link to the GitBook.
*/ */
export function Trademark(props: { export function Trademark(props: {
space: Space; context: GitBookSpaceContext;
customization: SiteCustomizationSettings;
placement: SiteInsightsTrademarkPlacement; placement: SiteInsightsTrademarkPlacement;
}) { }) {
return ( return (
@@ -71,12 +67,12 @@ export function Trademark(props: {
* Trademark link to the GitBook. * Trademark link to the GitBook.
*/ */
export function TrademarkLink(props: { export function TrademarkLink(props: {
space: Space; context: GitBookSpaceContext;
customization: SiteCustomizationSettings;
placement: SiteInsightsTrademarkPlacement; placement: SiteInsightsTrademarkPlacement;
}) { }) {
const { space, customization, placement } = props; const { context, placement } = props;
const language = getSpaceLanguage(customization); const { space } = context;
const language = getSpaceLanguage(context);
const url = new URL('https://www.gitbook.com'); const url = new URL('https://www.gitbook.com');
url.searchParams.set('utm_source', 'content'); url.searchParams.set('utm_source', 'content');
+31 -8
View File
@@ -1,25 +1,44 @@
import type { SiteCustomizationSettings } from '@gitbook/api'; import type { GitBookAnyContext } from '@/lib/context';
import { type TranslationLanguage, languages } from './translations'; import { type TranslationLanguage, languages } from './translations';
export * from './translate'; export * from './translate';
export const DEFAULT_LOCALE = 'en'; type TranslationLocale = keyof typeof languages;
export const DEFAULT_LOCALE = 'en' satisfies TranslationLocale;
/** /**
* Get the locale of the customization. * Get the locale to use for a space.
*/ */
export function getCustomizationLocale(customization: SiteCustomizationSettings): string { export function getSpaceLocale(context: GitBookAnyContext): TranslationLocale {
return customization.internationalization.locale; const { space } = context;
const customization = 'site' in context ? context.customization : null;
// If the language is configured in the space, use it in priority
if (space.language) {
if (checkIsValidLocale(space.language)) {
return space.language;
}
return DEFAULT_LOCALE;
}
// Otherwise fallback to the deprecated customization settings
if (customization) {
return checkIsValidLocale(customization.internationalization.locale)
? customization.internationalization.locale
: DEFAULT_LOCALE;
}
return DEFAULT_LOCALE;
} }
/** /**
* Create the translation context for a space to use in the server components. * Create the translation context for a space to use in the server components.
*/ */
export function getSpaceLanguage(customization: SiteCustomizationSettings): TranslationLanguage { export function getSpaceLanguage(context: GitBookAnyContext): TranslationLanguage {
const fallback = languages[DEFAULT_LOCALE]; const fallback = languages[DEFAULT_LOCALE];
const locale = getCustomizationLocale(customization); const locale = getSpaceLocale(context);
let language = fallback; let language = fallback;
// @ts-ignore // @ts-ignore
@@ -33,3 +52,7 @@ export function getSpaceLanguage(customization: SiteCustomizationSettings): Tran
...language, ...language,
}; };
} }
function checkIsValidLocale(locale: string): locale is TranslationLocale {
return locale in languages;
}
@@ -1,5 +1,3 @@
import type { CustomizationLocale } from '@gitbook/api';
import { de } from './de'; import { de } from './de';
import { en } from './en'; import { en } from './en';
import { es } from './es'; import { es } from './es';
@@ -14,11 +12,7 @@ import { zh } from './zh';
export * from './types'; export * from './types';
export const languages: { export const languages = {
[key: string]: TranslationLanguage;
} & {
[locale in CustomizationLocale]: TranslationLanguage;
} = {
de, de,
en, en,
fr, fr,
@@ -29,4 +23,6 @@ export const languages: {
no, no,
'pt-br': pt_br, 'pt-br': pt_br,
ru, ru,
} satisfies {
[key: string]: TranslationLanguage;
}; };