Compare commits

...

2 Commits

Author SHA1 Message Date
Peter White 0bd257d918 changeset 2026-10-06 16:02:05 +02:00
Peter White 8d238f76d4 Export subtree PDFs in batches of 20 with previous/next navigation 2026-10-06 16:02:05 +02:00
47 changed files with 548 additions and 214 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Export page subtrees as PDF in batches of 20 pages, with links to the previous and next batch, so large exports no longer crash.
@@ -3,6 +3,13 @@ import { describe, expect, it } from 'bun:test';
import { RevisionPageType } from '@gitbook/api'; import { RevisionPageType } from '@gitbook/api';
import { createPDFLinker } from './linker'; import { createPDFLinker } from './linker';
import { selectPages } from './selectPages';
import {
PDF_DEFAULT_LIMIT,
PDF_SUBTREE_DEFAULT_LIMIT,
getPDFSearchParams,
getPDFURLSearchParams,
} from './urls';
import { createLinker, linkerWithAbsoluteURLs } from '@/lib/links'; import { createLinker, linkerWithAbsoluteURLs } from '@/lib/links';
import { getLinkerForSiteSpace } from '@/lib/sites'; import { getLinkerForSiteSpace } from '@/lib/sites';
@@ -15,6 +22,10 @@ function createDocumentPage(id: string, path: string) {
} as any; } as any;
} }
function createDocumentPageWithChildren(id: string, children: any[]) {
return { ...createDocumentPage(id, id), pages: children };
}
function createGroupPage(id: string, path: string) { function createGroupPage(id: string, path: string) {
return { return {
id, id,
@@ -208,3 +219,183 @@ describe('createPDFLinker', () => {
).toBe('https://open-2v.gitbook.com/~space/HJ1ltuWFvsArFWtevnRn~gitbook/pdf/respond'); ).toBe('https://open-2v.gitbook.com/~space/HJ1ltuWFvsArFWtevnRn~gitbook/pdf/respond');
}); });
}); });
describe('createPDFLinker with batches', () => {
const baseLinker = createLinker({
host: 'open-2v.gitbook.com',
siteBasePath: '/~space/HJ1ltuWFvsArFWtevnRn',
spaceBasePath: '/~space/HJ1ltuWFvsArFWtevnRn',
});
const getBatchURL = (page: { id: string }) =>
page.id === 'other-batch'
? 'https://open-2v.gitbook.com/~gitbook/pdf?offset=20'
: undefined;
it('prefers the published URL for documents exported in another batch', () => {
const linker = createPDFLinker(
baseLinker,
[{ page: createDocumentPage('included', '') }],
createPublishedLinker(),
{ getBatchURL }
);
expect(
linker.toPathForPage({
pages: [
createDocumentPage('included', ''),
createDocumentPage('other-batch', 'respond'),
],
page: createDocumentPage('other-batch', 'respond'),
})
).toBe('https://docs.vectra.ai/deployment/respond');
});
it('links to the batch containing a page when there is no published URL', () => {
const linker = createPDFLinker(
baseLinker,
[{ page: createDocumentPage('included', '') }],
undefined,
{ getBatchURL }
);
expect(
linker.toPathForPage({
pages: [createDocumentPage('other-batch', 'respond')],
page: createDocumentPage('other-batch', 'respond'),
anchor: 'faq',
})
).toBe('https://open-2v.gitbook.com/~gitbook/pdf?offset=20#page-other-batch-faq');
});
it('links group pages to the batch containing them', () => {
const linker = createPDFLinker(
baseLinker,
[{ page: createDocumentPage('included', '') }],
createPublishedLinker(),
{ getBatchURL }
);
expect(
linker.toPathForPage({
pages: [createGroupPage('other-batch', 'group')],
page: createGroupPage('other-batch', 'group'),
})
).toBe('https://open-2v.gitbook.com/~gitbook/pdf?offset=20#page-other-batch');
});
});
describe('getPDFSearchParams', () => {
it('defaults to a small batch for subtree exports', () => {
expect(getPDFSearchParams(new URLSearchParams('page=abc&only=yes'))).toEqual({
page: 'abc',
only: true,
limit: PDF_SUBTREE_DEFAULT_LIMIT,
offset: 0,
});
expect(PDF_SUBTREE_DEFAULT_LIMIT).toBe(20);
});
it('keeps the larger default for whole-space and single-page exports', () => {
expect(getPDFSearchParams(new URLSearchParams('')).limit).toBe(PDF_DEFAULT_LIMIT);
expect(getPDFSearchParams(new URLSearchParams('page=abc')).limit).toBe(PDF_DEFAULT_LIMIT);
expect(PDF_DEFAULT_LIMIT).toBe(100);
});
it('parses an explicit limit and offset', () => {
const params = getPDFSearchParams(
new URLSearchParams('page=abc&only=yes&limit=5&offset=10')
);
expect(params.limit).toBe(5);
expect(params.offset).toBe(10);
});
it('ignores invalid limit and offset values', () => {
const params = getPDFSearchParams(
new URLSearchParams('page=abc&only=yes&limit=nope&offset=-3')
);
expect(params.limit).toBe(PDF_SUBTREE_DEFAULT_LIMIT);
expect(params.offset).toBe(0);
expect(getPDFSearchParams(new URLSearchParams('limit=0&offset=1.5'))).toMatchObject({
limit: PDF_DEFAULT_LIMIT,
offset: 0,
});
});
it('round-trips through getPDFURLSearchParams', () => {
const params = { page: 'abc', only: true, limit: 20, offset: 40, back: 'false' };
expect(getPDFSearchParams(getPDFURLSearchParams(params))).toEqual(params);
});
it('omits offset and limit when not set', () => {
expect(getPDFURLSearchParams({ page: 'abc', only: true }).toString()).toBe(
'page=abc&only=yes'
);
});
});
describe('selectPages', () => {
const children = Array.from({ length: 45 }, (_, index) =>
createDocumentPage(`child-${index}`, `child-${index}`)
);
const rootPages = [createDocumentPageWithChildren('root', children)];
const ids = (pages: { page: { id: string } }[] | undefined) =>
(pages ?? []).map(({ page }) => page.id);
const childIds = (from: number, to: number) => children.slice(from, to).map((page) => page.id);
it('renders the first batch of a subtree by default', () => {
const selection = selectPages(
rootPages,
getPDFSearchParams(new URLSearchParams('page=root&only=yes'))
);
expect(selection?.total).toBe(46);
expect(selection?.offset).toBe(0);
expect(ids(selection?.pages)).toEqual(['root', ...childIds(0, 19)]);
expect(selection?.allPages).toHaveLength(46);
});
it('slices the batch from offset to offset + limit', () => {
const selection = selectPages(rootPages, {
page: 'root',
only: true,
limit: 20,
offset: 20,
});
expect(selection?.offset).toBe(20);
expect(ids(selection?.pages)).toEqual(childIds(19, 39));
});
it('returns a shorter last batch', () => {
const selection = selectPages(rootPages, {
page: 'root',
only: true,
limit: 20,
offset: 40,
});
expect(ids(selection?.pages)).toEqual(childIds(39, 45));
});
it('falls back to the last batch when offset is past the end', () => {
const selection = selectPages(rootPages, {
page: 'root',
only: true,
limit: 20,
offset: 100,
});
expect(selection?.offset).toBe(26);
expect(selection?.pages).toHaveLength(20);
});
it('only selects the page itself without only', () => {
const selection = selectPages(rootPages, { page: 'root', limit: 100, offset: 0 });
expect(ids(selection?.pages)).toEqual(['root']);
expect(selection?.total).toBe(1);
});
it('returns null for an unknown page', () => {
expect(selectPages(rootPages, { page: 'missing', limit: 100, offset: 0 })).toBeNull();
});
});
+30 -73
View File
@@ -3,7 +3,6 @@ import { notFound } from 'next/navigation';
import * as React from 'react'; import * as React from 'react';
import { import {
type Revision,
type RevisionPageDocument, type RevisionPageDocument,
type RevisionPageGroup, type RevisionPageGroup,
type SiteCustomizationSettings, type SiteCustomizationSettings,
@@ -16,7 +15,8 @@ import { ImagesLoadingStatus } from './ImagesLoadingStatus';
import { createPDFLinker, getPagePDFContainerId } from './linker'; import { createPDFLinker, getPagePDFContainerId } from './linker';
import { PageControlButtons } from './PageControlButtons'; import { PageControlButtons } from './PageControlButtons';
import { PDFPrintControls } from './PDFPrintControls'; import { PDFPrintControls } from './PDFPrintControls';
import { type PDFSearchParams, getPDFSearchParams } from './urls'; import { selectPages } from './selectPages';
import { getPDFSearchParams, getPDFURLSearchParams } from './urls';
import { DocumentView } from '@/components/DocumentView'; import { DocumentView } from '@/components/DocumentView';
import { Trademark } from '@/components/TableOfContents/Trademark'; import { Trademark } from '@/components/TableOfContents/Trademark';
import type { PolymorphicComponentProp } from '@/components/utils/types'; import type { PolymorphicComponentProp } from '@/components/utils/types';
@@ -27,13 +27,10 @@ import type { GitBookSiteContext, GitBookSpaceContext } from '@/lib/context';
import { getPageDocument } from '@/lib/data'; import { getPageDocument } from '@/lib/data';
import { type GitBookLinker, createLinker, linkerWithAbsoluteURLs } from '@/lib/links'; import { type GitBookLinker, createLinker, linkerWithAbsoluteURLs } from '@/lib/links';
import './pdf.css'; import './pdf.css';
import { resolvePageId } from '@/lib/pages';
import { getLinkerForSiteSpace } from '@/lib/sites'; import { getLinkerForSiteSpace } from '@/lib/sites';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
import { defaultCustomization } from '@/lib/utils'; import { defaultCustomization } from '@/lib/utils';
const DEFAULT_LIMIT = 100;
/** /**
* Generate the metadata for the PDF page. * Generate the metadata for the PDF page.
*/ */
@@ -62,13 +59,37 @@ export async function PDFPage(props: {
const language = await getSpaceLanguage(baseContext); const language = await getSpaceLanguage(baseContext);
// Compute the pages to render // Compute the pages to render
const { pages, total } = selectPages(baseContext.revision.pages, pdfParams); const selection = selectPages(baseContext.revision.pages, pdfParams);
if (!selection) {
notFound();
}
const { pages, allPages, offset, total } = selection;
const batchParams = { ...pdfParams, offset };
const pageIds = pages.map( const pageIds = pages.map(
({ page }) => [page.id, getPagePDFContainerId(page)] as [string, string] ({ page }) => [page.id, getPagePDFContainerId(page)] as [string, string]
); );
// Build a linker that create anchor links for the pages rendered in the PDF page. // Build a linker that create anchor links for the pages rendered in the PDF page.
const linker = createPDFLinker(baseContext.linker, pages, getPublishedLinker(baseContext)); const pageIndexes = new Map(allPages.map(({ page }, index) => [page.id, index]));
const linker = createPDFLinker(baseContext.linker, pages, getPublishedLinker(baseContext), {
getBatchURL: (page) => {
const index = pageIndexes.get(page.id);
if (index === undefined) {
return undefined;
}
// Align on the current batch so these links match the previous/next controls.
const batchOffset = Math.max(
0,
offset + Math.floor((index - offset) / pdfParams.limit) * pdfParams.limit
);
return baseContext.linker.toAbsoluteURL(
baseContext.linker.toPathInSpace(
`~gitbook/pdf?${getPDFURLSearchParams({ ...pdfParams, offset: batchOffset }).toString()}`
)
);
},
});
const context: GitBookSpaceContext = { const context: GitBookSpaceContext = {
...baseContext, ...baseContext,
@@ -113,7 +134,7 @@ export async function PDFPage(props: {
</div> </div>
<PageControlButtons <PageControlButtons
params={pdfParams} params={batchParams}
pageIds={pageIds} pageIds={pageIds}
total={total} total={total}
trademark={ trademark={
@@ -126,7 +147,7 @@ export async function PDFPage(props: {
} }
/> />
{pdfParams.only ? null : ( {pdfParams.only || offset > 0 ? null : (
<PDFSpaceIntro space={context.space} customization={customization} /> <PDFSpaceIntro space={context.space} customization={customization} />
)} )}
{pages.map(({ page }) => {pages.map(({ page }) =>
@@ -273,67 +294,3 @@ function PrintPage(
</div> </div>
); );
} }
type FlatPageEntry = { page: RevisionPageDocument | RevisionPageGroup; depth: number };
/**
* Compute the ordered flat set of pages to render.
*/
function selectPages(
rootPages: Revision['pages'],
params: PDFSearchParams
): { pages: FlatPageEntry[]; total: number } {
const flattenPage = (
page: RevisionPageDocument | RevisionPageGroup,
depth: number
): FlatPageEntry[] => {
return [
{ page, depth },
...page.pages.flatMap((child) => {
if (child.type !== 'document') {
return [];
}
if (child.hidden) {
return [];
}
return flattenPage(child, depth + 1);
}),
];
};
const limitTo = (entries: FlatPageEntry[]) => {
return {
// Apply a soft-limit, the limit can be controlled by the URL to allow testing
pages: entries.slice(0, params.limit ?? DEFAULT_LIMIT),
total: entries.length,
};
};
if (params.page) {
const found = resolvePageId(rootPages, params.page);
if (!found) {
notFound();
}
if (!params.only) {
return limitTo([{ page: found.page, depth: 0 }]);
}
return limitTo(flattenPage(found.page, 0));
}
const allPages = rootPages.flatMap((page) => {
if (page.type !== 'document' && page.type !== 'group') {
return [];
}
if (page.hidden) {
return [];
}
return flattenPage(page, 0);
});
return limitTo(allPages);
}
@@ -10,8 +10,6 @@ import { Button } from '@/components/primitives';
import { t, tString, useLanguage } from '@/intl/client'; import { t, tString, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind'; import { tcls } from '@/lib/tailwind';
const limitExtend = 50;
/** /**
* Dynamic controls to show active page and to let the user select between modes. * Dynamic controls to show active page and to let the user select between modes.
*/ */
@@ -19,7 +17,7 @@ export function PageControlButtons(props: {
params: PDFSearchParams; params: PDFSearchParams;
/** Array of the [pageId, divId] */ /** Array of the [pageId, divId] */
pageIds: [string, string][]; pageIds: [string, string][];
/** Total number of pages targetted by the generation */ /** Total number of pages targetted by the generation, across all batches */
total: number; total: number;
/** Trademark to display */ /** Trademark to display */
trademark?: React.ReactNode; trademark?: React.ReactNode;
@@ -38,6 +36,10 @@ export function PageControlButtons(props: {
const activeIndex = (activeDivId ? divIds.indexOf(activeDivId) : 0) + 1; const activeIndex = (activeDivId ? divIds.indexOf(activeDivId) : 0) + 1;
const activePageId = pageIds[activeIndex - 1]?.[0]; const activePageId = pageIds[activeIndex - 1]?.[0];
const batchEnd = params.offset + pageIds.length;
const previousCount = Math.min(params.limit, params.offset);
const nextCount = Math.min(params.limit, total - batchEnd);
return ( return (
<> <>
<div <div
@@ -58,6 +60,8 @@ export function PageControlButtons(props: {
...params, ...params,
page: activePageId, page: activePageId,
only: true, only: true,
limit: undefined,
offset: undefined,
}).toString()}`} }).toString()}`}
variant="secondary" variant="secondary"
label={tString(language, 'pdf_mode_only_page')} label={tString(language, 'pdf_mode_only_page')}
@@ -68,6 +72,8 @@ export function PageControlButtons(props: {
...params, ...params,
page: undefined, page: undefined,
only: false, only: false,
limit: undefined,
offset: undefined,
}).toString()}`} }).toString()}`}
variant="secondary" variant="secondary"
label={tString(language, 'pdf_mode_all')} label={tString(language, 'pdf_mode_all')}
@@ -89,7 +95,7 @@ export function PageControlButtons(props: {
'z-50' 'z-50'
)} )}
> >
{total !== pageIds.length ? ( {previousCount > 0 || nextCount > 0 ? (
<div <div
role="banner" role="banner"
className={tcls( className={tcls(
@@ -112,19 +118,32 @@ export function PageControlButtons(props: {
className={tcls('size-6', 'mr-3', 'mt-1')} className={tcls('size-6', 'mr-3', 'mt-1')}
/>{' '} />{' '}
<div> <div>
<div>{t(language, 'pdf_limit_reached', total, pageIds.length)}</div>
<div> <div>
<a {t(language, 'pdf_batch_range', params.offset + 1, batchEnd, total)}
href={`?${getPDFURLSearchParams({ </div>
...params, <div className={tcls('flex', 'flex-row', 'flex-wrap', 'gap-x-3')}>
page: undefined, {previousCount > 0 ? (
only: false, <a
limit: params.limit + limitExtend, href={`?${getPDFURLSearchParams({
}).toString()}`} ...params,
className={tcls('underline')} offset: params.offset - previousCount,
> }).toString()}`}
{t(language, 'pdf_limit_reached_continue', limitExtend)} className={tcls('underline')}
</a> >
{t(language, 'pdf_batch_previous', previousCount)}
</a>
) : null}
{nextCount > 0 ? (
<a
href={`?${getPDFURLSearchParams({
...params,
offset: batchEnd,
}).toString()}`}
className={tcls('underline')}
>
{t(language, 'pdf_batch_next', nextCount)}
</a>
) : null}
</div> </div>
</div> </div>
</div> </div>
@@ -146,7 +165,7 @@ export function PageControlButtons(props: {
'border' 'border'
)} )}
> >
{t(language, 'pdf_page_of', activeIndex, pageIds.length)} {t(language, 'pdf_page_of', params.offset + activeIndex, total)}
</div> </div>
</div> </div>
</> </>
+21 -18
View File
@@ -20,36 +20,39 @@ export function getPagePDFContainerId(
/** /**
* Create a custom linker for PDF exports. * Create a custom linker for PDF exports.
* *
* This linker generates in-document anchor links for pages that are included * Pages rendered in the current batch get in-document anchors. Other pages link to the
* in the current PDF export, using `getPagePDFContainerId` to build the * published site when possible; pages exported in another batch otherwise link to that batch.
* target element ID. For pages that are not part of the exported PDF, it
* falls back to URLs pointing to the published site if `publishedLinker` is
* provided, preserving navigability for external content. Otherwise, it uses
* absolute URLs from the base linker.
*
* @param baseLinker - The base GitBook linker used to resolve standard paths and URLs.
* @param pages - The list of pages that are included in the current PDF export.
* @param publishedLinker - Optional linker for the published space for external page links.
* @returns A `GitBookLinker` configured to generate PDF-friendly links.
*/ */
export function createPDFLinker( export function createPDFLinker(
baseLinker: GitBookLinker, baseLinker: GitBookLinker,
pages: { page: Revision['pages'][number] }[], pages: { page: Revision['pages'][number] }[],
publishedLinker?: GitBookLinker publishedLinker?: GitBookLinker,
options: {
/** URL of the export batch rendering a page, for pages outside the current batch. */
getBatchURL?: (page: RevisionPageDocument | RevisionPageGroup) => string | undefined;
} = {}
): GitBookLinker { ): GitBookLinker {
const pageIds = new Set(pages.map((p) => p.page.id));
return { return {
...baseLinker, ...baseLinker,
toPathForPage(input) { toPathForPage(input) {
if (pages.some((p) => p.page.id === input.page.id)) { if (pageIds.has(input.page.id)) {
return `#${getPagePDFContainerId(input.page, input.anchor)}`; return `#${getPagePDFContainerId(input.page, input.anchor)}`;
} }
if (input.page.type === RevisionPageType.Group) {
return '#'; // A published page outlives the export URL, so prefer it for documents.
if (input.page.type !== RevisionPageType.Group && publishedLinker) {
return publishedLinker.toPathForPage(input);
} }
// For pages that are not embedded in this PDF export, keep links on the published site. const batchURL = options.getBatchURL?.(input.page);
if (publishedLinker) { if (batchURL) {
return publishedLinker.toPathForPage(input); return `${batchURL}#${getPagePDFContainerId(input.page, input.anchor)}`;
}
if (input.page.type === RevisionPageType.Group) {
return '#';
} }
return baseLinker.toAbsoluteURL(baseLinker.toPathForPage(input)); return baseLinker.toAbsoluteURL(baseLinker.toPathForPage(input));
@@ -0,0 +1,92 @@
import type { Revision, RevisionPageDocument, RevisionPageGroup } from '@gitbook/api';
import type { PDFSearchParams } from './urls';
import { resolvePageId } from '@/lib/pages';
export type FlatPageEntry = { page: RevisionPageDocument | RevisionPageGroup; depth: number };
export interface PDFPageSelection {
/** Pages rendered in the current batch */
pages: FlatPageEntry[];
/** Every page targeted by the export, across all batches */
allPages: FlatPageEntry[];
/** Index in `allPages` of the first page of the batch */
offset: number;
/** Total number of pages targeted by the export */
total: number;
}
/**
* Compute the ordered flat set of pages targeted by the export, and the batch to render.
* Returns `null` if the requested page doesn't exist.
*/
export function selectPages(
rootPages: Revision['pages'],
params: Pick<PDFSearchParams, 'page' | 'only' | 'limit' | 'offset'>
): PDFPageSelection | null {
const allPages = selectAllPages(rootPages, params);
if (!allPages) {
return null;
}
const total = allPages.length;
// Fall back to the last batch rather than rendering an empty one.
const offset = params.offset < total ? params.offset : Math.max(0, total - params.limit);
return {
pages: allPages.slice(offset, offset + params.limit),
allPages,
offset,
total,
};
}
function selectAllPages(
rootPages: Revision['pages'],
params: Pick<PDFSearchParams, 'page' | 'only'>
): FlatPageEntry[] | null {
if (params.page) {
const found = resolvePageId(rootPages, params.page);
if (!found) {
return null;
}
if (!params.only) {
return [{ page: found.page, depth: 0 }];
}
return flattenPage(found.page, 0);
}
return rootPages.flatMap((page) => {
if (page.type !== 'document' && page.type !== 'group') {
return [];
}
if (page.hidden) {
return [];
}
return flattenPage(page, 0);
});
}
function flattenPage(
page: RevisionPageDocument | RevisionPageGroup,
depth: number
): FlatPageEntry[] {
return [
{ page, depth },
...page.pages.flatMap((child) => {
if (child.type !== 'document') {
return [];
}
if (child.hidden) {
return [];
}
return flattenPage(child, depth + 1);
}),
];
}
+56 -12
View File
@@ -1,33 +1,61 @@
const DEFAULT_LIMIT = 100; /** Default number of pages rendered per request when exporting the whole space. */
export const PDF_DEFAULT_LIMIT = 100;
/**
* Default number of pages rendered per request when exporting a page and its subtree.
* Subtrees are often OpenAPI references, which are much heavier to render than prose
* and get the request killed for CPU/memory at higher limits.
*/
export const PDF_SUBTREE_DEFAULT_LIMIT = 20;
export interface PDFSearchParams { export interface PDFSearchParams {
/** Page to export. If none is passed, all pages are exported. */ /** Page to export. If none is passed, all pages are exported. */
page?: string; page?: string;
/** If true, only the `page` is exported, and not its descendant */ /** If true, the `page` is exported with its descendants */
only?: boolean; only?: boolean;
/** Limit the number of pages */ /** Maximum number of pages to render in this batch */
limit: number; limit: number;
/** Index of the first page to render in this batch */
offset: number;
/** URL to redirect back to */ /** URL to redirect back to */
back?: string; back?: string;
} }
/**
* Params accepted when building a PDF export URL. `limit` and `offset` fall back to the defaults.
*/
export type PDFURLParams = Omit<PDFSearchParams, 'limit' | 'offset'> & {
limit?: number;
offset?: number;
};
/**
* Default batch size for a given PDF export.
*/
export function getDefaultPDFLimit(params: Pick<PDFSearchParams, 'page' | 'only'>): number {
return params.page && params.only ? PDF_SUBTREE_DEFAULT_LIMIT : PDF_DEFAULT_LIMIT;
}
/** /**
* Get the PDF export params from the URL serch params. * Get the PDF export params from the URL serch params.
*/ */
export function getPDFSearchParams(searchParams: URLSearchParams): PDFSearchParams { export function getPDFSearchParams(searchParams: URLSearchParams): PDFSearchParams {
const page = searchParams.has('page') ? (searchParams.get('page') ?? '') : undefined;
const only = searchParams.has('only') ? true : undefined;
const params: PDFSearchParams = { const params: PDFSearchParams = {
limit: DEFAULT_LIMIT, limit:
parseInteger(searchParams.get('limit'), { min: 1 }) ??
getDefaultPDFLimit({ page, only }),
offset: parseInteger(searchParams.get('offset'), { min: 0 }) ?? 0,
}; };
if (searchParams.has('page')) { if (page !== undefined) {
params.page = searchParams.get('page') ?? ''; params.page = page;
} }
if (searchParams.has('only')) { if (only) {
params.only = true; params.only = true;
} }
if (searchParams.has('limit')) {
params.limit = Number(searchParams.get('limit'));
}
if (searchParams.has('back')) { if (searchParams.has('back')) {
params.back = searchParams.get('back') ?? ''; params.back = searchParams.get('back') ?? '';
} }
@@ -39,7 +67,7 @@ export function getPDFSearchParams(searchParams: URLSearchParams): PDFSearchPara
* Get the URL search params to use for a PDF export. * Get the URL search params to use for a PDF export.
*/ */
export function getPDFURLSearchParams( export function getPDFURLSearchParams(
params: PDFSearchParams, params: PDFURLParams,
searchParams = new URLSearchParams({}) searchParams = new URLSearchParams({})
): URLSearchParams { ): URLSearchParams {
if (params?.page) { if (params?.page) {
@@ -53,9 +81,16 @@ export function getPDFURLSearchParams(
searchParams.delete('only'); searchParams.delete('only');
} }
// Persist limit and back // Persist limit, offset and back
if (params?.limit) { if (params?.limit) {
searchParams.set('limit', String(params.limit)); searchParams.set('limit', String(params.limit));
} else {
searchParams.delete('limit');
}
if (params?.offset) {
searchParams.set('offset', String(params.offset));
} else {
searchParams.delete('offset');
} }
if (params?.back) { if (params?.back) {
searchParams.set('back', String(params.back)); searchParams.set('back', String(params.back));
@@ -63,3 +98,12 @@ export function getPDFURLSearchParams(
return searchParams; return searchParams;
} }
function parseInteger(value: string | null, options: { min: number }): number | undefined {
if (value === null || value.trim() === '') {
return undefined;
}
const parsed = Number(value);
return Number.isInteger(parsed) && parsed >= options.min ? parsed : undefined;
}
@@ -533,7 +533,6 @@ function getPageActionsURLs({
`~gitbook/pdf?${getPDFURLSearchParams({ `~gitbook/pdf?${getPDFURLSearchParams({
page: page.id, page: page.id,
only: true, only: true,
limit: 100,
}).toString()}` }).toString()}`
) )
: undefined, : undefined,
+3 -2
View File
@@ -96,8 +96,9 @@ export const ar: TranslationLanguage = {
pdf_page_of: '${1} من ${2}', pdf_page_of: '${1} من ${2}',
pdf_mode_only_page: 'هذه الصفحة فقط', pdf_mode_only_page: 'هذه الصفحة فقط',
pdf_mode_all: 'كل الصفحات', pdf_mode_all: 'كل الصفحات',
pdf_limit_reached: 'تعذر إنشاء ملف PDF لـ ${1} صفحات، وتوقف الإنشاء عند ${2}.', pdf_batch_range: 'عرض الصفحات ${1}–${2} من ${3}.',
pdf_limit_reached_continue: 'التوسيع بإضافة ${1} صفحات أخرى.', pdf_batch_previous: 'الصفحات الـ ${1} السابقة',
pdf_batch_next: 'الصفحات الـ ${1} التالية',
more: 'المزيد', more: 'المزيد',
link_tooltip_external_link: 'رابط خارجي إلى', link_tooltip_external_link: 'رابط خارجي إلى',
link_tooltip_email: 'إرسال بريد إلكتروني إلى', link_tooltip_email: 'إرسال بريد إلكتروني إلى',
+3 -3
View File
@@ -99,9 +99,9 @@ export const bg: TranslationLanguage = {
pdf_page_of: '${1} от ${2}', pdf_page_of: '${1} от ${2}',
pdf_mode_only_page: 'Само тази страница', pdf_mode_only_page: 'Само тази страница',
pdf_mode_all: 'Всички страници', pdf_mode_all: 'Всички страници',
pdf_limit_reached: pdf_batch_range: 'Показани са страници ${1}–${2} от ${3}.',
'PDF файлът за ${1} страници не можа да бъде генериран, генерирането спря на ${2}.', pdf_batch_previous: 'Предишни ${1} страници',
pdf_limit_reached_continue: 'Разширяване с още ${1} страници.', pdf_batch_next: 'Следващи ${1} страници',
more: 'Още', more: 'Още',
link_tooltip_external_link: 'Външна връзка към', link_tooltip_external_link: 'Външна връзка към',
link_tooltip_email: 'Изпращане на имейл до', link_tooltip_email: 'Изпращане на имейл до',
+3 -3
View File
@@ -97,9 +97,9 @@ export const cs: TranslationLanguage = {
pdf_page_of: '${1} z ${2}', pdf_page_of: '${1} z ${2}',
pdf_mode_only_page: 'Pouze tato stránka', pdf_mode_only_page: 'Pouze tato stránka',
pdf_mode_all: 'Všechny stránky', pdf_mode_all: 'Všechny stránky',
pdf_limit_reached: pdf_batch_range: 'Zobrazeny stránky ${1}–${2} z ${3}.',
'Nepodařilo se vygenerovat PDF pro ${1} stránek, generování se zastavilo na ${2}.', pdf_batch_previous: 'Předchozích ${1} stránek',
pdf_limit_reached_continue: 'Rozšířit o dalších ${1} stránek.', pdf_batch_next: 'Dalších ${1} stránek',
more: 'Více', more: 'Více',
link_tooltip_external_link: 'Externí odkaz na', link_tooltip_external_link: 'Externí odkaz na',
link_tooltip_email: 'Odeslat e-mail na', link_tooltip_email: 'Odeslat e-mail na',
+3 -2
View File
@@ -97,8 +97,9 @@ export const da: TranslationLanguage = {
pdf_page_of: '${1} af ${2}', pdf_page_of: '${1} af ${2}',
pdf_mode_only_page: 'Kun denne side', pdf_mode_only_page: 'Kun denne side',
pdf_mode_all: 'Alle sider', pdf_mode_all: 'Alle sider',
pdf_limit_reached: 'Kunne ikke generere PDF for ${1} sider, genereringen stoppede ved ${2}.', pdf_batch_range: 'Viser sider ${1}–${2} af ${3}.',
pdf_limit_reached_continue: 'Udvid med ${1} flere sider.', pdf_batch_previous: 'Forrige ${1} sider',
pdf_batch_next: 'Næste ${1} sider',
more: 'Mere', more: 'Mere',
link_tooltip_external_link: 'Eksternt link til', link_tooltip_external_link: 'Eksternt link til',
link_tooltip_email: 'Send e-mail til', link_tooltip_email: 'Send e-mail til',
+3 -3
View File
@@ -102,9 +102,9 @@ export const de: TranslationLanguage = {
pdf_page_of: '${1} von ${2}', pdf_page_of: '${1} von ${2}',
pdf_mode_only_page: 'Nur diese Seite', pdf_mode_only_page: 'Nur diese Seite',
pdf_mode_all: 'Alle Seiten', pdf_mode_all: 'Alle Seiten',
pdf_limit_reached: pdf_batch_range: 'Seiten ${1}–${2} von ${3}.',
'Das PDF konnte für ${1} Seiten nicht generiert werden, Generierung wurde bei ${2} gestoppt.', pdf_batch_previous: 'Vorherige ${1} Seiten',
pdf_limit_reached_continue: 'Mit ${1} weiteren Seiten erweitern.', pdf_batch_next: 'Nächste ${1} Seiten',
more: 'Mehr', more: 'Mehr',
link_tooltip_external_link: 'Externer Link zu', link_tooltip_external_link: 'Externer Link zu',
link_tooltip_email: 'E-Mail senden an', link_tooltip_email: 'E-Mail senden an',
+3 -3
View File
@@ -100,9 +100,9 @@ export const el: TranslationLanguage = {
pdf_page_of: '${1} από ${2}', pdf_page_of: '${1} από ${2}',
pdf_mode_only_page: 'Μόνο αυτή η σελίδα', pdf_mode_only_page: 'Μόνο αυτή η σελίδα',
pdf_mode_all: 'Όλες οι σελίδες', pdf_mode_all: 'Όλες οι σελίδες',
pdf_limit_reached: pdf_batch_range: 'Εμφάνιση σελίδων ${1}–${2} από ${3}.',
'Δεν ήταν δυνατή η δημιουργία PDF για ${1} σελίδες, η δημιουργία σταμάτησε στο ${2}.', pdf_batch_previous: 'Προηγούμενες ${1} σελίδες',
pdf_limit_reached_continue: 'Επέκταση με ${1} ακόμη σελίδες.', pdf_batch_next: 'Επόμενες ${1} σελίδες',
more: 'Περισσότερα', more: 'Περισσότερα',
link_tooltip_external_link: 'Εξωτερικός σύνδεσμος προς', link_tooltip_external_link: 'Εξωτερικός σύνδεσμος προς',
link_tooltip_email: 'Αποστολή email σε', link_tooltip_email: 'Αποστολή email σε',
+3 -2
View File
@@ -95,8 +95,9 @@ export const en = {
pdf_page_of: '${1} of ${2}', pdf_page_of: '${1} of ${2}',
pdf_mode_only_page: 'Only this page', pdf_mode_only_page: 'Only this page',
pdf_mode_all: 'All pages', pdf_mode_all: 'All pages',
pdf_limit_reached: "Couldn't generate the PDF for ${1} pages, generation stopped at ${2}.", pdf_batch_range: 'Showing pages ${1}–${2} of ${3}.',
pdf_limit_reached_continue: 'Extend with ${1} more pages.', pdf_batch_previous: 'Previous ${1} pages',
pdf_batch_next: 'Next ${1} pages',
more: 'More', more: 'More',
link_tooltip_external_link: 'External link to', link_tooltip_external_link: 'External link to',
link_tooltip_email: 'Send an email to', link_tooltip_email: 'Send an email to',
+3 -3
View File
@@ -101,9 +101,9 @@ export const es: TranslationLanguage = {
pdf_page_of: '${1} de ${2}', pdf_page_of: '${1} de ${2}',
pdf_mode_only_page: 'Solo esta página', pdf_mode_only_page: 'Solo esta página',
pdf_mode_all: 'Todas las páginas', pdf_mode_all: 'Todas las páginas',
pdf_limit_reached: pdf_batch_range: 'Mostrando las páginas ${1}–${2} de ${3}.',
'No se pudo generar el PDF para ${1} páginas, la generación se detuvo en ${2}.', pdf_batch_previous: '${1} páginas anteriores',
pdf_limit_reached_continue: 'Extender con ${1} páginas más.', pdf_batch_next: '${1} páginas siguientes',
more: 'Más', more: 'Más',
link_tooltip_external_link: 'Enlace externo a', link_tooltip_external_link: 'Enlace externo a',
link_tooltip_email: 'Enviar un correo electrónico a', link_tooltip_email: 'Enviar un correo electrónico a',
+3 -2
View File
@@ -97,8 +97,9 @@ export const et: TranslationLanguage = {
pdf_page_of: '${1}/${2}', pdf_page_of: '${1}/${2}',
pdf_mode_only_page: 'Ainult see leht', pdf_mode_only_page: 'Ainult see leht',
pdf_mode_all: 'Kõik lehed', pdf_mode_all: 'Kõik lehed',
pdf_limit_reached: 'PDF-i ei saanud luua ${1} lehe jaoks, loomine peatus kohas ${2}.', pdf_batch_range: 'Kuvatakse lehti ${1}–${2}/${3}.',
pdf_limit_reached_continue: 'Laienda veel ${1} lehe võrra.', pdf_batch_previous: 'Eelmised ${1} lehte',
pdf_batch_next: 'Järgmised ${1} lehte',
more: 'Rohkem', more: 'Rohkem',
link_tooltip_external_link: 'Väline link aadressile', link_tooltip_external_link: 'Väline link aadressile',
link_tooltip_email: 'Saada e-kiri aadressile', link_tooltip_email: 'Saada e-kiri aadressile',
+3 -2
View File
@@ -99,8 +99,9 @@ export const fi: TranslationLanguage = {
pdf_page_of: '${1}/${2}', pdf_page_of: '${1}/${2}',
pdf_mode_only_page: 'Vain tämä sivu', pdf_mode_only_page: 'Vain tämä sivu',
pdf_mode_all: 'Kaikki sivut', pdf_mode_all: 'Kaikki sivut',
pdf_limit_reached: 'PDF-tiedostoa ei voitu luoda ${1} sivulle, luonti pysähtyi kohtaan ${2}.', pdf_batch_range: 'Näytetään sivut ${1}–${2}/${3}.',
pdf_limit_reached_continue: 'Laajenna ${1} sivulla.', pdf_batch_previous: 'Edelliset ${1} sivua',
pdf_batch_next: 'Seuraavat ${1} sivua',
more: 'Lisää', more: 'Lisää',
link_tooltip_external_link: 'Ulkoinen linkki kohteeseen', link_tooltip_external_link: 'Ulkoinen linkki kohteeseen',
link_tooltip_email: 'Lähetä sähköpostia osoitteeseen', link_tooltip_email: 'Lähetä sähköpostia osoitteeseen',
+3 -2
View File
@@ -98,8 +98,9 @@ export const fr: TranslationLanguage = {
pdf_page_of: '${1} sur ${2}', pdf_page_of: '${1} sur ${2}',
pdf_mode_only_page: 'Cette page uniquement', pdf_mode_only_page: 'Cette page uniquement',
pdf_mode_all: 'Toutes les pages', pdf_mode_all: 'Toutes les pages',
pdf_limit_reached: 'Impossible de générer le PDF pour ${1} pages, arrêté à ${2}.', pdf_batch_range: 'Pages ${1} à ${2} sur ${3}.',
pdf_limit_reached_continue: 'Ajouter ${1} pages supplémentaires', pdf_batch_previous: '${1} pages précédentes',
pdf_batch_next: '${1} pages suivantes',
more: 'Plus', more: 'Plus',
link_tooltip_external_link: 'Lien externe vers', link_tooltip_external_link: 'Lien externe vers',
link_tooltip_email: 'Envoyer un e-mail à', link_tooltip_email: 'Envoyer un e-mail à',
+3 -2
View File
@@ -96,8 +96,9 @@ export const he: TranslationLanguage = {
pdf_page_of: '${1} מתוך ${2}', pdf_page_of: '${1} מתוך ${2}',
pdf_mode_only_page: 'רק הדף הזה', pdf_mode_only_page: 'רק הדף הזה',
pdf_mode_all: 'כל הדפים', pdf_mode_all: 'כל הדפים',
pdf_limit_reached: 'לא ניתן ליצור PDF עבור ${1} דפים, היצירה נעצרה ב-${2}.', pdf_batch_range: 'מוצגים דפים ${1}–${2} מתוך ${3}.',
pdf_limit_reached_continue: 'הרחבה עם עוד ${1} דפים.', pdf_batch_previous: '${1} הדפים הקודמים',
pdf_batch_next: '${1} הדפים הבאים',
more: 'עוד', more: 'עוד',
link_tooltip_external_link: 'קישור חיצוני אל', link_tooltip_external_link: 'קישור חיצוני אל',
link_tooltip_email: 'שליחת אימייל אל', link_tooltip_email: 'שליחת אימייל אל',
+3 -2
View File
@@ -96,8 +96,9 @@ export const hi: TranslationLanguage = {
pdf_page_of: '${1} में से ${2}', pdf_page_of: '${1} में से ${2}',
pdf_mode_only_page: 'केवल यह पृष्ठ', pdf_mode_only_page: 'केवल यह पृष्ठ',
pdf_mode_all: 'सभी पृष्ठ', pdf_mode_all: 'सभी पृष्ठ',
pdf_limit_reached: '${1} पृष्ठों के लिए PDF नहीं बन सका, जनरेशन ${2} पर रुक गया।', pdf_batch_range: '${3} में से पृष्ठ ${1}–${2} दिखाए जा रहे हैं।',
pdf_limit_reached_continue: '${1} और पृष्ठों के साथ बढ़ाएं।', pdf_batch_previous: 'पिछले ${1} पृष्ठ',
pdf_batch_next: 'अगले ${1} पृष्ठ',
more: 'अधिक', more: 'अधिक',
link_tooltip_external_link: 'बाहरी लिंक', link_tooltip_external_link: 'बाहरी लिंक',
link_tooltip_email: 'ईमेल भेजें', link_tooltip_email: 'ईमेल भेजें',
+3 -3
View File
@@ -97,9 +97,9 @@ export const hr: TranslationLanguage = {
pdf_page_of: '${1} od ${2}', pdf_page_of: '${1} od ${2}',
pdf_mode_only_page: 'Samo ova stranica', pdf_mode_only_page: 'Samo ova stranica',
pdf_mode_all: 'Sve stranice', pdf_mode_all: 'Sve stranice',
pdf_limit_reached: pdf_batch_range: 'Prikazane stranice ${1}–${2} od ${3}.',
'Nije moguće generirati PDF za ${1} stranica, generiranje je zaustavljeno na ${2}.', pdf_batch_previous: 'Prethodnih ${1} stranica',
pdf_limit_reached_continue: 'Proširi za još ${1} stranica.', pdf_batch_next: 'Sljedećih ${1} stranica',
more: 'Više', more: 'Više',
link_tooltip_external_link: 'Vanjska poveznica na', link_tooltip_external_link: 'Vanjska poveznica na',
link_tooltip_email: 'Pošalji e-poštu na', link_tooltip_email: 'Pošalji e-poštu na',
+3 -3
View File
@@ -97,9 +97,9 @@ export const hu: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: 'Csak ez az oldal', pdf_mode_only_page: 'Csak ez az oldal',
pdf_mode_all: 'Minden oldal', pdf_mode_all: 'Minden oldal',
pdf_limit_reached: pdf_batch_range: '${1}–${2}. oldal, összesen ${3}.',
'Nem sikerült PDF-et létrehozni ${1} oldalhoz, a létrehozás itt megállt: ${2}.', pdf_batch_previous: 'Előző ${1} oldal',
pdf_limit_reached_continue: 'Bővítés további ${1} oldallal.', pdf_batch_next: 'Következő ${1} oldal',
more: 'Továbbiak', more: 'Továbbiak',
link_tooltip_external_link: 'Külső hivatkozás ide:', link_tooltip_external_link: 'Külső hivatkozás ide:',
link_tooltip_email: 'E-mail küldése ide:', link_tooltip_email: 'E-mail küldése ide:',
+3 -2
View File
@@ -97,8 +97,9 @@ export const id: TranslationLanguage = {
pdf_page_of: '${1} dari ${2}', pdf_page_of: '${1} dari ${2}',
pdf_mode_only_page: 'Hanya halaman ini', pdf_mode_only_page: 'Hanya halaman ini',
pdf_mode_all: 'Semua halaman', pdf_mode_all: 'Semua halaman',
pdf_limit_reached: 'Tidak dapat membuat PDF untuk ${1} halaman, pembuatan berhenti di ${2}.', pdf_batch_range: 'Menampilkan halaman ${1}–${2} dari ${3}.',
pdf_limit_reached_continue: 'Perluas dengan ${1} halaman lagi.', pdf_batch_previous: '${1} halaman sebelumnya',
pdf_batch_next: '${1} halaman berikutnya',
more: 'Lainnya', more: 'Lainnya',
link_tooltip_external_link: 'Tautan eksternal ke', link_tooltip_external_link: 'Tautan eksternal ke',
link_tooltip_email: 'Kirim email ke', link_tooltip_email: 'Kirim email ke',
+3 -3
View File
@@ -100,9 +100,9 @@ export const it: TranslationLanguage = {
pdf_page_of: '${1} di ${2}', pdf_page_of: '${1} di ${2}',
pdf_mode_only_page: 'Solo questa pagina', pdf_mode_only_page: 'Solo questa pagina',
pdf_mode_all: 'Tutte le pagine', pdf_mode_all: 'Tutte le pagine',
pdf_limit_reached: pdf_batch_range: 'Pagine ${1}–${2} di ${3}.',
'Impossibile generare il PDF per ${1} pagine, la generazione si è fermata a ${2}.', pdf_batch_previous: '${1} pagine precedenti',
pdf_limit_reached_continue: 'Estendi con altre ${1} pagine.', pdf_batch_next: '${1} pagine successive',
more: 'Altro', more: 'Altro',
link_tooltip_external_link: 'Link esterno a', link_tooltip_external_link: 'Link esterno a',
link_tooltip_email: "Invia un'email a", link_tooltip_email: "Invia un'email a",
+3 -2
View File
@@ -98,8 +98,9 @@ export const ja: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: 'このページのみ', pdf_mode_only_page: 'このページのみ',
pdf_mode_all: '全てのページ', pdf_mode_all: '全てのページ',
pdf_limit_reached: '${1}ページのPDFを生成できませんでした、${2}で生成が停止しました。', pdf_batch_range: '${3}ページ中 ${1}–${2}ページを表示しています。',
pdf_limit_reached_continue: 'さらに${1}ページで拡張', pdf_batch_previous: '前の${1}ページ',
pdf_batch_next: '次の${1}ページ',
more: 'もっと見る', more: 'もっと見る',
link_tooltip_external_link: '外部リンクへ', link_tooltip_external_link: '外部リンクへ',
link_tooltip_email: 'メールを送信', link_tooltip_email: 'メールを送信',
+3 -2
View File
@@ -97,8 +97,9 @@ export const ko: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: '이 페이지만', pdf_mode_only_page: '이 페이지만',
pdf_mode_all: '모든 페이지', pdf_mode_all: '모든 페이지',
pdf_limit_reached: '${1}페이지에 대한 PDF를 생성할 수 없어 ${2}에서 생성이 중지되었습니다.', pdf_batch_range: '전체 ${3}페이지 중 ${1}–${2}페이지를 표시하고 있습니다.',
pdf_limit_reached_continue: '${1}페이지 더 확장하기.', pdf_batch_previous: '이전 ${1}페이지',
pdf_batch_next: '다음 ${1}페이지',
more: '더 보기', more: '더 보기',
link_tooltip_external_link: '외부 링크', link_tooltip_external_link: '외부 링크',
link_tooltip_email: '이메일 보내기', link_tooltip_email: '이메일 보내기',
+3 -2
View File
@@ -98,8 +98,9 @@ export const lt: TranslationLanguage = {
pdf_page_of: '${1} iš ${2}', pdf_page_of: '${1} iš ${2}',
pdf_mode_only_page: 'Tik šis puslapis', pdf_mode_only_page: 'Tik šis puslapis',
pdf_mode_all: 'Visi puslapiai', pdf_mode_all: 'Visi puslapiai',
pdf_limit_reached: 'Nepavyko sugeneruoti PDF ${1} puslapiams, generavimas sustojo ties ${2}.', pdf_batch_range: 'Rodomi puslapiai ${1}–${2} iš ${3}.',
pdf_limit_reached_continue: 'Išplėsti dar ${1} puslapiais.', pdf_batch_previous: 'Ankstesni ${1} puslapiai',
pdf_batch_next: 'Kiti ${1} puslapiai',
more: 'Daugiau', more: 'Daugiau',
link_tooltip_external_link: 'Išorinė nuoroda į', link_tooltip_external_link: 'Išorinė nuoroda į',
link_tooltip_email: 'Siųsti el. laišką į', link_tooltip_email: 'Siųsti el. laišką į',
+3 -2
View File
@@ -96,8 +96,9 @@ export const lv: TranslationLanguage = {
pdf_page_of: '${1} no ${2}', pdf_page_of: '${1} no ${2}',
pdf_mode_only_page: 'Tikai šī lapa', pdf_mode_only_page: 'Tikai šī lapa',
pdf_mode_all: 'Visas lapas', pdf_mode_all: 'Visas lapas',
pdf_limit_reached: 'Neizdevās ģenerēt PDF ${1} lapām, ģenerēšana apstājās pie ${2}.', pdf_batch_range: 'Rāda lapas ${1}–${2} no ${3}.',
pdf_limit_reached_continue: 'Paplašināt ar vēl ${1} lapām.', pdf_batch_previous: 'Iepriekšējās ${1} lapas',
pdf_batch_next: 'Nākamās ${1} lapas',
more: 'Vairāk', more: 'Vairāk',
link_tooltip_external_link: 'Ārēja saite uz', link_tooltip_external_link: 'Ārēja saite uz',
link_tooltip_email: 'Sūtīt e-pastu uz', link_tooltip_email: 'Sūtīt e-pastu uz',
+3 -2
View File
@@ -97,8 +97,9 @@ export const ms: TranslationLanguage = {
pdf_page_of: '${1} daripada ${2}', pdf_page_of: '${1} daripada ${2}',
pdf_mode_only_page: 'Halaman ini sahaja', pdf_mode_only_page: 'Halaman ini sahaja',
pdf_mode_all: 'Semua halaman', pdf_mode_all: 'Semua halaman',
pdf_limit_reached: 'Tidak dapat menjana PDF untuk ${1} halaman, penjanaan berhenti pada ${2}.', pdf_batch_range: 'Memaparkan halaman ${1}–${2} daripada ${3}.',
pdf_limit_reached_continue: 'Lanjutkan dengan ${1} halaman lagi.', pdf_batch_previous: '${1} halaman sebelumnya',
pdf_batch_next: '${1} halaman seterusnya',
more: 'Lagi', more: 'Lagi',
link_tooltip_external_link: 'Pautan luaran ke', link_tooltip_external_link: 'Pautan luaran ke',
link_tooltip_email: 'Hantar e-mel kepada', link_tooltip_email: 'Hantar e-mel kepada',
+3 -2
View File
@@ -100,8 +100,9 @@ export const nl: TranslationLanguage = {
pdf_page_of: '${1} van ${2}', pdf_page_of: '${1} van ${2}',
pdf_mode_only_page: 'Alleen deze pagina', pdf_mode_only_page: 'Alleen deze pagina',
pdf_mode_all: "Alle pagina's", pdf_mode_all: "Alle pagina's",
pdf_limit_reached: "Kon de PDF niet genereren voor ${1} pagina's, generatie gestopt bij ${2}.", pdf_batch_range: "Pagina's ${1}–${2} van ${3}.",
pdf_limit_reached_continue: "Verleng met ${1} extra pagina's.", pdf_batch_previous: "Vorige ${1} pagina's",
pdf_batch_next: "Volgende ${1} pagina's",
more: 'Meer', more: 'Meer',
link_tooltip_external_link: 'Externe link naar', link_tooltip_external_link: 'Externe link naar',
link_tooltip_email: 'E-mail versturen naar', link_tooltip_email: 'E-mail versturen naar',
+3 -2
View File
@@ -99,8 +99,9 @@ export const no: TranslationLanguage = {
pdf_page_of: '${1} av ${2}', pdf_page_of: '${1} av ${2}',
pdf_mode_only_page: 'Kun denne siden', pdf_mode_only_page: 'Kun denne siden',
pdf_mode_all: 'Alle sider', pdf_mode_all: 'Alle sider',
pdf_limit_reached: 'Kunne ikke generere PDF for ${1} sider, generering stoppet ved ${2}.', pdf_batch_range: 'Viser sider ${1}–${2} av ${3}.',
pdf_limit_reached_continue: 'Utvid med ${1} flere sider.', pdf_batch_previous: 'Forrige ${1} sider',
pdf_batch_next: 'Neste ${1} sider',
more: 'Mer', more: 'Mer',
link_tooltip_external_link: 'Ekstern lenke til', link_tooltip_external_link: 'Ekstern lenke til',
link_tooltip_email: 'Send e-post til', link_tooltip_email: 'Send e-post til',
+3 -3
View File
@@ -97,9 +97,9 @@ export const pl: TranslationLanguage = {
pdf_page_of: '${1} z ${2}', pdf_page_of: '${1} z ${2}',
pdf_mode_only_page: 'Tylko ta strona', pdf_mode_only_page: 'Tylko ta strona',
pdf_mode_all: 'Wszystkie strony', pdf_mode_all: 'Wszystkie strony',
pdf_limit_reached: pdf_batch_range: 'Strony ${1}–${2} z ${3}.',
'Nie udało się wygenerować PDF dla ${1} stron, generowanie zatrzymało się na ${2}.', pdf_batch_previous: 'Poprzednie ${1} stron',
pdf_limit_reached_continue: 'Rozszerz o kolejne ${1} stron.', pdf_batch_next: 'Następne ${1} stron',
more: 'Więcej', more: 'Więcej',
link_tooltip_external_link: 'Link zewnętrzny do', link_tooltip_external_link: 'Link zewnętrzny do',
link_tooltip_email: 'Wyślij e-mail do', link_tooltip_email: 'Wyślij e-mail do',
@@ -101,9 +101,9 @@ export const pt_br: TranslationLanguage = {
pdf_page_of: '${1} de ${2}', pdf_page_of: '${1} de ${2}',
pdf_mode_only_page: 'Somente esta página', pdf_mode_only_page: 'Somente esta página',
pdf_mode_all: 'Todas as páginas', pdf_mode_all: 'Todas as páginas',
pdf_limit_reached: pdf_batch_range: 'Mostrando as páginas ${1}–${2} de ${3}.',
'Não foi possível gerar o PDF para ${1} páginas, a geração foi interrompida em ${2}.', pdf_batch_previous: '${1} páginas anteriores',
pdf_limit_reached_continue: 'Estender com mais ${1} páginas.', pdf_batch_next: 'Próximas ${1} páginas',
more: 'Mais', more: 'Mais',
link_tooltip_external_link: 'Link externo para', link_tooltip_external_link: 'Link externo para',
link_tooltip_email: 'Enviar e-mail para', link_tooltip_email: 'Enviar e-mail para',
+3 -2
View File
@@ -99,8 +99,9 @@ export const pt: TranslationLanguage = {
pdf_page_of: '${1} de ${2}', pdf_page_of: '${1} de ${2}',
pdf_mode_only_page: 'Apenas esta página', pdf_mode_only_page: 'Apenas esta página',
pdf_mode_all: 'Todas as páginas', pdf_mode_all: 'Todas as páginas',
pdf_limit_reached: 'Não foi possível gerar o PDF para ${1} páginas; a geração parou em ${2}.', pdf_batch_range: 'A mostrar as páginas ${1}–${2} de ${3}.',
pdf_limit_reached_continue: 'Expandir com mais ${1} páginas.', pdf_batch_previous: '${1} páginas anteriores',
pdf_batch_next: '${1} páginas seguintes',
more: 'Mais', more: 'Mais',
link_tooltip_external_link: 'Ligação externa para', link_tooltip_external_link: 'Ligação externa para',
link_tooltip_email: 'Enviar e-mail para', link_tooltip_email: 'Enviar e-mail para',
+3 -3
View File
@@ -100,9 +100,9 @@ export const ro: TranslationLanguage = {
pdf_page_of: '${1} din ${2}', pdf_page_of: '${1} din ${2}',
pdf_mode_only_page: 'Doar această pagină', pdf_mode_only_page: 'Doar această pagină',
pdf_mode_all: 'Toate paginile', pdf_mode_all: 'Toate paginile',
pdf_limit_reached: pdf_batch_range: 'Se afișează paginile ${1}–${2} din ${3}.',
'Nu s-a putut genera PDF-ul pentru ${1} pagini, generarea s-a oprit la ${2}.', pdf_batch_previous: '${1} pagini anterioare',
pdf_limit_reached_continue: 'Extinde cu încă ${1} pagini.', pdf_batch_next: 'Următoarele ${1} pagini',
more: 'Mai multe', more: 'Mai multe',
link_tooltip_external_link: 'Link extern către', link_tooltip_external_link: 'Link extern către',
link_tooltip_email: 'Trimite e-mail către', link_tooltip_email: 'Trimite e-mail către',
+3 -3
View File
@@ -100,9 +100,9 @@ export const ru: TranslationLanguage = {
pdf_page_of: '${1} из ${2}', pdf_page_of: '${1} из ${2}',
pdf_mode_only_page: 'Только эта страница', pdf_mode_only_page: 'Только эта страница',
pdf_mode_all: 'Все страницы', pdf_mode_all: 'Все страницы',
pdf_limit_reached: pdf_batch_range: 'Страницы ${1}–${2} из ${3}.',
'Не удалось создать PDF-файл для ${1} страниц, генерация остановлена на ${2}.', pdf_batch_previous: 'Предыдущие ${1} страниц',
pdf_limit_reached_continue: 'Расширьте ещё на ${1} страниц.', pdf_batch_next: 'Следующие ${1} страниц',
more: 'Ещё', more: 'Ещё',
link_tooltip_external_link: 'Внешняя ссылка на', link_tooltip_external_link: 'Внешняя ссылка на',
link_tooltip_email: 'Отправить письмо на', link_tooltip_email: 'Отправить письмо на',
+3 -3
View File
@@ -99,9 +99,9 @@ export const sk: TranslationLanguage = {
pdf_page_of: '${1} z ${2}', pdf_page_of: '${1} z ${2}',
pdf_mode_only_page: 'Iba táto stránka', pdf_mode_only_page: 'Iba táto stránka',
pdf_mode_all: 'Všetky stránky', pdf_mode_all: 'Všetky stránky',
pdf_limit_reached: pdf_batch_range: 'Zobrazené stránky ${1}–${2} z ${3}.',
'Nepodarilo sa vygenerovať PDF pre ${1} stránok, generovanie sa zastavilo na ${2}.', pdf_batch_previous: 'Predchádzajúcich ${1} stránok',
pdf_limit_reached_continue: 'Rozšíriť o ďalších ${1} stránok.', pdf_batch_next: 'Ďalších ${1} stránok',
more: 'Viac', more: 'Viac',
link_tooltip_external_link: 'Externý odkaz na', link_tooltip_external_link: 'Externý odkaz na',
link_tooltip_email: 'Odoslať e-mail na', link_tooltip_email: 'Odoslať e-mail na',
+3 -3
View File
@@ -97,9 +97,9 @@ export const sl: TranslationLanguage = {
pdf_page_of: '${1} od ${2}', pdf_page_of: '${1} od ${2}',
pdf_mode_only_page: 'Samo ta stran', pdf_mode_only_page: 'Samo ta stran',
pdf_mode_all: 'Vse strani', pdf_mode_all: 'Vse strani',
pdf_limit_reached: pdf_batch_range: 'Prikazane strani ${1}–${2} od ${3}.',
'PDF-ja ni bilo mogoče ustvariti za ${1} strani, ustvarjanje se je ustavilo pri ${2}.', pdf_batch_previous: 'Prejšnjih ${1} strani',
pdf_limit_reached_continue: 'Razširi še za ${1} strani.', pdf_batch_next: 'Naslednjih ${1} strani',
more: 'Več', more: 'Več',
link_tooltip_external_link: 'Zunanja povezava do', link_tooltip_external_link: 'Zunanja povezava do',
link_tooltip_email: 'Pošlji e-pošto na', link_tooltip_email: 'Pošlji e-pošto na',
+3 -3
View File
@@ -97,9 +97,9 @@ export const sv: TranslationLanguage = {
pdf_page_of: '${1} av ${2}', pdf_page_of: '${1} av ${2}',
pdf_mode_only_page: 'Endast denna sida', pdf_mode_only_page: 'Endast denna sida',
pdf_mode_all: 'Alla sidor', pdf_mode_all: 'Alla sidor',
pdf_limit_reached: pdf_batch_range: 'Visar sidorna ${1}–${2} av ${3}.',
'Det gick inte att generera PDF för ${1} sidor, genereringen stoppades vid ${2}.', pdf_batch_previous: 'Föregående ${1} sidor',
pdf_limit_reached_continue: 'Utöka med ytterligare ${1} sidor.', pdf_batch_next: 'Nästa ${1} sidor',
more: 'Mer', more: 'Mer',
link_tooltip_external_link: 'Extern länk till', link_tooltip_external_link: 'Extern länk till',
link_tooltip_email: 'Skicka e-post till', link_tooltip_email: 'Skicka e-post till',
+3 -2
View File
@@ -95,8 +95,9 @@ export const th: TranslationLanguage = {
pdf_page_of: '${1} จาก ${2}', pdf_page_of: '${1} จาก ${2}',
pdf_mode_only_page: 'เฉพาะหน้านี้', pdf_mode_only_page: 'เฉพาะหน้านี้',
pdf_mode_all: 'ทุกหน้า', pdf_mode_all: 'ทุกหน้า',
pdf_limit_reached: 'ไม่สามารถสร้าง PDF สำหรับ ${1} หน้าได้ การสร้างหยุดที่ ${2}', pdf_batch_range: 'แสดงหน้า ${1}–${2} จาก ${3}',
pdf_limit_reached_continue: 'ขยายอีก ${1} หน้า', pdf_batch_previous: '${1} หน้าก่อนหน้า',
pdf_batch_next: '${1} หน้าถัดไป',
more: 'เพิ่มเติม', more: 'เพิ่มเติม',
link_tooltip_external_link: 'ลิงก์ภายนอกไปยัง', link_tooltip_external_link: 'ลิงก์ภายนอกไปยัง',
link_tooltip_email: 'ส่งอีเมลไปยัง', link_tooltip_email: 'ส่งอีเมลไปยัง',
+3 -2
View File
@@ -96,8 +96,9 @@ export const tr: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: 'Yalnızca bu sayfa', pdf_mode_only_page: 'Yalnızca bu sayfa',
pdf_mode_all: 'Tüm sayfalar', pdf_mode_all: 'Tüm sayfalar',
pdf_limit_reached: '${1} sayfa için PDF oluşturulamadı, oluşturma ${2} konumunda durdu.', pdf_batch_range: '${3} sayfadan ${1}–${2} arası gösteriliyor.',
pdf_limit_reached_continue: '${1} sayfa daha ekleyerek genişlet.', pdf_batch_previous: 'Önceki ${1} sayfa',
pdf_batch_next: 'Sonraki ${1} sayfa',
more: 'Daha fazla', more: 'Daha fazla',
link_tooltip_external_link: 'Harici bağlantı', link_tooltip_external_link: 'Harici bağlantı',
link_tooltip_email: 'E-posta gönder', link_tooltip_email: 'E-posta gönder',
+3 -2
View File
@@ -96,8 +96,9 @@ export const uk: TranslationLanguage = {
pdf_page_of: '${1} з ${2}', pdf_page_of: '${1} з ${2}',
pdf_mode_only_page: 'Лише ця сторінка', pdf_mode_only_page: 'Лише ця сторінка',
pdf_mode_all: 'Усі сторінки', pdf_mode_all: 'Усі сторінки',
pdf_limit_reached: 'Не вдалося створити PDF для ${1} сторінок, створення зупинилося на ${2}.', pdf_batch_range: 'Сторінки ${1}–${2} з ${3}.',
pdf_limit_reached_continue: 'Розширити ще на ${1} сторінок.', pdf_batch_previous: 'Попередні ${1} сторінок',
pdf_batch_next: 'Наступні ${1} сторінок',
more: 'Більше', more: 'Більше',
link_tooltip_external_link: 'Зовнішнє посилання на', link_tooltip_external_link: 'Зовнішнє посилання на',
link_tooltip_email: 'Надіслати електронний лист на', link_tooltip_email: 'Надіслати електронний лист на',
+3 -2
View File
@@ -96,8 +96,9 @@ export const vi: TranslationLanguage = {
pdf_page_of: '${1} trên ${2}', pdf_page_of: '${1} trên ${2}',
pdf_mode_only_page: 'Chỉ trang này', pdf_mode_only_page: 'Chỉ trang này',
pdf_mode_all: 'Tất cả trang', pdf_mode_all: 'Tất cả trang',
pdf_limit_reached: 'Không thể tạo PDF cho ${1} trang, quá trình tạo đã dừng tại ${2}.', pdf_batch_range: 'Đang hiển thị trang ${1}–${2} trên ${3}.',
pdf_limit_reached_continue: 'Mở rộng thêm ${1} trang.', pdf_batch_previous: '${1} trang trước',
pdf_batch_next: '${1} trang tiếp theo',
more: 'Thêm', more: 'Thêm',
link_tooltip_external_link: 'Liên kết ngoài tới', link_tooltip_external_link: 'Liên kết ngoài tới',
link_tooltip_email: 'Gửi email tới', link_tooltip_email: 'Gửi email tới',
@@ -94,8 +94,9 @@ export const yue: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: '只限呢頁', pdf_mode_only_page: '只限呢頁',
pdf_mode_all: '所有頁面', pdf_mode_all: '所有頁面',
pdf_limit_reached: '無法為 ${1} 頁產生 PDF,已喺 ${2} 停止。', pdf_batch_range: '而家顯示緊第 ${1}–${2} 頁,總共 ${3} 頁。',
pdf_limit_reached_continue: '再加 ${1} 頁。', pdf_batch_previous: '上 ${1} 頁',
pdf_batch_next: '下 ${1} 頁',
more: '更多', more: '更多',
link_tooltip_external_link: '外部連結到', link_tooltip_external_link: '外部連結到',
link_tooltip_email: '傳送電郵到', link_tooltip_email: '傳送電郵到',
@@ -94,8 +94,9 @@ export const zh_tw: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: '僅此頁面', pdf_mode_only_page: '僅此頁面',
pdf_mode_all: '所有頁面', pdf_mode_all: '所有頁面',
pdf_limit_reached: '無法為 ${1} 頁產生 PDF,已在 ${2} 停止產生。', pdf_batch_range: '正在顯示第 ${1}–${2} 頁,共 ${3} 頁。',
pdf_limit_reached_continue: '再擴展 ${1} 頁。', pdf_batch_previous: '上 ${1} 頁',
pdf_batch_next: '下 ${1} 頁',
more: '更多', more: '更多',
link_tooltip_external_link: '外部連結至', link_tooltip_external_link: '外部連結至',
link_tooltip_email: '傳送電子郵件至', link_tooltip_email: '傳送電子郵件至',
+3 -2
View File
@@ -95,8 +95,9 @@ export const zh: TranslationLanguage = {
pdf_page_of: '${1} / ${2}', pdf_page_of: '${1} / ${2}',
pdf_mode_only_page: '仅本页', pdf_mode_only_page: '仅本页',
pdf_mode_all: '所有页面', pdf_mode_all: '所有页面',
pdf_limit_reached: '无法为 ${1} 页生成 PDF,生成在 ${2} 页时停止。', pdf_batch_range: '正在显示第 ${1}–${2} 页,共 ${3} 页。',
pdf_limit_reached_continue: '再扩展 ${1} 页。', pdf_batch_previous: '上 ${1} 页',
pdf_batch_next: '下 ${1} 页',
more: '更多', more: '更多',
link_tooltip_external_link: '外部链接到', link_tooltip_external_link: '外部链接到',
link_tooltip_email: '发送邮件到', link_tooltip_email: '发送邮件到',