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;
}
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 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());
return (
<CustomizationRootLayout forcedTheme={forcedTheme} customization={context.customization}>
<CustomizationRootLayout forcedTheme={forcedTheme} context={context}>
<SiteLayout
context={context}
forcedTheme={forcedTheme}
@@ -19,7 +19,7 @@ export default async function SiteStaticLayout({
const withTracking = shouldTrackEvents();
return (
<CustomizationRootLayout customization={context.customization}>
<CustomizationRootLayout context={context}>
<SiteLayout
context={context}
withTracking={withTracking}
@@ -72,7 +72,7 @@ function DescriptionForPageContentToolCall(props: {
}) {
const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization);
const language = getSpaceLanguage(context);
return (
<p>
@@ -102,7 +102,7 @@ function DescriptionForMCPToolCall(props: {
}) {
const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization);
const language = getSpaceLanguage(context);
return (
<p>
@@ -130,7 +130,7 @@ async function DescriptionForSearchToolCall(props: {
}) {
const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization);
const language = getSpaceLanguage(context);
// Resolve all hrefs for search results in parallel
const searchResultsWithHrefs = await Promise.all(
@@ -241,7 +241,7 @@ function DescriptionForGetPagesToolCall(props: {
}) {
const { toolCall, context } = props;
const language = getSpaceLanguage(context.customization);
const language = getSpaceLanguage(context);
return (
<p>
@@ -20,10 +20,7 @@ export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
: null;
const { contentContext } = context;
const language =
contentContext && 'customization' in contentContext
? getSpaceLanguage(contentContext.customization)
: languages.en;
const language = contentContext ? getSpaceLanguage(contentContext) : languages.en;
if (!contentContext || !resolved) {
return (
@@ -11,7 +11,7 @@ import { Heading } from '../Heading';
import './scalar.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 {
AnyOpenAPIOperationsBlock,
@@ -30,10 +30,7 @@ export function getOpenAPIContext(args: {
const { props, specUrl, context } = args;
const { block } = props;
const customization = context && 'customization' in context ? context.customization : null;
const customizationLocale = customization
? getCustomizationLocale(customization)
: DEFAULT_LOCALE;
const customizationLocale = context ? getSpaceLocale(context) : DEFAULT_LOCALE;
const locale = checkIsValidLocale(customizationLocale) ? customizationLocale : DEFAULT_LOCALE;
return {
@@ -21,7 +21,7 @@ export async function EmbeddableRootLayout({
children,
}: React.PropsWithChildren<EmbeddableRootLayoutProps>) {
return (
<CustomizationRootLayout customization={context.customization}>
<CustomizationRootLayout context={context}>
<SiteLayoutClientContexts
forcedTheme={context.customization.themes.default}
externalLinksTarget={context.customization.externalLinks.target}
@@ -138,7 +138,7 @@ export function Header(props: {
);
})}
<HeaderLinkMore
label={t(getSpaceLanguage(customization), 'more')}
label={t(getSpaceLanguage(context), 'more')}
links={customization.header.links}
context={context}
/>
@@ -57,7 +57,7 @@ export async function PDFPage(props: {
const customization =
'customization' in baseContext ? baseContext.customization : defaultCustomization();
const language = getSpaceLanguage(customization);
const language = getSpaceLanguage(baseContext);
// Compute the pages to render
const { pages, total } = selectPages(baseContext.revision.pages, pdfParams);
@@ -149,8 +149,7 @@ export async function PDFPage(props: {
trademark={
customization.trademark.enabled ? (
<TrademarkLink
space={context.space}
customization={customization}
context={context}
placement={SiteInsightsTrademarkPlacement.Pdf}
/>
) : null
@@ -1,6 +1,5 @@
import { CustomizationRootLayout } from '@/components/RootLayout';
import type { GitBookSiteContext, GitBookSpaceContext } from '@/lib/context';
import { defaultCustomization } from '@/lib/utils';
import { CustomizationThemeMode } from '@gitbook/api';
/**
@@ -13,12 +12,7 @@ export async function PDFRootLayout(props: {
const { context, children } = props;
return (
<CustomizationRootLayout
customization={
'customization' in context ? context.customization : defaultCustomization()
}
forcedTheme={CustomizationThemeMode.Light}
>
<CustomizationRootLayout context={context} forcedTheme={CustomizationThemeMode.Light}>
{children}
</CustomizationRootLayout>
);
@@ -128,7 +128,7 @@ export function PageAside(props: {
function PageAsideHeader(props: { context: GitBookSiteContext }) {
const { context } = props;
const language = getSpaceLanguage(context.customization);
const language = getSpaceLanguage(context);
return (
<div
@@ -42,7 +42,7 @@ export function PageBody(props: {
: false;
const pageWidthWide = page.layout.width === 'wide';
const siteWidthWide = pageWidthWide || contentFullWidth;
const language = getSpaceLanguage(customization);
const language = getSpaceLanguage(context);
const updatedAt = page.updatedAt ?? page.createdAt;
return (
@@ -17,9 +17,9 @@ export async function PageFooterNavigation(props: {
page: RevisionPageDocument;
}) {
const { context, page } = props;
const { customization, revision, linker } = context;
const { revision, linker } = context;
const { previous, next } = resolvePrevNextPages(revision.pages, page);
const language = getSpaceLanguage(customization);
const language = getSpaceLanguage(context);
const previousHref = previous
? linker.toPathForPage({ pages: revision.pages, page: previous })
: '';
@@ -31,7 +31,9 @@ import { RootLayoutClientContexts } from './RootLayoutClientContexts';
import '@gitbook/icons/style.css';
import './globals.css';
import type { GitBookAnyContext } from '@/lib/context';
import { GITBOOK_FONTS_URL, GITBOOK_ICONS_TOKEN, GITBOOK_ICONS_URL } from '@/lib/env';
import { defaultCustomization } from '@/lib/utils';
import { AnnouncementDismissedScript } from '../Announcement';
function preloadFont(fontData: FontData) {
@@ -56,12 +58,14 @@ function preloadFont(fontData: FontData) {
*/
export async function CustomizationRootLayout(props: {
forcedTheme?: CustomizationThemeMode | null;
customization: SiteCustomizationSettings;
context: GitBookAnyContext;
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 mixColor = getTintMixColor(customization.styling.primaryColor, tintColor);
const sidebarStyles = getSidebarStyles(customization);
@@ -141,7 +145,6 @@ export async function CustomizationRootLayout(props: {
)
};
--header-link: ${hexToRgb(
// @ts-expect-error
customization.header.linkColor?.light ??
colorContrast(
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-link: ${hexToRgb(
// @ts-expect-error
customization.header.linkColor?.dark ??
colorContrast(
tintColor?.dark ?? customization.styling.primaryColor.dark
@@ -337,7 +339,6 @@ function generateColorVariable(
return Object.entries(shades)
.map(([key, value]) => {
const rgbValue = hexToRgb(value); // Check the original hex value
// @ts-expect-error
const contrastValue = withContrast ? hexToRgb(colorContrast(value)) : undefined; // Add contrast if needed
return `--${name}-${key}: ${rgbValue}; ${
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
}) {
const { innerHeader, context, header } = props;
const { space, customization, revision } = context;
const { customization, revision } = context;
const pages = await encodeClientTableOfContents(context, revision.pages, revision.pages);
@@ -115,8 +115,7 @@ export async function TableOfContents(props: {
/>
{customization.trademark.enabled ? (
<Trademark
space={space}
customization={customization}
context={context}
placement={SiteInsightsTrademarkPlacement.Sidebar}
/>
) : null}
@@ -1,21 +1,17 @@
import type {
SiteCustomizationSettings,
SiteInsightsTrademarkPlacement,
Space,
} from '@gitbook/api';
import type { SiteInsightsTrademarkPlacement } from '@gitbook/api';
import { Icon } from '@gitbook/icons';
import { getSpaceLanguage, t } from '@/intl/server';
import { tcls } from '@/lib/tailwind';
import type { GitBookSpaceContext } from '@/lib/context';
import { Link } from '../primitives';
/**
* Trademark link to the GitBook.
*/
export function Trademark(props: {
space: Space;
customization: SiteCustomizationSettings;
context: GitBookSpaceContext;
placement: SiteInsightsTrademarkPlacement;
}) {
return (
@@ -71,12 +67,12 @@ export function Trademark(props: {
* Trademark link to the GitBook.
*/
export function TrademarkLink(props: {
space: Space;
customization: SiteCustomizationSettings;
context: GitBookSpaceContext;
placement: SiteInsightsTrademarkPlacement;
}) {
const { space, customization, placement } = props;
const language = getSpaceLanguage(customization);
const { context, placement } = props;
const { space } = context;
const language = getSpaceLanguage(context);
const url = new URL('https://www.gitbook.com');
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';
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 {
return customization.internationalization.locale;
export function getSpaceLocale(context: GitBookAnyContext): TranslationLocale {
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.
*/
export function getSpaceLanguage(customization: SiteCustomizationSettings): TranslationLanguage {
export function getSpaceLanguage(context: GitBookAnyContext): TranslationLanguage {
const fallback = languages[DEFAULT_LOCALE];
const locale = getCustomizationLocale(customization);
const locale = getSpaceLocale(context);
let language = fallback;
// @ts-ignore
@@ -33,3 +52,7 @@ export function getSpaceLanguage(customization: SiteCustomizationSettings): Tran
...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 { en } from './en';
import { es } from './es';
@@ -14,11 +12,7 @@ import { zh } from './zh';
export * from './types';
export const languages: {
[key: string]: TranslationLanguage;
} & {
[locale in CustomizationLocale]: TranslationLanguage;
} = {
export const languages = {
de,
en,
fr,
@@ -29,4 +23,6 @@ export const languages: {
no,
'pt-br': pt_br,
ru,
} satisfies {
[key: string]: TranslationLanguage;
};