From 1c6b97eb272f0cf3ca39ba0221b32677607ca0d2 Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Thu, 25 Jun 2026 16:27:35 +0530 Subject: [PATCH 1/2] fix(homepage): fix missing translations for homepage card widgets --- .../.changeset/yellow-rings-decide.md | 5 + .../homepage/packages/app-legacy/src/App.tsx | 12 +- .../plugins/homepage/app-config.dynamic.yaml | 21 +++- .../plugins/homepage/report-alpha.api.md | 9 +- .../homepage/plugins/homepage/report.api.md | 16 +++ .../components/CustomizableGridLayout.tsx | 14 ++- .../src/alpha/extensions/homePageCards.tsx | 52 +++++++- .../plugins/homepage/src/alpha/index.ts | 2 + .../homepage/src/components/SearchBar.tsx | 31 +++++ .../TranslatedUpstreamHomePageCards.tsx | 13 ++ .../homepage/plugins/homepage/src/plugin.ts | 17 +++ .../plugins/homepage/src/translations/de.ts | 9 ++ .../plugins/homepage/src/translations/es.ts | 9 ++ .../plugins/homepage/src/translations/fr.ts | 9 ++ .../plugins/homepage/src/translations/it.ts | 10 ++ .../plugins/homepage/src/translations/ja.ts | 8 ++ .../plugins/homepage/src/translations/ref.ts | 9 ++ .../homepage/src/translations/utils.ts | 12 +- .../plugins/homepage/src/utils/constants.ts | 5 +- .../homepage/src/utils/customizable-cards.ts | 12 +- .../utils/translateHomepageWidgets.test.ts | 119 ++++++++++++++++++ .../src/utils/translateHomepageWidgets.ts | 74 +++++++++++ .../utils/updateWidgetComponentData.test.ts | 50 ++++++++ .../src/utils/updateWidgetComponentData.ts | 39 ++++++ .../src/utils/useHomePageCardTitle.test.ts | 55 ++++++++ .../src/utils/useHomePageCardTitle.ts | 5 + .../src/utils/widgetTranslationKeys.ts | 61 +++++++++ 27 files changed, 652 insertions(+), 26 deletions(-) create mode 100644 workspaces/homepage/.changeset/yellow-rings-decide.md create mode 100644 workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.test.ts create mode 100644 workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.ts create mode 100644 workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.test.ts create mode 100644 workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.ts create mode 100644 workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.test.ts create mode 100644 workspaces/homepage/plugins/homepage/src/utils/widgetTranslationKeys.ts diff --git a/workspaces/homepage/.changeset/yellow-rings-decide.md b/workspaces/homepage/.changeset/yellow-rings-decide.md new file mode 100644 index 0000000000..24dad9ce3e --- /dev/null +++ b/workspaces/homepage/.changeset/yellow-rings-decide.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-homepage': patch +--- + +fix missing translations for homepage card widgets diff --git a/workspaces/homepage/packages/app-legacy/src/App.tsx b/workspaces/homepage/packages/app-legacy/src/App.tsx index 3fefad22ba..6c8682138c 100644 --- a/workspaces/homepage/packages/app-legacy/src/App.tsx +++ b/workspaces/homepage/packages/app-legacy/src/App.tsx @@ -74,13 +74,11 @@ import { TemplateSection, SearchBar, Headline, - // Markdown, - // MarkdownCard, - // Placeholder, CatalogStarredEntitiesCard, RecentlyVisitedCard, TopVisitedCard, FeaturedDocsCard, + JokeCard, WorldClock, HomePageCardMountPoint, } from '@red-hat-developer-hub/backstage-plugin-homepage'; @@ -346,6 +344,14 @@ const cardMountPoints: HomePageCardMountPoint[] = [ titleKey: 'featuredDocs.title', }, }, + { + Component: JokeCard as ComponentType, + config: { + id: 'joke-card', + titleKey: 'randomJoke.title', + descriptionKey: 'randomJoke.description', + }, + }, { Component: WorldClock as ComponentType, config: { diff --git a/workspaces/homepage/plugins/homepage/app-config.dynamic.yaml b/workspaces/homepage/plugins/homepage/app-config.dynamic.yaml index 122c4159c0..2ccb01010c 100644 --- a/workspaces/homepage/plugins/homepage/app-config.dynamic.yaml +++ b/workspaces/homepage/plugins/homepage/app-config.dynamic.yaml @@ -42,8 +42,8 @@ dynamicPlugins: - mountPoint: home.page/widgets importName: RecentlyVisitedCard config: - title: 'Recently Visited' - description: 'Quick access to recently viewed entities and pages' + titleKey: recentlyVisited.title + descriptionKey: recentlyVisited.description priority: 2 layouts: xl: { w: 4, h: 3 } @@ -55,8 +55,8 @@ dynamicPlugins: - mountPoint: home.page/widgets importName: TopVisitedCard config: - title: 'Most Visited' - description: 'Your most frequently accessed entities and services' + titleKey: topVisited.title + descriptionKey: topVisited.description priority: 1 layouts: xl: { w: 4, h: 3 } @@ -65,3 +65,16 @@ dynamicPlugins: sm: { w: 12, h: 3 } xs: { w: 12, h: 3 } xxs: { w: 12, h: 3 } + - mountPoint: home.page/widgets + importName: JokeCard + config: + titleKey: randomJoke.title + descriptionKey: randomJoke.description + priority: 3 + layouts: + xl: { w: 4, h: 4 } + lg: { w: 4, h: 4 } + md: { w: 6, h: 4 } + sm: { w: 12, h: 4 } + xs: { w: 12, h: 4 } + xxs: { w: 12, h: 4 } diff --git a/workspaces/homepage/plugins/homepage/report-alpha.api.md b/workspaces/homepage/plugins/homepage/report-alpha.api.md index 1d41467755..ed604441a4 100644 --- a/workspaces/homepage/plugins/homepage/report-alpha.api.md +++ b/workspaces/homepage/plugins/homepage/report-alpha.api.md @@ -17,7 +17,9 @@ export const homepageTranslationRef: TranslationRef< readonly 'header.local': string; readonly 'header.welcome': string; readonly 'header.welcomePersonalized': string; + readonly 'search.title': string; readonly 'search.placeholder': string; + readonly 'search.clearButton': string; readonly 'homePage.empty': string; readonly 'quickAccess.title': string; readonly 'quickAccess.error': string; @@ -26,7 +28,11 @@ export const homepageTranslationRef: TranslationRef< readonly 'featuredDocs.learnMore': string; readonly 'starredEntities.title': string; readonly 'recentlyVisited.title': string; + readonly 'recentlyVisited.description': string; readonly 'topVisited.title': string; + readonly 'topVisited.description': string; + readonly 'randomJoke.title': string; + readonly 'randomJoke.description': string; readonly 'templates.title': string; readonly 'templates.error': string; readonly 'templates.empty': string; @@ -34,6 +40,7 @@ export const homepageTranslationRef: TranslationRef< readonly 'templates.emptyDescription': string; readonly 'templates.register': string; readonly 'templates.viewAll': string; + readonly 'onboarding.title': string; readonly 'onboarding.guest': string; readonly 'onboarding.greeting.goodMorning': string; readonly 'onboarding.greeting.goodAfternoon': string; @@ -55,9 +62,9 @@ export const homepageTranslationRef: TranslationRef< readonly 'entities.close': string; readonly 'entities.empty': string; readonly 'entities.fetchError': string; + readonly 'entities.description': string; readonly 'entities.emptyDescription': string; readonly 'entities.register': string; - readonly 'entities.description': string; readonly 'entities.browseTheCatalog': string; } >; diff --git a/workspaces/homepage/plugins/homepage/report.api.md b/workspaces/homepage/plugins/homepage/report.api.md index db64c8a442..7cf0bd8be6 100644 --- a/workspaces/homepage/plugins/homepage/report.api.md +++ b/workspaces/homepage/plugins/homepage/report.api.md @@ -144,6 +144,14 @@ export interface HomePageCardMountPointConfig { titleKey?: string; } +// @public (undocumented) +export const JokeCard: (props: JokeCardProps) => JSX_2.Element; + +// @public (undocumented) +export type JokeCardProps = TranslatableCardTitleProps & { + defaultCategory?: 'any' | 'programming'; +}; + // @public (undocumented) export interface Layout { // (undocumented) @@ -242,6 +250,14 @@ export const TemplateSection: () => JSX_2.Element; // @public (undocumented) export const TopVisitedCard: ComponentType; +// @public +export interface TranslatableCardTitleProps { + // (undocumented) + title?: string; + // (undocumented) + titleKey?: string; +} + // @public (undocumented) export const VisitListener: () => JSX_2.Element | null; diff --git a/workspaces/homepage/plugins/homepage/src/alpha/components/CustomizableGridLayout.tsx b/workspaces/homepage/plugins/homepage/src/alpha/components/CustomizableGridLayout.tsx index 2b33370f08..e2c6a7623f 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/components/CustomizableGridLayout.tsx +++ b/workspaces/homepage/plugins/homepage/src/alpha/components/CustomizableGridLayout.tsx @@ -32,6 +32,8 @@ import GlobalStyles from '@mui/material/GlobalStyles'; import { cardWrapperSx } from '../../styles/cardWrapperSx'; import { HomePageCardConfig } from '../../types'; import { useContainerQuery } from '../../hooks/useContainerQuery'; +import { useTranslation } from '../../hooks/useTranslation'; +import { translateHomepageWidget } from '../../utils/translateHomepageWidgets'; import 'react-grid-layout/css/styles.css'; import { isCardADefaultConfiguration } from '../utils'; @@ -53,13 +55,19 @@ export const CustomizableGridLayout = ({ homepageCards, }: CustomizableGridLayoutProps) => { const theme = useTheme(); + const { t } = useTranslation(); const gridContainerRef = useRef(null); useContainerQuery(gridContainerRef, { notifyWindowResize: true }); + const translatedHomepageCards = useMemo( + () => homepageCards.map(card => translateHomepageWidget(card, t)), + [homepageCards, t], + ); + const config = useMemo(() => { const defaultConfig: LayoutConfiguration[] = []; - homepageCards.forEach(homepageCard => { + translatedHomepageCards.forEach(homepageCard => { if (!homepageCard.node) { return; } @@ -81,7 +89,7 @@ export const CustomizableGridLayout = ({ }); return defaultConfig; - }, [homepageCards]); + }, [translatedHomepageCards]); return ( <> @@ -106,7 +114,7 @@ export const CustomizableGridLayout = ({ compactType="vertical" style={{ margin: '-10px' }} > - {homepageCards.map((card, index) => ( + {translatedHomepageCards.map((card, index) => ( {card.component} ))} diff --git a/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx b/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx index 51690f5a21..a3754141e3 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx +++ b/workspaces/homepage/plugins/homepage/src/alpha/extensions/homePageCards.tsx @@ -17,6 +17,7 @@ import { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha'; import homePlugin from '@backstage/plugin-home/alpha'; import { compatWrapper } from '@backstage/core-compat-api'; +import { InfoCard } from '@backstage/core-components'; import { homepageMessages } from '../../translations/ref'; import { createTranslatedCardRenderer } from '../../utils/translatedCardRenderer'; @@ -33,6 +34,28 @@ const defaultCardLayout = { }, } as const; +/** + * Renders widget content without an InfoCard shell (used by Search). + * @alpha + */ +const headlessCardRenderer = ({ Content }: { Content: React.ComponentType }) => + compatWrapper(); + +/** + * Renders widget content inside an InfoCard without a title header. + * @alpha + */ +const untitledInfoCardRenderer = ({ + Content, +}: { + Content: React.ComponentType; +}) => + compatWrapper( + + + , + ); + /** * NFS widget: OnboardingSection (migrated from mountPoint home.page/cards). * @alpha @@ -41,11 +64,13 @@ export const onboardingSectionWidget = HomePageWidgetBlueprint.make({ name: 'rhdh-onboarding-section', params: { name: 'Red Hat Developer Hub - Onboarding', + title: homepageMessages.onboarding.title, layout: defaultCardLayout, components: () => import('../../components/OnboardingSection/OnboardingSection').then( m => ({ Content: m.OnboardingSectionContent, + Renderer: untitledInfoCardRenderer, }), ), }, @@ -115,6 +140,7 @@ export const searchBarWidget = HomePageWidgetBlueprint.make({ name: 'search-bar', params: { name: 'Search', + title: homepageMessages.search.title, layout: { ...defaultCardLayout, height: { @@ -127,8 +153,7 @@ export const searchBarWidget = HomePageWidgetBlueprint.make({ components: () => import('../../components/SearchBar').then(m => ({ Content: m.SearchBar, - Renderer: ({ Content }: { Content: React.ComponentType }) => - compatWrapper(), + Renderer: headlessCardRenderer, })), }, }); @@ -201,6 +226,7 @@ export const RecentlyVisitedWidget = HomePageWidgetBlueprint.make({ layout: defaultCardLayout, name: 'Recently visited', title: homepageMessages.recentlyVisited.title, + description: homepageMessages.recentlyVisited.description, components: () => import('../../components/legacy/TranslatedUpstreamHomePageCards').then( m => ({ @@ -221,6 +247,7 @@ export const TopVisitedWidget = HomePageWidgetBlueprint.make({ layout: defaultCardLayout, name: 'Top visited', title: homepageMessages.topVisited.title, + description: homepageMessages.topVisited.description, components: () => import('../../components/legacy/TranslatedUpstreamHomePageCards').then( m => ({ @@ -230,3 +257,24 @@ export const TopVisitedWidget = HomePageWidgetBlueprint.make({ ), }, }); + +/** + * NFS widget: RandomJoke (overrides upstream home plugin widget). + * @alpha + */ +export const randomJokeWidget = homePlugin + .getExtension('home-page-widget:home/random-joke') + .override({ + params: { + name: 'HomePageRandomJoke', + title: homepageMessages.randomJoke.title, + description: homepageMessages.randomJoke.description, + components: () => + import('../../components/legacy/TranslatedUpstreamHomePageCards').then( + m => ({ + Content: m.JokeCard, + Renderer: upstreamHomeCardRenderer, + }), + ), + }, + }); diff --git a/workspaces/homepage/plugins/homepage/src/alpha/index.ts b/workspaces/homepage/plugins/homepage/src/alpha/index.ts index 1b599a1159..5eb730341c 100644 --- a/workspaces/homepage/plugins/homepage/src/alpha/index.ts +++ b/workspaces/homepage/plugins/homepage/src/alpha/index.ts @@ -27,6 +27,7 @@ import { searchBarWidget, templateSectionWidget, TopVisitedWidget, + randomJokeWidget, } from './extensions/homePageCards'; import { homepageTranslations } from '../translations'; @@ -57,6 +58,7 @@ export const homePageModule = createFrontendModule({ TopVisitedWidget, RecentlyVisitedWidget, catalogStarredWidget, + randomJokeWidget, disableToolkit, ], }); diff --git a/workspaces/homepage/plugins/homepage/src/components/SearchBar.tsx b/workspaces/homepage/plugins/homepage/src/components/SearchBar.tsx index c4b1b27f6e..1a4b85aa78 100644 --- a/workspaces/homepage/plugins/homepage/src/components/SearchBar.tsx +++ b/workspaces/homepage/plugins/homepage/src/components/SearchBar.tsx @@ -19,6 +19,8 @@ import { useNavigate } from 'react-router-dom'; import { SearchBarBase } from '@backstage/plugin-search-react'; +import Button from '@mui/material/Button'; +import InputAdornment from '@mui/material/InputAdornment'; import { styled } from '@mui/material/styles'; import { useTranslation } from '../hooks/useTranslation'; @@ -40,6 +42,11 @@ const StyledSearchBar = styled(SearchBarBase)(({ theme }) => ({ '& .MuiOutlinedInput-notchedOutline, & fieldset': { border: 'none', }, + '& .MuiInputAdornment-positionEnd .MuiButton-root': { + color: theme.palette.text.primary, + fontWeight: theme.typography.fontWeightRegular, + textTransform: 'none', + }, })); /** @@ -71,6 +78,12 @@ export const SearchBar = ({ path, queryParam }: SearchBarProps) => { navigate(`${url.pathname}${search ? '?' : ''}${search}`); }, [navigate, path, queryParam]); + const handleClear = useCallback(() => { + setValue(''); + }, []); + + const clearLabel = t('search.clearButton'); + return ( { onSubmit={handleSubmit} margin="none" inputProps={{ ref }} + clearButton={false} + endAdornment={ + + + + } /> ); }; diff --git a/workspaces/homepage/plugins/homepage/src/components/legacy/TranslatedUpstreamHomePageCards.tsx b/workspaces/homepage/plugins/homepage/src/components/legacy/TranslatedUpstreamHomePageCards.tsx index b82651cf59..4cddcbfd9d 100644 --- a/workspaces/homepage/plugins/homepage/src/components/legacy/TranslatedUpstreamHomePageCards.tsx +++ b/workspaces/homepage/plugins/homepage/src/components/legacy/TranslatedUpstreamHomePageCards.tsx @@ -15,6 +15,7 @@ */ import { + HomePageRandomJoke, HomePageRecentlyVisited, HomePageStarredEntities, HomePageTopVisited, @@ -61,3 +62,15 @@ export const TopVisitedCard = (props: TopVisitedCardProps) => { const title = useHomePageCardTitle('topVisited.title', props); return ; }; + +/** @public */ +export type JokeCardProps = TranslatableCardTitleProps & { + defaultCategory?: 'any' | 'programming'; +}; + +/** @public */ +export const JokeCard = (props: JokeCardProps) => { + const { titleKey: _titleKey, title: _title, ...cardProps } = props; + const title = useHomePageCardTitle('randomJoke.title', props); + return ; +}; diff --git a/workspaces/homepage/plugins/homepage/src/plugin.ts b/workspaces/homepage/plugins/homepage/src/plugin.ts index ae925807b0..0bed2562cd 100644 --- a/workspaces/homepage/plugins/homepage/src/plugin.ts +++ b/workspaces/homepage/plugins/homepage/src/plugin.ts @@ -66,6 +66,8 @@ export type { MarkdownCardProps } from './components/MarkdownCard'; export type { PlaceholderProps } from './components/Placeholder'; export type { LocalClockProps } from './components/LocalClock'; export type { WorldClockProps } from './components/WorldClock'; +export type { JokeCardProps } from './components/legacy/TranslatedUpstreamHomePageCards'; +export type { TranslatableCardTitleProps } from './utils/useHomePageCardTitle'; export type { HomePageCardMountPoint, HomePageCardMountPointConfig, @@ -349,3 +351,18 @@ export const TemplateSection = dynamicHomePagePlugin.provide( }, }), ); + +/** + * @public + */ +export const JokeCard = dynamicHomePagePlugin.provide( + createComponentExtension({ + name: 'JokeCard', + component: { + lazy: () => + import('./components/legacy/TranslatedUpstreamHomePageCards').then( + m => m.JokeCard, + ), + }, + }), +); diff --git a/workspaces/homepage/plugins/homepage/src/translations/de.ts b/workspaces/homepage/plugins/homepage/src/translations/de.ts index 584b46b162..d3ed681104 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/de.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/de.ts @@ -29,6 +29,8 @@ const homepageTranslationDe = createTranslationMessages({ 'header.local': 'Lokal', 'homePage.empty': 'Keine Homepage-Karten konfiguriert oder gefunden.', 'search.placeholder': 'Suchen', + 'search.title': 'Suchen', + 'search.clearButton': 'Löschen', 'quickAccess.title': 'Schnellzugriff', 'quickAccess.fetchError': 'Daten konnten nicht abgerufen werden.', 'quickAccess.error': 'Unbekannter Fehler', @@ -36,7 +38,13 @@ const homepageTranslationDe = createTranslationMessages({ 'featuredDocs.learnMore': ' Mehr erfahren', 'starredEntities.title': 'Markierte Katalogentitäten', 'recentlyVisited.title': 'Zuletzt besucht', + 'recentlyVisited.description': + 'Schnellzugriff auf kürzlich angesehene Entitäten und Seiten', 'topVisited.title': 'Am häufigsten besucht', + 'topVisited.description': + 'Ihre am häufigsten aufgerufenen Entitäten und Dienste', + 'randomJoke.title': 'Zufälliger Witz', + 'randomJoke.description': 'Zeigt einen zufälligen Programmierwitz', 'templates.title': 'Vorlagen erkunden', 'templates.fetchError': 'Daten konnten nicht abgerufen werden.', 'templates.error': 'Unbekannter Fehler', @@ -45,6 +53,7 @@ const homepageTranslationDe = createTranslationMessages({ 'Sobald Vorlagen hinzugefügt werden, wird dieser Bereich relevante Inhalte anzeigen, die auf Ihre Erfahrung zugeschnitten sind.', 'templates.register': 'Vorlage registrieren', 'templates.viewAll': 'Alle {{count}} Vorlagen anzeigen', + 'onboarding.title': 'Red Hat Developer Hub – Einführung', 'onboarding.greeting.goodMorning': 'Guten Morgen', 'onboarding.greeting.goodAfternoon': 'Guten Tag', 'onboarding.greeting.goodEvening': 'Guten Abend', diff --git a/workspaces/homepage/plugins/homepage/src/translations/es.ts b/workspaces/homepage/plugins/homepage/src/translations/es.ts index 208975942d..9662aa7387 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/es.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/es.ts @@ -30,6 +30,8 @@ const homepageTranslationEs = createTranslationMessages({ 'homePage.empty': 'No se configuraron o encontraron tarjetas de página de inicio (puntos de montaje).', 'search.placeholder': 'Buscar', + 'search.title': 'Buscar', + 'search.clearButton': 'Borrar', 'quickAccess.title': 'Acceso rápido', 'quickAccess.fetchError': 'No se pudieron obtener los datos.', 'quickAccess.error': 'Error desconocido', @@ -37,7 +39,13 @@ const homepageTranslationEs = createTranslationMessages({ 'featuredDocs.learnMore': ' Saber más', 'starredEntities.title': 'Entidades del catálogo marcadas', 'recentlyVisited.title': 'Visitados recientemente', + 'recentlyVisited.description': + 'Acceso rápido a entidades y páginas visitadas recientemente', 'topVisited.title': 'Más visitados', + 'topVisited.description': + 'Tus entidades y servicios a los que accedes con más frecuencia', + 'randomJoke.title': 'Chiste aleatorio', + 'randomJoke.description': 'Muestra un chiste de programación aleatorio', 'templates.title': 'Explorar plantillas', 'templates.fetchError': 'No se pudieron obtener los datos.', 'templates.error': 'Error desconocido', @@ -46,6 +54,7 @@ const homepageTranslationEs = createTranslationMessages({ 'Una vez que se agreguen plantillas, este espacio mostrará contenido relevante adaptado a tu experiencia.', 'templates.register': 'Registrar una plantilla', 'templates.viewAll': 'Ver todas las {{count}} plantillas', + 'onboarding.title': 'Red Hat Developer Hub - Onboarding', 'onboarding.greeting.goodMorning': 'Buenos días', 'onboarding.greeting.goodAfternoon': 'Buenas tardes', 'onboarding.greeting.goodEvening': 'Buenas noches', diff --git a/workspaces/homepage/plugins/homepage/src/translations/fr.ts b/workspaces/homepage/plugins/homepage/src/translations/fr.ts index b0fdaabc12..a6b0db1cd7 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/fr.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/fr.ts @@ -29,6 +29,8 @@ const homepageTranslationFr = createTranslationMessages({ 'header.local': 'Locale', 'homePage.empty': "Aucune carte de page d'accueil configurée ou trouvée.", 'search.placeholder': 'Recherche', + 'search.title': 'Recherche', + 'search.clearButton': 'Effacer', 'quickAccess.title': 'Accès rapide', 'quickAccess.fetchError': 'Impossible de récupérer les données.', 'quickAccess.error': 'Erreur inconnue', @@ -36,7 +38,13 @@ const homepageTranslationFr = createTranslationMessages({ 'featuredDocs.learnMore': ' En savoir plus', 'starredEntities.title': 'Entités du catalogue favoris', 'recentlyVisited.title': 'Récemment visités', + 'recentlyVisited.description': + 'Accès rapide aux entités et pages récemment consultées', 'topVisited.title': 'Les plus visités', + 'topVisited.description': + 'Vos entités et services les plus fréquemment consultés', + 'randomJoke.title': 'Blague aléatoire', + 'randomJoke.description': 'Affiche une blague de programmation aléatoire', 'templates.title': 'Explorer les modèles', 'templates.fetchError': 'Impossible de récupérer les données.', 'templates.error': 'Erreur inconnue', @@ -45,6 +53,7 @@ const homepageTranslationFr = createTranslationMessages({ 'Une fois les modèles ajoutés, cet espace présentera du contenu pertinent adapté à votre expérience.', 'templates.register': 'Enregistrer un modèle', 'templates.viewAll': 'Afficher tous les {{count}} modèles', + 'onboarding.title': 'Red Hat Developer Hub - Onboarding', 'onboarding.greeting.goodMorning': 'Bonjour', 'onboarding.greeting.goodAfternoon': 'Bon après-midi', 'onboarding.greeting.goodEvening': 'Bonne soirée', diff --git a/workspaces/homepage/plugins/homepage/src/translations/it.ts b/workspaces/homepage/plugins/homepage/src/translations/it.ts index f8ec13e021..83aa200159 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/it.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/it.ts @@ -29,6 +29,8 @@ const homepageTranslationIt = createTranslationMessages({ 'header.local': 'Locale', 'homePage.empty': 'Nessuna scheda home page configurata o trovata.', 'search.placeholder': 'Ricerca', + 'search.title': 'Ricerca', + 'search.clearButton': 'Cancella', 'quickAccess.title': 'Accesso rapido', 'quickAccess.fetchError': 'Impossibile recuperare i dati.', 'quickAccess.error': 'Errore sconosciuto', @@ -36,7 +38,14 @@ const homepageTranslationIt = createTranslationMessages({ 'featuredDocs.learnMore': ' Per saperne di più', 'starredEntities.title': 'Entità del catalogo preferite', 'recentlyVisited.title': 'Visitati di recente', + 'recentlyVisited.description': + 'Accesso rapido alle entità e alle pagine visualizzate di recente', 'topVisited.title': 'I più visitati', + 'topVisited.description': + 'Le entità e i servizi a cui accedi più frequentemente', + 'randomJoke.title': 'Barzelletta casuale', + 'randomJoke.description': + 'Mostra una barzelletta di programmazione casuale', 'templates.title': 'Esplora i modelli', 'templates.fetchError': 'Impossibile recuperare i dati.', 'templates.error': 'Errore sconosciuto', @@ -45,6 +54,7 @@ const homepageTranslationIt = createTranslationMessages({ "Una volta aggiunti i modelli, in quest'area verranno visualizzati contenuti pertinenti e personalizzati in base all'esperienza dell'utente.", 'templates.register': 'Registra un modello', 'templates.viewAll': 'Visualizza tutti i {{count}} modelli', + 'onboarding.title': 'Red Hat Developer Hub - Onboarding', 'onboarding.greeting.goodMorning': 'Buon giorno', 'onboarding.greeting.goodAfternoon': 'Buon pomeriggio', 'onboarding.greeting.goodEvening': 'Buona sera', diff --git a/workspaces/homepage/plugins/homepage/src/translations/ja.ts b/workspaces/homepage/plugins/homepage/src/translations/ja.ts index e8d0ebacdf..e3a5348939 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/ja.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/ja.ts @@ -29,6 +29,8 @@ const homepageTranslationJa = createTranslationMessages({ 'header.local': 'ローカル', 'homePage.empty': 'ホームページカード が設定されていないか見つかりません。', 'search.placeholder': '検索', + 'search.title': '検索', + 'search.clearButton': 'クリア', 'quickAccess.title': 'クイックアクセス', 'quickAccess.fetchError': 'データを取得できませんでした。', 'quickAccess.error': '不明なエラー', @@ -36,7 +38,12 @@ const homepageTranslationJa = createTranslationMessages({ 'featuredDocs.learnMore': ' 詳細', 'starredEntities.title': 'スター付きカタログエンティティ', 'recentlyVisited.title': '最近の訪問', + 'recentlyVisited.description': + '最近表示したエンティティやページへのクイックアクセス', 'topVisited.title': 'よく訪問される項目', + 'topVisited.description': '最も頻繁にアクセスするエンティティとサービス', + 'randomJoke.title': 'ランダムなジョーク', + 'randomJoke.description': 'ランダムなプログラミングジョークを表示します', 'templates.title': 'テンプレートの探索', 'templates.fetchError': 'データを取得できませんでした。', 'templates.error': '不明なエラー', @@ -45,6 +52,7 @@ const homepageTranslationJa = createTranslationMessages({ 'テンプレートが追加されると、利用状況に合わせてカスタマイズされた関連コンテンツがこのスペースに表示されます。', 'templates.register': 'テンプレートの登録', 'templates.viewAll': '{{count}} 個のテンプレートをすべて表示する', + 'onboarding.title': 'Red Hat Developer Hub - オンボーディング', 'onboarding.greeting.goodMorning': 'おはようございます', 'onboarding.greeting.goodAfternoon': 'こんにちは', 'onboarding.greeting.goodEvening': 'こんばんは', diff --git a/workspaces/homepage/plugins/homepage/src/translations/ref.ts b/workspaces/homepage/plugins/homepage/src/translations/ref.ts index 76540c14ce..6359f1caee 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/ref.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/ref.ts @@ -33,6 +33,8 @@ export const homepageMessages = { }, search: { placeholder: 'Search', + title: 'Search', + clearButton: 'Clear', }, quickAccess: { title: 'Quick Access', @@ -48,9 +50,15 @@ export const homepageMessages = { }, recentlyVisited: { title: 'Recently Visited', + description: 'Quick access to recently viewed entities and pages', }, topVisited: { title: 'Top Visited', + description: 'Your most frequently accessed entities and services', + }, + randomJoke: { + title: 'Random Joke', + description: 'Shows a random programming joke', }, templates: { title: 'Explore Templates', @@ -63,6 +71,7 @@ export const homepageMessages = { viewAll: 'View all {{count}} templates', }, onboarding: { + title: 'Red Hat Developer Hub - Onboarding', greeting: { goodMorning: 'Good morning', goodAfternoon: 'Good afternoon', diff --git a/workspaces/homepage/plugins/homepage/src/translations/utils.ts b/workspaces/homepage/plugins/homepage/src/translations/utils.ts index 69e79bd104..af597494c5 100644 --- a/workspaces/homepage/plugins/homepage/src/translations/utils.ts +++ b/workspaces/homepage/plugins/homepage/src/translations/utils.ts @@ -13,9 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; -import { homepageTranslationRef } from './ref'; +/** + * Lightweight translation callback used by homepage utilities and tests. + * Compatible with `TranslationFunction` from `useTranslation`, without + * pulling in its expensive generic instantiation during full typechecks. + */ +export type HomepageTranslateFn = (key: string, options?: any) => string; /** * Utility function to get translated text with fallback to original text @@ -26,7 +30,7 @@ import { homepageTranslationRef } from './ref'; * @returns Translated text or fallback text */ export const getTranslatedTextWithFallback = ( - t: TranslationFunction, + t: HomepageTranslateFn, translationKey: string | undefined, fallbackText: string | undefined, ): string | undefined => { @@ -34,6 +38,6 @@ export const getTranslatedTextWithFallback = ( return fallbackText; } - const translation = t(translationKey as keyof typeof t, {}); + const translation = t(translationKey, {}); return translation !== translationKey ? translation : fallbackText; }; diff --git a/workspaces/homepage/plugins/homepage/src/utils/constants.ts b/workspaces/homepage/plugins/homepage/src/utils/constants.ts index 381c99a2bb..351fffe43e 100644 --- a/workspaces/homepage/plugins/homepage/src/utils/constants.ts +++ b/workspaces/homepage/plugins/homepage/src/utils/constants.ts @@ -16,13 +16,12 @@ import OpenInNewIcon from '@mui/icons-material/OpenInNew'; import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; -import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; import { LearningSectionItem } from '../types'; -import { homepageTranslationRef } from '../translations'; +import type { HomepageTranslateFn } from '../translations/utils'; export const getLearningItems = ( - t: TranslationFunction, + t: HomepageTranslateFn, ): LearningSectionItem[] => [ { title: t('onboarding.getStarted.title'), diff --git a/workspaces/homepage/plugins/homepage/src/utils/customizable-cards.ts b/workspaces/homepage/plugins/homepage/src/utils/customizable-cards.ts index 56a6dbcb2d..9425523bf4 100644 --- a/workspaces/homepage/plugins/homepage/src/utils/customizable-cards.ts +++ b/workspaces/homepage/plugins/homepage/src/utils/customizable-cards.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; - import { HomePageCardMountPoint } from '../types'; -import { homepageTranslationRef } from '../translations'; -import { getTranslatedTextWithFallback } from '../translations/utils'; +import { + getTranslatedTextWithFallback, + type HomepageTranslateFn, +} from '../translations/utils'; /** * Util function that decides if a `home.page/card` mount point will be rendered @@ -48,7 +48,7 @@ function getComponentDisplayName( } export function getCardTitle( - t: TranslationFunction, + t: HomepageTranslateFn, cardMountPoint: HomePageCardMountPoint, ): string | undefined { return ( @@ -61,7 +61,7 @@ export function getCardTitle( } export function getCardDescription( - t: TranslationFunction, + t: HomepageTranslateFn, cardMountPoint: HomePageCardMountPoint, ): string | undefined { const title = getCardTitle(t, cardMountPoint); diff --git a/workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.test.ts b/workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.test.ts new file mode 100644 index 0000000000..244ab53d81 --- /dev/null +++ b/workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.test.ts @@ -0,0 +1,119 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { + attachComponentData, + getComponentData, +} from '@backstage/core-plugin-api'; +import { createElement } from 'react'; + +import { HomePageCardConfig } from '../types'; +import { mockT } from '../test-utils/mockTranslations'; +import { translateHomepageWidget } from './translateHomepageWidgets'; + +function createWidgetWithMetadata() { + const WidgetWithMetadata = ({ title }: { title?: string }) => + createElement('div', null, title); + attachComponentData(WidgetWithMetadata, 'title', 'Original title'); + attachComponentData( + WidgetWithMetadata, + 'description', + 'Recently visited description', + ); + return WidgetWithMetadata; +} + +function createCard( + overrides: Partial = {}, +): HomePageCardConfig { + const WidgetWithMetadata = createWidgetWithMetadata(); + + return { + node: {} as HomePageCardConfig['node'], + component: createElement(WidgetWithMetadata, { title: 'Original title' }), + name: 'Recently visited', + title: 'Recently visited', + description: 'Recently visited description', + ...overrides, + }; +} + +describe('translateHomepageWidget', () => { + it('returns the card unchanged when the widget name is not mapped', () => { + const card = createCard({ name: 'Unknown widget' }); + + expect(translateHomepageWidget(card, mockT)).toBe(card); + }); + + it('returns the card unchanged when the component is not a valid element', () => { + const card = createCard({ + component: 'not-an-element' as unknown as HomePageCardConfig['component'], + }); + + expect(translateHomepageWidget(card, mockT)).toBe(card); + }); + + it('translates title and description and updates component metadata', () => { + const card = createCard(); + + const translated = translateHomepageWidget(card, mockT); + + expect(translated.title).toBe('Recently Visited'); + expect(translated.description).toBe( + 'Quick access to recently viewed entities and pages', + ); + expect(translated.component.props.title).toBe('Recently Visited'); + expect(getComponentData(translated.component, 'title')).toBe( + 'Recently Visited', + ); + expect(getComponentData(translated.component, 'description')).toBe( + 'Quick access to recently viewed entities and pages', + ); + }); + + it('clears description metadata when the widget has no description key', () => { + const card = createCard({ + name: 'Search', + title: 'Search', + description: 'Search your developer portal', + }); + + const translated = translateHomepageWidget(card, mockT); + + expect(translated.title).toBe('Search'); + expect(translated.description).toBe('Search your developer portal'); + expect(getComponentData(translated.component, 'description')).toBe( + undefined, + ); + expect(translated.component).toBe(card.component); + }); + + it('does not pass a translated title to the card when hideTitleOnCard is set', () => { + const WidgetWithMetadata = createWidgetWithMetadata(); + const card = createCard({ + name: 'Red Hat Developer Hub - Onboarding', + title: 'Red Hat Developer Hub - Onboarding', + description: undefined, + component: createElement(WidgetWithMetadata), + }); + + const translated = translateHomepageWidget(card, mockT); + + expect(translated.title).toBe('Red Hat Developer Hub - Onboarding'); + expect(translated.component).toBe(card.component); + expect(translated.component.props.title).toBeUndefined(); + }); +}); diff --git a/workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.ts b/workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.ts new file mode 100644 index 0000000000..ecbe3c5d20 --- /dev/null +++ b/workspaces/homepage/plugins/homepage/src/utils/translateHomepageWidgets.ts @@ -0,0 +1,74 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { + cloneElement, + isValidElement, + type ComponentType, + type ReactElement, +} from 'react'; + +import { HomePageCardConfig } from '../types'; +import { + getTranslatedTextWithFallback, + type HomepageTranslateFn, +} from '../translations/utils'; +import { widgetTranslationKeysByName } from './widgetTranslationKeys'; +import { updateWidgetComponentData } from './updateWidgetComponentData'; + +type HomePageWidgetElement = ReactElement<{ title?: string }>; + +export type TranslateHomepageWidgetTranslationFn = HomepageTranslateFn; + +export function translateHomepageWidget( + card: HomePageCardConfig, + t: TranslateHomepageWidgetTranslationFn, +): HomePageCardConfig { + const keys = card.name ? widgetTranslationKeysByName[card.name] : undefined; + if (!keys || !isValidElement(card.component)) { + return card; + } + + const fallbackTitle = card.title ?? card.name; + const title = + getTranslatedTextWithFallback(t, keys.titleKey, fallbackTitle) ?? + fallbackTitle; + const description = keys.descriptionKey + ? (getTranslatedTextWithFallback( + t, + keys.descriptionKey, + card.description, + ) ?? card.description) + : card.description; + + const widgetType = card.component.type as ComponentType; + + updateWidgetComponentData(widgetType, 'title', title); + updateWidgetComponentData( + widgetType, + 'description', + keys.descriptionKey ? description : undefined, + ); + + return { + ...card, + title, + description, + component: keys.hideTitleOnCard + ? card.component + : cloneElement(card.component as HomePageWidgetElement, { title }), + }; +} diff --git a/workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.test.ts b/workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.test.ts new file mode 100644 index 0000000000..d3580408b1 --- /dev/null +++ b/workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { + attachComponentData, + getComponentData, +} from '@backstage/core-plugin-api'; +import { createElement } from 'react'; + +import { updateWidgetComponentData } from './updateWidgetComponentData'; + +const Widget = () => createElement('div'); + +describe('updateWidgetComponentData', () => { + it('does nothing when the component has no backstage data container', () => { + const component = () => createElement('div'); + + expect(() => + updateWidgetComponentData(component, 'title', 'Translated'), + ).not.toThrow(); + }); + + it('updates attached component metadata in place', () => { + attachComponentData(Widget, 'title', 'Original title'); + attachComponentData(Widget, 'description', 'Original description'); + + updateWidgetComponentData(Widget, 'title', 'Translated title'); + updateWidgetComponentData(Widget, 'description', undefined); + + expect(getComponentData({ type: Widget } as any, 'title')).toBe( + 'Translated title', + ); + expect(getComponentData({ type: Widget } as any, 'description')).toBe( + undefined, + ); + }); +}); diff --git a/workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.ts b/workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.ts new file mode 100644 index 0000000000..9155905084 --- /dev/null +++ b/workspaces/homepage/plugins/homepage/src/utils/updateWidgetComponentData.ts @@ -0,0 +1,39 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 type { ComponentType } from 'react'; + +// Matches @backstage/core-plugin-api component data storage. +const COMPONENT_DATA_KEY = '__backstage_data'; + +/** + * Updates metadata attached to a widget component type so CustomHomepageGrid + * can read translated title/description without replacing the component type + * (which would break useElementFilter / Add widget discovery). + */ +export function updateWidgetComponentData( + component: ComponentType, + type: string, + data: unknown, +): void { + const container = ( + component as { + [COMPONENT_DATA_KEY]?: { map: Map }; + } + )[COMPONENT_DATA_KEY]; + + container?.map.set(type, data); +} diff --git a/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.test.ts b/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.test.ts new file mode 100644 index 0000000000..3f329a266c --- /dev/null +++ b/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { mockUseTranslation } from '../test-utils/mockTranslations'; + +jest.mock('../hooks/useTranslation', () => ({ + useTranslation: mockUseTranslation, +})); + +import { renderHook } from '@testing-library/react'; + +import { useHomePageCardTitle } from './useHomePageCardTitle'; + +describe('useHomePageCardTitle', () => { + it('uses the default translation key', () => { + const { result } = renderHook(() => + useHomePageCardTitle('randomJoke.title', {}), + ); + + expect(result.current).toBe('Random Joke'); + }); + + it('uses a custom titleKey when provided', () => { + const { result } = renderHook(() => + useHomePageCardTitle('randomJoke.title', { + titleKey: 'search.title', + }), + ); + + expect(result.current).toBe('Search'); + }); + + it('falls back to the provided title when the translation is missing', () => { + const { result } = renderHook(() => + useHomePageCardTitle('missing.translation.key', { + title: 'Custom title', + }), + ); + + expect(result.current).toBe('Custom title'); + }); +}); diff --git a/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.ts b/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.ts index 1b6251c1d7..03e23d5948 100644 --- a/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.ts +++ b/workspaces/homepage/plugins/homepage/src/utils/useHomePageCardTitle.ts @@ -17,6 +17,11 @@ import { useTranslation } from '../hooks/useTranslation'; import { getTranslatedTextWithFallback } from '../translations/utils'; +/** + * Props for home page cards that support translated titles. + * + * @public + */ export interface TranslatableCardTitleProps { title?: string; titleKey?: string; diff --git a/workspaces/homepage/plugins/homepage/src/utils/widgetTranslationKeys.ts b/workspaces/homepage/plugins/homepage/src/utils/widgetTranslationKeys.ts new file mode 100644 index 0000000000..33023f492b --- /dev/null +++ b/workspaces/homepage/plugins/homepage/src/utils/widgetTranslationKeys.ts @@ -0,0 +1,61 @@ +/* + * Copyright Red Hat, Inc. + * + * 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. + */ + +/** + * Maps NFS homepage widget extension names (`params.name`) to translation keys + * for title and description shown in the Add widget dialog. + */ +export const widgetTranslationKeysByName: Record< + string, + { + titleKey: string; + descriptionKey?: string; + /** Title is for the Add widget dialog only, not shown on the card. */ + hideTitleOnCard?: boolean; + } +> = { + 'Red Hat Developer Hub - Onboarding': { + titleKey: 'onboarding.title', + hideTitleOnCard: true, + }, + 'Red Hat Developer Hub - Software Catalog': { + titleKey: 'entities.title', + descriptionKey: 'entities.description', + }, + 'Red Hat Developer Hub - Explore templates': { + titleKey: 'templates.title', + }, + 'Quick Access Card': { titleKey: 'quickAccess.title' }, + Search: { + titleKey: 'search.title', + hideTitleOnCard: true, + }, + 'Featured docs': { titleKey: 'featuredDocs.title' }, + CatalogStarred: { titleKey: 'starredEntities.title' }, + 'Your Starred Entities': { titleKey: 'starredEntities.title' }, + 'Recently visited': { + titleKey: 'recentlyVisited.title', + descriptionKey: 'recentlyVisited.description', + }, + 'Top visited': { + titleKey: 'topVisited.title', + descriptionKey: 'topVisited.description', + }, + HomePageRandomJoke: { + titleKey: 'randomJoke.title', + descriptionKey: 'randomJoke.description', + }, +}; From 32935beb338c660a373205c14e28edf08664d797 Mon Sep 17 00:00:00 2001 From: Debsmita Santra Date: Thu, 25 Jun 2026 22:11:01 +0530 Subject: [PATCH 2/2] fix e2e tests --- .../e2e-tests/homepageCustomizable.test.ts | 18 +++++++++++++----- .../e2e-tests/pages/homePageCustomization.ts | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts b/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts index 2cbe1785b4..8555713123 100644 --- a/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts +++ b/workspaces/homepage/e2e-tests/homepageCustomizable.test.ts @@ -18,12 +18,14 @@ import { test, expect, BrowserContext, Page } from '@playwright/test'; import { TestUtils } from './utils/testUtils.js'; import { HomePageCustomization } from './pages/homePageCustomization.js'; import { runAccessibilityTests } from './utils/accessibility.js'; +import { getTranslations } from './utils/translations.js'; test.describe.serial('Dynamic Home Page Customization', () => { let testUtils: TestUtils; let homePageCustomization: HomePageCustomization; let sharedPage: Page; let sharedContext: BrowserContext; + const translations = getTranslations('en'); test.beforeAll(async ({ browser }) => { sharedContext = await browser.newContext(); @@ -80,13 +82,15 @@ test.describe.serial('Dynamic Home Page Customization', () => { }); test('Verify Add Widget Button Adds Cards', async () => { - await homePageCustomization.addWidget('Onboarding'); + await homePageCustomization.addWidget(translations.onboarding.title); await expect( sharedPage.getByText(/Good (morning|afternoon|evening)/), ).toBeVisible(); - await homePageCustomization.addWidget('Quick Access'); - await expect(sharedPage.getByText('Quick Access')).toBeVisible(); + await homePageCustomization.addWidget(translations.quickAccess.title); + await expect( + sharedPage.getByText(translations.quickAccess.title), + ).toBeVisible(); }); // ── Persistent storage ──────────────────────────────────────────────── @@ -103,7 +107,9 @@ test.describe.serial('Dynamic Home Page Customization', () => { await homePageCustomization.verifyCardHidden( 'Good (morning|afternoon|evening)', ); - await homePageCustomization.verifyCardVisible('Quick Access'); + await homePageCustomization.verifyCardVisible( + translations.quickAccess.title, + ); const countAfterReload = await homePageCustomization.getVisibleCardCount(); expect(countAfterReload).toBe(countBeforeReload); @@ -120,7 +126,9 @@ test.describe.serial('Dynamic Home Page Customization', () => { await homePageCustomization.verifyCardHidden( 'Good (morning|afternoon|evening)', ); - await homePageCustomization.verifyCardVisible('Quick Access'); + await homePageCustomization.verifyCardVisible( + translations.quickAccess.title, + ); const countAfterLogout = await homePageCustomization.getVisibleCardCount(); expect(countAfterLogout).toBe(countBeforeLogout); diff --git a/workspaces/homepage/e2e-tests/pages/homePageCustomization.ts b/workspaces/homepage/e2e-tests/pages/homePageCustomization.ts index 606298849e..4669664ac2 100644 --- a/workspaces/homepage/e2e-tests/pages/homePageCustomization.ts +++ b/workspaces/homepage/e2e-tests/pages/homePageCustomization.ts @@ -232,7 +232,7 @@ export class HomePageCustomization { await this.page.waitForTimeout(1000); // Wait for dialog to open // Select the specific widget type from the dialog - await this.page.getByRole('button', { name: title }).click(); + await this.page.getByRole('button', { name: title, exact: true }).click(); await this.page.waitForTimeout(1000); }