userSetting plugin support i18n

Signed-off-by: mario ma <mario.ma.node@gmail.com>
This commit is contained in:
mario ma
2025-03-25 18:12:59 +08:00
parent 48ebe1c801
commit a7bfdb6c4d
15 changed files with 301 additions and 99 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-user-settings': patch
---
userSetting plugin support i18n
+31
View File
@@ -106,6 +106,16 @@ export const settingsNavItem: ExtensionDefinition<{
export const userSettingsTranslationRef: TranslationRef<
'user-settings',
{
readonly 'featureFlags.title': 'Feature Flags';
readonly 'featureFlags.description': 'Please refresh the page when toggling feature flags';
readonly 'featureFlags.emptyFlags.title': 'No Feature Flags';
readonly 'featureFlags.emptyFlags.action.title': 'An example for how to add a feature flag is highlighted below:';
readonly 'featureFlags.emptyFlags.action.readMoreButtonTitle': 'Read More';
readonly 'featureFlags.emptyFlags.description': 'Feature Flags make it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc.';
readonly 'featureFlags.flagItem.title.disable': 'Disable';
readonly 'featureFlags.flagItem.title.enable': 'Enable';
readonly 'featureFlags.flagItem.subTitle.registeredInApplication': 'Registered in the application';
readonly 'featureFlags.flagItem.subTitle.registeredInPlugin': 'Registered in {{pluginId}} plugin';
readonly 'languageToggle.select': 'Select language {{language}}';
readonly 'languageToggle.title': 'Language';
readonly 'languageToggle.description': 'Change the language';
@@ -116,6 +126,27 @@ export const userSettingsTranslationRef: TranslationRef<
readonly 'themeToggle.names.dark': 'Dark';
readonly 'themeToggle.names.light': 'Light';
readonly 'themeToggle.selectAuto': 'Select Auto Theme';
readonly 'signOutMenu.title': 'Sign Out';
readonly 'pinToggle.title': 'Pin Sidebar';
readonly 'pinToggle.description': 'Prevent the sidebar from collapsing';
readonly 'identityCard.title': 'Backstage Identity';
readonly 'identityCard.noIdentityTitle': 'No Backstage Identity';
readonly 'identityCard.userEntity': 'User Entity';
readonly 'identityCard.ownershipEntities': 'Ownership Entities';
readonly 'defaultProviderSettings.description': 'Provides authentication towards {{provider}} APIs and identities';
readonly 'emptyProviders.title': 'No Authentication Providers';
readonly 'emptyProviders.action.title': 'Open app-config.yaml and make the changes as highlighted below:';
readonly 'emptyProviders.action.readMoreButtonTitle': 'Read More';
readonly 'emptyProviders.description': 'You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.';
readonly 'providerSettingsItem.title.signIn': 'Sign in to {{title}}';
readonly 'providerSettingsItem.title.signOut': 'Sign out from {{title}}';
readonly 'providerSettingsItem.buttonTitle.signIn': 'Sign in';
readonly 'providerSettingsItem.buttonTitle.signOut': 'Sign out';
readonly 'authProviders.title': 'Available Providers';
readonly 'defaultSettingsPage.tabsTitle.featureFlags': 'Feature Flags';
readonly 'defaultSettingsPage.tabsTitle.authProviders': 'Authentication Providers';
readonly 'defaultSettingsPage.tabsTitle.general': 'General';
readonly 'settingsLayout.title': 'Settings';
}
>;
@@ -27,18 +27,23 @@ import {
atlassianAuthApiRef,
oneloginAuthApiRef,
} from '@backstage/core-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
/** @public */
export const DefaultProviderSettings = (props: {
configuredProviders: string[];
}) => {
const { configuredProviders } = props;
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<>
{configuredProviders.includes('google') && (
<ProviderSettingsItem
title="Google"
description="Provides authentication towards Google APIs and identities"
description={t('defaultProviderSettings.description', {
provider: 'Google',
})}
apiRef={googleAuthApiRef}
icon={Star}
/>
@@ -46,7 +51,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('microsoft') && (
<ProviderSettingsItem
title="Microsoft"
description="Provides authentication towards Microsoft APIs and identities"
description={t('defaultProviderSettings.description', {
provider: 'Microsoft',
})}
apiRef={microsoftAuthApiRef}
icon={Star}
/>
@@ -54,7 +61,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('github') && (
<ProviderSettingsItem
title="GitHub"
description="Provides authentication towards GitHub APIs"
description={t('defaultProviderSettings.description', {
provider: 'GitHub',
})}
apiRef={githubAuthApiRef}
icon={Star}
/>
@@ -62,7 +71,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('gitlab') && (
<ProviderSettingsItem
title="GitLab"
description="Provides authentication towards GitLab APIs"
description={t('defaultProviderSettings.description', {
provider: 'GitLab',
})}
apiRef={gitlabAuthApiRef}
icon={Star}
/>
@@ -70,7 +81,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('okta') && (
<ProviderSettingsItem
title="Okta"
description="Provides authentication towards Okta APIs"
description={t('defaultProviderSettings.description', {
provider: 'Okta',
})}
apiRef={oktaAuthApiRef}
icon={Star}
/>
@@ -78,7 +91,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('bitbucket') && (
<ProviderSettingsItem
title="Bitbucket"
description="Provides authentication towards Bitbucket APIs"
description={t('defaultProviderSettings.description', {
provider: 'Bitbucket',
})}
apiRef={bitbucketAuthApiRef}
icon={Star}
/>
@@ -86,7 +101,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('onelogin') && (
<ProviderSettingsItem
title="OneLogin"
description="Provides authentication towards OneLogin APIs"
description={t('defaultProviderSettings.description', {
provider: 'OneLogin',
})}
apiRef={oneloginAuthApiRef}
icon={Star}
/>
@@ -94,7 +111,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('atlassian') && (
<ProviderSettingsItem
title="Atlassian"
description="Provides authentication towards Atlassian APIs"
description={t('defaultProviderSettings.description', {
provider: 'Atlassian',
})}
apiRef={atlassianAuthApiRef}
icon={Star}
/>
@@ -102,7 +121,9 @@ export const DefaultProviderSettings = (props: {
{configuredProviders.includes('bitbucketServer') && (
<ProviderSettingsItem
title="Bitbucket Server"
description="Provides authentication towards Bitbucket Server APIs"
description={t('defaultProviderSettings.description', {
provider: 'Bitbucket Server',
})}
apiRef={bitbucketServerAuthApiRef}
icon={Star}
/>
@@ -17,6 +17,8 @@
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import { CodeSnippet, EmptyState } from '@backstage/core-components';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
const EXAMPLE = `auth:
providers:
@@ -26,32 +28,34 @@ const EXAMPLE = `auth:
clientSecret: \${AUTH_GOOGLE_CLIENT_SECRET}
`;
export const EmptyProviders = () => (
<EmptyState
missing="content"
title="No Authentication Providers"
description="You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself."
action={
<>
<Typography variant="body1">
Open <code>app-config.yaml</code> and make the changes as highlighted
below:
</Typography>
<CodeSnippet
text={EXAMPLE}
language="yaml"
showLineNumbers
highlightedNumbers={[3, 4, 5, 6, 7, 8]}
customStyle={{ background: 'inherit', fontSize: '115%' }}
/>
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/auth/add-auth-provider"
>
Read More
</Button>
</>
}
/>
);
export const EmptyProviders = () => {
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<EmptyState
missing="content"
title={t('emptyProviders.title')}
description={t('emptyProviders.description')}
action={
<>
<Typography variant="body1">
{t('emptyProviders.action.title')}
</Typography>
<CodeSnippet
text={EXAMPLE}
language="yaml"
showLineNumbers
highlightedNumbers={[3, 4, 5, 6, 7, 8]}
customStyle={{ background: 'inherit', fontSize: '115%' }}
/>
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/auth/add-auth-provider"
>
{t('emptyProviders.action.readMoreButtonTitle')}
</Button>
</>
}
/>
);
};
@@ -34,6 +34,8 @@ import {
IconComponent,
} from '@backstage/core-plugin-api';
import { ProviderSettingsAvatar } from './ProviderSettingsAvatar';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
const emptyProfile: ProfileInfo = {};
@@ -50,6 +52,7 @@ export const ProviderSettingsItem = (props: {
const errorApi = useApi(errorApiRef);
const [signedIn, setSignedIn] = useState(false);
const [profile, setProfile] = useState<ProfileInfo>(emptyProfile);
const { t } = useTranslationRef(userSettingsTranslationRef);
useEffect(() => {
let didCancel = false;
@@ -128,7 +131,11 @@ export const ProviderSettingsItem = (props: {
<Tooltip
placement="top"
arrow
title={signedIn ? `Sign out from ${title}` : `Sign in to ${title}`}
title={
signedIn
? t('providerSettingsItem.title.signOut', { title })
: t('providerSettingsItem.title.signIn', { title })
}
>
<Button
variant="outlined"
@@ -138,7 +145,9 @@ export const ProviderSettingsItem = (props: {
action.catch(error => errorApi.post(error));
}}
>
{signedIn ? `Sign out` : `Sign in`}
{signedIn
? t('providerSettingsItem.buttonTitle.signOut')
: t('providerSettingsItem.buttonTitle.signIn')}
</Button>
</Tooltip>
</ListItemSecondaryAction>
@@ -19,6 +19,8 @@ import { EmptyProviders } from './EmptyProviders';
import { DefaultProviderSettings } from './DefaultProviderSettings';
import { configApiRef, useApi } from '@backstage/core-plugin-api';
import { InfoCard } from '@backstage/core-components';
import { userSettingsTranslationRef } from '../../translation';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
/** @public */
export const UserSettingsAuthProviders = (props: {
@@ -31,13 +33,14 @@ export const UserSettingsAuthProviders = (props: {
const providers = providerSettings ?? (
<DefaultProviderSettings configuredProviders={configuredProviders} />
);
const { t } = useTranslationRef(userSettingsTranslationRef);
if (!providerSettings && !configuredProviders?.length) {
return <EmptyProviders />;
}
return (
<InfoCard title="Available Providers">
<InfoCard title={t('authProviders.title')}>
<List dense>{providers}</List>
</InfoCard>
);
@@ -19,6 +19,8 @@ import { UserSettingsAuthProviders } from '../AuthProviders';
import { UserSettingsFeatureFlags } from '../FeatureFlags';
import { UserSettingsGeneral } from '../General';
import { SettingsLayout, SettingsLayoutRouteProps } from '../SettingsLayout';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
/**
* @public
@@ -28,19 +30,26 @@ export const DefaultSettingsPage = (props: {
providerSettings?: JSX.Element;
}) => {
const { providerSettings, tabs } = props;
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<SettingsLayout>
<SettingsLayout.Route path="general" title="General">
<SettingsLayout.Route
path="general"
title={t('defaultSettingsPage.tabsTitle.general')}
>
<UserSettingsGeneral />
</SettingsLayout.Route>
<SettingsLayout.Route
path="auth-providers"
title="Authentication Providers"
title={t('defaultSettingsPage.tabsTitle.authProviders')}
>
<UserSettingsAuthProviders providerSettings={providerSettings} />
</SettingsLayout.Route>
<SettingsLayout.Route path="feature-flags" title="Feature Flags">
<SettingsLayout.Route
path="feature-flags"
title={t('defaultSettingsPage.tabsTitle.featureFlags')}
>
<UserSettingsFeatureFlags />
</SettingsLayout.Route>
{tabs}
@@ -17,6 +17,8 @@
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import { CodeSnippet, EmptyState } from '@backstage/core-components';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
const EXAMPLE = `import { createPlugin } from '@backstage/core-plugin-api';
@@ -26,31 +28,35 @@ export default createPlugin({
});
`;
export const EmptyFlags = () => (
<EmptyState
missing="content"
title="No Feature Flags"
description="Feature Flags make it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc."
action={
<>
<Typography variant="body1">
An example for how to add a feature flag is highlighted below:
</Typography>
<CodeSnippet
text={EXAMPLE}
language="typescript"
showLineNumbers
highlightedNumbers={[6]}
customStyle={{ background: 'inherit', fontSize: '115%' }}
/>
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/api/utility-apis"
>
Read More
</Button>
</>
}
/>
);
export const EmptyFlags = () => {
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<EmptyState
missing="content"
title={t('featureFlags.emptyFlags.title')}
description={t('featureFlags.emptyFlags.description')}
action={
<>
<Typography variant="body1">
{t('featureFlags.emptyFlags.action.title')}
</Typography>
<CodeSnippet
text={EXAMPLE}
language="typescript"
showLineNumbers
highlightedNumbers={[6]}
customStyle={{ background: 'inherit', fontSize: '115%' }}
/>
<Button
variant="contained"
color="primary"
href="https://backstage.io/docs/api/utility-apis"
>
{t('featureFlags.emptyFlags.action.readMoreButtonTitle')}
</Button>
</>
}
/>
);
};
@@ -20,6 +20,9 @@ import ListItemIcon from '@material-ui/core/ListItemIcon';
import Switch from '@material-ui/core/Switch';
import Tooltip from '@material-ui/core/Tooltip';
import { FeatureFlag } from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
import { TranslationFunction } from '@backstage/core-plugin-api/alpha';
type Props = {
flag: FeatureFlag;
@@ -27,22 +30,39 @@ type Props = {
toggleHandler: Function;
};
const getSecondaryText = (flag: FeatureFlag) => {
const getSecondaryText = (
flag: FeatureFlag,
t: TranslationFunction<typeof userSettingsTranslationRef.T>,
) => {
if (flag.description) {
return flag.description;
}
return flag.pluginId
? `Registered in ${flag.pluginId} plugin`
: 'Registered in the application';
? t('featureFlags.flagItem.subTitle.registeredInPlugin', {
pluginId: flag.pluginId,
})
: t('featureFlags.flagItem.subTitle.registeredInApplication');
};
export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => (
<ListItem divider button onClick={() => toggleHandler(flag.name)}>
<ListItemIcon>
<Tooltip placement="top" arrow title={enabled ? 'Disable' : 'Enable'}>
<Switch color="primary" checked={enabled} name={flag.name} />
</Tooltip>
</ListItemIcon>
<ListItemText primary={flag.name} secondary={getSecondaryText(flag)} />
</ListItem>
);
export const FlagItem = ({ flag, enabled, toggleHandler }: Props) => {
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<ListItem divider button onClick={() => toggleHandler(flag.name)}>
<ListItemIcon>
<Tooltip
placement="top"
arrow
title={
enabled
? t('featureFlags.flagItem.title.disable')
: t('featureFlags.flagItem.title.enable')
}
>
<Switch color="primary" checked={enabled} name={flag.name} />
</Tooltip>
</ListItemIcon>
<ListItemText primary={flag.name} secondary={getSecondaryText(flag, t)} />
</ListItem>
);
};
@@ -31,6 +31,8 @@ import {
} from '@backstage/core-plugin-api';
import { InfoCard } from '@backstage/core-components';
import ClearIcon from '@material-ui/icons/Clear';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
export const sortFlags = (
flags: FeatureFlag[],
@@ -59,6 +61,7 @@ export const UserSettingsFeatureFlags = () => {
const [state, setState] = useState<Record<string, boolean>>(initialFlagState);
const [filterInput, setFilterInput] = useState<string>('');
const { t } = useTranslationRef(userSettingsTranslationRef);
const toggleFlag = useCallback(
(flagName: string) => {
@@ -96,9 +99,9 @@ export const UserSettingsFeatureFlags = () => {
const Header = () => (
<Grid container style={{ justifyContent: 'space-between' }}>
<Grid item xs={6} md={8}>
<Typography variant="h5">Feature Flags</Typography>
<Typography variant="h5">{t('featureFlags.title')}</Typography>
<Typography variant="subtitle1">
Please refresh the page when toggling feature flags
{t('featureFlags.description')}
</Typography>
</Grid>
{featureFlags.length >= 10 && (
@@ -19,25 +19,28 @@ import { EntityRefLinks } from '@backstage/plugin-catalog-react';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { useUserProfile } from '../useUserProfileInfo';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
const Contents = () => {
const { backstageIdentity } = useUserProfile();
const { t } = useTranslationRef(userSettingsTranslationRef);
if (!backstageIdentity) {
return <Typography>No Backstage Identity</Typography>;
return <Typography>{t('identityCard.noIdentityTitle')}</Typography>;
}
return (
<Grid container spacing={1}>
<Grid item xs={12}>
<Typography variant="subtitle1" gutterBottom>
User Entity:{' '}
{t('identityCard.userEntity')}:{' '}
<EntityRefLinks entityRefs={[backstageIdentity.userEntityRef]} />
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1">
Ownership Entities:{' '}
{t('identityCard.ownershipEntities')}:{' '}
<EntityRefLinks entityRefs={backstageIdentity.ownershipEntityRefs} />
</Typography>
</Grid>
@@ -46,8 +49,12 @@ const Contents = () => {
};
/** @public */
export const UserSettingsIdentityCard = () => (
<InfoCard title="Backstage Identity">
<Contents />
</InfoCard>
);
export const UserSettingsIdentityCard = () => {
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<InfoCard title={t('identityCard.title')}>
<Contents />
</InfoCard>
);
};
@@ -26,6 +26,8 @@ import {
errorApiRef,
useApi,
} from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
/** @public */
export const UserSettingsMenu = () => {
@@ -33,6 +35,7 @@ export const UserSettingsMenu = () => {
const identityApi = useApi(identityApiRef);
const [open, setOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<undefined | HTMLElement>(undefined);
const { t } = useTranslationRef(userSettingsTranslationRef);
const handleOpen = (event: MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
@@ -63,7 +66,7 @@ export const UserSettingsMenu = () => {
<ListItemIcon>
<SignOutIcon />
</ListItemIcon>
Sign Out
{t('signOutMenu.title')}
</MenuItem>
</Menu>
</>
@@ -20,16 +20,19 @@ import ListItemText from '@material-ui/core/ListItemText';
import Switch from '@material-ui/core/Switch';
import Tooltip from '@material-ui/core/Tooltip';
import { useSidebarPinState } from '@backstage/core-components';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
/** @public */
export const UserSettingsPinToggle = () => {
const { isPinned, toggleSidebarPinState } = useSidebarPinState();
const { t } = useTranslationRef(userSettingsTranslationRef);
return (
<ListItem>
<ListItemText
primary="Pin Sidebar"
secondary="Prevent the sidebar from collapsing"
primary={t('pinToggle.title')}
secondary={t('pinToggle.description')}
/>
<ListItemSecondaryAction>
<Tooltip
@@ -26,6 +26,8 @@ import {
attachComponentData,
useElementFilter,
} from '@backstage/core-plugin-api';
import { useTranslationRef } from '@backstage/frontend-plugin-api';
import { userSettingsTranslationRef } from '../../translation';
/** @public */
export type SettingsLayoutRouteProps = {
@@ -57,6 +59,7 @@ export type SettingsLayoutProps = {
export const SettingsLayout = (props: SettingsLayoutProps) => {
const { title, children } = props;
const { isMobile } = useSidebarPinState();
const { t } = useTranslationRef(userSettingsTranslationRef);
const routes = useElementFilter(children, elements =>
elements
@@ -71,7 +74,7 @@ export const SettingsLayout = (props: SettingsLayoutProps) => {
return (
<Page themeId="home">
{!isMobile && <Header title={title ?? 'Settings'} />}
{!isMobile && <Header title={title ?? t('settingsLayout.title')} />}
<RoutedTabs routes={routes} />
</Page>
);
+75
View File
@@ -36,5 +36,80 @@ export const userSettingsTranslationRef = createTranslationRef({
auto: 'Auto',
},
},
signOutMenu: {
title: 'Sign Out',
},
pinToggle: {
title: 'Pin Sidebar',
description: 'Prevent the sidebar from collapsing',
},
identityCard: {
title: 'Backstage Identity',
noIdentityTitle: 'No Backstage Identity',
userEntity: 'User Entity',
ownershipEntities: 'Ownership Entities',
},
defaultProviderSettings: {
description:
'Provides authentication towards {{provider}} APIs and identities',
},
emptyProviders: {
title: 'No Authentication Providers',
description:
'You can add Authentication Providers to Backstage which allows you to use these providers to authenticate yourself.',
action: {
title:
'Open app-config.yaml and make the changes as highlighted below:',
readMoreButtonTitle: 'Read More',
},
},
providerSettingsItem: {
title: {
signIn: 'Sign in to {{title}}',
signOut: 'Sign out from {{title}}',
},
buttonTitle: {
signIn: 'Sign in',
signOut: 'Sign out',
},
},
authProviders: {
title: 'Available Providers',
},
defaultSettingsPage: {
tabsTitle: {
general: 'General',
authProviders: 'Authentication Providers',
featureFlags: 'Feature Flags',
},
},
featureFlags: {
title: 'Feature Flags',
description: 'Please refresh the page when toggling feature flags',
emptyFlags: {
title: 'No Feature Flags',
description:
'Feature Flags make it possible for plugins to register features in Backstage for users to opt into. You can use this to split out logic in your code for manual A/B testing, etc.',
action: {
title:
'An example for how to add a feature flag is highlighted below:',
readMoreButtonTitle: 'Read More',
},
},
flagItem: {
title: {
disable: 'Disable',
enable: 'Enable',
},
subTitle: {
registeredInApplication: 'Registered in the application',
registeredInPlugin: 'Registered in {{pluginId}} plugin',
},
},
},
general: {},
settingsLayout: {
title: 'Settings',
},
},
});