Skip to content

Commit 5c8f1d0

Browse files
feat(i18n): add translation support to ai-integrations plugin (#1386)
1 parent 7e624ce commit 5c8f1d0

30 files changed

Lines changed: 1096 additions & 55 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-ai-experience': minor
3+
---
4+
5+
Add internationalization (i18n) support with German, French and Spanish translations in ai-integrations plugin.

workspaces/ai-integrations/packages/app/src/App.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ import {
5757
AiNewsPage,
5858
} from '@red-hat-developer-hub/backstage-plugin-ai-experience';
5959
import { githubAuthApiRef } from '@backstage/core-plugin-api';
60+
import { aiExperienceTranslationsResource } from '@red-hat-developer-hub/backstage-plugin-ai-experience';
6061

6162
const githubProvider = {
6263
id: 'github-auth-provider',
@@ -67,6 +68,11 @@ const githubProvider = {
6768

6869
const app = createApp({
6970
apis,
71+
__experimentalTranslations: {
72+
defaultLanguage: 'en',
73+
availableLanguages: ['en', 'de', 'fr', 'es'],
74+
resources: [aiExperienceTranslationsResource],
75+
},
7076
bindRoutes({ bind }) {
7177
bind(catalogPlugin.externalRoutes, {
7278
createComponent: scaffolderPlugin.routes.root,

workspaces/ai-integrations/plugins/ai-experience/dev/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@ import {
2222
AiExperiencePage,
2323
AiNewsPage,
2424
} from '../src/plugin';
25+
import { aiExperienceTranslationsResource } from '../src/translations';
2526

2627
createDevApp()
2728
.registerPlugin(aiExperiencePlugin)
2829
.addThemes(getAllThemes())
30+
.addTranslationResource(aiExperienceTranslationsResource)
31+
.setAvailableLanguages(['en', 'de', 'fr', 'es'])
32+
.setDefaultLanguage('en')
2933
.addPage({
3034
element: <AiExperiencePage />,
3135
title: 'Root Page',

workspaces/ai-integrations/plugins/ai-experience/report.api.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
99
import { IconComponent } from '@backstage/core-plugin-api';
1010
import { JSX as JSX_2 } from 'react';
1111
import { RouteRef } from '@backstage/core-plugin-api';
12+
import { TranslationRef } from '@backstage/core-plugin-api/alpha';
13+
import { TranslationResource } from '@backstage/core-plugin-api/alpha';
1214

1315
// @public
1416
export const AiExperiencePage: () => JSX_2.Element;
@@ -22,6 +24,54 @@ export const aiExperiencePlugin: BackstagePlugin<
2224
{}
2325
>;
2426

27+
// @public
28+
export const aiExperienceTranslationRef: TranslationRef<
29+
'plugin.ai-experience',
30+
{
31+
readonly 'page.title': string;
32+
readonly 'page.subtitle': string;
33+
readonly 'news.pageTitle': string;
34+
readonly 'news.fetchingRssFeed': string;
35+
readonly 'news.noContentAvailable': string;
36+
readonly 'news.noContentDescription': string;
37+
readonly 'news.noRssContent': string;
38+
readonly 'learn.learn.title': string;
39+
readonly 'learn.learn.description': string;
40+
readonly 'learn.learn.cta': string;
41+
readonly 'learn.getStarted.title': string;
42+
readonly 'learn.getStarted.description': string;
43+
readonly 'learn.getStarted.cta': string;
44+
readonly 'learn.explore.title': string;
45+
readonly 'learn.explore.description': string;
46+
readonly 'learn.explore.cta': string;
47+
readonly 'modal.title.preview': string;
48+
readonly 'modal.title.edit': string;
49+
readonly 'modal.close': string;
50+
readonly 'modal.edit': string;
51+
readonly 'modal.save': string;
52+
readonly 'modal.cancel': string;
53+
readonly 'common.template': string;
54+
readonly 'common.latest': string;
55+
readonly 'common.more': string;
56+
readonly 'common.viewMore': string;
57+
readonly 'common.guest': string;
58+
readonly 'greeting.goodMorning': string;
59+
readonly 'greeting.goodAfternoon': string;
60+
readonly 'greeting.goodEvening': string;
61+
readonly 'sections.exploreAiModels': string;
62+
readonly 'sections.exploreAiTemplates': string;
63+
readonly 'sections.discoverModels': string;
64+
readonly 'sections.viewAllModels': string;
65+
readonly 'sections.viewAllTemplates': string;
66+
readonly 'accessibility.close': string;
67+
readonly 'accessibility.aiIllustration': string;
68+
readonly 'accessibility.aiModelsIllustration': string;
69+
}
70+
>;
71+
72+
// @public
73+
export const aiExperienceTranslationsResource: TranslationResource<'plugin.ai-experience'>;
74+
2575
// @public (undocumented)
2676
export const AiNewsIcon: IconComponent;
2777

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export * from './translations';

workspaces/ai-integrations/plugins/ai-experience/src/components/AiExperienceHomePage/AiExperienceHomePage.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@
1414
* limitations under the License.
1515
*/
1616
import React from 'react';
17-
17+
import { useAsync } from 'react-use';
1818
import { Content, Page } from '@backstage/core-components';
19+
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
1920
import Box from '@mui/material/Box';
20-
21-
import SectionWrapper from '../SectionWrapper';
21+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
22+
import useGreeting from '../../hooks/useGreeting';
23+
import { useTranslation } from '../../hooks/useTranslation';
2224
import LearnSection from '../LearnSection';
2325
import ModelSection from '../ModelSection';
26+
import SectionWrapper from '../SectionWrapper';
2427
import TemplateSection from '../TemplateSection';
25-
import useGreeting from '../../hooks/useGreeting';
26-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
27-
import { identityApiRef, useApi } from '@backstage/core-plugin-api';
28-
import { useAsync } from 'react-use';
2928

3029
export const AiExperienceHomePage = () => {
3130
const greeting = useGreeting();
3231
const identityApi = useApi(identityApiRef);
32+
const { t } = useTranslation();
3333

3434
const { value: profile } = useAsync(() => identityApi.getProfileInfo());
3535

@@ -39,22 +39,24 @@ export const AiExperienceHomePage = () => {
3939
<Content>
4040
<Box>
4141
<SectionWrapper
42-
title={`${greeting} ${profile?.displayName ?? 'Guest'}!`}
42+
title={`${greeting} ${
43+
profile?.displayName ?? t('common.guest')
44+
}!`}
4345
>
4446
<Box sx={{ padding: '20px 10px 30px 40px' }}>
4547
<LearnSection />
4648
</Box>
4749
</SectionWrapper>
4850
</Box>
4951
<Box sx={{ pt: 3 }}>
50-
<SectionWrapper title="Explore AI models">
52+
<SectionWrapper title={t('sections.exploreAiModels')}>
5153
<Box sx={{ padding: '20px 10px 10px 0' }}>
5254
<ModelSection />
5355
</Box>
5456
</SectionWrapper>
5557
</Box>
5658
<Box sx={{ pt: 3 }}>
57-
<SectionWrapper title="Explore AI templates">
59+
<SectionWrapper title={t('sections.exploreAiTemplates')}>
5860
<Box sx={{ padding: '20px 10px 10px 0' }}>
5961
<TemplateSection />
6062
</Box>

workspaces/ai-integrations/plugins/ai-experience/src/components/LearnSection/CardWrapper.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,12 @@ const CardWrapper: React.FC<CardWrapperProps> = ({
6161
{title}
6262
</Typography>
6363
</Box>
64-
<Box>
64+
<Box sx={{ pt: 1, pb: 2 }}>
6565
<Typography
6666
variant="body2"
6767
sx={{
6868
fontSize: 16,
6969
fontWeight: '500',
70-
pt: 1,
71-
pb: 2,
7270
display: '-webkit-box',
7371
WebkitBoxOrient: 'vertical',
7472
WebkitLineClamp: 2,

workspaces/ai-integrations/plugins/ai-experience/src/components/LearnSection/LearnSection.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ import Box from '@mui/material/Box';
2121
import CardWrapper from './CardWrapper';
2222
import HomePageAiIllustration from '../../images/homepage-ai-illustration.svg';
2323
import { LEARNING_SECTION_ITEMS } from '../../utils/constants';
24+
import { useTranslation } from '../../hooks/useTranslation';
2425

2526
export const LearnSection = () => {
27+
const { t } = useTranslation();
2628
return (
2729
<Grid container spacing={3}>
2830
<Grid
@@ -37,7 +39,7 @@ export const LearnSection = () => {
3739
<Box
3840
component="img"
3941
src={HomePageAiIllustration}
40-
alt="AI illustration"
42+
alt={t('accessibility.aiIllustration')}
4143
/>
4244
</Grid>
4345
{LEARNING_SECTION_ITEMS.map(item => (
@@ -46,15 +48,15 @@ export const LearnSection = () => {
4648
xs={12}
4749
md={6}
4850
lg={3}
49-
key={item.title}
51+
key={String(item.titleKey)}
5052
display="flex"
5153
justifyContent="left"
5254
alignItems="center"
5355
>
5456
<CardWrapper
55-
title={item.title}
56-
description={item.description}
57-
buttonText={item.buttonText}
57+
title={t(item.titleKey as any, {})}
58+
description={t(item.descriptionKey as any, {})}
59+
buttonText={t(item.buttonTextKey as any, {})}
5860
buttonLink={item.buttonLink}
5961
target={item.target}
6062
/>

workspaces/ai-integrations/plugins/ai-experience/src/components/ModelSection/ModelSection.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import CardWrapper from './CardWrapper';
2828
import HomePageAiModels from '../../images/homepage-ai-models.svg';
2929
import { useModels } from '../../hooks/useModels';
3030
import { ViewMoreLink } from '../Links/ViewMoreLink';
31+
import { useTranslation } from '../../hooks/useTranslation';
3132

3233
export const ModelSection = () => {
3334
const [isRemoveFirstCard, setIsRemoveFirstCard] = React.useState(false);
@@ -39,6 +40,7 @@ export const ModelSection = () => {
3940
const isExtraSmallScreen = useMediaQuery(theme.breakpoints.down(900));
4041
const smallScreenWidth = isExtraSmallScreen ? 266 : 180;
4142
const imageWidth = isSmallScreen ? smallScreenWidth : 266;
43+
const { t } = useTranslation();
4244

4345
const handleClose = () => {
4446
setShowDiscoveryCard(false);
@@ -47,7 +49,7 @@ export const ModelSection = () => {
4749
}, 500);
4850
};
4951
const { data } = useModels();
50-
const models = data?.items ?? [];
52+
const models = data?.items;
5153
const params = new URLSearchParams({
5254
'filters[kind]': 'resource',
5355
'filters[type]': 'ai-model',
@@ -59,7 +61,12 @@ export const ModelSection = () => {
5961
<React.Fragment>
6062
<Grid container spacing={1} alignItems="stretch">
6163
{!isRemoveFirstCard && (
62-
<Grid item xs={12} md={5} key="AI models illustration">
64+
<Grid
65+
item
66+
xs={12}
67+
md={5}
68+
key={t('accessibility.aiModelsIllustration')}
69+
>
6370
<Box
6471
sx={{
6572
border: `1px solid ${theme.palette.grey[400]}`,
@@ -86,20 +93,19 @@ export const ModelSection = () => {
8693
component="img"
8794
src={HomePageAiModels}
8895
onLoad={() => setImgLoaded(true)}
89-
alt="AI models illustration"
96+
alt={t('accessibility.aiModelsIllustration')}
9097
height={300}
9198
width={imageWidth}
9299
/>
93100
<Box sx={{ p: 2 }}>
94101
<Box>
95102
<Typography variant="body2" paragraph>
96-
Discover the AI models and services that are available in
97-
your organization
103+
{t('sections.discoverModels')}
98104
</Typography>
99105
</Box>
100106
<IconButton
101107
onClick={handleClose}
102-
aria-label="close"
108+
aria-label={t('accessibility.close')}
103109
sx={{ position: 'absolute', top: 8, right: 8 }}
104110
>
105111
<CloseIcon sx={{ width: '16px', height: '16px' }} />
@@ -118,7 +124,7 @@ export const ModelSection = () => {
118124
<CardWrapper
119125
link={`/catalog/default/resource/${item.metadata.name}`}
120126
title={item.spec?.profile?.displayName ?? item.metadata.name}
121-
version="latest"
127+
version={t('common.latest')}
122128
description={item.metadata.description ?? ''}
123129
tags={item.metadata?.tags ?? []}
124130
/>
@@ -127,7 +133,9 @@ export const ModelSection = () => {
127133
</Grid>
128134
<Box sx={{ pt: 2 }}>
129135
<ViewMoreLink to={catalogModelLink}>
130-
View all {data?.totalItems ? data?.totalItems : ''} models
136+
{t('sections.viewAllModels' as any, {
137+
count: data?.totalItems ? data?.totalItems.toString() : '',
138+
})}
131139
</ViewMoreLink>
132140
</Box>
133141
</React.Fragment>

workspaces/ai-integrations/plugins/ai-experience/src/components/ModelSection/TagList.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import React from 'react';
1818
import Box from '@mui/material/Box';
1919
import Chip from '@mui/material/Chip';
2020
import Typography from '@mui/material/Typography';
21+
import { useTranslation } from '../../hooks/useTranslation';
2122

2223
interface TagListProps {
2324
tags: string[];
2425
}
2526

2627
const TagList: React.FC<TagListProps> = ({ tags }) => {
28+
const { t } = useTranslation();
2729
const hiddenCount = tags.length - 3;
2830
return (
2931
<Box
@@ -60,7 +62,7 @@ const TagList: React.FC<TagListProps> = ({ tags }) => {
6062
`${theme.palette.mode === 'light' ? '#0066CC' : '#1FA7F8'}`,
6163
}}
6264
>
63-
{`${hiddenCount} more`}
65+
{`${hiddenCount} ${t('common.more')}`}
6466
</Typography>
6567
}
6668
variant="outlined"

0 commit comments

Comments
 (0)