mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-23 11:03:39 +00:00
Start translations
This commit is contained in:
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import { CONTAINER_MAX_WIDTH_NORMAL, CONTAINER_PADDING } from '../layout';
|
||||
|
||||
export function Footer(props: { space: Space; asFullWidth: boolean }) {
|
||||
const { asFullWidth } = props;
|
||||
const { space, asFullWidth } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -31,7 +31,7 @@ export function Footer(props: { space: Space; asFullWidth: boolean }) {
|
||||
<div className={tcls('flex-1')} />
|
||||
<div>
|
||||
<React.Suspense fallback={null}>
|
||||
<ThemeToggler />
|
||||
<ThemeToggler space={space} />
|
||||
</React.Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Link from 'next/link';
|
||||
import { Suspense } from 'react';
|
||||
import { SearchButton } from '../Search';
|
||||
import { CONTAINER_MAX_WIDTH_NORMAL, CONTAINER_PADDING } from '@/components/layout';
|
||||
import { t } from '@/lib/intl';
|
||||
|
||||
/**
|
||||
* Render the header for the space.
|
||||
@@ -50,7 +51,7 @@ export function Header(props: { space: Space; asFullWidth: boolean }) {
|
||||
</Link>
|
||||
<div>
|
||||
<Suspense fallback={null}>
|
||||
<SearchButton />
|
||||
<SearchButton>{t({ space }, 'search')}</SearchButton>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { getDocumentSections } from '@/lib/document';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { RevisionPageDocument } from '@gitbook/api';
|
||||
import { RevisionPageDocument, Space } from '@gitbook/api';
|
||||
import React from 'react';
|
||||
import { ScrollSectionsList } from './ScrollSectionsList';
|
||||
import { t } from '@/lib/intl';
|
||||
|
||||
/**
|
||||
* Aside listing the headings in the document.
|
||||
*/
|
||||
export function PageAside(props: { page: RevisionPageDocument; document: any }) {
|
||||
const { document } = props;
|
||||
export function PageAside(props: { space: Space; page: RevisionPageDocument; document: any }) {
|
||||
const { space, document } = props;
|
||||
const sections = getDocumentSections(document);
|
||||
|
||||
return (
|
||||
@@ -29,7 +30,7 @@ export function PageAside(props: { page: RevisionPageDocument; document: any })
|
||||
{sections.length > 0 ? (
|
||||
<>
|
||||
<div className={tcls('text-sm', 'text-slate-500', 'font-semibold', 'pb-3')}>
|
||||
On this page
|
||||
{t({ space }, 'on_this_page')}
|
||||
</div>
|
||||
<div className={tcls('overflow-auto', 'flex-1')}>
|
||||
<React.Suspense fallback={null}>
|
||||
|
||||
@@ -7,8 +7,8 @@ import { IconSearch } from '@/components/icons/IconSearch';
|
||||
/**
|
||||
* Button to open the search modal.
|
||||
*/
|
||||
export function SearchButton(props: { style?: ClassValue }) {
|
||||
const { style } = props;
|
||||
export function SearchButton(props: { children: React.ReactNode; style?: ClassValue }) {
|
||||
const { style, children } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@@ -41,7 +41,7 @@ export function SearchButton(props: { style?: ClassValue }) {
|
||||
)}
|
||||
>
|
||||
<IconSearch className={tcls('w-4', 'h-4', 'me-2')} />
|
||||
Search
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,9 +35,14 @@ export function SpaceContent(props: {
|
||||
asFullWidth ? null : [CONTAINER_MAX_WIDTH_NORMAL, 'mx-auto'],
|
||||
)}
|
||||
>
|
||||
<TableOfContents revision={revision} activePage={page} ancestors={ancestors} />
|
||||
<TableOfContents
|
||||
space={space}
|
||||
revision={revision}
|
||||
activePage={page}
|
||||
ancestors={ancestors}
|
||||
/>
|
||||
<PageBody space={space} revision={revision} page={page} document={document} />
|
||||
<PageAside page={page} document={document} />
|
||||
<PageAside space={space} page={page} document={document} />
|
||||
</div>
|
||||
<Footer space={space} asFullWidth={asFullWidth} />
|
||||
<React.Suspense fallback={null}>
|
||||
|
||||
@@ -2,13 +2,16 @@ import { Revision, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api'
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
import { PagesList } from './PagesList';
|
||||
import { Trademark } from './Trademark';
|
||||
import { IntlContext } from '@/lib/intl';
|
||||
|
||||
export function TableOfContents(props: {
|
||||
revision: Revision;
|
||||
activePage: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
}) {
|
||||
const { revision, activePage, ancestors } = props;
|
||||
export function TableOfContents(
|
||||
props: IntlContext & {
|
||||
revision: Revision;
|
||||
activePage: RevisionPageDocument;
|
||||
ancestors: Array<RevisionPageDocument | RevisionPageGroup>;
|
||||
},
|
||||
) {
|
||||
const { space, revision, activePage, ancestors } = props;
|
||||
|
||||
return (
|
||||
<aside
|
||||
@@ -29,7 +32,7 @@ export function TableOfContents(props: {
|
||||
<div className={tcls('flex-1', 'overflow-y-auto', 'pt-6', 'pb-14', 'pr-4')}>
|
||||
<PagesList pages={revision.pages} activePage={activePage} ancestors={ancestors} />
|
||||
</div>
|
||||
<Trademark />
|
||||
<Trademark space={space} />
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { IntlContext, t } from '@/lib/intl';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
/**
|
||||
* Link to the GitBook platform.
|
||||
*/
|
||||
export function Trademark() {
|
||||
export function Trademark(props: IntlContext) {
|
||||
return (
|
||||
<div className={tcls('absolute', 'bottom-0', 'right-0', 'left-0', 'flex', 'flex-col')}>
|
||||
<a
|
||||
@@ -22,7 +23,7 @@ export function Trademark() {
|
||||
'hover:bg-slate-100',
|
||||
)}
|
||||
>
|
||||
Powered by GitBook
|
||||
{t(props, 'powered_by_gitbook')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import React from 'react';
|
||||
import Moon from '@geist-ui/icons/moon';
|
||||
import Sun from '@geist-ui/icons/sun';
|
||||
import Monitor from '@geist-ui/icons/monitor';
|
||||
import { IntlContext, tString } from '@/lib/intl';
|
||||
|
||||
type ThemeMode = 'light' | 'system' | 'dark';
|
||||
|
||||
@@ -13,7 +14,7 @@ const LOCALSTORAGE_KEY = 'color-theme';
|
||||
/**
|
||||
* Buttons to toggle between light/system/dark modes.
|
||||
*/
|
||||
export function ThemeToggler(props: {}) {
|
||||
export function ThemeToggler(props: IntlContext) {
|
||||
const [mode, setMode] = React.useState<ThemeMode>('system');
|
||||
|
||||
const onSwitchMode = (to: ThemeMode) => {
|
||||
@@ -59,19 +60,19 @@ export function ThemeToggler(props: {}) {
|
||||
active={mode === 'light'}
|
||||
icon={Sun}
|
||||
onClick={() => onSwitchMode('light')}
|
||||
title="Switch to light theme"
|
||||
title={tString(props, 'switch_to_light_theme')}
|
||||
/>
|
||||
<ThemeButton
|
||||
active={mode === 'system'}
|
||||
icon={Monitor}
|
||||
onClick={() => onSwitchMode('system')}
|
||||
title="Switch to system theme"
|
||||
title={tString(props, 'switch_to_system_theme')}
|
||||
/>
|
||||
<ThemeButton
|
||||
active={mode === 'dark'}
|
||||
icon={Moon}
|
||||
onClick={() => onSwitchMode('dark')}
|
||||
title="Switch to dark theme"
|
||||
title={tString(props, 'switch_to_dark_theme')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import * as translations from '@/translations';
|
||||
import { Space } from '@gitbook/api';
|
||||
import React from 'react';
|
||||
|
||||
export interface IntlContext {
|
||||
space: Space;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a string.
|
||||
*/
|
||||
export function t(
|
||||
context: IntlContext,
|
||||
id: keyof typeof translations.en,
|
||||
...args: React.ReactNode[]
|
||||
): React.ReactNode {
|
||||
const locale = 'en'; // TODO
|
||||
|
||||
// fallback to english if no translation
|
||||
const string = translations[locale]?.[id] || translations.en[id];
|
||||
|
||||
// Now we are going to replace the arguments
|
||||
// but we want to return a string as long as it's possible
|
||||
// (eg. if there isn't any argument that is a ReactNode)
|
||||
const parts: React.ReactNode[] = [];
|
||||
let currentStringToReplace: string = string;
|
||||
|
||||
args.forEach((arg, i) => {
|
||||
if (typeof arg === 'string') {
|
||||
currentStringToReplace = currentStringToReplace.replace(`\${${i + 1}}`, arg);
|
||||
} else {
|
||||
const [partToPush, partToReplace] = currentStringToReplace.split(`\${${i + 1}}`);
|
||||
parts.push(<React.Fragment key={`string-${i}`}>{partToPush}</React.Fragment>);
|
||||
parts.push(<React.Fragment key={`arg-${i}`}>{arg}</React.Fragment>);
|
||||
currentStringToReplace = partToReplace;
|
||||
}
|
||||
});
|
||||
|
||||
if (!parts.length) {
|
||||
return currentStringToReplace;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{parts}
|
||||
{currentStringToReplace}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of `t` that returns a string.
|
||||
*/
|
||||
export function tString(
|
||||
context: IntlContext,
|
||||
id: keyof typeof translations.en,
|
||||
...args: React.ReactNode[]
|
||||
): string {
|
||||
const result = t(context, id, ...args);
|
||||
return reactToString(result);
|
||||
}
|
||||
|
||||
function reactToString(el: React.ReactNode): string {
|
||||
if (typeof el === 'string' || typeof el === 'number' || typeof el === 'boolean') {
|
||||
return `${el}`;
|
||||
}
|
||||
|
||||
if (el === null || el === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(el)) {
|
||||
return el.map(reactToString).join('');
|
||||
}
|
||||
|
||||
if ('props' in el) {
|
||||
return el.props.children.map(reactToString).join('');
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported type ${typeof el}`);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"powered_by_gitbook": "Powered by GitBook",
|
||||
"switch_to_dark_theme": "Switch to dark theme",
|
||||
"switch_to_light_theme": "Switch to light theme",
|
||||
"switch_to_system_theme": "Switch to system theme",
|
||||
"search": "Search",
|
||||
"on_this_page": "On this page"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import en from './en.json';
|
||||
|
||||
export { en };
|
||||
Reference in New Issue
Block a user