From 9375901235f6c43be81e3ffbb2f86263fe12c37a Mon Sep 17 00:00:00 2001 From: rohitratannagar Date: Sat, 8 Aug 2026 04:40:53 +0530 Subject: [PATCH 1/3] fix(intelligent-assistant): add singular/plural handling for notebook card resource count The NotebookCard on the notebooks listing page always displayed "Resources" (plural) regardless of count. Replace the static notebooks.documents key with notebooks.documents_one/_other plural keys so "1 Resource" vs "2 Resources" renders correctly. Co-authored-by: Cursor --- .../e2e-tests/pages/NotebookSurfacePage.ts | 10 +++++++--- .../intelligent-assistant/report-alpha.api.md | 6 ++++-- .../__tests__/DocumentSidebar.test.tsx | 17 +++++++++++++++++ .../components/__tests__/NotebookCard.test.tsx | 17 ++++++++++++----- .../components/notebooks/DocumentSidebar.tsx | 4 ++-- .../src/components/notebooks/NotebookCard.tsx | 4 +++- .../src/translations/de.ts | 6 ++++-- .../src/translations/es.ts | 6 ++++-- .../src/translations/fr.ts | 6 ++++-- .../src/translations/it.ts | 6 ++++-- .../src/translations/ja.ts | 6 ++++-- .../src/translations/ref.ts | 6 ++++-- 12 files changed, 69 insertions(+), 25 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts index 5a1c7dd7b1..8995547313 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts @@ -342,11 +342,15 @@ export class NotebookSurfacePage { } /** - * Shown on each card as count + plural label (same pattern as NotebookCard.tsx: - * `{ document_count } { t('notebooks.documents') }`, not `notebook.view.documents.count`). + * Shown on each card as a pluralized count label (same pattern as NotebookCard.tsx: + * `t('notebooks.documents', { count })` with `_one`/`_other` suffixes). */ formatNotebookCardDocumentsSummary(documentCount: number): string { - return `${documentCount} ${this.t['notebooks.documents']}`; + const key = + documentCount === 1 + ? 'notebooks.documents_one' + : 'notebooks.documents_other'; + return (this.t[key] as string).replace('{{count}}', String(documentCount)); } async expectUntitledNotebookCardCount(expected: number): Promise { diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md b/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md index f05c7c0c4d..e0ce74b179 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/report-alpha.api.md @@ -50,7 +50,8 @@ export const intelligentAssistantTranslationRef: TranslationRef< readonly 'notebooks.empty.title': string; readonly 'notebooks.empty.description': string; readonly 'notebooks.empty.action': string; - readonly 'notebooks.documents': string; + readonly 'notebooks.documents_one': string; + readonly 'notebooks.documents_other': string; readonly 'notebooks.actions.rename': string; readonly 'notebooks.actions.delete': string; readonly 'notebooks.rename.inline.tooltip': string; @@ -66,7 +67,8 @@ export const intelligentAssistantTranslationRef: TranslationRef< readonly 'notebooks.card.openAria': string; readonly 'notebook.view.title': string; readonly 'notebook.view.close': string; - readonly 'notebook.view.documents.count': string; + readonly 'notebook.view.documents.count_one': string; + readonly 'notebook.view.documents.count_other': string; readonly 'notebook.view.documents.add': string; readonly 'notebook.view.upload.heading': string; readonly 'notebook.view.upload.action': string; diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/DocumentSidebar.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/DocumentSidebar.test.tsx index 614509ed4c..5dd1905080 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/DocumentSidebar.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/DocumentSidebar.test.tsx @@ -73,6 +73,23 @@ describe('DocumentSidebar', () => { expect(screen.getByText('config.yaml')).toBeInTheDocument(); }); + it('should use singular form for single document count', () => { + const documents = [mockDocument('doc-1', 'readme.md')]; + render(); + + expect(screen.getByText('1 Resource')).toBeInTheDocument(); + }); + + it('should use plural form for multiple document count', () => { + const documents = [ + mockDocument('doc-1', 'readme.md'), + mockDocument('doc-2', 'config.yaml'), + ]; + render(); + + expect(screen.getByText('2 Resources')).toBeInTheDocument(); + }); + it('should display FileTypeIcon badges for documents', () => { const documents = [mockDocument('doc-1', 'report.pdf')]; render(); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx index a2823cfcb7..896304a0c3 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/__tests__/NotebookCard.test.tsx @@ -75,9 +75,15 @@ describe('NotebookCard', () => { expect(screen.getByText('My Notebook')).toBeInTheDocument(); }); - it('should render the document count', () => { + it('should use singular form for single document count', () => { + const singleDocNotebook = { ...mockNotebook, document_count: 1 }; + render(); + expect(screen.getByText('1 Resource')).toBeInTheDocument(); + }); + + it('should use plural form for multiple document count', () => { render(); - expect(screen.getByText(/2/)).toBeInTheDocument(); + expect(screen.getByText('2 Resources')).toBeInTheDocument(); }); it('should call onClick with notebook when card is clicked', () => { @@ -223,8 +229,9 @@ describe('NotebookCard', () => { }); }); - it('should render document_count from the notebook session', () => { - render(); - expect(screen.getByText(/2/)).toBeInTheDocument(); + it('should use plural form for zero document count', () => { + const zeroDocNotebook = { ...mockNotebook, document_count: 0 }; + render(); + expect(screen.getByText('0 Resources')).toBeInTheDocument(); }); }); diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx index b9b0ab6cbb..8d3122e86f 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/components/notebooks/DocumentSidebar.tsx @@ -235,9 +235,9 @@ export const DocumentSidebar = ({
- {t('notebook.view.documents.count', { + {(t as Function)('notebook.view.documents.count', { count: totalCount, - } as any)} + })} {isAddDisabled ? (
- {notebook.document_count ?? 0} {t('notebooks.documents')} + {(t as Function)('notebooks.documents', { + count: notebook.document_count ?? 0, + })}
diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts index 37fbec9b75..d668c2c719 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/de.ts @@ -232,7 +232,8 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'notebook.upload.modal.title': 'Ressource zum Notizbuch hinzufügen', 'notebook.view.close': 'Notizbuch schließen', 'notebook.view.documents.add': 'Hinzufügen', - 'notebook.view.documents.count': '{{count}} Ressourcen', + 'notebook.view.documents.count_one': '{{count}} Ressource', + 'notebook.view.documents.count_other': '{{count}} Ressourcen', 'notebook.view.documents.maxReached': 'Maximal 10 Ressourcen sind erlaubt. Löschen Sie eine Ressource, um eine neue hochzuladen.', 'notebook.view.documents.uploading': 'Ressource wird hochgeladen', @@ -257,7 +258,8 @@ const intelligentAssistantTranslationDe = createTranslationMessages({ 'Dieses Notizbuch wird hier nicht mehr angezeigt. Dadurch werden auch zugehörige Aktivitäten wie Eingaben, Antworten und Feedback aus Ihrer Aktivität gelöscht.', 'notebooks.delete.title': '{{name}} löschen?', 'notebooks.delete.toast': 'Notizbuch gelöscht!', - 'notebooks.documents': 'Ressourcen', + 'notebooks.documents_one': '{{count}} Ressource', + 'notebooks.documents_other': '{{count}} Ressourcen', 'notebooks.empty.action': 'Neues Notizbuch erstellen', 'notebooks.empty.description': 'Erstellen Sie ein neues Notizbuch, um Ihre Quellen zu organisieren und KI-gestützte Erkenntnisse zu gewinnen.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts index 61c78bffdb..25156a6d35 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/es.ts @@ -229,7 +229,8 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'notebook.upload.modal.title': 'Agregar un recurso al cuaderno', 'notebook.view.close': 'Cerrar cuaderno', 'notebook.view.documents.add': 'Agregar', - 'notebook.view.documents.count': '{{count}} Recursos', + 'notebook.view.documents.count_one': '{{count}} Recurso', + 'notebook.view.documents.count_other': '{{count}} Recursos', 'notebook.view.documents.maxReached': 'Se permiten un máximo de 10 recursos. Elimina un recurso para subir uno nuevo.', 'notebook.view.documents.uploading': 'Subiendo recurso', @@ -253,7 +254,8 @@ const intelligentAssistantTranslationEs = createTranslationMessages({ 'Ya no verás este cuaderno aquí. Esto también eliminará actividad relacionada como solicitudes, respuestas y comentarios de tu actividad.', 'notebooks.delete.title': '¿Eliminar {{name}}?', 'notebooks.delete.toast': '¡Cuaderno eliminado!', - 'notebooks.documents': 'Recursos', + 'notebooks.documents_one': '{{count}} Recurso', + 'notebooks.documents_other': '{{count}} Recursos', 'notebooks.empty.action': 'Crear un cuaderno nuevo', 'notebooks.empty.description': 'Crea un nuevo cuaderno para organizar tus fuentes y generar información con IA.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts index 23599d2142..0749ea9c73 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/fr.ts @@ -231,7 +231,8 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'notebook.upload.modal.title': 'Ajouter une ressource au carnet', 'notebook.view.close': 'Fermer le carnet', 'notebook.view.documents.add': 'Ajouter', - 'notebook.view.documents.count': '{{count}} Ressources', + 'notebook.view.documents.count_one': '{{count}} Ressource', + 'notebook.view.documents.count_other': '{{count}} Ressources', 'notebook.view.documents.maxReached': 'Maximum 10 ressources autorisées. Supprimez une ressource pour en charger une nouvelle.', 'notebook.view.documents.uploading': 'Chargement de la ressource', @@ -256,7 +257,8 @@ const intelligentAssistantTranslationFr = createTranslationMessages({ 'Vous ne verrez plus ce carnet ici. Cela supprimera également l’activité associée comme les requêtes, réponses et retours depuis votre activité.', 'notebooks.delete.title': 'Supprimer {{name}} ?', 'notebooks.delete.toast': 'Carnet supprimé !', - 'notebooks.documents': 'Ressources', + 'notebooks.documents_one': '{{count}} Ressource', + 'notebooks.documents_other': '{{count}} Ressources', 'notebooks.empty.action': 'Créer un nouveau carnet', 'notebooks.empty.description': 'Créez un nouveau carnet pour organiser vos sources et générer des informations alimentées par l’IA.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts index 1ab1f5e419..8066195256 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/it.ts @@ -229,7 +229,8 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'notebook.upload.modal.title': 'Aggiungi una risorsa al quaderno', 'notebook.view.close': 'Chiudi quaderno', 'notebook.view.documents.add': 'Aggiungi', - 'notebook.view.documents.count': '{{count}} Risorse', + 'notebook.view.documents.count_one': '{{count}} Risorsa', + 'notebook.view.documents.count_other': '{{count}} Risorse', 'notebook.view.documents.maxReached': 'Sono consentite al massimo 10 risorse. Elimina una risorsa per caricarne una nuova.', 'notebook.view.documents.uploading': 'Caricamento risorsa', @@ -254,7 +255,8 @@ const intelligentAssistantTranslationIt = createTranslationMessages({ 'Non vedrai più questo quaderno qui. Questo eliminerà anche le attività correlate come prompt, risposte e feedback dalla tua attività.', 'notebooks.delete.title': 'Eliminare {{name}}?', 'notebooks.delete.toast': 'Quaderno eliminato!', - 'notebooks.documents': 'Risorse', + 'notebooks.documents_one': '{{count}} Risorsa', + 'notebooks.documents_other': '{{count}} Risorse', 'notebooks.empty.action': 'Crea un nuovo quaderno', 'notebooks.empty.description': 'Crea un nuovo quaderno per organizzare le tue fonti e generare insight basati su IA.', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts index d86e572a83..1e0fad744b 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ja.ts @@ -225,7 +225,8 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'notebook.upload.modal.title': 'ノートブックにリソースを追加', 'notebook.view.close': 'ノートブックを閉じる', 'notebook.view.documents.add': '追加', - 'notebook.view.documents.count': '{{count}} 件のリソース', + 'notebook.view.documents.count_one': '{{count}} 件のリソース', + 'notebook.view.documents.count_other': '{{count}} 件のリソース', 'notebook.view.documents.maxReached': '最大10個のリソースが許可されています。新しいリソースをアップロードするには、リソースを削除してください。', 'notebook.view.documents.uploading': 'リソースをアップロード中', @@ -250,7 +251,8 @@ const intelligentAssistantTranslationJa = createTranslationMessages({ 'このノートブックはここに表示されなくなります。アクティビティに関連するプロンプト、応答、フィードバックも削除されます。', 'notebooks.delete.title': '{{name}} を削除しますか?', 'notebooks.delete.toast': 'ノートブックを削除しました!', - 'notebooks.documents': 'リソース', + 'notebooks.documents_one': '{{count}} 件のリソース', + 'notebooks.documents_other': '{{count}} 件のリソース', 'notebooks.empty.action': '新しいノートブックを作成', 'notebooks.empty.description': '新しいノートブックを作成してソースを整理し、AI による洞察を生成します。', diff --git a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts index aefd87cd1a..c02969cde6 100644 --- a/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts +++ b/workspaces/intelligent-assistant/plugins/intelligent-assistant/src/translations/ref.ts @@ -35,7 +35,8 @@ export const intelligentAssistantMessages = { 'notebooks.empty.description': 'Start a new notebook to organize your sources and generate AI-powered insights.', 'notebooks.empty.action': 'Create a new notebook', - 'notebooks.documents': 'Resources', + 'notebooks.documents_one': '{{count}} Resource', + 'notebooks.documents_other': '{{count}} Resources', 'notebooks.actions.rename': 'Rename', 'notebooks.actions.delete': 'Delete', 'notebooks.rename.inline.tooltip': 'Click to rename', @@ -59,7 +60,8 @@ export const intelligentAssistantMessages = { // Notebook view 'notebook.view.title': 'Untitled notebook', 'notebook.view.close': 'Close notebook', - 'notebook.view.documents.count': '{{count}} Resources', + 'notebook.view.documents.count_one': '{{count}} Resource', + 'notebook.view.documents.count_other': '{{count}} Resources', 'notebook.view.documents.add': 'Add', 'notebook.view.upload.heading': 'Upload a resource to get started', 'notebook.view.upload.action': 'Upload a resource', From 4dbe97db896f9fd29889d13f37ad6d4642fdbb00 Mon Sep 17 00:00:00 2001 From: rohitratannagar Date: Sat, 8 Aug 2026 04:51:00 +0530 Subject: [PATCH 2/3] chore(intelligent-assistant): add missing changeset for plural resource count fix --- .../.changeset/fix-plural-resource-count.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 workspaces/intelligent-assistant/.changeset/fix-plural-resource-count.md diff --git a/workspaces/intelligent-assistant/.changeset/fix-plural-resource-count.md b/workspaces/intelligent-assistant/.changeset/fix-plural-resource-count.md new file mode 100644 index 0000000000..15a236caf8 --- /dev/null +++ b/workspaces/intelligent-assistant/.changeset/fix-plural-resource-count.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-intelligent-assistant': patch +--- + +Fix singular/plural handling for notebook card and document sidebar resource count using i18next `_one`/`_other` suffix keys. From 041e23d047f7a6c4e763258bf92e265581e3518e Mon Sep 17 00:00:00 2001 From: rohitratannagar Date: Sat, 8 Aug 2026 05:32:51 +0530 Subject: [PATCH 3/3] fix(intelligent-assistant): use CLDR-aware plural rules in e2e notebook page object French CLDR treats count=0 as singular, so the naive `=== 1` check produced the wrong expected string for the fr locale e2e run. Co-authored-by: Cursor --- .../lightspeed.notebooks.conversation.test.ts | 2 +- .../e2e-tests/lightspeed.notebooks.test.ts | 2 +- .../e2e-tests/pages/NotebookSurfacePage.ts | 10 ++++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.conversation.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.conversation.test.ts index f2752e6127..6a9dadf49f 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.conversation.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.conversation.test.ts @@ -49,7 +49,7 @@ test.describe('Intelligent assistant notebooks conversation', () => { const boot = await bootstrapLightspeedE2ePage(browser); sharedPage = boot.page; translations = boot.translations; - notebooks = new NotebookSurfacePage(sharedPage, translations); + notebooks = new NotebookSurfacePage(sharedPage, translations, boot.locale); }); test.afterAll(async () => { diff --git a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts index b0efce6648..708e4db1ed 100644 --- a/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts +++ b/workspaces/intelligent-assistant/e2e-tests/lightspeed.notebooks.test.ts @@ -44,7 +44,7 @@ test.describe('Intelligent assistant notebooks', () => { const boot = await bootstrapLightspeedE2ePage(browser); sharedPage = boot.page; translations = boot.translations; - notebooks = new NotebookSurfacePage(sharedPage, translations); + notebooks = new NotebookSurfacePage(sharedPage, translations, boot.locale); }); test('fullscreen list: header and empty state', async () => { diff --git a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts index 8995547313..c7c38383d8 100644 --- a/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts +++ b/workspaces/intelligent-assistant/e2e-tests/pages/NotebookSurfacePage.ts @@ -33,10 +33,15 @@ export const NOTEBOOK_UNTITLED_GRID_NAME = 'Untitled Notebook'; * Same role as {@link ./LightspeedPage.ts}: shared locators/assertions keep specs short. */ export class NotebookSurfacePage { + private readonly pluralRules: Intl.PluralRules; + constructor( private readonly page: Page, private readonly t: LightspeedMessages, - ) {} + locale = 'en', + ) { + this.pluralRules = new Intl.PluralRules(locale); + } /** * Scoped to the fullscreen chatbot region that contains notebooks (list + notebook editor). @@ -346,8 +351,9 @@ export class NotebookSurfacePage { * `t('notebooks.documents', { count })` with `_one`/`_other` suffixes). */ formatNotebookCardDocumentsSummary(documentCount: number): string { + const category = this.pluralRules.select(documentCount); const key = - documentCount === 1 + category === 'one' ? 'notebooks.documents_one' : 'notebooks.documents_other'; return (this.t[key] as string).replace('{{count}}', String(documentCount));