From ff7e12632dde109b909948ed693f4da36e91eab5 Mon Sep 17 00:00:00 2001 From: rui ma Date: Mon, 8 Jan 2024 23:55:00 +0800 Subject: [PATCH 1/7] feat: support i18n for core component Signed-off-by: rui ma --- .changeset/flat-badgers-attack.md | 5 + packages/core-components/api-report-alpha.md | 73 ++++++++++ packages/core-components/package.json | 4 + packages/core-components/src/alpha.ts | 16 +++ .../components/AlertDisplay/AlertDisplay.tsx | 14 +- .../components/AutoLogout/Autologout.test.tsx | 50 +++---- .../AutoLogout/StillTherePrompt.tsx | 15 +- .../CopyTextButton/CopyTextButton.tsx | 5 +- .../MissingAnnotationEmptyState.tsx | 26 ++-- .../src/components/Link/Link.tsx | 5 +- .../LoginRequestListItem.tsx | 5 +- .../OAuthRequestDialog/OAuthRequestDialog.tsx | 13 +- .../SimpleStepper/SimpleStepperFooter.tsx | 82 ++++++----- .../SupportButton/SupportButton.tsx | 7 +- .../src/components/Table/Filters.tsx | 7 +- .../src/hooks/useSupportConfig.ts | 38 ++--- .../layout/ErrorBoundary/ErrorBoundary.tsx | 13 +- .../src/layout/ErrorPage/ErrorPage.tsx | 17 ++- .../ProxiedSignInPage/ProxiedSignInPage.tsx | 10 +- .../src/layout/Sidebar/Bar.tsx | 5 +- .../src/layout/SignInPage/SignInPage.tsx | 5 +- .../src/layout/SignInPage/commonProvider.tsx | 7 +- .../src/layout/SignInPage/customProvider.tsx | 17 ++- packages/core-components/src/translation.ts | 130 ++++++++++++++++++ .../components/EntityBadgesDialog.test.tsx | 4 +- .../BitriseBuildsComponent.test.tsx | 6 +- plugins/git-release-manager/package.json | 1 + .../src/features/Features.test.tsx | 36 +++-- .../src/features/Info/Info.test.tsx | 5 +- .../src/features/Patch/PatchBody.test.tsx | 49 +++++-- .../GoCdBuildsComponent.test.tsx | 4 +- .../components/VisitList/VisitList.test.tsx | 112 +++++++-------- .../Pods/FixDialog/FixDialog.test.tsx | 15 +- .../TechDocsSearchResultListItem.test.tsx | 8 +- .../src/components/TodoList/TodoList.test.tsx | 4 +- .../EntityVaultCard/EntityVaultCard.test.tsx | 9 +- yarn.lock | 1 + 37 files changed, 575 insertions(+), 248 deletions(-) create mode 100644 .changeset/flat-badgers-attack.md create mode 100644 packages/core-components/api-report-alpha.md create mode 100644 packages/core-components/src/alpha.ts create mode 100644 packages/core-components/src/translation.ts diff --git a/.changeset/flat-badgers-attack.md b/.changeset/flat-badgers-attack.md new file mode 100644 index 0000000000..69250d0ded --- /dev/null +++ b/.changeset/flat-badgers-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Support i18n for core components diff --git a/packages/core-components/api-report-alpha.md b/packages/core-components/api-report-alpha.md new file mode 100644 index 0000000000..a51e590812 --- /dev/null +++ b/packages/core-components/api-report-alpha.md @@ -0,0 +1,73 @@ +## API Report File for "@backstage/core-components" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; + +// @alpha (undocumented) +export const coreComponentsTranslationRef: TranslationRef< + 'core-components', + { + readonly 'link.openNewWindow': 'Opens in a new window'; + readonly 'table.filter.title': 'Filters'; + readonly 'table.filter.clearAll': 'Clear all'; + readonly 'signIn.title': 'Sign In'; + readonly 'signIn.loginFailed': 'Login failed'; + readonly 'signIn.customProvider.title': 'Custom User'; + readonly 'signIn.customProvider.subtitle2': 'This selection will not be stored.'; + readonly 'signIn.customProvider.subtitle': 'Enter your own User ID and credentials.'; + readonly 'signIn.customProvider.userId': 'User ID'; + readonly 'signIn.customProvider.tokenInvalid': 'Token is not a valid OpenID Connect JWT Token'; + readonly 'signIn.customProvider.continue': 'Continue'; + readonly 'signIn.customProvider.idToken': 'ID Token (optional)'; + readonly 'signIn.guestProvider.title': 'Guest'; + readonly 'signIn.guestProvider.description': 'You will not have a verified identity, meaning some features might be unavailable.'; + readonly 'signIn.guestProvider.enter': 'Enter'; + readonly 'signIn.guestProvider.subtitle': 'Enter as a Guest User.'; + readonly 'sidebar.shipToContent': 'Skip to content'; + readonly 'sidebar.starredIntroText': 'Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav.Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!'; + readonly 'sidebar.recentlyViewedIntroText': 'And your recently viewed plugins will pop up here!'; + readonly 'sidebar.dismiss': 'Dismiss'; + readonly 'copyTextButton.tooltipText': 'Text copied to clipboard'; + readonly 'simpleStepper.finish': 'Finish'; + readonly 'simpleStepper.reset': 'Reset'; + readonly 'simpleStepper.next': 'Next'; + readonly 'simpleStepper.skip': 'Skip'; + readonly 'simpleStepper.back': 'Back'; + readonly 'errorPage.title': 'Looks like someone dropped the mic!'; + readonly 'errorPage.subtitle': 'ERROR {{status}}: {{statusMessage}}'; + readonly 'errorPage.goBack': 'Go back'; + readonly 'errorPage.orPlease': '... or please '; + readonly 'errorPage.contactSupport': 'contact support'; + readonly 'errorPage.isBug': 'if you think this is a bug.'; + readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; + readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; + readonly 'emptyState.missingAnnotation.readMore': 'Read more'; + readonly 'emptyState.missingAnnotation.descriptionPrefix_one': 'The annotation '; + readonly 'emptyState.missingAnnotation.descriptionPrefix_other': 'The annotations '; + readonly 'emptyState.missingAnnotation.descriptionSuffix_one': ' is missing. You need to add the annotation to your component if you want to enable this tool.'; + readonly 'emptyState.missingAnnotation.descriptionSuffix_other': ' are missing. You need to add the annotations to your component if you want to enable this tool.'; + readonly 'supportConfig.title': 'Support Not Configured'; + readonly 'supportConfig.links.title': 'Add `app.support` config key'; + readonly 'errorBoundary.title': 'Please contact {{slackChannel}} for help.'; + readonly 'oauthRequestDialog.title': 'Login Required'; + readonly 'oauthRequestDialog.authRedirectTitle': 'This will trigger a http redirect to OAuth Login.'; + readonly 'oauthRequestDialog.login': 'Log in'; + readonly 'oauthRequestDialog.rejectAll': 'Reject All'; + readonly 'supportButton.title': 'Support'; + readonly 'supportButton.close': 'Close'; + readonly 'alertDisplay.message_one': '({{ num }} older message)'; + readonly 'alertDisplay.message_other': '({{ num }} older messages)'; + readonly 'autoLogout.stillTherePrompt.title': 'Logging out due to inactivity'; + readonly 'autoLogout.stillTherePrompt.description': 'You are about to be disconnected in'; + readonly 'autoLogout.stillTherePrompt.second_one': 'second'; + readonly 'autoLogout.stillTherePrompt.second_other': 'seconds'; + readonly 'autoLogout.stillTherePrompt.descriptionSuffix': 'Are you still there?'; + readonly 'autoLogout.stillTherePrompt.buttonText': "Yes! Don't log me out"; + readonly 'proxiedSignInPage.title': 'You do not appear to be signed in. Please try reloading the browser page.'; + } +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 73de44d5ed..04edbeb212 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -22,11 +22,15 @@ "types": "src/index.ts", "exports": { ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, "typesVersions": { "*": { + "alpha": [ + "src/alpha.ts" + ], "testUtils": [ "src/testUtils.ts" ], diff --git a/packages/core-components/src/alpha.ts b/packages/core-components/src/alpha.ts new file mode 100644 index 0000000000..e1f7678bae --- /dev/null +++ b/packages/core-components/src/alpha.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2023 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. + */ +export * from './translation'; diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx index b1c8273749..19527f76c2 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ import { alertApiRef, AlertMessage, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import IconButton from '@material-ui/core/IconButton'; import Snackbar from '@material-ui/core/Snackbar'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import { Alert } from '@material-ui/lab'; -import pluralize from 'pluralize'; import React, { useEffect, useState } from 'react'; +import { coreComponentsTranslationRef } from '../../translation'; /** * Properties for {@link AlertDisplay} @@ -63,6 +64,7 @@ export type AlertDisplayProps = { export function AlertDisplay(props: AlertDisplayProps) { const [messages, setMessages] = useState>([]); const alertApi = useApi(alertApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const { anchorOrigin = { vertical: 'top', horizontal: 'center' }, @@ -121,10 +123,12 @@ export function AlertDisplay(props: AlertDisplayProps) { {String(firstMessage.message)} {messages.length > 1 && ( - {` (${messages.length - 1} older ${pluralize( - 'message', - messages.length - 1, - )})`} + + {t('alertDisplay.message', { + num: String(messages.length - 1), + count: messages.length - 1, + })} + )} diff --git a/packages/core-components/src/components/AutoLogout/Autologout.test.tsx b/packages/core-components/src/components/AutoLogout/Autologout.test.tsx index 0d414524a5..164ad716d2 100644 --- a/packages/core-components/src/components/AutoLogout/Autologout.test.tsx +++ b/packages/core-components/src/components/AutoLogout/Autologout.test.tsx @@ -18,16 +18,18 @@ import { createMocks } from 'react-idle-timer'; import { MessageChannel } from 'worker_threads'; import { ApiProvider } from '@backstage/core-app-api'; import { identityApiRef } from '@backstage/core-plugin-api'; -import { TestApiRegistry } from '@backstage/test-utils'; +import { TestApiRegistry, renderInTestApp } from '@backstage/test-utils'; import React from 'react'; import { AutoLogout } from './AutoLogout'; -import { cleanup, render } from '@testing-library/react'; +import { cleanup } from '@testing-library/react'; // Mock the signOut function of identityApiRef const mockSignOut = jest.fn(); -const mockIdentityApi = { signOut: mockSignOut }; - +const mockIdentityApi = { + signOut: mockSignOut, + getCredentials: jest.fn().mockReturnValue({ token: 'xxx' }), +}; const apis = TestApiRegistry.from([identityApiRef, mockIdentityApi]); describe('AutoLogout', () => { @@ -44,27 +46,29 @@ describe('AutoLogout', () => { }); it('should throw error if idleTimeoutMinutes is smaller than promptBeforeSeconds', async () => { - expect(() => - render( - - - , - ), - ).toThrow(); + await expect( + async () => + await renderInTestApp( + + + , + ), + ).rejects.toThrow(); }); it('should throw error if idleTimeoutMinutes is smaller than 30 seconds', async () => { - expect(() => - render( - - -
Test Child
-
, - ), - ).toThrow(); + await expect( + async () => + await renderInTestApp( + + +
Test Child
+
, + ), + ).rejects.toThrow(); }); }); diff --git a/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx b/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx index e0cfc8dd56..c704cf9910 100644 --- a/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx +++ b/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx @@ -22,6 +22,8 @@ import DialogContentText from '@material-ui/core/DialogContentText'; import DialogTitle from '@material-ui/core/DialogTitle'; import React, { useEffect } from 'react'; import { IIdleTimer } from 'react-idle-timer'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export interface StillTherePromptProps { idleTimer: IIdleTimer; @@ -41,6 +43,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { remainingTime, setRemainingTime, } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); useEffect(() => { const interval = setInterval(() => { @@ -61,18 +64,18 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { remainingTime - promptTimeoutMillis / 1000, 0, ); - const seconds = timeTillPrompt > 1 ? 'seconds' : 'second'; return ( - Logging out due to inactivity + {t('autoLogout.stillTherePrompt.title')} - You are about to be disconnected in{' '} + {t('autoLogout.stillTherePrompt.description')}{' '} - {Math.ceil(remainingTime / 1000)} {seconds} + {Math.ceil(remainingTime / 1000)}{' '} + {t('autoLogout.stillTherePrompt.second', { count: timeTillPrompt })} - . Are you still there? + . {t('autoLogout.stillTherePrompt.descriptionSuffix')} @@ -82,7 +85,7 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { variant="contained" size="small" > - Yes! Don't log me out + {t('autoLogout.stillTherePrompt.buttonText')} diff --git a/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx b/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx index bd900a931d..91b046354a 100644 --- a/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx +++ b/packages/core-components/src/components/CopyTextButton/CopyTextButton.tsx @@ -20,6 +20,8 @@ import Tooltip from '@material-ui/core/Tooltip'; import CopyIcon from '@material-ui/icons/FileCopy'; import React, { MouseEventHandler, useEffect, useState } from 'react'; import useCopyToClipboard from 'react-use/lib/useCopyToClipboard'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** * Properties for {@link CopyTextButton} @@ -78,10 +80,11 @@ export interface CopyTextButtonProps { * ``` */ export function CopyTextButton(props: CopyTextButtonProps) { + const { t } = useTranslationRef(coreComponentsTranslationRef); const { text, tooltipDelay = 1000, - tooltipText = 'Text copied to clipboard', + tooltipText = t('copyTextButton.tooltipText'), 'aria-label': ariaLabel = 'Copy text', } = props; const errorApi = useApi(errorApiRef); diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx index 81b4714531..8c3f8c186f 100644 --- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx @@ -23,6 +23,8 @@ import React from 'react'; import { CodeSnippet } from '../CodeSnippet'; import { Link } from '../Link'; import { EmptyState } from './EmptyState'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { coreComponentsTranslationRef } from '../../translation'; const COMPONENT_YAML_TEMPLATE = `apiVersion: backstage.io/v1alpha1 kind: Component @@ -76,11 +78,13 @@ function generateComponentYaml(annotations: string[]) { return COMPONENT_YAML_TEMPLATE.replace(ANNOTATION_YAML, annotationYaml); } -function generateDescription(annotations: string[]) { - const isSingular = annotations.length <= 1; +function useGenerateDescription(annotations: string[]) { + const { t } = useTranslationRef(coreComponentsTranslationRef); return ( <> - The {isSingular ? 'annotation' : 'annotations'}{' '} + {t('emptyState.missingAnnotation.descriptionPrefix', { + count: annotations.length, + })} {annotations .map(ann => {ann}) .reduce((prev, curr) => ( @@ -88,9 +92,9 @@ function generateDescription(annotations: string[]) { {prev}, {curr} ))}{' '} - {isSingular ? 'is' : 'are'} missing. You need to add the{' '} - {isSingular ? 'annotation' : 'annotations'} to your component if you want - to enable this tool. + {t('emptyState.missingAnnotation.descriptionSuffix', { + count: annotations.length, + })} ); } @@ -106,17 +110,17 @@ export function MissingAnnotationEmptyState(props: Props) { readMoreUrl || 'https://backstage.io/docs/features/software-catalog/well-known-annotations'; const classes = useStyles(); + const { t } = useTranslationRef(coreComponentsTranslationRef); return ( - Add the annotation to your component YAML as shown in the - highlighted example below: + {t('emptyState.missingAnnotation.actionTitle')} } diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index e957390da8..b426402ba9 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; // eslint-disable-next-line no-restricted-imports import MaterialLink, { LinkProps as MaterialLinkProps, @@ -29,6 +30,7 @@ import { LinkProps as RouterLinkProps, Route, } from 'react-router-dom'; +import { coreComponentsTranslationRef } from '../../translation'; export function isReactRouterBeta(): boolean { const [obj] = createRoutesFromChildren(} />); @@ -161,6 +163,7 @@ export const Link = React.forwardRef( ({ onClick, noTrack, ...props }, ref) => { const classes = useStyles(); const analytics = useAnalytics(); + const { t } = useTranslationRef(coreComponentsTranslationRef); // Adding the base path to URLs breaks react-router v6 stable, so we only // do it for beta. The react router version won't change at runtime so it is @@ -199,7 +202,7 @@ export const Link = React.forwardRef( > {props.children} - , Opens in a new window + {`, ${t('link.openNewWindow')}`} ) : ( diff --git a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx index 782624362d..375ea66953 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/LoginRequestListItem.tsx @@ -23,6 +23,8 @@ import Button from '@material-ui/core/Button'; import React, { useState } from 'react'; import { isError } from '@backstage/errors'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type LoginRequestListItemClassKey = 'root'; @@ -44,6 +46,7 @@ type RowProps = { const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => { const classes = useItemStyles(); const [error, setError] = useState(); + const { t } = useTranslationRef(coreComponentsTranslationRef); const handleContinue = async () => { setBusy(true); @@ -68,7 +71,7 @@ const LoginRequestListItem = ({ request, busy, setBusy }: RowProps) => { secondary={error && {error}} /> ); diff --git a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx index 7c17e74e4a..2257b776d9 100644 --- a/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx +++ b/packages/core-components/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx @@ -30,6 +30,8 @@ import { oauthRequestApiRef, } from '@backstage/core-plugin-api'; import Typography from '@material-ui/core/Typography'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type OAuthRequestDialogClassKey = | 'dialog' @@ -63,6 +65,7 @@ export function OAuthRequestDialog(_props: {}) { const [busy, setBusy] = useState(false); const oauthRequestApi = useApi(oauthRequestApiRef); const configApi = useApi(configApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const authRedirect = configApi.getOptionalBoolean('enableExperimentalRedirectFlow') ?? false; @@ -94,12 +97,10 @@ export function OAuthRequestDialog(_props: {}) { variant="h1" variantMapping={{ h1: 'span' }} > - Login Required + {t('oauthRequestDialog.title')} {authRedirect ? ( - - This will trigger a http redirect to OAuth Login. - + {t('oauthRequestDialog.authRedirectTitle')} ) : null} @@ -118,7 +119,9 @@ export function OAuthRequestDialog(_props: {}) { - + ); diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx index 224b66533a..8db3ac8d72 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepperFooter.tsx @@ -20,6 +20,8 @@ import React, { PropsWithChildren, ReactNode, useContext } from 'react'; import { VerticalStepperContext } from './SimpleStepper'; import { StepActions } from './types'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type SimpleStepperFooterClassKey = 'root'; @@ -55,9 +57,12 @@ interface BackBtnProps extends CommonBtnProps { disabled?: boolean; stepIndex: number; } -export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => ( - -); +export const RestartBtn = ({ text, handleClick }: RestartBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; const NextBtn = ({ text, @@ -65,39 +70,48 @@ const NextBtn = ({ disabled, last, stepIndex, -}: NextBtnProps) => ( - -); +}: NextBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; -const SkipBtn = ({ text, handleClick, disabled, stepIndex }: SkipBtnProps) => ( - -); +const SkipBtn = ({ text, handleClick, disabled, stepIndex }: SkipBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; -const BackBtn = ({ text, handleClick, disabled, stepIndex }: BackBtnProps) => ( - -); +const BackBtn = ({ text, handleClick, disabled, stepIndex }: BackBtnProps) => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return ( + + ); +}; export type SimpleStepperFooterProps = { actions?: StepActions; diff --git a/packages/core-components/src/components/SupportButton/SupportButton.tsx b/packages/core-components/src/components/SupportButton/SupportButton.tsx index 726500c77e..623eda3098 100644 --- a/packages/core-components/src/components/SupportButton/SupportButton.tsx +++ b/packages/core-components/src/components/SupportButton/SupportButton.tsx @@ -31,6 +31,8 @@ import React, { MouseEventHandler, useState } from 'react'; import { SupportItem, SupportItemLink, useSupportConfig } from '../../hooks'; import { HelpIcon } from '../../icons'; import { Link } from '../Link'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type SupportButtonProps = { title?: string; @@ -85,6 +87,7 @@ const SupportListItem = ({ item }: { item: SupportItem }) => { }; export function SupportButton(props: SupportButtonProps) { + const { t } = useTranslationRef(coreComponentsTranslationRef); const { title, items, children } = props; const { items: configItems } = useSupportConfig(); @@ -125,7 +128,7 @@ export function SupportButton(props: SupportButtonProps) { onClick={onClickHandler} startIcon={} > - Support + {t('supportButton.title')} )} @@ -171,7 +174,7 @@ export function SupportButton(props: SupportButtonProps) { onClick={popoverCloseHandler} aria-label="Close" > - Close + {t('supportButton.close')} diff --git a/packages/core-components/src/components/Table/Filters.tsx b/packages/core-components/src/components/Table/Filters.tsx index 568b22b933..999676f4d9 100644 --- a/packages/core-components/src/components/Table/Filters.tsx +++ b/packages/core-components/src/components/Table/Filters.tsx @@ -21,6 +21,8 @@ import React, { useEffect, useState } from 'react'; import { Select } from '../Select'; import { SelectProps } from '../Select/Select'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type TableFiltersClassKey = 'root' | 'value' | 'heder' | 'filters'; @@ -76,6 +78,7 @@ export const Filters = (props: Props) => { const classes = useFilterStyles(); const { onChangeFilters } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); const [selectedFilters, setSelectedFilters] = useState({ ...props.selectedFilters, @@ -96,9 +99,9 @@ export const Filters = (props: Props) => { return ( - Filters + {t('table.filter.title')} diff --git a/packages/core-components/src/hooks/useSupportConfig.ts b/packages/core-components/src/hooks/useSupportConfig.ts index e80eb457a1..49cc44c0f7 100644 --- a/packages/core-components/src/hooks/useSupportConfig.ts +++ b/packages/core-components/src/hooks/useSupportConfig.ts @@ -15,6 +15,8 @@ */ import { useApiHolder, configApiRef } from '@backstage/core-plugin-api'; +import { coreComponentsTranslationRef } from '../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export type SupportItemLink = { url: string; @@ -32,30 +34,34 @@ export type SupportConfig = { items: SupportItem[]; }; -const DEFAULT_SUPPORT_CONFIG: SupportConfig = { - url: 'https://github.com/backstage/backstage/issues', - items: [ - { - title: 'Support Not Configured', - icon: 'warning', - links: [ - { - // TODO: Update to dedicated support page on backstage.io/docs - title: 'Add `app.support` config key', - url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml', - }, - ], - }, - ], +const useDefaultSupportConfig = () => { + const { t } = useTranslationRef(coreComponentsTranslationRef); + return { + url: 'https://github.com/backstage/backstage/issues', + items: [ + { + title: t('supportConfig.title'), + icon: 'warning', + links: [ + { + // TODO: Update to dedicated support page on backstage.io/docs + title: t('supportConfig.links.title'), + url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml', + }, + ], + }, + ], + }; }; export function useSupportConfig(): SupportConfig { const apiHolder = useApiHolder(); const config = apiHolder.get(configApiRef); const supportConfig = config?.getOptionalConfig('app.support'); + const defaultSupportConfig = useDefaultSupportConfig(); if (!supportConfig) { - return DEFAULT_SUPPORT_CONFIG; + return defaultSupportConfig; } return { diff --git a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx index 0ecaedfc19..8f746882a1 100644 --- a/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/core-components/src/layout/ErrorBoundary/ErrorBoundary.tsx @@ -18,6 +18,8 @@ import Typography from '@material-ui/core/Typography'; import React, { ComponentClass, Component, ErrorInfo } from 'react'; import { LinkButton } from '../../components/LinkButton'; import { ErrorPanel } from '../../components/ErrorPanel'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type SlackChannel = { name: string; @@ -37,14 +39,21 @@ type State = { const SlackLink = (props: { slackChannel?: string | SlackChannel }) => { const { slackChannel } = props; + const { t } = useTranslationRef(coreComponentsTranslationRef); if (!slackChannel) { return null; } else if (typeof slackChannel === 'string') { - return Please contact {slackChannel} for help.; + return ( + {t('errorBoundary.title', { slackChannel })} + ); } else if (!slackChannel.href) { return ( - Please contact {slackChannel.name} for help. + + {t('errorBoundary.title', { + slackChannel: slackChannel.name, + })} + ); } diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index 131df3da3a..f80161ff3c 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -23,6 +23,8 @@ import { Link } from '../../components/Link'; import { useSupportConfig } from '../../hooks'; import { MicDrop } from './MicDrop'; import { StackDetails } from './StackDetails'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; interface IErrorPageProps { status?: string; @@ -68,6 +70,7 @@ export function ErrorPage(props: IErrorPageProps) { const classes = useStyles(); const navigate = useNavigate(); const support = useSupportConfig(); + const { t } = useTranslationRef(coreComponentsTranslationRef); return ( @@ -77,21 +80,23 @@ export function ErrorPage(props: IErrorPageProps) { variant="body1" className={classes.subtitle} > - ERROR {status}: {statusMessage} + {t('errorPage.subtitle', { status: status || '', statusMessage })} {additionalInfo} - Looks like someone dropped the mic! + {t('errorPage.title')} navigate(-1)}> - Go back + {t('errorPage.goBack')} - ... or please{' '} - contact support if you - think this is a bug. + {t('errorPage.orPlease')} + + {t('errorPage.contactSupport')} + + {t('errorPage.isBug')} {stack && } diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx index ea095cd8ab..98fca7c6ef 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx @@ -24,6 +24,8 @@ import { useAsync, useMountEffect } from '@react-hookz/web'; import { ErrorPanel } from '../../components/ErrorPanel'; import { Progress } from '../../components/Progress'; import { ProxiedSignInIdentity } from './ProxiedSignInIdentity'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** * Props for {@link ProxiedSignInPage}. @@ -61,6 +63,7 @@ export type ProxiedSignInPageProps = SignInPageProps & { */ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { const discoveryApi = useApi(discoveryApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const [{ status, error }, { execute }] = useAsync(async () => { const identity = new ProxiedSignInIdentity({ @@ -79,12 +82,7 @@ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { if (status === 'loading') { return ; } else if (error) { - return ( - - ); + return ; } return null; diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 550c614522..f8c6036158 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -34,6 +34,8 @@ import { MobileSidebar } from './MobileSidebar'; import { useContent } from './Page'; import { SidebarOpenStateProvider } from './SidebarOpenStateContext'; import { useSidebarPinState } from './SidebarPinStateContext'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { coreComponentsTranslationRef } from '../../translation'; /** @public */ export type SidebarClassKey = 'drawer' | 'drawerOpen'; @@ -250,6 +252,7 @@ function A11ySkipSidebar() { const { sidebarConfig } = useContext(SidebarConfigContext); const { focusContent, contentRef } = useContent(); const classes = useStyles({ sidebarConfig }); + const { t } = useTranslationRef(coreComponentsTranslationRef); if (!contentRef?.current) { return null; @@ -260,7 +263,7 @@ function A11ySkipSidebar() { variant="contained" className={classnames(classes.visuallyHidden)} > - Skip to content + {t('sidebar.shipToContent')} ); } diff --git a/packages/core-components/src/layout/SignInPage/SignInPage.tsx b/packages/core-components/src/layout/SignInPage/SignInPage.tsx index 6092f2862f..95d96b701a 100644 --- a/packages/core-components/src/layout/SignInPage/SignInPage.tsx +++ b/packages/core-components/src/layout/SignInPage/SignInPage.tsx @@ -35,6 +35,8 @@ import { Page } from '../Page'; import { getSignInProviders, useSignInProviders } from './providers'; import { GridItem, useStyles } from './styles'; import { IdentityProviders, SignInProviderConfig } from './types'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type MultiSignInPageProps = SignInPageProps & { providers: IdentityProviders; @@ -95,6 +97,7 @@ export const SingleSignInPage = ({ const classes = useStyles(); const authApi = useApi(provider.apiRef); const configApi = useApi(configApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const [error, setError] = useState(); @@ -174,7 +177,7 @@ export const SingleSignInPage = ({ login({ showPopup: true }); }} > - Sign In + {t('signIn.title')} } > diff --git a/packages/core-components/src/layout/SignInPage/commonProvider.tsx b/packages/core-components/src/layout/SignInPage/commonProvider.tsx index 1827981e6f..07a9d9c98e 100644 --- a/packages/core-components/src/layout/SignInPage/commonProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/commonProvider.tsx @@ -28,6 +28,8 @@ import { useApi, errorApiRef } from '@backstage/core-plugin-api'; import { GridItem } from './styles'; import { ForwardedError } from '@backstage/errors'; import { UserIdentity } from './UserIdentity'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const Component: ProviderComponent = ({ config, @@ -38,6 +40,7 @@ const Component: ProviderComponent = ({ const { apiRef, title, message } = config as SignInProviderConfig; const authApi = useApi(apiRef); const errorApi = useApi(errorApiRef); + const { t } = useTranslationRef(coreComponentsTranslationRef); const handleLogin = async () => { try { @@ -63,7 +66,7 @@ const Component: ProviderComponent = ({ ); } catch (error) { onSignInFailure(); - errorApi.post(new ForwardedError('Login failed', error)); + errorApi.post(new ForwardedError(t('signIn.loginFailed'), error)); } }; @@ -74,7 +77,7 @@ const Component: ProviderComponent = ({ title={title} actions={ } > diff --git a/packages/core-components/src/layout/SignInPage/customProvider.tsx b/packages/core-components/src/layout/SignInPage/customProvider.tsx index ad02715447..ebab5dce5c 100644 --- a/packages/core-components/src/layout/SignInPage/customProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/customProvider.tsx @@ -27,6 +27,8 @@ import { InfoCard } from '../InfoCard/InfoCard'; import { ProviderComponent, ProviderLoader, SignInProvider } from './types'; import { GridItem } from './styles'; import { UserIdentity } from './UserIdentity'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; // accept base64url format according to RFC7515 (https://tools.ietf.org/html/rfc7515#section-3) const ID_TOKEN_REGEX = /^[a-z0-9_\-]+\.[a-z0-9_\-]+\.[a-z0-9_\-]+$/i; @@ -63,6 +65,7 @@ const asInputRef = (renderResult: UseFormRegisterReturn) => { const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { const classes = useFormStyles(); + const { t } = useTranslationRef(coreComponentsTranslationRef); const { register, handleSubmit, formState } = useForm({ mode: 'onChange', }); @@ -84,18 +87,18 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { return ( - + - Enter your own User ID and credentials. + {t('signIn.customProvider.subtitle')}
- This selection will not be stored. + {t('signIn.customProvider.subtitle2')}
@@ -111,10 +114,10 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { validate: token => !token || ID_TOKEN_REGEX.test(token) || - 'Token is not a valid OpenID Connect JWT Token', + t('signIn.customProvider.tokenInvalid'), }), )} - label="ID Token (optional)" + label={t('signIn.customProvider.idToken')} margin="normal" autoComplete="off" error={Boolean(errors.idToken)} @@ -130,7 +133,7 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { className={classes.button} disabled={!formState?.isDirty || !isEmpty(errors)} > - Continue + {t('signIn.customProvider.continue')}
diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts new file mode 100644 index 0000000000..a9fc17e7f6 --- /dev/null +++ b/packages/core-components/src/translation.ts @@ -0,0 +1,130 @@ +/* + * Copyright 2023 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'; + +/** @alpha */ +export const coreComponentsTranslationRef = createTranslationRef({ + id: 'core-components', + messages: { + signIn: { + title: 'Sign In', + loginFailed: 'Login failed', + customProvider: { + title: 'Custom User', + subtitle: 'Enter your own User ID and credentials.', + subtitle2: 'This selection will not be stored.', + userId: 'User ID', + tokenInvalid: 'Token is not a valid OpenID Connect JWT Token', + continue: 'Continue', + idToken: 'ID Token (optional)', + }, + guestProvider: { + title: 'Guest', + subtitle: 'Enter as a Guest User.', + description: + 'You will not have a verified identity, meaning some features might be unavailable.', + enter: 'Enter', + }, + }, + sidebar: { + shipToContent: 'Skip to content', + starredIntroText: + 'Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav.Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!', + recentlyViewedIntroText: + 'And your recently viewed plugins will pop up here!', + dismiss: 'Dismiss', + }, + copyTextButton: { + tooltipText: 'Text copied to clipboard', + }, + simpleStepper: { + reset: 'Reset', + finish: 'Finish', + next: 'Next', + skip: 'Skip', + back: 'Back', + }, + errorPage: { + subtitle: 'ERROR {{status}}: {{statusMessage}}', + title: 'Looks like someone dropped the mic!', + goBack: 'Go back', + orPlease: '... or please ', + contactSupport: 'contact support', + isBug: 'if you think this is a bug.', + }, + emptyState: { + missingAnnotation: { + title: 'Missing Annotation', + actionTitle: + 'Add the annotation to your component YAML as shown in the highlighted example below:', + readMore: 'Read more', + descriptionPrefix_one: 'The annotation ', + descriptionPrefix_other: 'The annotations ', + descriptionSuffix_one: + ' is missing. You need to add the annotation to your component if you want to enable this tool.', + descriptionSuffix_other: + ' are missing. You need to add the annotations to your component if you want to enable this tool.', + }, + }, + supportConfig: { + title: 'Support Not Configured', + links: { + title: 'Add `app.support` config key', + }, + }, + errorBoundary: { + title: 'Please contact {{slackChannel}} for help.', + }, + oauthRequestDialog: { + title: 'Login Required', + authRedirectTitle: 'This will trigger a http redirect to OAuth Login.', + login: 'Log in', + rejectAll: 'Reject All', + }, + link: { + openNewWindow: 'Opens in a new window', + }, + supportButton: { + title: 'Support', + close: 'Close', + }, + table: { + filter: { + title: 'Filters', + clearAll: 'Clear all', + }, + }, + alertDisplay: { + message_one: '({{ num }} older message)', + message_other: '({{ num }} older messages)', + }, + autoLogout: { + stillTherePrompt: { + title: 'Logging out due to inactivity', + description: 'You are about to be disconnected in', + second_one: 'second', + second_other: 'seconds', + descriptionSuffix: 'Are you still there?', + buttonText: "Yes! Don't log me out", + }, + }, + proxiedSignInPage: { + title: + 'You do not appear to be signed in. Please try reloading the browser page.', + }, + }, +}); diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx index 5d0e7fe849..94efa1eec8 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.test.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { Entity } from '@backstage/catalog-model'; -import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { BadgesApi, badgesApiRef } from '../api'; import { EntityBadgesDialog } from './EntityBadgesDialog'; import { EntityProvider } from '@backstage/plugin-catalog-react'; @@ -43,7 +43,7 @@ describe('EntityBadgesDialog', () => { kind: 'MockKind', } as Entity; - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( ({ describe('BitriseArtifactsComponent', () => { entityValue = { entity: { metadata: {} } }; - const renderComponent = () => render(); + const renderComponent = () => renderInTestApp(); it('should display an empty state if an app annotation is missing', async () => { - const rendered = renderComponent(); + const rendered = await renderComponent(); expect(await rendered.findByText('Missing Annotation')).toBeInTheDocument(); }); diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 85b31d6311..5dc5315209 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -52,6 +52,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", diff --git a/plugins/git-release-manager/src/features/Features.test.tsx b/plugins/git-release-manager/src/features/Features.test.tsx index abde878648..2bd411b196 100644 --- a/plugins/git-release-manager/src/features/Features.test.tsx +++ b/plugins/git-release-manager/src/features/Features.test.tsx @@ -21,6 +21,10 @@ import { Features } from './Features'; import { mockCalverProject } from '../test-helpers/test-helpers'; import { TEST_IDS } from '../test-helpers/test-ids'; import { mockApiClient } from '../test-helpers/mock-api-client'; +import { MockErrorApi, TestApiProvider } from '@backstage/test-utils'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { MockTranslationApi } from '@backstage/test-utils'; +import { errorApiRef } from '@backstage/core-plugin-api'; jest.mock('@backstage/core-plugin-api', () => ({ ...jest.requireActual('@backstage/core-plugin-api'), @@ -35,18 +39,26 @@ jest.mock('../contexts/ProjectContext', () => ({ describe('Features', () => { it('should omit features omitted via configuration', async () => { const { getByTestId } = render( - [
Custom 1
,
Custom 2
], - }, - }} - />, + + [
Custom 1
,
Custom 2
], + }, + }} + /> + , +
, ); await waitFor(() => getByTestId(TEST_IDS.info.info)); diff --git a/plugins/git-release-manager/src/features/Info/Info.test.tsx b/plugins/git-release-manager/src/features/Info/Info.test.tsx index 1fcd53eb9c..c747a84d6e 100644 --- a/plugins/git-release-manager/src/features/Info/Info.test.tsx +++ b/plugins/git-release-manager/src/features/Info/Info.test.tsx @@ -15,14 +15,13 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; - import { mockCalverProject, mockReleaseBranch, mockReleaseCandidateCalver, } from '../../test-helpers/test-helpers'; import { Info } from './Info'; +import { renderInTestApp } from '@backstage/test-utils'; jest.mock('../../contexts/ProjectContext', () => ({ useProjectContext: () => ({ @@ -32,7 +31,7 @@ jest.mock('../../contexts/ProjectContext', () => ({ describe('Info', () => { it('should return early if no latestRelease exists', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( ({ ...jest.requireActual('@backstage/core-plugin-api'), @@ -56,13 +60,21 @@ describe('PatchBody', () => { }); const { getByTestId } = render( - , + + + , + , ); expect(getByTestId(TEST_IDS.patch.loading)).toBeInTheDocument(); @@ -74,13 +86,20 @@ describe('PatchBody', () => { it('should render not-prerelease description', async () => { const { getByTestId } = render( - , + + + , ); expect(getByTestId(TEST_IDS.patch.loading)).toBeInTheDocument(); diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index ebc3502901..57a219eb50 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -18,7 +18,7 @@ import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/core-app-api'; import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { GoCdBuildsComponent } from './GoCdBuildsComponent'; import { gocdApiRef } from '../../plugin'; import { GoCdApi } from '../../api/gocdApi'; @@ -41,7 +41,7 @@ describe('GoCdArtifactsComponent', () => { }; const renderComponent = () => - renderWithEffects( + renderInTestApp( ', () => { it('renders with mandatory parameters', async () => { - const { getByText } = await render( + const { getByText } = await renderInTestApp( , ); expect(getByText('My title')).toBeInTheDocument(); }); it('renders skeleton when loading is true', async () => { - const { container } = await render( + const { container } = await renderInTestApp( , ); expect(container.querySelectorAll('li')).toHaveLength(8); @@ -36,7 +35,7 @@ describe('', () => { }); it('renders specified amount of items', async () => { - const { container } = await render( + const { container } = await renderInTestApp( ', () => { }); it('renders some items hidden', async () => { - const { container } = await render( + const { container } = await renderInTestApp( ', () => { }); it('renders all items when not collapsed', async () => { - const { container } = await render( + const { container } = await renderInTestApp( ', () => { }); it('renders visit with time-ago', async () => { - const { container, getByText } = await render( - - - , - , + const { container, getByText } = await renderInTestApp( + , ); expect(container.querySelectorAll('li')).toHaveLength(1); expect(getByText('Explore Backstage')).toBeInTheDocument(); @@ -102,23 +98,20 @@ describe('', () => { }); it('renders visit with hits', async () => { - const { container, getByText } = await render( - - - , - , + const { container, getByText } = await renderInTestApp( + , ); expect(container.querySelectorAll('li')).toHaveLength(1); expect(getByText('Explore Backstage')).toBeInTheDocument(); @@ -126,23 +119,20 @@ describe('', () => { }); it('renders text warning about few items', async () => { - const { getByText } = await render( - - - , - , + const { getByText } = await renderInTestApp( + , ); expect( getByText('The more pages you visit, the more pages will appear here.'), @@ -150,10 +140,8 @@ describe('', () => { }); it('renders text warning about no items', async () => { - const { getByText } = await render( - - , - , + const { getByText } = await renderInTestApp( + , ); expect(getByText('There are no visits to show yet.')).toBeInTheDocument(); }); diff --git a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx index 68bfce9c96..7972f4401c 100644 --- a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.test.tsx @@ -15,10 +15,9 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; - import { FixDialog } from './FixDialog'; import { Pod } from 'kubernetes-models/v1/Pod'; +import { renderInTestApp } from '@backstage/test-utils'; jest.mock('../Events', () => ({ Events: () => { @@ -33,8 +32,8 @@ jest.mock('../PodLogs', () => ({ })); describe('FixDialog', () => { - it('docs link should render', () => { - const { getByText } = render( + it('docs link should render', async () => { + const { getByText } = await renderInTestApp( { expect(getByText('fix1')).toBeInTheDocument(); expect(getByText('fix2')).toBeInTheDocument(); }); - it('events button should render', () => { - const { getByText } = render( + it('events button should render', async () => { + const { getByText } = await renderInTestApp( { expect(getByText('fix1')).toBeInTheDocument(); expect(getByText('fix2')).toBeInTheDocument(); }); - it('Logs button should render', () => { - const { getByText } = render( + it('Logs button should render', async () => { + const { getByText } = await renderInTestApp( { @@ -46,7 +46,7 @@ const validResultWithTitle = { describe('TechDocsSearchResultListItem test', () => { it('should render search doc passed in', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( , ); @@ -61,7 +61,7 @@ describe('TechDocsSearchResultListItem test', () => { }); it('should use title if defined', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( { }); it('should use entity title if defined', async () => { - const { findByText } = render( + const { findByText } = await renderInTestApp( , ); diff --git a/plugins/todo/src/components/TodoList/TodoList.test.tsx b/plugins/todo/src/components/TodoList/TodoList.test.tsx index 15d4334610..872538c5b7 100644 --- a/plugins/todo/src/components/TodoList/TodoList.test.tsx +++ b/plugins/todo/src/components/TodoList/TodoList.test.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-react'; -import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import React from 'react'; import { TodoApi, todoApiRef } from '../../api'; import { TodoList } from './TodoList'; @@ -43,7 +43,7 @@ describe('TodoList', () => { kind: 'MockKind', } as Entity; - const rendered = await renderWithEffects( + const rendered = await renderInTestApp( diff --git a/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx b/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx index ec62ea8757..ccf99bb259 100644 --- a/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx +++ b/plugins/vault/src/components/EntityVaultCard/EntityVaultCard.test.tsx @@ -16,9 +16,12 @@ import React from 'react'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { + renderInTestApp, + setupRequestMockHandlers, +} from '@backstage/test-utils'; import { ComponentEntity } from '@backstage/catalog-model'; -import { render, waitFor } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; import { EntityVaultCard } from './EntityVaultCard'; import { EntityProvider } from '@backstage/plugin-catalog-react'; @@ -40,7 +43,7 @@ describe('EntityVaultCard', () => { }; it('should render missing entity annotation', async () => { - const rendered = render( + const rendered = await renderInTestApp( , diff --git a/yarn.lock b/yarn.lock index 578ae66433..6ffc2bcb4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6722,6 +6722,7 @@ __metadata: "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.61 From f3e5540a68d546f03e7d71c2fb636312b3c42711 Mon Sep 17 00:00:00 2001 From: rui ma Date: Tue, 9 Jan 2024 10:03:40 +0800 Subject: [PATCH 2/7] fix: test faild Signed-off-by: rui ma --- plugins/git-release-manager/src/features/Features.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/git-release-manager/src/features/Features.test.tsx b/plugins/git-release-manager/src/features/Features.test.tsx index 2bd411b196..ca1517b5dd 100644 --- a/plugins/git-release-manager/src/features/Features.test.tsx +++ b/plugins/git-release-manager/src/features/Features.test.tsx @@ -23,7 +23,7 @@ import { TEST_IDS } from '../test-helpers/test-ids'; import { mockApiClient } from '../test-helpers/mock-api-client'; import { MockErrorApi, TestApiProvider } from '@backstage/test-utils'; import { translationApiRef } from '@backstage/core-plugin-api/alpha'; -import { MockTranslationApi } from '@backstage/test-utils'; +import { MockTranslationApi } from '@backstage/test-utils/alpha'; import { errorApiRef } from '@backstage/core-plugin-api'; jest.mock('@backstage/core-plugin-api', () => ({ From 0afea5c0eda27febad0a9f835a2d42ce09be8cfe Mon Sep 17 00:00:00 2001 From: rui ma Date: Thu, 1 Feb 2024 13:45:49 +0800 Subject: [PATCH 3/7] fix: optimize translation keys Signed-off-by: rui ma --- packages/core-components/api-report-alpha.md | 22 +++++-------- .../components/AlertDisplay/AlertDisplay.tsx | 1 - .../src/hooks/useSupportConfig.ts | 4 +-- .../src/layout/ErrorPage/ErrorPage.tsx | 18 ++++++----- .../src/layout/Sidebar/Bar.tsx | 2 +- .../src/layout/SignInPage/customProvider.tsx | 7 +++-- packages/core-components/src/translation.ts | 31 ++++++------------- 7 files changed, 35 insertions(+), 50 deletions(-) diff --git a/packages/core-components/api-report-alpha.md b/packages/core-components/api-report-alpha.md index a51e590812..f4f35ae68a 100644 --- a/packages/core-components/api-report-alpha.md +++ b/packages/core-components/api-report-alpha.md @@ -15,20 +15,15 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'signIn.title': 'Sign In'; readonly 'signIn.loginFailed': 'Login failed'; readonly 'signIn.customProvider.title': 'Custom User'; - readonly 'signIn.customProvider.subtitle2': 'This selection will not be stored.'; - readonly 'signIn.customProvider.subtitle': 'Enter your own User ID and credentials.'; + readonly 'signIn.customProvider.subtitle': 'Enter your own User ID and credentials.\n This selection will not be stored.'; readonly 'signIn.customProvider.userId': 'User ID'; readonly 'signIn.customProvider.tokenInvalid': 'Token is not a valid OpenID Connect JWT Token'; readonly 'signIn.customProvider.continue': 'Continue'; readonly 'signIn.customProvider.idToken': 'ID Token (optional)'; readonly 'signIn.guestProvider.title': 'Guest'; - readonly 'signIn.guestProvider.description': 'You will not have a verified identity, meaning some features might be unavailable.'; readonly 'signIn.guestProvider.enter': 'Enter'; - readonly 'signIn.guestProvider.subtitle': 'Enter as a Guest User.'; - readonly 'sidebar.shipToContent': 'Skip to content'; - readonly 'sidebar.starredIntroText': 'Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav.Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!'; - readonly 'sidebar.recentlyViewedIntroText': 'And your recently viewed plugins will pop up here!'; - readonly 'sidebar.dismiss': 'Dismiss'; + readonly 'signIn.guestProvider.subtitle': 'Enter as a Guest User.\n You will not have a verified identity, meaning some features might be unavailable.'; + readonly shipToContent: 'Skip to content'; readonly 'copyTextButton.tooltipText': 'Text copied to clipboard'; readonly 'simpleStepper.finish': 'Finish'; readonly 'simpleStepper.reset': 'Reset'; @@ -38,9 +33,6 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'errorPage.title': 'Looks like someone dropped the mic!'; readonly 'errorPage.subtitle': 'ERROR {{status}}: {{statusMessage}}'; readonly 'errorPage.goBack': 'Go back'; - readonly 'errorPage.orPlease': '... or please '; - readonly 'errorPage.contactSupport': 'contact support'; - readonly 'errorPage.isBug': 'if you think this is a bug.'; readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; readonly 'emptyState.missingAnnotation.readMore': 'Read more'; @@ -48,8 +40,8 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'emptyState.missingAnnotation.descriptionPrefix_other': 'The annotations '; readonly 'emptyState.missingAnnotation.descriptionSuffix_one': ' is missing. You need to add the annotation to your component if you want to enable this tool.'; readonly 'emptyState.missingAnnotation.descriptionSuffix_other': ' are missing. You need to add the annotations to your component if you want to enable this tool.'; - readonly 'supportConfig.title': 'Support Not Configured'; - readonly 'supportConfig.links.title': 'Add `app.support` config key'; + readonly 'supportConfig.default.title': 'Support Not Configured'; + readonly 'supportConfig.default.linkTitle': 'Add `app.support` config key'; readonly 'errorBoundary.title': 'Please contact {{slackChannel}} for help.'; readonly 'oauthRequestDialog.title': 'Login Required'; readonly 'oauthRequestDialog.authRedirectTitle': 'This will trigger a http redirect to OAuth Login.'; @@ -57,8 +49,8 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'oauthRequestDialog.rejectAll': 'Reject All'; readonly 'supportButton.title': 'Support'; readonly 'supportButton.close': 'Close'; - readonly 'alertDisplay.message_one': '({{ num }} older message)'; - readonly 'alertDisplay.message_other': '({{ num }} older messages)'; + readonly 'alertDisplay.message_one': '({{ count }} older message)'; + readonly 'alertDisplay.message_other': '({{ count }} older messages)'; readonly 'autoLogout.stillTherePrompt.title': 'Logging out due to inactivity'; readonly 'autoLogout.stillTherePrompt.description': 'You are about to be disconnected in'; readonly 'autoLogout.stillTherePrompt.second_one': 'second'; diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx index 19527f76c2..0291b6861d 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx @@ -125,7 +125,6 @@ export function AlertDisplay(props: AlertDisplayProps) { {messages.length > 1 && ( {t('alertDisplay.message', { - num: String(messages.length - 1), count: messages.length - 1, })} diff --git a/packages/core-components/src/hooks/useSupportConfig.ts b/packages/core-components/src/hooks/useSupportConfig.ts index 49cc44c0f7..2b969d8b4d 100644 --- a/packages/core-components/src/hooks/useSupportConfig.ts +++ b/packages/core-components/src/hooks/useSupportConfig.ts @@ -40,12 +40,12 @@ const useDefaultSupportConfig = () => { url: 'https://github.com/backstage/backstage/issues', items: [ { - title: t('supportConfig.title'), + title: t('supportConfig.default.title'), icon: 'warning', links: [ { // TODO: Update to dedicated support page on backstage.io/docs - title: t('supportConfig.links.title'), + title: t('supportConfig.default.linkTitle'), url: 'https://github.com/backstage/backstage/blob/master/app-config.yaml', }, ], diff --git a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx index f80161ff3c..72c00ed6a9 100644 --- a/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx +++ b/packages/core-components/src/layout/ErrorPage/ErrorPage.tsx @@ -66,7 +66,13 @@ const useStyles = makeStyles( * */ export function ErrorPage(props: IErrorPageProps) { - const { status, statusMessage, additionalInfo, supportUrl, stack } = props; + const { + status = '', + statusMessage, + additionalInfo, + supportUrl, + stack, + } = props; const classes = useStyles(); const navigate = useNavigate(); const support = useSupportConfig(); @@ -80,7 +86,7 @@ export function ErrorPage(props: IErrorPageProps) { variant="body1" className={classes.subtitle} > - {t('errorPage.subtitle', { status: status || '', statusMessage })} + {t('errorPage.subtitle', { status, statusMessage })} {additionalInfo} @@ -92,11 +98,9 @@ export function ErrorPage(props: IErrorPageProps) { navigate(-1)}> {t('errorPage.goBack')} - {t('errorPage.orPlease')} - - {t('errorPage.contactSupport')} - - {t('errorPage.isBug')} + ... or please{' '} + contact support if you + think this is a bug. {stack && } diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index f8c6036158..200eb0b8e5 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -263,7 +263,7 @@ function A11ySkipSidebar() { variant="contained" className={classnames(classes.visuallyHidden)} > - {t('sidebar.shipToContent')} + {t('shipToContent')} ); } diff --git a/packages/core-components/src/layout/SignInPage/customProvider.tsx b/packages/core-components/src/layout/SignInPage/customProvider.tsx index ebab5dce5c..7d1de40d7b 100644 --- a/packages/core-components/src/layout/SignInPage/customProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/customProvider.tsx @@ -46,6 +46,9 @@ const useFormStyles = makeStyles( alignSelf: 'center', marginTop: theme.spacing(2), }, + subTitle: { + whiteSpace: 'pre-line', + }, }), { name: 'BackstageCustomProvider' }, ); @@ -88,10 +91,8 @@ const Component: ProviderComponent = ({ onSignInStarted, onSignInSuccess }) => { return ( - + {t('signIn.customProvider.subtitle')} -
- {t('signIn.customProvider.subtitle2')}
diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index a9fc17e7f6..5d7754e765 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -25,8 +25,8 @@ export const coreComponentsTranslationRef = createTranslationRef({ loginFailed: 'Login failed', customProvider: { title: 'Custom User', - subtitle: 'Enter your own User ID and credentials.', - subtitle2: 'This selection will not be stored.', + subtitle: + 'Enter your own User ID and credentials.\n This selection will not be stored.', userId: 'User ID', tokenInvalid: 'Token is not a valid OpenID Connect JWT Token', continue: 'Continue', @@ -34,20 +34,12 @@ export const coreComponentsTranslationRef = createTranslationRef({ }, guestProvider: { title: 'Guest', - subtitle: 'Enter as a Guest User.', - description: - 'You will not have a verified identity, meaning some features might be unavailable.', + subtitle: + 'Enter as a Guest User.\n You will not have a verified identity, meaning some features might be unavailable.', enter: 'Enter', }, }, - sidebar: { - shipToContent: 'Skip to content', - starredIntroText: - 'Fun fact! As you explore all the awesome plugins in Backstage, you can actually pin them to this side nav.Keep an eye out for the little star icon (⭐) next to the plugin name and give it a click!', - recentlyViewedIntroText: - 'And your recently viewed plugins will pop up here!', - dismiss: 'Dismiss', - }, + shipToContent: 'Skip to content', copyTextButton: { tooltipText: 'Text copied to clipboard', }, @@ -62,9 +54,6 @@ export const coreComponentsTranslationRef = createTranslationRef({ subtitle: 'ERROR {{status}}: {{statusMessage}}', title: 'Looks like someone dropped the mic!', goBack: 'Go back', - orPlease: '... or please ', - contactSupport: 'contact support', - isBug: 'if you think this is a bug.', }, emptyState: { missingAnnotation: { @@ -81,9 +70,9 @@ export const coreComponentsTranslationRef = createTranslationRef({ }, }, supportConfig: { - title: 'Support Not Configured', - links: { - title: 'Add `app.support` config key', + default: { + title: 'Support Not Configured', + linkTitle: 'Add `app.support` config key', }, }, errorBoundary: { @@ -109,8 +98,8 @@ export const coreComponentsTranslationRef = createTranslationRef({ }, }, alertDisplay: { - message_one: '({{ num }} older message)', - message_other: '({{ num }} older messages)', + message_one: '({{ count }} older message)', + message_other: '({{ count }} older messages)', }, autoLogout: { stillTherePrompt: { From 8ab41e507a8860f29af1b8df414c0044506e3bf8 Mon Sep 17 00:00:00 2001 From: rui ma Date: Sun, 18 Feb 2024 11:20:19 +0800 Subject: [PATCH 4/7] fix: StackDetails support translations Signed-off-by: rui ma --- packages/core-components/api-report-alpha.md | 2 ++ .../core-components/src/layout/ErrorPage/StackDetails.tsx | 7 +++++-- packages/core-components/src/translation.ts | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/core-components/api-report-alpha.md b/packages/core-components/api-report-alpha.md index f4f35ae68a..210485b38d 100644 --- a/packages/core-components/api-report-alpha.md +++ b/packages/core-components/api-report-alpha.md @@ -33,6 +33,8 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'errorPage.title': 'Looks like someone dropped the mic!'; readonly 'errorPage.subtitle': 'ERROR {{status}}: {{statusMessage}}'; readonly 'errorPage.goBack': 'Go back'; + readonly 'errorPage.showMoreDetails': 'Show more details'; + readonly 'errorPage.showLessDetails': 'Show less details'; readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; readonly 'emptyState.missingAnnotation.readMore': 'Read more'; diff --git a/packages/core-components/src/layout/ErrorPage/StackDetails.tsx b/packages/core-components/src/layout/ErrorPage/StackDetails.tsx index b46a937485..56fabfd5f8 100644 --- a/packages/core-components/src/layout/ErrorPage/StackDetails.tsx +++ b/packages/core-components/src/layout/ErrorPage/StackDetails.tsx @@ -19,6 +19,8 @@ import { useState } from 'react'; import { Link } from '../../components/Link'; import { CodeSnippet } from '../../components'; import { makeStyles } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { coreComponentsTranslationRef } from '../../translation'; interface IStackDetailsProps { stack: string; @@ -46,6 +48,7 @@ const useStyles = makeStyles( export function StackDetails(props: IStackDetailsProps) { const { stack } = props; const classes = useStyles(); + const { t } = useTranslationRef(coreComponentsTranslationRef); const [detailsOpen, setDetailsOpen] = useState(false); @@ -53,7 +56,7 @@ export function StackDetails(props: IStackDetailsProps) { return ( setDetailsOpen(true)}> - Show more details + {t('errorPage.showMoreDetails')} ); @@ -63,7 +66,7 @@ export function StackDetails(props: IStackDetailsProps) { <> setDetailsOpen(false)}> - Show less details + {t('errorPage.showLessDetails')} Date: Tue, 20 Feb 2024 23:07:37 +0800 Subject: [PATCH 5/7] fix: revert suffix translation keys Signed-off-by: rui ma --- packages/core-components/api-report-alpha.md | 8 -------- .../src/components/AutoLogout/StillTherePrompt.tsx | 8 ++++---- .../EmptyState/MissingAnnotationEmptyState.tsx | 12 +++++------- packages/core-components/src/translation.ts | 10 ---------- 4 files changed, 9 insertions(+), 29 deletions(-) diff --git a/packages/core-components/api-report-alpha.md b/packages/core-components/api-report-alpha.md index 210485b38d..6cfada994e 100644 --- a/packages/core-components/api-report-alpha.md +++ b/packages/core-components/api-report-alpha.md @@ -38,10 +38,6 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'emptyState.missingAnnotation.title': 'Missing Annotation'; readonly 'emptyState.missingAnnotation.actionTitle': 'Add the annotation to your component YAML as shown in the highlighted example below:'; readonly 'emptyState.missingAnnotation.readMore': 'Read more'; - readonly 'emptyState.missingAnnotation.descriptionPrefix_one': 'The annotation '; - readonly 'emptyState.missingAnnotation.descriptionPrefix_other': 'The annotations '; - readonly 'emptyState.missingAnnotation.descriptionSuffix_one': ' is missing. You need to add the annotation to your component if you want to enable this tool.'; - readonly 'emptyState.missingAnnotation.descriptionSuffix_other': ' are missing. You need to add the annotations to your component if you want to enable this tool.'; readonly 'supportConfig.default.title': 'Support Not Configured'; readonly 'supportConfig.default.linkTitle': 'Add `app.support` config key'; readonly 'errorBoundary.title': 'Please contact {{slackChannel}} for help.'; @@ -54,10 +50,6 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'alertDisplay.message_one': '({{ count }} older message)'; readonly 'alertDisplay.message_other': '({{ count }} older messages)'; readonly 'autoLogout.stillTherePrompt.title': 'Logging out due to inactivity'; - readonly 'autoLogout.stillTherePrompt.description': 'You are about to be disconnected in'; - readonly 'autoLogout.stillTherePrompt.second_one': 'second'; - readonly 'autoLogout.stillTherePrompt.second_other': 'seconds'; - readonly 'autoLogout.stillTherePrompt.descriptionSuffix': 'Are you still there?'; readonly 'autoLogout.stillTherePrompt.buttonText': "Yes! Don't log me out"; readonly 'proxiedSignInPage.title': 'You do not appear to be signed in. Please try reloading the browser page.'; } diff --git a/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx b/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx index c704cf9910..9496199fd9 100644 --- a/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx +++ b/packages/core-components/src/components/AutoLogout/StillTherePrompt.tsx @@ -64,18 +64,18 @@ export const StillTherePrompt = (props: StillTherePromptProps) => { remainingTime - promptTimeoutMillis / 1000, 0, ); + const seconds = timeTillPrompt > 1 ? 'seconds' : 'second'; return ( {t('autoLogout.stillTherePrompt.title')} - {t('autoLogout.stillTherePrompt.description')}{' '} + You are about to be disconnected in{' '} - {Math.ceil(remainingTime / 1000)}{' '} - {t('autoLogout.stillTherePrompt.second', { count: timeTillPrompt })} + {Math.ceil(remainingTime / 1000)} {seconds} - . {t('autoLogout.stillTherePrompt.descriptionSuffix')} + . Are you still there? diff --git a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx index 8c3f8c186f..e185dff8a6 100644 --- a/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx +++ b/packages/core-components/src/components/EmptyState/MissingAnnotationEmptyState.tsx @@ -79,12 +79,10 @@ function generateComponentYaml(annotations: string[]) { } function useGenerateDescription(annotations: string[]) { - const { t } = useTranslationRef(coreComponentsTranslationRef); + const isSingular = annotations.length <= 1; return ( <> - {t('emptyState.missingAnnotation.descriptionPrefix', { - count: annotations.length, - })} + The {isSingular ? 'annotation' : 'annotations'}{' '} {annotations .map(ann => {ann}) .reduce((prev, curr) => ( @@ -92,9 +90,9 @@ function useGenerateDescription(annotations: string[]) { {prev}, {curr} ))}{' '} - {t('emptyState.missingAnnotation.descriptionSuffix', { - count: annotations.length, - })} + {isSingular ? 'is' : 'are'} missing. You need to add the{' '} + {isSingular ? 'annotation' : 'annotations'} to your component if you want + to enable this tool. ); } diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index bb120f7c18..cc7fe1f3e1 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -63,12 +63,6 @@ export const coreComponentsTranslationRef = createTranslationRef({ actionTitle: 'Add the annotation to your component YAML as shown in the highlighted example below:', readMore: 'Read more', - descriptionPrefix_one: 'The annotation ', - descriptionPrefix_other: 'The annotations ', - descriptionSuffix_one: - ' is missing. You need to add the annotation to your component if you want to enable this tool.', - descriptionSuffix_other: - ' are missing. You need to add the annotations to your component if you want to enable this tool.', }, }, supportConfig: { @@ -106,10 +100,6 @@ export const coreComponentsTranslationRef = createTranslationRef({ autoLogout: { stillTherePrompt: { title: 'Logging out due to inactivity', - description: 'You are about to be disconnected in', - second_one: 'second', - second_other: 'seconds', - descriptionSuffix: 'Are you still there?', buttonText: "Yes! Don't log me out", }, }, From 0f1b3a0e60c06d731e14e294a5e6a4e9030929d2 Mon Sep 17 00:00:00 2001 From: rui ma Date: Tue, 27 Feb 2024 21:12:31 +0800 Subject: [PATCH 6/7] fix: change shipToContent to skipToContent Signed-off-by: rui ma --- packages/core-components/api-report-alpha.md | 2 +- packages/core-components/src/layout/Sidebar/Bar.tsx | 2 +- .../src/layout/SignInPage/guestProvider.tsx | 9 +++++++-- packages/core-components/src/translation.ts | 2 +- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/core-components/api-report-alpha.md b/packages/core-components/api-report-alpha.md index 6cfada994e..f2b21f6d1e 100644 --- a/packages/core-components/api-report-alpha.md +++ b/packages/core-components/api-report-alpha.md @@ -23,7 +23,7 @@ export const coreComponentsTranslationRef: TranslationRef< readonly 'signIn.guestProvider.title': 'Guest'; readonly 'signIn.guestProvider.enter': 'Enter'; readonly 'signIn.guestProvider.subtitle': 'Enter as a Guest User.\n You will not have a verified identity, meaning some features might be unavailable.'; - readonly shipToContent: 'Skip to content'; + readonly skipToContent: 'Skip to content'; readonly 'copyTextButton.tooltipText': 'Text copied to clipboard'; readonly 'simpleStepper.finish': 'Finish'; readonly 'simpleStepper.reset': 'Reset'; diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 200eb0b8e5..c5aa9585bb 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -263,7 +263,7 @@ function A11ySkipSidebar() { variant="contained" className={classnames(classes.visuallyHidden)} > - {t('shipToContent')} + {t('skipToContent')} ); } diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index 563d1cc124..e4e02d74fe 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -25,6 +25,8 @@ import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; import { GuestUserIdentity } from './GuestUserIdentity'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import { ResponseError } from '@backstage/errors'; +import { coreComponentsTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const getIdentity = async (identity: ProxiedSignInIdentity) => { try { @@ -48,6 +50,7 @@ const Component: ProviderComponent = ({ }) => { const discoveryApi = useApi(discoveryApiRef); const [_, setUseLegacyGuestToken] = useLocalStorage('enableLegacyGuestToken'); + const { t } = useTranslationRef(coreComponentsTranslationRef); const handle = async () => { onSignInStarted(); @@ -85,11 +88,13 @@ const Component: ProviderComponent = ({ variant="fullHeight" actions={ } > - Sign in as a Guest. + + {t('signIn.guestProvider.subtitle')} + ); diff --git a/packages/core-components/src/translation.ts b/packages/core-components/src/translation.ts index cc7fe1f3e1..2acb4ea735 100644 --- a/packages/core-components/src/translation.ts +++ b/packages/core-components/src/translation.ts @@ -39,7 +39,7 @@ export const coreComponentsTranslationRef = createTranslationRef({ enter: 'Enter', }, }, - shipToContent: 'Skip to content', + skipToContent: 'Skip to content', copyTextButton: { tooltipText: 'Text copied to clipboard', }, From 3b0c17f031911c5d64d9dba0126f85df44420bff Mon Sep 17 00:00:00 2001 From: rui ma Date: Tue, 27 Feb 2024 23:16:34 +0800 Subject: [PATCH 7/7] feat: add translationApiRef to storybook wrappers Signed-off-by: rui ma --- storybook/.storybook/apis.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/storybook/.storybook/apis.js b/storybook/.storybook/apis.js index 37cc8eb605..905c5f2dc8 100644 --- a/storybook/.storybook/apis.js +++ b/storybook/.storybook/apis.js @@ -24,6 +24,9 @@ import { featureFlagsApiRef, } from '@backstage/core-plugin-api'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { MockTranslationApi } from '@backstage/test-utils/alpha'; + const configApi = new ConfigReader({}); const featureFlagsApi = new LocalStorageFeatureFlags(); const alertApi = new AlertApiForwarder(); @@ -55,6 +58,7 @@ const oktaAuthApi = OktaAuth.create({ basePath: '/auth/', oauthRequestApi, }); +const translationApi = MockTranslationApi.create(); export const apis = [ [configApiRef, configApi], @@ -67,4 +71,5 @@ export const apis = [ [githubAuthApiRef, githubAuthApi], [gitlabAuthApiRef, gitlabAuthApi], [oktaAuthApiRef, oktaAuthApi], + [translationApiRef, translationApi], ];