diff --git a/.changeset/dry-melons-joke.md b/.changeset/dry-melons-joke.md new file mode 100644 index 0000000000..c2b1c27fd7 --- /dev/null +++ b/.changeset/dry-melons-joke.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Make search accessible diff --git a/packages/gitbook/src/components/Search/SearchContainer.tsx b/packages/gitbook/src/components/Search/SearchContainer.tsx index dc70365445..3ecf6a5872 100644 --- a/packages/gitbook/src/components/Search/SearchContainer.tsx +++ b/packages/gitbook/src/components/Search/SearchContainer.tsx @@ -1,5 +1,6 @@ 'use client'; +import { t, useLanguage } from '@/intl/client'; import { CustomizationSearchStyle } from '@gitbook/api'; import { useRouter } from 'next/navigation'; import React, { useRef } from 'react'; @@ -16,6 +17,8 @@ import { SearchInput } from './SearchInput'; import { SearchResults, type SearchResultsRef } from './SearchResults'; import { SearchScopeToggle } from './SearchScopeToggle'; import { useSearch } from './useSearch'; +import { useSearchResults } from './useSearchResults'; +import { useSearchResultsCursor } from './useSearchResultsCursor'; interface SearchContainerProps { siteSpaceId: string; @@ -131,19 +134,6 @@ export function SearchContainer(props: SearchContainerProps) { }; }, [onClose]); - const onKeyDown = (event: React.KeyboardEvent) => { - if (event.key === 'ArrowUp') { - event.preventDefault(); - resultsRef.current?.moveUp(); - } else if (event.key === 'ArrowDown') { - event.preventDefault(); - resultsRef.current?.moveDown(); - } else if (event.key === 'Enter') { - event.preventDefault(); - resultsRef.current?.select(); - } - }; - const onChange = (value: string) => { setSearchState((prev) => ({ ask: withAI && !withSearchAI ? (prev?.ask ?? null) : null, // When typing, we reset ask to get back to normal search (unless non-search assistants are defined) @@ -157,10 +147,37 @@ export function SearchContainer(props: SearchContainerProps) { const normalizedQuery = state?.query?.trim() ?? ''; const normalizedAsk = state?.ask?.trim() ?? ''; - const showAsk = withSearchAI && normalizedAsk; // withSearchAI && normalizedAsk; + const showAsk = withSearchAI && normalizedAsk; const visible = viewport === 'desktop' ? !isMobile : viewport === 'mobile' ? isMobile : true; + const searchResultsId = `search-results-${React.useId()}`; + const { results, fetching } = useSearchResults({ + disabled: !(state?.query || withAI), + query: normalizedQuery, + siteSpaceId, + global: state?.global ?? false, + withAI: withAI, + }); + const searchValue = state?.query ?? (withSearchAI || !withAI ? state?.ask : null) ?? ''; + + const { cursor, moveBy: moveCursorBy } = useSearchResultsCursor({ + query: normalizedQuery, + results, + }); + const onKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'ArrowUp') { + event.preventDefault(); + moveCursorBy(-1); + } else if (event.key === 'ArrowDown') { + event.preventDefault(); + moveCursorBy(1); + } else if (event.key === 'Enter') { + event.preventDefault(); + resultsRef.current?.select(); + } + }; + return ( ) : null} {showAsk ? : null} @@ -184,10 +203,7 @@ export function SearchContainer(props: SearchContainerProps) { ) : null } rootProps={{ - open: visible && (state?.open ?? false), - onOpenChange: (open) => { - open ? onOpen() : onClose(); - }, + open: Boolean(visible && (state?.open ?? false)), modal: isMobile, }} contentProps={{ @@ -198,8 +214,10 @@ export function SearchContainer(props: SearchContainerProps) { onInteractOutside: (event) => { // Don't close if clicking on the search input itself if (searchInputRef.current?.contains(event.target as Node)) { + event.preventDefault(); return; } + onClose(); }, sideOffset: 8, collisionPadding: { @@ -216,14 +234,23 @@ export function SearchContainer(props: SearchContainerProps) { > + aria-controls={searchResultsId} + aria-activedescendant={ + cursor !== null ? `${searchResultsId}-${cursor}` : undefined + } + > + + {assistants .filter((assistant) => assistant.ui === true) @@ -241,3 +268,21 @@ export function SearchContainer(props: SearchContainerProps) { ); } + +/* + * Screen reader announcement for search results. + * Without it there is no feedback for screen reader users when a search returns no results. + */ +function LiveResultsAnnouncer(props: { count: number; showing: boolean }) { + const { count, showing } = props; + const language = useLanguage(); + return ( +
+ {showing + ? count > 0 + ? t(language, 'search_results_count', count) + : t(language, 'search_no_results') + : ''} +
+ ); +} diff --git a/packages/gitbook/src/components/Search/SearchInput.tsx b/packages/gitbook/src/components/Search/SearchInput.tsx index 8f25c6fa2a..84fc6a67bf 100644 --- a/packages/gitbook/src/components/Search/SearchInput.tsx +++ b/packages/gitbook/src/components/Search/SearchInput.tsx @@ -16,6 +16,7 @@ interface SearchInputProps { withAI: boolean; isOpen: boolean; className?: string; + children?: React.ReactNode; } // Size classes for medium size button @@ -26,7 +27,17 @@ const sizeClasses = ['text-sm', 'px-3.5', 'py-1.5', 'md:circular-corners:px-4']; */ export const SearchInput = React.forwardRef( function SearchInput(props, ref) { - const { onChange, onKeyDown, onFocus, value, withAI, isOpen, className } = props; + const { + onChange, + onKeyDown, + onFocus, + value, + withAI, + isOpen, + className, + children, + ...rest + } = props; const inputRef = useRef(null); const language = useLanguage(); @@ -84,8 +95,9 @@ export const SearchInput = React.forwardRef( className="size-4 shrink-0 animate-scale-in" /> )} - + {children} ( 'peer z-10 min-w-0 grow bg-transparent py-0.5 text-tint-strong theme-bold:text-header-link outline-hidden transition-[width] duration-300 contain-paint placeholder:text-tint theme-bold:placeholder:text-current theme-bold:placeholder:opacity-7', isOpen ? '' : 'max-md:opacity-0' )} + role="combobox" + autoComplete="off" + aria-autocomplete="list" + aria-haspopup="listbox" + aria-expanded={value && isOpen ? 'true' : 'false'} + // Forward ref={inputRef} /> {!isOpen ? : null} diff --git a/packages/gitbook/src/components/Search/SearchPageResultItem.tsx b/packages/gitbook/src/components/Search/SearchPageResultItem.tsx index 1df80813a4..208b016f12 100644 --- a/packages/gitbook/src/components/Search/SearchPageResultItem.tsx +++ b/packages/gitbook/src/components/Search/SearchPageResultItem.tsx @@ -14,7 +14,7 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt }, ref: React.Ref ) { - const { query, item, active } = props; + const { query, item, active, ...rest } = props; const language = useLanguage(); const breadcrumbs = @@ -41,6 +41,8 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt spaceId: item.spaceId, }, }} + aria-label={tString(language, 'search_page_result_title', item.title)} + {...rest} > {breadcrumbs.length > 0 ? (
) { - const { question, recommended = false, active, assistant } = props; + const { question, recommended = false, active, assistant, ...rest } = props; const language = useLanguage(); const getLinkProp = useSearchLink(); @@ -38,6 +38,7 @@ export const SearchQuestionResultItem = React.forwardRef(function SearchQuestion active={active} leadingIcon={recommended ? 'search' : assistant.icon} className={recommended ? 'pr-1.5' : ''} + {...rest} > {recommended ? ( question diff --git a/packages/gitbook/src/components/Search/SearchResultItem.tsx b/packages/gitbook/src/components/Search/SearchResultItem.tsx index d39a9088e3..c9d3bac9e1 100644 --- a/packages/gitbook/src/components/Search/SearchResultItem.tsx +++ b/packages/gitbook/src/components/Search/SearchResultItem.tsx @@ -51,6 +51,7 @@ export const SearchResultItem = React.forwardRef(function SearchResultItem( : null, className )} + role="option" {...rest} >
diff --git a/packages/gitbook/src/components/Search/SearchResults.tsx b/packages/gitbook/src/components/Search/SearchResults.tsx index 687f31f383..b61b6d53d9 100644 --- a/packages/gitbook/src/components/Search/SearchResults.tsx +++ b/packages/gitbook/src/components/Search/SearchResults.tsx @@ -1,29 +1,19 @@ 'use client'; -import { readStreamableValue } from 'ai/rsc'; import assertNever from 'assert-never'; import React from 'react'; -import type { Assistant } from '@/components/AI'; +import { type Assistant, useAI } from '@/components/AI'; import { t, useLanguage } from '@/intl/client'; import { tcls } from '@/lib/tailwind'; -import { assert } from 'ts-essentials'; -import { useAI } from '../AI'; -import { useTrackEvent } from '../Insights'; + import { Loading } from '../primitives'; import { SearchPageResultItem } from './SearchPageResultItem'; import { SearchQuestionResultItem } from './SearchQuestionResultItem'; import { SearchSectionResultItem } from './SearchSectionResultItem'; -import { - type OrderedComputedResult, - searchAllSiteContent, - searchSiteSpaceContent, - streamRecommendedQuestions, -} from './server-actions'; +import type { OrderedComputedResult } from './server-actions'; export interface SearchResultsRef { - moveUp(): void; - moveDown(): void; select(): void; } @@ -32,14 +22,6 @@ type ResultType = | { type: 'question'; id: string; query: string; assistant: Assistant } | { type: 'recommended-question'; id: string; question: string }; -/** - * We cache the recommended questions globally to avoid calling the API multiple times - * when re-opening the search modal. The cache is per space, so that we can - * have different recommended questions for different spaces of the same site. - * It should not be used outside of an useEffect. - */ -const cachedRecommendedQuestions: Map = new Map(); - /** * Fetch the results of the keyboard navigable elements to display for a query: * - Recommended questions if no query is provided. @@ -49,133 +31,19 @@ const cachedRecommendedQuestions: Map = new Map(); export const SearchResults = React.forwardRef(function SearchResults( props: { children?: React.ReactNode; + id: string; query: string; - global: boolean; - siteSpaceId: string; + results: ResultType[]; + fetching: boolean; + cursor: number | null; }, ref: React.Ref ) { - const { children, query, global, siteSpaceId } = props; + const { children, id, query, results, fetching, cursor } = props; const language = useLanguage(); - const trackEvent = useTrackEvent(); - const [resultsState, setResultsState] = React.useState<{ - results: ResultType[]; - fetching: boolean; - }>({ results: [], fetching: true }); - const [cursor, setCursor] = React.useState(null); - const refs = React.useRef<(null | HTMLAnchorElement)[]>([]); - - const { assistants } = useAI(); - const withAI = assistants.length > 0; - - React.useEffect(() => { - if (!query) { - if (!withAI) { - setResultsState({ results: [], fetching: false }); - return; - } - - if (cachedRecommendedQuestions.has(siteSpaceId)) { - const results = cachedRecommendedQuestions.get(siteSpaceId); - assert( - results, - `Cached recommended questions should be set for site-space ${siteSpaceId}` - ); - setResultsState({ results, fetching: false }); - return; - } - - setResultsState({ results: [], fetching: false }); - - let cancelled = false; - - // We currently have a bug where the same question can be returned multiple times. - // This is a workaround to avoid that. - const questions = new Set(); - const recommendedQuestions: ResultType[] = []; - - const timeout = setTimeout(async () => { - if (cancelled) { - return; - } - - const response = await streamRecommendedQuestions({ siteSpaceId }); - for await (const entry of readStreamableValue(response.stream)) { - if (!entry) { - continue; - } - - const { question } = entry; - if (questions.has(question)) { - continue; - } - - questions.add(question); - recommendedQuestions.push({ - type: 'recommended-question', - id: question, - question, - }); - cachedRecommendedQuestions.set(siteSpaceId, recommendedQuestions); - - if (!cancelled) { - setResultsState({ results: [...recommendedQuestions], fetching: false }); - } - } - }, 100); - - return () => { - cancelled = true; - clearTimeout(timeout); - }; - } - setResultsState((prev) => ({ results: prev.results, fetching: true })); - let cancelled = false; - const timeout = setTimeout(async () => { - const results = await (global - ? searchAllSiteContent(query) - : searchSiteSpaceContent(query)); - - if (cancelled) { - return; - } - - if (!results) { - setResultsState({ results: [], fetching: false }); - return; - } - - setResultsState({ results, fetching: false }); - - trackEvent({ - type: 'search_type_query', - query, - }); - }, 350); - - return () => { - cancelled = true; - clearTimeout(timeout); - }; - }, [query, global, trackEvent, withAI, siteSpaceId]); - const results: ResultType[] = React.useMemo(() => { - if (!withAI) { - return resultsState.results; - } - return withAskTriggers(resultsState.results, query, assistants); - }, [resultsState.results, query, withAI]); - - React.useEffect(() => { - if (!query) { - // Reset the cursor when there's no query - setCursor(null); - } else if (results.length > 0) { - // Auto-focus the first result - setCursor(0); - } - }, [results, query]); + const refs = React.useRef<(null | HTMLAnchorElement)[]>([]); // Scroll to the active result. React.useEffect(() => { @@ -189,19 +57,6 @@ export const SearchResults = React.forwardRef(function SearchResults( }); }, [cursor]); - const moveBy = React.useCallback( - (delta: number) => { - setCursor((prev) => { - if (prev === null) { - return 0; - } - - return Math.max(Math.min(prev + delta, results.length - 1), 0); - }); - }, - [results] - ); - const select = React.useCallback(() => { if (cursor === null || !refs.current[cursor]) { return; @@ -213,18 +68,14 @@ export const SearchResults = React.forwardRef(function SearchResults( React.useImperativeHandle( ref, () => ({ - moveUp: () => { - moveBy(-1); - }, - moveDown: () => { - moveBy(1); - }, select, }), - [moveBy, select] + [select] ); - if (resultsState.fetching) { + const { assistants } = useAI(); + + if (fetching) { return (
@@ -258,8 +109,19 @@ export const SearchResults = React.forwardRef(function SearchResults( ) ) : ( <> -
+
{results.map((item, index) => { + const resultItemProps = { + 'aria-posinset': index + 1, + 'aria-setsize': results.length, + id: `${id}-${index}`, + }; switch (item.type) { case 'page': { return ( @@ -271,6 +133,7 @@ export const SearchResults = React.forwardRef(function SearchResults( query={query} item={item} active={index === cursor} + {...resultItemProps} /> ); } @@ -284,6 +147,7 @@ export const SearchResults = React.forwardRef(function SearchResults( question={query} active={index === cursor} assistant={item.assistant} + {...resultItemProps} /> ); } @@ -298,6 +162,7 @@ export const SearchResults = React.forwardRef(function SearchResults( active={index === cursor} assistant={assistants[0]!} recommended + {...resultItemProps} /> ); } @@ -311,6 +176,7 @@ export const SearchResults = React.forwardRef(function SearchResults( query={query} item={item} active={index === cursor} + {...resultItemProps} /> ); } @@ -325,28 +191,3 @@ export const SearchResults = React.forwardRef(function SearchResults(
); }); - -/** - * Add a "Ask " item at the top of the results list. - */ -function withAskTriggers( - results: ResultType[], - query: string, - assistants: Assistant[] -): ResultType[] { - const without = results.filter((result) => result.type !== 'question'); - - if (query.length === 0) { - return without; - } - - return [ - ...assistants.map((assistant, index) => ({ - type: 'question' as const, - id: `question-${index}`, - query, - assistant, - })), - ...(without ?? []), - ]; -} diff --git a/packages/gitbook/src/components/Search/SearchSectionResultItem.tsx b/packages/gitbook/src/components/Search/SearchSectionResultItem.tsx index 917786ad50..95df9f16e4 100644 --- a/packages/gitbook/src/components/Search/SearchSectionResultItem.tsx +++ b/packages/gitbook/src/components/Search/SearchSectionResultItem.tsx @@ -13,7 +13,7 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe }, ref: React.Ref ) { - const { query, item, active } = props; + const { query, item, active, ...rest } = props; const language = useLanguage(); return ( @@ -32,6 +32,18 @@ export const SearchSectionResultItem = React.forwardRef(function SearchSectionRe spaceId: item.spaceId, }, }} + aria-label={ + item.title + ? tString(language, 'search_section_result_title', item.title) + : item.body + ? tString( + language, + 'search_section_result_content', + getAbbreviatedBody(item.body, query) + ) + : tString(language, 'search_section_result_default') + } + {...rest} >
{item.title ? ( @@ -51,7 +63,12 @@ function highlightQueryInBody(body: string, query: string) { // Ensure the query to be highlighted is visible in the body. return (

- +

); } + +function getAbbreviatedBody(body: string, query: string) { + const idx = body.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()); + return idx < 20 ? body.slice(0, 100) : `…${body.slice(idx - 10, idx + query.length + 30)}…`; +} diff --git a/packages/gitbook/src/components/Search/useSearchResults.ts b/packages/gitbook/src/components/Search/useSearchResults.ts new file mode 100644 index 0000000000..2bffd5f5e8 --- /dev/null +++ b/packages/gitbook/src/components/Search/useSearchResults.ts @@ -0,0 +1,175 @@ +import { readStreamableValue } from 'ai/rsc'; +import React from 'react'; + +import { assert } from 'ts-essentials'; + +import { + type OrderedComputedResult, + searchAllSiteContent, + searchSiteSpaceContent, + streamRecommendedQuestions, +} from './server-actions'; + +import { type Assistant, useAI } from '@/components/AI'; +import { useTrackEvent } from '../Insights'; + +export type ResultType = + | OrderedComputedResult + | { type: 'question'; id: string; query: string; assistant: Assistant } + | { type: 'recommended-question'; id: string; question: string }; + +/** + * We cache the recommended questions globally to avoid calling the API multiple times + * when re-opening the search modal. The cache is per space, so that we can + * have different recommended questions for different spaces of the same site. + * It should not be used outside of an useEffect. + */ +const cachedRecommendedQuestions: Map = new Map(); + +export function useSearchResults(props: { + disabled: boolean; + query: string; + siteSpaceId: string; + global: boolean; + withAI: boolean; +}) { + const { disabled, query, siteSpaceId, global } = props; + + const trackEvent = useTrackEvent(); + + const [resultsState, setResultsState] = React.useState<{ + results: ResultType[]; + fetching: boolean; + }>({ results: [], fetching: false }); + + const { assistants } = useAI(); + const withAI = assistants.length > 0; + + React.useEffect(() => { + if (disabled) { + return; + } + if (!query) { + if (!withAI) { + setResultsState({ results: [], fetching: false }); + return; + } + + if (cachedRecommendedQuestions.has(siteSpaceId)) { + const results = cachedRecommendedQuestions.get(siteSpaceId); + assert( + results, + `Cached recommended questions should be set for site-space ${siteSpaceId}` + ); + setResultsState({ results, fetching: false }); + return; + } + + setResultsState({ results: [], fetching: false }); + + let cancelled = false; + + // We currently have a bug where the same question can be returned multiple times. + // This is a workaround to avoid that. + const questions = new Set(); + const recommendedQuestions: ResultType[] = []; + + const timeout = setTimeout(async () => { + if (cancelled) { + return; + } + + const response = await streamRecommendedQuestions({ siteSpaceId }); + for await (const entry of readStreamableValue(response.stream)) { + if (!entry) { + continue; + } + + const { question } = entry; + if (questions.has(question)) { + continue; + } + + questions.add(question); + recommendedQuestions.push({ + type: 'recommended-question', + id: question, + question, + }); + cachedRecommendedQuestions.set(siteSpaceId, recommendedQuestions); + + if (!cancelled) { + setResultsState({ results: [...recommendedQuestions], fetching: false }); + } + } + }, 100); + + return () => { + cancelled = true; + clearTimeout(timeout); + }; + } + setResultsState((prev) => ({ results: prev.results, fetching: true })); + let cancelled = false; + const timeout = setTimeout(async () => { + const results = await (global + ? searchAllSiteContent(query) + : searchSiteSpaceContent(query)); + + if (cancelled) { + return; + } + + if (!results) { + setResultsState({ results: [], fetching: false }); + return; + } + + setResultsState({ results, fetching: false }); + + trackEvent({ + type: 'search_type_query', + query, + }); + }, 350); + + return () => { + cancelled = true; + clearTimeout(timeout); + }; + }, [disabled, query, global, trackEvent, withAI, siteSpaceId]); + + const aiEnrichedResults: ResultType[] = React.useMemo(() => { + if (!withAI) { + return resultsState.results; + } + return withAskTriggers(resultsState.results, query, assistants); + }, [resultsState.results, query, withAI]); + + return { ...resultsState, results: aiEnrichedResults }; +} + +/** + * Add a "Ask " item at the top of the results list. + */ +function withAskTriggers( + results: ResultType[], + query: string, + assistants: Assistant[] +): ResultType[] { + const without = results.filter((result) => result.type !== 'question'); + + if (query.length === 0) { + return without; + } + + return [ + ...assistants.map((assistant, index) => ({ + type: 'question' as const, + id: `question-${index}`, + query, + assistant, + })), + ...(without ?? []), + ]; +} diff --git a/packages/gitbook/src/components/Search/useSearchResultsCursor.ts b/packages/gitbook/src/components/Search/useSearchResultsCursor.ts new file mode 100644 index 0000000000..8fe3d5f92b --- /dev/null +++ b/packages/gitbook/src/components/Search/useSearchResultsCursor.ts @@ -0,0 +1,35 @@ +import React from 'react'; +import type { ResultType } from './useSearchResults'; + +export function useSearchResultsCursor(props: { query: string; results: ResultType[] }) { + const [cursor, setCursor] = React.useState(null); + const { query, results } = props; + + React.useEffect(() => { + if (!props.query) { + // Reset the cursor when there's no query + setCursor(null); + } else if (results.length > 0) { + // Auto-focus the first result + setCursor(0); + } + }, [results, query]); + + const moveBy = React.useCallback( + (delta: number) => { + setCursor((prev) => { + if (prev === null) { + return 0; + } + return Math.max(Math.min(prev + delta, results.length - 1), 0); + }); + }, + [results] + ); + + return { + cursor, + setCursor, + moveBy, + }; +} diff --git a/packages/gitbook/src/intl/translations/de.ts b/packages/gitbook/src/intl/translations/de.ts index e2d86f9c0f..bb07fbd90e 100644 --- a/packages/gitbook/src/intl/translations/de.ts +++ b/packages/gitbook/src/intl/translations/de.ts @@ -12,6 +12,10 @@ export const de = { search_no_results_for: 'Keine Ergebnisse für "${1}".', search_no_results: 'Keine Ergebnisse', search_results_count: '${1} Ergebnisse', + search_page_result_title: 'Seite mit Titel ${1}', + search_section_result_title: 'Abschnitt mit Titel ${1}', + search_section_result_content: 'Abschnitt mit Inhalt ${1}', + search_section_result_default: 'Abschnitt', search_scope_space: '${1}', search_scope_all: 'Alle Inhalte', ask: 'Fragen', diff --git a/packages/gitbook/src/intl/translations/en.ts b/packages/gitbook/src/intl/translations/en.ts index fc4170a348..e0e2bc94c2 100644 --- a/packages/gitbook/src/intl/translations/en.ts +++ b/packages/gitbook/src/intl/translations/en.ts @@ -12,6 +12,10 @@ export const en = { search_no_results_for: 'No results for "${1}".', search_no_results: 'No results', search_results_count: '${1} results', + search_page_result_title: 'Page with title ${1}', + search_section_result_title: 'Section with title ${1}', + search_section_result_content: 'Section with content ${1}', + search_section_result_default: 'Section', search_scope_space: '${1}', search_scope_all: 'All content', ask: 'Ask', diff --git a/packages/gitbook/src/intl/translations/es.ts b/packages/gitbook/src/intl/translations/es.ts index 655fc04188..a7449a79d6 100644 --- a/packages/gitbook/src/intl/translations/es.ts +++ b/packages/gitbook/src/intl/translations/es.ts @@ -14,6 +14,10 @@ export const es: TranslationLanguage = { search_no_results_for: 'No hay resultados para "${1}".', search_no_results: 'No hay resultados', search_results_count: '${1} resultados', + search_page_result_title: 'Página con título ${1}', + search_section_result_title: 'Sección con título ${1}', + search_section_result_content: 'Sección con contenido ${1}', + search_section_result_default: 'Sección', search_scope_space: '${1}', search_scope_all: 'Todo el contenido', ask: 'Preguntar', diff --git a/packages/gitbook/src/intl/translations/fr.ts b/packages/gitbook/src/intl/translations/fr.ts index a84a0d52b7..657d1b4142 100644 --- a/packages/gitbook/src/intl/translations/fr.ts +++ b/packages/gitbook/src/intl/translations/fr.ts @@ -12,6 +12,10 @@ export const fr = { search_no_results_for: 'Aucun résultat pour « ${1} ».', search_no_results: 'Aucun résultat', search_results_count: '${1} résultats', + search_page_result_title: 'Page avec titre ${1}', + search_section_result_title: 'Section avec titre ${1}', + search_section_result_content: 'Section avec contenu ${1}', + search_section_result_default: 'Section', search_scope_space: '${1}', search_scope_all: 'Tous les contenus', ask: 'Poser une question', diff --git a/packages/gitbook/src/intl/translations/ja.ts b/packages/gitbook/src/intl/translations/ja.ts index 63a7d32290..6866d52134 100644 --- a/packages/gitbook/src/intl/translations/ja.ts +++ b/packages/gitbook/src/intl/translations/ja.ts @@ -14,6 +14,10 @@ export const ja: TranslationLanguage = { search_no_results_for: '"${1}" の結果はありません。', search_no_results: '結果がありません', search_results_count: '${1}件の結果', + search_page_result_title: 'タイトル「${1}」のページ', + search_section_result_title: 'タイトル「${1}」のセクション', + search_section_result_content: 'コンテンツ「${1}」のセクション', + search_section_result_default: 'セクション', search_scope_space: '${1}', search_scope_all: '全てのコンテンツ', ask: '質問する', diff --git a/packages/gitbook/src/intl/translations/nl.ts b/packages/gitbook/src/intl/translations/nl.ts index 32bc0080e8..cf287933db 100644 --- a/packages/gitbook/src/intl/translations/nl.ts +++ b/packages/gitbook/src/intl/translations/nl.ts @@ -14,6 +14,10 @@ export const nl: TranslationLanguage = { search_no_results_for: 'Geen resultaten voor "${1}".', search_no_results: 'Geen resultaten', search_results_count: '${1} resultaten', + search_page_result_title: 'Pagina met titel ${1}', + search_section_result_title: 'Sectie met titel ${1}', + search_section_result_content: 'Sectie met inhoud ${1}', + search_section_result_default: 'Sectie', search_scope_space: '${1}', search_scope_all: 'Alle inhoud', ask: 'Vragen', diff --git a/packages/gitbook/src/intl/translations/no.ts b/packages/gitbook/src/intl/translations/no.ts index 2c5995d4d0..313338df3a 100644 --- a/packages/gitbook/src/intl/translations/no.ts +++ b/packages/gitbook/src/intl/translations/no.ts @@ -14,6 +14,10 @@ export const no: TranslationLanguage = { search_no_results_for: 'Ingen resultater for "${1}".', search_no_results: 'Ingen resultater', search_results_count: '${1} resultater', + search_page_result_title: 'Side med tittel ${1}', + search_section_result_title: 'Seksjon med tittel ${1}', + search_section_result_content: 'Seksjon med innhold ${1}', + search_section_result_default: 'Seksjon', search_scope_space: '${1}', search_scope_all: 'Alt innhold', ask: 'Spør', diff --git a/packages/gitbook/src/intl/translations/pt-br.ts b/packages/gitbook/src/intl/translations/pt-br.ts index d505b7950d..b9ee6e76d3 100644 --- a/packages/gitbook/src/intl/translations/pt-br.ts +++ b/packages/gitbook/src/intl/translations/pt-br.ts @@ -12,6 +12,10 @@ export const pt_br = { search_no_results_for: 'Sem resultados para "${1}".', search_no_results: 'Sem resultados', search_results_count: '${1} resultados', + search_page_result_title: 'Página com título ${1}', + search_section_result_title: 'Seção com título ${1}', + search_section_result_content: 'Seção com conteúdo ${1}', + search_section_result_default: 'Seção', search_scope_space: '${1}', search_scope_all: 'Todo o conteúdo', ask: 'Perguntar', diff --git a/packages/gitbook/src/intl/translations/ru.ts b/packages/gitbook/src/intl/translations/ru.ts index c5ba4a023d..b193c37367 100644 --- a/packages/gitbook/src/intl/translations/ru.ts +++ b/packages/gitbook/src/intl/translations/ru.ts @@ -12,6 +12,10 @@ export const ru = { search_no_results_for: 'Нет результатов для "${1}".', search_no_results: 'Нет результатов', search_results_count: '${1} — число результатов', + search_page_result_title: 'Страница с заголовком ${1}', + search_section_result_title: 'Раздел с заголовком ${1}', + search_section_result_content: 'Раздел с контентом ${1}', + search_section_result_default: 'Раздел', search_scope_space: '${1}', search_scope_all: 'Все материалы', ask: 'Спросить', diff --git a/packages/gitbook/src/intl/translations/zh.ts b/packages/gitbook/src/intl/translations/zh.ts index 760e58dc42..1c13abec81 100644 --- a/packages/gitbook/src/intl/translations/zh.ts +++ b/packages/gitbook/src/intl/translations/zh.ts @@ -14,6 +14,10 @@ export const zh: TranslationLanguage = { search_no_results_for: '没有找到"${1}"的结果。', search_no_results: '没有找到结果', search_results_count: '${1} 个结果', + search_page_result_title: '标题为"${1}"的页面', + search_section_result_title: '标题为"${1}"的章节', + search_section_result_content: '内容为"${1}"的章节', + search_section_result_default: '章节', search_scope_space: '${1}', search_scope_all: '所有内容', ask: '询问',