search plugin support i18n

Signed-off-by: mario ma <mario.ma.node@gmail.com>
This commit is contained in:
mario ma
2025-03-26 23:10:41 +08:00
parent 714e86684e
commit fa485943e4
18 changed files with 182 additions and 24 deletions
+1
View File
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export * from './blueprints';
export { searchReactTranslationRef } from '../translation';
@@ -38,6 +38,8 @@ import {
} from 'react';
import useDebounce from 'react-use/esm/useDebounce';
import { SearchContextProvider, useSearch } from '../../context';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { searchReactTranslationRef } from '../../translation';
/**
* Props for {@link SearchBarBase}.
@@ -81,6 +83,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => {
const configApi = useApi(configApiRef);
const [value, setValue] = useState<string>('');
const forwardedValueRef = useRef<string>('');
const { t } = useTranslationRef(searchReactTranslationRef);
useEffect(() => {
setValue(prevValue => {
@@ -129,12 +132,15 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => {
}
}, [onChange, onClear]);
const ariaLabel: string | undefined = label ? undefined : 'Search';
const ariaLabel: string | undefined = label
? undefined
: t('searchBar.title');
const inputPlaceholder =
placeholder ??
`Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`;
t('searchBar.placeholder', {
org: configApi.getOptionalString('app.title') || 'Backstage',
});
const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon;
const startAdornment = (
@@ -31,6 +31,8 @@ import {
} from './SearchFilter.Autocomplete';
import { useAsyncFilterValues, useDefaultFilterValue } from './hooks';
import { ensureFilterValueWithLabel, FilterValue } from './types';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { searchReactTranslationRef } from '../../translation';
const useStyles = makeStyles({
label: {
@@ -165,6 +167,7 @@ export const SelectFilter = (props: SearchFilterComponentProps) => {
values: givenValues,
valuesDebounceMs,
} = props;
const { t } = useTranslationRef(searchReactTranslationRef);
useDefaultFilterValue(name, defaultValue);
const asyncValues =
typeof givenValues === 'function' ? givenValues : undefined;
@@ -179,7 +182,10 @@ export const SelectFilter = (props: SearchFilterComponentProps) => {
valuesDebounceMs,
);
const allOptionValue = useRef(uuid());
const allOption = { value: allOptionValue.current, label: 'All' };
const allOption = {
value: allOptionValue.current,
label: t('searchFilter.allOptionTitle'),
};
const { filters, setFilters } = useSearch();
const handleChange = (value: SelectedItems) => {
@@ -23,6 +23,8 @@ import {
} from 'react';
import TablePagination from '@material-ui/core/TablePagination';
import { useSearch } from '../../context';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { searchReactTranslationRef } from '../../translation';
const encodePageCursor = (pageCursor: number): string => {
return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64');
@@ -119,15 +121,18 @@ export type SearchPaginationBaseProps = {
* @public
*/
export const SearchPaginationBase = (props: SearchPaginationBaseProps) => {
const { t } = useTranslationRef(searchReactTranslationRef);
const {
total: count = -1,
cursor: pageCursor,
hasNextPage,
onCursorChange: onPageCursorChange,
limit: rowsPerPage = 25,
limitLabel: labelRowsPerPage = 'Results per page:',
limitLabel: labelRowsPerPage = t('searchPagination.limitLabel'),
limitText: labelDisplayedRows = ({ from, to }) =>
count > 0 ? `of ${count}` : `${from}-${to}`,
count > 0
? t('searchPagination.limitText', { num: `${count}` })
: `${from}-${to}`,
limitOptions: rowsPerPageOptions,
onLimitChange: onPageLimitChange,
...rest
@@ -32,6 +32,8 @@ import {
SearchResultListItemExtensions,
SearchResultListItemExtensionsProps,
} from '../../extensions';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { searchReactTranslationRef } from '../../translation';
/**
* Props for {@link SearchResultContext}
@@ -186,11 +188,12 @@ export type SearchResultProps = Pick<SearchResultStateProps, 'query'> &
* @public
*/
export const SearchResultComponent = (props: SearchResultProps) => {
const { t } = useTranslationRef(searchReactTranslationRef);
const {
query,
children,
noResultsComponent = (
<EmptyState missing="data" title="Sorry, no results were found" />
<EmptyState missing="data" title={t('noResultsDescription')} />
),
...rest
} = props;
@@ -51,6 +51,8 @@ import { useSearchResultListItemExtensions } from '../../extensions';
import { DefaultResultListItem } from '../DefaultResultListItem';
import { SearchResultState, SearchResultStateProps } from '../SearchResult';
import { searchReactTranslationRef } from '../../translation';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
const useStyles = makeStyles((theme: Theme) => ({
listSubheader: {
@@ -356,6 +358,7 @@ export function SearchResultGroupLayout<FilterOption>(
) {
const classes = useStyles();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const { t } = useTranslationRef(searchReactTranslationRef);
const {
error,
@@ -365,7 +368,7 @@ export function SearchResultGroupLayout<FilterOption>(
titleProps = {},
link = (
<>
See all
{t('searchResultGroup.linkTitle')}
<ArrowRightIcon className={classes.listSubheaderLinkIcon} />
</>
),
@@ -387,7 +390,7 @@ export function SearchResultGroupLayout<FilterOption>(
),
disableRenderingWithNoResults,
noResultsComponent = disableRenderingWithNoResults ? null : (
<EmptyState missing="data" title="Sorry, no results were found" />
<EmptyState missing="data" title={t('noResultsDescription')} />
),
...rest
} = props;
@@ -434,7 +437,7 @@ export function SearchResultGroupLayout<FilterOption>(
component="button"
icon={<AddIcon />}
variant="outlined"
label="Add filter"
label={t('searchResultGroup.addFilterButtonTitle')}
aria-controls="filters-menu"
aria-haspopup="true"
onClick={handleClick}
@@ -30,6 +30,8 @@ import { useSearchResultListItemExtensions } from '../../extensions';
import { DefaultResultListItem } from '../DefaultResultListItem';
import { SearchResultState, SearchResultStateProps } from '../SearchResult';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { searchReactTranslationRef } from '../../translation';
/**
* Props for {@link SearchResultListLayout}
@@ -72,6 +74,7 @@ export type SearchResultListLayoutProps = ListProps & {
* @public
*/
export const SearchResultListLayout = (props: SearchResultListLayoutProps) => {
const { t } = useTranslationRef(searchReactTranslationRef);
const {
error,
loading,
@@ -84,7 +87,7 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => {
),
disableRenderingWithNoResults,
noResultsComponent = disableRenderingWithNoResults ? null : (
<EmptyState missing="data" title="Sorry, no results were found" />
<EmptyState missing="data" title={t('noResultsDescription')} />
),
...rest
} = props;
@@ -20,6 +20,8 @@ import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
import { useSearch } from '../../context';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { searchReactTranslationRef } from '../../translation';
const useStyles = makeStyles(theme => ({
root: {
@@ -36,6 +38,7 @@ const useStyles = makeStyles(theme => ({
export const SearchResultPager = () => {
const { fetchNextPage, fetchPreviousPage } = useSearch();
const classes = useStyles();
const { t } = useTranslationRef(searchReactTranslationRef);
if (!fetchNextPage && !fetchPreviousPage) {
return <></>;
@@ -49,7 +52,7 @@ export const SearchResultPager = () => {
onClick={fetchPreviousPage}
startIcon={<ArrowBackIosIcon />}
>
Previous
{t('searchResultPager.previous')}
</Button>
<Button
@@ -58,7 +61,7 @@ export const SearchResultPager = () => {
onClick={fetchNextPage}
endIcon={<ArrowForwardIosIcon />}
>
Next
{t('searchResultPager.next')}
</Button>
</nav>
);
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createTranslationRef } from '@backstage/core-plugin-api/alpha';
export const searchReactTranslationRef = createTranslationRef({
id: 'search-react',
messages: {
searchBar: {
title: 'Search',
placeholder: 'Search in {{org}}',
},
searchFilter: {
allOptionTitle: 'All',
},
searchPagination: {
limitLabel: 'Results per page:',
limitText: 'of {{num}}',
},
noResultsDescription: 'Sorry, no results were found',
searchResultGroup: {
linkTitle: 'See All',
addFilterButtonTitle: 'Add filter',
},
searchResultPager: {
previous: 'Previous',
next: 'Next',
},
},
});