Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 86 additions & 59 deletions src/components/Search/SearchAutocompleteList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type {ForwardedRef, RefObject} from 'react';
import React, {useContext, useEffect, useRef, useState} from 'react';
import React, {useContext, useEffect, useMemo, useRef, useState} from 'react';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {OptionsListStateContext, useOptionsList} from '@components/OptionListContextProvider';
import OptionsListSkeletonView from '@components/OptionsListSkeletonView';
Expand All @@ -11,6 +11,7 @@ import type {Section, SelectionListWithSectionsHandle} from '@components/Selecti
import useAutocompleteSuggestions from '@hooks/useAutocompleteSuggestions';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebounce from '@hooks/useDebounce';
import useDebouncedAccessibilityAnnouncement from '@hooks/useDebouncedAccessibilityAnnouncement';
import useFeedKeysWithAssignedCards from '@hooks/useFeedKeysWithAssignedCards';
import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -352,73 +353,86 @@ function SearchAutocompleteList({
}, [autocompleteQueryWithoutFilters, debounceHandleSearch]);

/* Sections generation */
const sections: Array<Section<AutocompleteListItem>> = [];
let sectionIndex = 0;
const {sections, styledRecentReports, suggestionsCount} = useMemo(() => {
const nextSections: Array<Section<AutocompleteListItem>> = [];
let sectionIndex = 0;
let nextSuggestionsCount = 0;

const pushSection = (section: Section<AutocompleteListItem>) => {
nextSections.push(section);
nextSuggestionsCount += section.data.filter((item) => item.keyForList !== CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.FIND_ITEM).length;
};

if (searchQueryItem) {
sections.push({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++});
}
if (searchQueryItem) {
pushSection({data: [searchQueryItem as AutocompleteListItem], sectionIndex: sectionIndex++});
}

const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex);
const additionalSections = getAdditionalSections?.(searchOptions, sectionIndex);

if (additionalSections) {
for (const section of additionalSections) {
sections.push(section);
sectionIndex++;
if (additionalSections) {
for (const section of additionalSections) {
pushSection(section);
sectionIndex++;
}
}
}

if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) {
sections.push({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++});
}
const styledRecentReports = recentReportsOptions.map((option) => {
const report = getReportOrDraftReport(option.reportID);
const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT;
const keyForList = option.keyForList ?? option.reportID ?? (option.accountID ? String(option.accountID) : undefined);
return {
...option,
keyForList,
pressableStyle: styles.br2,
text: StringUtils.lineBreaksToSpaces(shouldParserToHTML ? Parser.htmlToText(option.text ?? '') : (option.text ?? '')),
wrapperStyle: [styles.pr3, styles.pl3],
} as AutocompleteListItem;
});

sections.push({title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined, data: styledRecentReports, sectionIndex: sectionIndex++});
if (!autocompleteQueryValue && recentSearchesData && recentSearchesData.length > 0) {
pushSection({title: translate('search.recentSearches'), data: recentSearchesData as AutocompleteListItem[], sectionIndex: sectionIndex++});
}

if (autocompleteSuggestions.length > 0) {
const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => {
const nextStyledRecentReports = recentReportsOptions.map((option) => {
const report = getReportOrDraftReport(option.reportID);
const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
const shouldParserToHTML = reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT;
const keyForList = option.keyForList ?? option.reportID ?? (option.accountID ? String(option.accountID) : undefined);
return {
text: getAutocompleteDisplayText(filterKey, text),
mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined,
singleIcon: expensifyIcons.MagnifyingGlass,
searchQuery: text,
autocompleteID,
keyForList: autocompleteID ?? text, // in case we have a unique identifier then use it because text might not be unique
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION,
};
...option,
keyForList,
pressableStyle: styles.br2,
text: StringUtils.lineBreaksToSpaces(shouldParserToHTML ? Parser.htmlToText(option.text ?? '') : (option.text ?? '')),
wrapperStyle: [styles.pr3, styles.pl3],
} as AutocompleteListItem;
});

sections.push({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++});
}
pushSection({
title: autocompleteQueryValue.trim() === '' ? translate('search.recentChats') : undefined,
data: nextStyledRecentReports,
sectionIndex: sectionIndex++,
});

if (autocompleteSuggestions.length > 0) {
const autocompleteData: AutocompleteListItem[] = autocompleteSuggestions.map(({filterKey, text, autocompleteID, mapKey}) => {
return {
text: getAutocompleteDisplayText(filterKey, text),
mapKey: mapKey ? getSubstitutionMapKey(mapKey, text) : undefined,
singleIcon: expensifyIcons.MagnifyingGlass,
searchQuery: text,
autocompleteID,
keyForList: autocompleteID ?? text, // in case we have a unique identifier then use it because text might not be unique
searchItemType: CONST.SEARCH.SEARCH_ROUTER_ITEM_TYPE.AUTOCOMPLETE_SUGGESTION,
};
});

pushSection({title: translate('search.suggestions'), data: autocompleteData, sectionIndex: sectionIndex++});
}

return {sections: nextSections, styledRecentReports: nextStyledRecentReports, suggestionsCount: nextSuggestionsCount};
}, [autocompleteQueryValue, autocompleteSuggestions, expensifyIcons, getAdditionalSections, recentReportsOptions, recentSearchesData, searchOptions, searchQueryItem, styles, translate]);

const sectionItemText = sections?.at(1)?.data?.[0]?.text ?? '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CLEAN-REACT-PATTERNS-0 (docs)

React Compiler is enabled and will automatically memoize this pure computation. firstRecentReportFlatIndex is derived from firstRecentReportKey and sections with no side effects -- the compiler handles this without manual useMemo.

Remove the useMemo and compute the value directly:

let firstRecentReportFlatIndex = -1;
if (firstRecentReportKey) {
    let flatIndex = 0;
    for (const section of sections) {
        // ... existing iteration logic ...
    }
}

Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

const normalizedReferenceText = sectionItemText.toLowerCase();
const trimmedAutocompleteQueryValue = autocompleteQueryValue.trim();
const isLoading = !isRecentSearchesDataLoaded || !areOptionsInitialized;
const suggestionsAnnouncement = suggestionsCount > 0 ? translate('search.suggestionsAvailable', {count: suggestionsCount}, trimmedAutocompleteQueryValue) : '';
useDebouncedAccessibilityAnnouncement(suggestionsAnnouncement, !!suggestionsAnnouncement, autocompleteQueryValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should 3rd param be trimmedAutocompleteQueryValue?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That third param is only used as the debounce signal in useDebouncedAccessibilityAnnouncement, so using autocompleteQueryValue makes whitespace edits reset the timer too. The announcement text itself already uses the trimmed value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No announcement on search router. Works fine on Report search though

Image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no announcement when no results found. Should we also fix this or out of scope?

Copy link
Member

@rushatgabhane rushatgabhane Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we fixed this on main, let's not undo that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure we fixed this on main, let's not undo that

Which PR?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@aimane-chnaif aimane-chnaif Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page is a bit different. This search is using separate input, not part of SelectionListWithSections

search

And even though no results found, there's always item to be clickable. Not sure what to announce in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fix it btw, feel free to implement if you want

 const suggestionsAnnouncement = suggestionsCount > 0 ? translate('search.suggestionsAvailable', {count: suggestionsCount}, trimmedAutocompleteQueryValue) : '';
 useDebouncedAccessibilityAnnouncement(suggestionsAnnouncement, !!suggestionsAnnouncement, autocompleteQueryValue);
+
+const noResultsFoundText = translate('common.noResultsFound');
+const shouldAnnounceNoResults = suggestionsCount === 0 && !!trimmedAutocompleteQueryValue;
+useDebouncedAccessibilityAnnouncement(noResultsFoundText, shouldAnnounceNoResults, autocompleteQueryValue);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated (with isLoading check as well since it shouldn't be announced when results are loading)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aimane-chnaif Can you verify?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified


const firstRecentReportKey = styledRecentReports.at(0)?.keyForList;
const noResultsFoundText = translate('common.noResultsFound');
const shouldAnnounceNoResults = !isLoading && suggestionsCount === 0 && !!trimmedAutocompleteQueryValue;
useDebouncedAccessibilityAnnouncement(noResultsFoundText, shouldAnnounceNoResults, autocompleteQueryValue);

// When options initialize after the list is already mounted, initiallyFocusedItemKey has no effect
// because useState(initialFocusedIndex) in useArrowKeyFocusManager only reads the initial value.
// Imperatively focus the first recent report once options become available (desktop only).
useEffect(() => {
if (shouldUseNarrowLayout || !areOptionsInitialized || hasSetInitialFocusRef.current || !firstRecentReportKey) {
return;
}
hasSetInitialFocusRef.current = true;

// Compute the flat index of firstRecentReportKey by replicating the flattening logic
// from useFlattenedSections: each section may prepend a header row when it has a title/customHeader.
const firstRecentReportKey = styledRecentReports.at(0)?.keyForList;
let firstRecentReportFlatIndex = -1;
if (firstRecentReportKey) {
let flatIndex = 0;
for (const section of sections) {
const hasData = (section.data?.length ?? 0) > 0;
Expand All @@ -428,13 +442,28 @@ function SearchAutocompleteList({
}
for (const item of section.data ?? []) {
if (item.keyForList === firstRecentReportKey) {
innerListRef.current?.updateAndScrollToFocusedIndex(flatIndex, false);
return;
firstRecentReportFlatIndex = flatIndex;
break;
}
flatIndex++;
}
if (firstRecentReportFlatIndex !== -1) {
break;
}
}
}, [areOptionsInitialized, firstRecentReportKey, sections, shouldUseNarrowLayout]);
}

// When options initialize after the list is already mounted, initiallyFocusedItemKey has no effect
// because useState(initialFocusedIndex) in useArrowKeyFocusManager only reads the initial value.
// Imperatively focus the first recent report once options become available (desktop only).
useEffect(() => {
if (shouldUseNarrowLayout || !areOptionsInitialized || hasSetInitialFocusRef.current || firstRecentReportFlatIndex === -1) {
return;
}
hasSetInitialFocusRef.current = true;

innerListRef.current?.updateAndScrollToFocusedIndex(firstRecentReportFlatIndex, false);
}, [areOptionsInitialized, firstRecentReportFlatIndex, shouldUseNarrowLayout]);

useEffect(() => {
const targetText = autocompleteQueryValue;
Expand All @@ -444,8 +473,6 @@ function SearchAutocompleteList({
}
}, [autocompleteQueryValue, onHighlightFirstItem, normalizedReferenceText]);

const isLoading = !isRecentSearchesDataLoaded || !areOptionsInitialized;

const reasonAttributes: SkeletonSpanReasonAttributes = {
context: 'SearchAutocompleteList',
isRecentSearchesDataLoaded,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ function BaseSelectionListWithSections<TItem extends ListItem>({
accessibilityLabel={textInputOptions?.label}
options={textInputOptions}
onSubmit={selectFocusedItem}
dataLength={flattenedData.length}
dataLength={itemsCount}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this change for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to keep the announced count aligned with actual suggestion rows. flattenedData.length includes section headers, while itemsCount only counts real items, so dataLength={itemsCount} avoids overcounting when headers are present.

isLoading={isLoadingNewOptions}
onFocusChange={(v: boolean) => (isTextInputFocusedRef.current = v)}
shouldShowLoadingPlaceholder={shouldShowLoadingPlaceholder}
Expand Down
7 changes: 7 additions & 0 deletions src/components/SelectionList/components/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,15 @@ function TextInput({
const isNoResultsFoundMessage = headerMessage === noResultsFoundText;
const noData = dataLength === 0 && !shouldShowLoadingPlaceholder;
const shouldShowHeaderMessage = !!shouldShowTextInput && !!headerMessage && (!isLoadingNewOptions || !isNoResultsFoundMessage || noData);
const trimmedSearchValue = value?.trim() ?? '';
const suggestionsCount = dataLength ?? 0;
const suggestionsAnnouncement =
!!shouldShowTextInput && !shouldShowLoadingPlaceholder && !isLoadingNewOptions && suggestionsCount > 0
? translate('search.suggestionsAvailable', {count: suggestionsCount}, trimmedSearchValue)
: '';

useDebouncedAccessibilityAnnouncement(headerMessage ?? '', shouldShowHeaderMessage, value ?? '');
useDebouncedAccessibilityAnnouncement(suggestionsAnnouncement, !!suggestionsAnnouncement, value ?? '');

const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const mergedRef = mergeRefs<BaseTextInputRef>(ref, optionsRef);
Expand Down
23 changes: 21 additions & 2 deletions src/hooks/useAccessibilityAnnouncement/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,35 @@

let wrapper: HTMLDivElement | null = null;

function getAnnouncementRoot(): HTMLElement {
const activeElement = document.activeElement;
const activeDialog = activeElement instanceof HTMLElement ? activeElement.closest<HTMLElement>('[role="dialog"][aria-modal="true"]') : null;

if (activeDialog) {
return activeDialog;
}

const modalDialogs = document.querySelectorAll<HTMLElement>('[role="dialog"][aria-modal="true"]');
return modalDialogs.item(modalDialogs.length - 1) ?? document.body;
}

function getWrapper(): HTMLDivElement {
if (wrapper && document.body.contains(wrapper)) {
const root = getAnnouncementRoot();

if (wrapper && root.contains(wrapper)) {
return wrapper;
}

if (wrapper && wrapper.parentElement && wrapper.parentElement !== root) {

Check failure on line 46 in src/hooks/useAccessibilityAnnouncement/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer using an optional chain expression instead, as it's more concise and easier to read

Check failure on line 46 in src/hooks/useAccessibilityAnnouncement/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Prefer using an optional chain expression instead, as it's more concise and easier to read

Check failure on line 46 in src/hooks/useAccessibilityAnnouncement/index.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer using an optional chain expression instead, as it's more concise and easier to read

Check failure on line 46 in src/hooks/useAccessibilityAnnouncement/index.ts

View workflow job for this annotation

GitHub Actions / ESLint check

Prefer using an optional chain expression instead, as it's more concise and easier to read
wrapper.parentElement.removeChild(wrapper);
wrapper = null;
}

wrapper = document.createElement('div');
wrapper.setAttribute('aria-live', 'assertive');
wrapper.setAttribute('aria-atomic', 'true');
Object.assign(wrapper.style, VISUALLY_HIDDEN_STYLE);
document.body.appendChild(wrapper);
root.appendChild(wrapper);

return wrapper;
}
Expand Down
4 changes: 4 additions & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7417,6 +7417,10 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und
searchIn: 'Suchen in',
searchPlaceholder: 'Nach etwas suchen',
suggestions: 'Vorschläge',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Vorschläge verfügbar${query ? ` für ${query}` : ''}. ${count} Ergebnis.`,
other: (resultCount: number) => `Vorschläge verfügbar${query ? ` für ${query}` : ''}. ${resultCount} Ergebnisse.`,
}),
exportSearchResults: {
title: 'Export erstellen',
description: 'Wow, das sind aber viele Elemente! Wir bündeln sie, und Concierge schickt dir in Kürze eine Datei.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7404,6 +7404,10 @@ const translations = {
searchIn: 'Search in',
searchPlaceholder: 'Search for something',
suggestions: 'Suggestions',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Suggestions available${query ? ` for ${query}` : ''}. ${count} result.`,
other: (resultCount: number) => `Suggestions available${query ? ` for ${query}` : ''}. ${resultCount} results.`,
}),
exportSearchResults: {
title: 'Create export',
description: "Whoa, that's a lot of items! We'll bundle them up, and Concierge will send you a file shortly.",
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7292,6 +7292,10 @@ ${amount} para ${merchant} - ${date}`,
searchIn: 'Buscar en',
searchPlaceholder: 'Busca algo',
suggestions: 'Sugerencias',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Sugerencias disponibles${query ? ` para ${query}` : ''}. ${count} resultado.`,
other: (resultCount: number) => `Sugerencias disponibles${query ? ` para ${query}` : ''}. ${resultCount} resultados.`,
}),
exportSearchResults: {
title: 'Crear exportación',
description: '¡Wow, esos son muchos elementos! Los agruparemos y Concierge te enviará un archivo en breve.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7441,6 +7441,10 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip
searchIn: 'Rechercher dans',
searchPlaceholder: 'Rechercher quelque chose',
suggestions: 'Suggestions',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Suggestions disponibles${query ? ` pour ${query}` : ''}. ${count} résultat.`,
other: (resultCount: number) => `Suggestions disponibles${query ? ` pour ${query}` : ''}. ${resultCount} résultats.`,
}),
exportSearchResults: {
title: 'Créer l’export',
description: 'Ouah, ça fait beaucoup d’éléments ! Nous allons les regrouper et Concierge vous enverra un fichier sous peu.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7405,6 +7405,10 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo
searchIn: 'Cerca in',
searchPlaceholder: 'Cerca qualcosa',
suggestions: 'Suggerimenti',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Suggerimenti disponibili${query ? ` per ${query}` : ''}. ${count} risultato.`,
other: (resultCount: number) => `Suggerimenti disponibili${query ? ` per ${query}` : ''}. ${resultCount} risultati.`,
}),
exportSearchResults: {
title: 'Crea esportazione',
description: 'Wow, sono davvero tanti elementi! Li raggrupperemo e Concierge ti invierà un file a breve.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7313,6 +7313,10 @@ ${reportName}
searchIn: '検索対象',
searchPlaceholder: '何かを検索',
suggestions: '提案',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `候補があります${query ? `: ${query}` : ''}。${count}件の結果。`,
other: (resultCount: number) => `候補があります${query ? `: ${query}` : ''}。${resultCount}件の結果。`,
}),
exportSearchResults: {
title: 'エクスポートを作成',
description: 'おっと、アイテムがたくさんありますね!まとめて整理して、間もなくConciergeからファイルをお送りします。',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7371,6 +7371,10 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar
searchIn: 'Zoeken in',
searchPlaceholder: 'Zoek iets',
suggestions: 'Suggesties',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Suggesties beschikbaar${query ? ` voor ${query}` : ''}. ${count} resultaat.`,
other: (resultCount: number) => `Suggesties beschikbaar${query ? ` voor ${query}` : ''}. ${resultCount} resultaten.`,
}),
exportSearchResults: {
title: 'Export maken',
description: 'Wow, dat zijn veel items! We bundelen ze, en Concierge stuurt je binnenkort een bestand.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7372,6 +7372,10 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i
searchIn: 'Szukaj w',
searchPlaceholder: 'Wyszukaj coś',
suggestions: 'Sugestie',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Dostępne sugestie${query ? ` dla ${query}` : ''}. ${count} wynik.`,
other: (resultCount: number) => `Dostępne sugestie${query ? ` dla ${query}` : ''}. ${resultCount} wyniki.`,
}),
exportSearchResults: {
title: 'Utwórz eksport',
description: 'Wow, ale dużo pozycji! Spakujemy je, a Concierge wkrótce wyśle Ci plik.',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7359,6 +7359,10 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e
searchIn: 'Pesquisar em',
searchPlaceholder: 'Pesquisar algo',
suggestions: 'Sugestões',
suggestionsAvailable: ({count}: {count: number}, query = '') => ({
one: `Sugestões disponíveis${query ? ` para ${query}` : ''}. ${count} resultado.`,
other: (resultCount: number) => `Sugestões disponíveis${query ? ` para ${query}` : ''}. ${resultCount} resultados.`,
}),
exportSearchResults: {
title: 'Criar exportação',
description: 'Uau, são muitos itens! Vamos agrupá-los e o Concierge enviará um arquivo para você em breve.',
Expand Down
Loading
Loading