From 685df71a430bb94115372a53f4be86b63b0109f8 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Fri, 14 Nov 2025 12:58:07 +0300 Subject: [PATCH 1/5] fix: [Translation Migration] Fix split or incomplete translations in reportActionsView --- cspell.json | 1 + .../BaseHTMLEngineProvider.tsx | 1 + .../HTMLRenderers/UserDetailsRenderer.tsx | 53 +++++++++++++++++++ .../HTMLEngineProvider/HTMLRenderers/index.ts | 2 + src/components/ReportWelcomeText.tsx | 31 ++--------- src/languages/de.ts | 3 +- src/languages/en.ts | 3 +- src/languages/es.ts | 4 +- src/languages/fr.ts | 3 +- src/languages/it.ts | 3 +- src/languages/ja.ts | 3 +- src/languages/nl.ts | 3 +- src/languages/params.ts | 5 ++ src/languages/pl.ts | 3 +- src/languages/pt-BR.ts | 3 +- src/languages/zh-hans.ts | 3 +- src/libs/SidebarUtils.ts | 38 ++++++------- tests/unit/SidebarUtilsTest.ts | 5 +- 18 files changed, 106 insertions(+), 61 deletions(-) create mode 100644 src/components/HTMLEngineProvider/HTMLRenderers/UserDetailsRenderer.tsx diff --git a/cspell.json b/cspell.json index ec608a23cfa7..160416da3569 100644 --- a/cspell.json +++ b/cspell.json @@ -50,6 +50,7 @@ "asar", "ASPAC", "assetlinks", + "accountid", "attributes.accountid", "attributes.reportid", "authorised", diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx index 5b890761fa33..c6fdfac13884 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx @@ -145,6 +145,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim 'mention-report': HTMLElementModel.fromCustomModel({tagName: 'mention-report', contentModel: HTMLContentModel.textual}), 'mention-here': HTMLElementModel.fromCustomModel({tagName: 'mention-here', contentModel: HTMLContentModel.textual}), 'mention-short': HTMLElementModel.fromCustomModel({tagName: 'mention-short', contentModel: HTMLContentModel.textual}), + 'user-details': HTMLElementModel.fromCustomModel({tagName: 'user-details', contentModel: HTMLContentModel.textual}), 'copy-text': HTMLElementModel.fromCustomModel({tagName: 'copy-text', contentModel: HTMLContentModel.textual}), 'concierge-link': HTMLElementModel.fromCustomModel({tagName: 'concierge-link', contentModel: HTMLContentModel.textual}), 'next-step': HTMLElementModel.fromCustomModel({ diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/UserDetailsRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/UserDetailsRenderer.tsx new file mode 100644 index 000000000000..fe30cc678525 --- /dev/null +++ b/src/components/HTMLEngineProvider/HTMLRenderers/UserDetailsRenderer.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html'; +import {TNodeChildrenRenderer} from 'react-native-render-html'; +import Text from '@components/Text'; +import UserDetailsTooltip from '@components/UserDetailsTooltip'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import {isOptimisticPersonalDetail} from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; + +type UserDetailsRendererProps = CustomRendererProps; + +function UserDetailsRenderer({tnode, ...defaultRendererProps}: UserDetailsRendererProps) { + const styles = useThemeStyles(); + const accountID = tnode.attributes.accountid ? parseInt(tnode.attributes.accountid, 10) : undefined; + + if (!accountID) { + // Fallback: render without tooltip if no accountID + return ; + } + + const isOptimistic = isOptimisticPersonalDetail(accountID); + + return ( + + {isOptimistic ? ( + + + + ) : ( + Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute()))} + suppressHighlighting + role={CONST.ROLE.LINK} + > + + + )} + + ); +} + +UserDetailsRenderer.displayName = 'UserDetailsRenderer'; + +export default UserDetailsRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts index 273cbc9b4337..d81af4f20835 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/index.ts +++ b/src/components/HTMLEngineProvider/HTMLRenderers/index.ts @@ -15,6 +15,7 @@ import PreRenderer from './PreRenderer'; import RBRRenderer from './RBRRenderer'; import ShortMentionRenderer from './ShortMentionRenderer'; import TaskTitleRenderer from './TaskTitleRenderer'; +import UserDetailsRenderer from './UserDetailsRenderer'; import VideoRenderer from './VideoRenderer'; /** @@ -37,6 +38,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = { 'mention-report': MentionReportRenderer, 'mention-here': MentionHereRenderer, 'mention-short': ShortMentionRenderer, + 'user-details': UserDetailsRenderer, 'copy-text': CopyTextRenderer, emoji: EmojiRenderer, 'next-step-email': NextStepEmailRenderer, diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 47138ed0f59e..990ed989e8dd 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -18,7 +18,6 @@ import { isChatRoom as isChatRoomReportUtils, isConciergeChatReport, isInvoiceRoom as isInvoiceRoomReportUtils, - isOptimisticPersonalDetail, isPolicyExpenseChat as isPolicyExpenseChatReportUtils, isSelfDM as isSelfDMReportUtils, isSystemChat as isSystemChatReportUtils, @@ -32,7 +31,6 @@ import ROUTES from '@src/ROUTES'; import type {OnyxInputOrEntry, PersonalDetails, PersonalDetailsList, Policy, Report} from '@src/types/onyx'; import RenderHTML from './RenderHTML'; import Text from './Text'; -import UserDetailsTooltip from './UserDetailsTooltip'; type ReportWelcomeTextProps = { /** The report currently being looked at */ @@ -159,32 +157,11 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { {welcomeMessage.messageText} )} - {isDefault && displayNamesWithTooltips.length > 0 && ( + {isDefault && !!welcomeMessage.messageHtml && ( - {welcomeMessage.phrase1} - {displayNamesWithTooltips.map(({displayName, accountID}, index) => ( - // eslint-disable-next-line react/no-array-index-key - - - {isOptimisticPersonalDetail(accountID) ? ( - {displayName} - ) : ( - Navigation.navigate(ROUTES.PROFILE.getRoute(accountID, Navigation.getActiveRoute()))} - suppressHighlighting - > - {displayName} - - )} - - {index === displayNamesWithTooltips.length - 1 && .} - {index === displayNamesWithTooltips.length - 2 && {`${displayNamesWithTooltips.length > 2 ? ',' : ''} ${translate('common.and')} `}} - {index < displayNamesWithTooltips.length - 2 && , } - - ))} - {shouldShowUsePlusButtonText && {translate('reportActionsView.usePlusButton', {additionalText})}} - {isConciergeChatReport(report) && {translate('reportActionsView.askConcierge')}} + + {shouldShowUsePlusButtonText && {` ${translate('reportActionsView.usePlusButton', {additionalText}).trim()}`}} + {isConciergeChatReport(report) && {` ${translate('reportActionsView.askConcierge').trim()}`}} )} diff --git a/src/languages/de.ts b/src/languages/de.ts index f629386f556f..45065b3de8c1 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -934,7 +935,7 @@ const translations: TranslationDeepObject = { `Dieser Chatraum ist für alles, was mit ${reportName} zu tun hat.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `Dieser Chat ist für Rechnungen zwischen ${invoicePayer} und ${invoiceReceiver}. Verwenden Sie die Schaltfläche +, um eine Rechnung zu senden.`, - beginningOfChatHistory: 'Dieser Chat ist mit', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Dieser Chat ist mit ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `Hier wird ${submitterDisplayName} die Ausgaben an ${workspaceName} übermitteln. Verwenden Sie einfach die Schaltfläche +.`, beginningOfChatHistorySelfDM: 'Dies ist Ihr persönlicher Bereich. Nutzen Sie ihn für Notizen, Aufgaben, Entwürfe und Erinnerungen.', diff --git a/src/languages/en.ts b/src/languages/en.ts index 4f8dbf6991ec..aeb5d3558ee7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -28,6 +28,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -923,7 +924,7 @@ const translations = { `This chat room is for anything ${reportName} related.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `This chat is for invoices between ${invoicePayer} and ${invoiceReceiver}. Use the + button to send an invoice.`, - beginningOfChatHistory: 'This chat is with ', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `This chat is with ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `This is where ${submitterDisplayName} will submit expenses to ${workspaceName}. Just use the + button.`, beginningOfChatHistorySelfDM: 'This is your personal space. Use it for notes, tasks, drafts, and reminders.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 4d03a8e4be65..74d974ffce1c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,7 +1,7 @@ import {CONST as COMMON_CONST} from 'expensify-common'; import CONST from '@src/CONST'; import type en from './en'; -import type {ViolationsRterParams} from './params'; +import type {BeginningOfChatHistoryParams, ViolationsRterParams} from './params'; import type {TranslationDeepObject} from './types'; /* eslint-disable max-len */ @@ -605,7 +605,7 @@ const translations: TranslationDeepObject = { `Esta sala de chat es para cualquier cosa relacionada con ${reportName}.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}) => `Este chat es para facturas entre ${invoicePayer} y ${invoiceReceiver}. Usa el botón + para enviar una factura.`, - beginningOfChatHistory: 'Este chat es con ', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Este chat es con ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}) => `Aquí es donde ${submitterDisplayName} enviará los gastos al espacio de trabajo ${workspaceName}. Solo usa el botón +.`, beginningOfChatHistorySelfDM: 'Este es tu espacio personal. Úsalo para notas, tareas, borradores y recordatorios.', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 395b2b6618df..6ba63cd8fa8b 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -936,7 +937,7 @@ const translations: TranslationDeepObject = { `Ce salon de discussion est destiné à tout ce qui concerne ${reportName}.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `Ce chat concerne les factures entre ${invoicePayer} et ${invoiceReceiver}. Utilisez le bouton + pour envoyer une facture.`, - beginningOfChatHistory: 'Ce chat est avec', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Ce chat est avec ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `C'est ici que ${submitterDisplayName} soumettra ses dépenses à ${workspaceName}. Il suffit d'utiliser le bouton +.`, beginningOfChatHistorySelfDM: "C'est votre espace personnel. Utilisez-le pour des notes, des tâches, des brouillons et des rappels.", diff --git a/src/languages/it.ts b/src/languages/it.ts index ea66725885f6..0c38159c5b76 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -933,7 +934,7 @@ const translations: TranslationDeepObject = { `Questa chat è per tutto ciò che riguarda ${reportName}.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `Questa chat è per le fatture tra ${invoicePayer} e ${invoiceReceiver}. Utilizzare il pulsante + per inviare una fattura.`, - beginningOfChatHistory: 'Questa chat è con', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Questa chat è con ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `È qui che ${submitterDisplayName} presenterà le spese a ${workspaceName}. Basta usare il pulsante +.`, beginningOfChatHistorySelfDM: 'Questo è il tuo spazio personale. Usalo per appunti, compiti, bozze e promemoria.', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 002e7af07f29..242cc31d68d6 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -934,7 +935,7 @@ const translations: TranslationDeepObject = { `このチャットルームは、${reportName}に関することなら何でもどうぞ。`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `このチャットは、${invoicePayer}${invoiceReceiver}間の請求書用です。請求書を送信するには、+ ボタンを使用してください。`, - beginningOfChatHistory: 'このチャットは', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `このチャットは${users}とのチャットです。`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `ここで${submitterDisplayName}${workspaceName}に経費を提出します。+ボタンをクリックしてください。`, beginningOfChatHistorySelfDM: 'これはあなたの個人スペースです。メモ、タスク、下書き、リマインダーに使用してください。', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index b243bcf017b9..043ddd984092 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -932,7 +933,7 @@ const translations: TranslationDeepObject = { `Deze chatroom is voor alles wat met ${reportName} te maken heeft.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `Deze chat is voor facturen tussen ${invoicePayer} en ${invoiceReceiver}. Gebruik de + knop om een factuur te sturen.`, - beginningOfChatHistory: 'Deze chat is met', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Deze chat is met ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `Dit is waar ${submitterDisplayName} kosten zal indienen bij ${workspaceName}. Gebruik gewoon de + knop.`, beginningOfChatHistorySelfDM: 'Dit is je persoonlijke ruimte. Gebruik het voor notities, taken, concepten en herinneringen.', diff --git a/src/languages/params.ts b/src/languages/params.ts index 0eea85c55d65..58ed58861811 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -85,6 +85,10 @@ type BeginningOfChatHistoryInvoiceRoomParams = { invoiceReceiver: string; }; +type BeginningOfChatHistoryParams = { + users: string; +}; + type LearnMoreRouteParams = { learnMoreMethodsRoute: string; formattedPrice: string; @@ -1043,6 +1047,7 @@ export type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfArchivedRoomParams, BeginningOfChatHistoryUserRoomParams, BeginningOfChatHistoryAnnounceRoomPartTwo, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index e982c3ef022e..3c2ff08d63c9 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -933,7 +934,7 @@ const translations: TranslationDeepObject = { `Ten czat jest przeznaczony do wszystkiego, co związane z ${reportName}.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `Ten czat służy do wystawiania faktur między ${invoicePayer} i ${invoiceReceiver}. Użyj przycisku +, aby wysłać fakturę.`, - beginningOfChatHistory: 'Ta rozmowa jest z', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Ta rozmowa jest z ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `W tym miejscu ${submitterDisplayName} będzie przesyłać wydatki do ${workspaceName}. Wystarczy użyć przycisku +.`, beginningOfChatHistorySelfDM: 'To jest Twoja przestrzeń osobista. Używaj jej do notatek, zadań, szkiców i przypomnień.', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f7eaad8ee24a..3bbe40241bae 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -932,7 +933,7 @@ const translations: TranslationDeepObject = { `Esta sala de bate-papo é para qualquer coisa relacionada ao ${reportName}.`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `Este bate-papo é para faturas entre ${invoicePayer} e a ${invoiceReceiver}. Use o botão + para enviar uma fatura.`, - beginningOfChatHistory: 'Este chat é com', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `Este chat é com ${users}.`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `É aqui que ${submitterDisplayName} enviará as despesas para a ${workspaceName}. Basta usar o botão +.`, beginningOfChatHistorySelfDM: 'Este é o seu espaço pessoal. Use-o para anotações, tarefas, rascunhos e lembretes.', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index f58244f358dd..a0eb8b9669aa 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -39,6 +39,7 @@ import type { BeginningOfChatHistoryAnnounceRoomParams, BeginningOfChatHistoryDomainRoomParams, BeginningOfChatHistoryInvoiceRoomParams, + BeginningOfChatHistoryParams, BeginningOfChatHistoryPolicyExpenseChatParams, BeginningOfChatHistoryUserRoomParams, BillableDefaultDescriptionParams, @@ -926,7 +927,7 @@ const translations: TranslationDeepObject = { `本聊天室用于与 ${reportName} 有关的任何内容。`, beginningOfChatHistoryInvoiceRoom: ({invoicePayer, invoiceReceiver}: BeginningOfChatHistoryInvoiceRoomParams) => `该聊天用于 ${invoicePayer}${invoiceReceiver} 之间的发票。使用 + 按钮发送发票。`, - beginningOfChatHistory: '此聊天是与', + beginningOfChatHistory: ({users}: BeginningOfChatHistoryParams) => `此聊天是与${users}的聊天。`, beginningOfChatHistoryPolicyExpenseChat: ({workspaceName, submitterDisplayName}: BeginningOfChatHistoryPolicyExpenseChatParams) => `这是${submitterDisplayName}${workspaceName} 提交费用的地方。使用 + 按钮即可。`, beginningOfChatHistorySelfDM: '这是您的个人空间。用于记录笔记、任务、草稿和提醒。', diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 6f62407f8ace..72aa3980055d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -159,10 +159,6 @@ type MiniReport = { lastVisibleActionCreated?: string; }; -function ensureSingleSpacing(text: string) { - return text.replace(CONST.REGEX.WHITESPACE, ' ').trim(); -} - function shouldDisplayReportInLHN( report: Report, reports: OnyxCollection, @@ -1036,31 +1032,29 @@ function getWelcomeMessage( welcomeMessage.messageText = translateLocal('reportActionsView.beginningOfChatHistorySystemDM'); return welcomeMessage; } - // eslint-disable-next-line @typescript-eslint/no-deprecated - welcomeMessage.phrase1 = translateLocal('reportActionsView.beginningOfChatHistory'); const isMultipleParticipant = participantPersonalDetailList.length > 1; const displayNamesWithTooltips = getDisplayNamesWithTooltips(participantPersonalDetailList, isMultipleParticipant, localeCompare); - const displayNamesWithTooltipsText = displayNamesWithTooltips - .map(({displayName}, index) => { - if (index === displayNamesWithTooltips.length - 1) { - return `${displayName}.`; + + // Build HTML string with user-details tags and proper grammar + const usersHtml = displayNamesWithTooltips + .map(({displayName, accountID}, index) => { + const participantCount = displayNamesWithTooltips.length; + const userTag = `${displayName ?? ''}`; + + // Add grammar (commas, "and", period) + if (index === participantCount - 1) { + return userTag; } - if (index === displayNamesWithTooltips.length - 2) { - if (displayNamesWithTooltips.length > 2) { - // eslint-disable-next-line @typescript-eslint/no-deprecated - return `${displayName}, ${translateLocal('common.and')}`; - } + if (index === participantCount - 2) { // eslint-disable-next-line @typescript-eslint/no-deprecated - return `${displayName} ${translateLocal('common.and')}`; + return `${userTag}${participantCount > 2 ? ',' : ''} ${translateLocal('common.and')} `; } - if (index < displayNamesWithTooltips.length - 2) { - return `${displayName},`; - } - return ''; + return `${userTag}, `; }) - .join(' '); + .join(''); - welcomeMessage.messageText = displayNamesWithTooltips.length ? ensureSingleSpacing(`${welcomeMessage.phrase1} ${displayNamesWithTooltipsText}`) : ''; + // eslint-disable-next-line @typescript-eslint/no-deprecated + welcomeMessage.messageHtml = displayNamesWithTooltips.length ? translateLocal('reportActionsView.beginningOfChatHistory', {users: usersHtml}) : ''; return welcomeMessage; } diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index ba613d10d933..3b002b115d31 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -771,7 +771,10 @@ describe('SidebarUtils', () => { ) .then(() => { const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, participantPersonalDetailList, localeCompare); - expect(result.messageText).toBe('This chat is with One and Two.'); + expect(result.messageHtml).toContain('This chat is with'); + expect(result.messageHtml).toContain(''); + expect(result.messageHtml).toContain(''); + expect(result.messageHtml).toContain(' and'); }) ); }); From 15caba1a226376da63cd4961de9bfac49d583943 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Fri, 14 Nov 2025 13:02:34 +0300 Subject: [PATCH 2/5] fix: eslint error --- src/components/ReportWelcomeText.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 990ed989e8dd..134be98604b7 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -11,7 +11,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import {getPersonalDetailsForAccountIDs} from '@libs/OptionsListUtils'; import { - getDisplayNamesWithTooltips, getParticipantsAccountIDsForDisplay, getPolicyName, getReportName, @@ -66,12 +65,6 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { const isSystemChat = isSystemChatReportUtils(report); const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM || isSystemChat); const participantAccountIDs = getParticipantsAccountIDsForDisplay(report, undefined, true, true, reportMetadata); - const isMultipleParticipant = participantAccountIDs.length > 1; - const displayNamesWithTooltips = getDisplayNamesWithTooltips( - getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails as OnyxInputOrEntry), - isMultipleParticipant, - localeCompare, - ); const moneyRequestOptions = temporary_getMoneyRequestOptions(report, policy, participantAccountIDs, isReportArchived, isRestrictedToPreferredPolicy); const policyName = getPolicyName({report}); From 3f45aa2b3efdff36ef6ae48ec99fd5086dc3ba5c Mon Sep 17 00:00:00 2001 From: TaduJR Date: Tue, 18 Nov 2025 11:13:10 +0300 Subject: [PATCH 3/5] chore: move additional text generation to SidebarUtils for cleaner code --- src/components/ReportWelcomeText.tsx | 20 +++++++++++--------- src/libs/SidebarUtils.ts | 20 +++++++++++++++++++- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 134be98604b7..64d24970e59c 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -15,7 +15,6 @@ import { getPolicyName, getReportName, isChatRoom as isChatRoomReportUtils, - isConciergeChatReport, isInvoiceRoom as isInvoiceRoomReportUtils, isPolicyExpenseChat as isPolicyExpenseChatReportUtils, isSelfDM as isSelfDMReportUtils, @@ -126,7 +125,16 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { const participantPersonalDetailListExcludeCurrentUser = Object.values( getPersonalDetailsForAccountIDs(participantAccountIDsExcludeCurrentUser, personalDetails as OnyxInputOrEntry), ); - const welcomeMessage = SidebarUtils.getWelcomeMessage(report, policy, participantPersonalDetailListExcludeCurrentUser, localeCompare, isReportArchived, reportDetailsLink); + const welcomeMessage = SidebarUtils.getWelcomeMessage( + report, + policy, + participantPersonalDetailListExcludeCurrentUser, + localeCompare, + isReportArchived, + reportDetailsLink, + shouldShowUsePlusButtonText, + additionalText, + ); return ( <> @@ -150,13 +158,7 @@ function ReportWelcomeText({report, policy}: ReportWelcomeTextProps) { {welcomeMessage.messageText} )} - {isDefault && !!welcomeMessage.messageHtml && ( - - - {shouldShowUsePlusButtonText && {` ${translate('reportActionsView.usePlusButton', {additionalText}).trim()}`}} - {isConciergeChatReport(report) && {` ${translate('reportActionsView.askConcierge').trim()}`}} - - )} + {isDefault && !!welcomeMessage.messageHtml && } ); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index c4859cf886dc..743a5038d443 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1003,6 +1003,8 @@ function getWelcomeMessage( localeCompare: LocaleContextProps['localeCompare'], isReportArchived = false, reportDetailsLink = '', + shouldShowUsePlusButtonText = false, + additionalText = '', ): WelcomeMessage { const welcomeMessage: WelcomeMessage = {}; if (isChatThread(report) || isTaskReport(report)) { @@ -1060,8 +1062,24 @@ function getWelcomeMessage( }) .join(''); + if (!displayNamesWithTooltips.length) { + return welcomeMessage; + } + // eslint-disable-next-line @typescript-eslint/no-deprecated - welcomeMessage.messageHtml = displayNamesWithTooltips.length ? translateLocal('reportActionsView.beginningOfChatHistory', {users: usersHtml}) : ''; + let messageHtml = translateLocal('reportActionsView.beginningOfChatHistory', {users: usersHtml}); + + // Append additional text for plus button or Concierge + if (shouldShowUsePlusButtonText) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + messageHtml += translateLocal('reportActionsView.usePlusButton', {additionalText}); + } + if (isConciergeChatReport(report)) { + // eslint-disable-next-line @typescript-eslint/no-deprecated + messageHtml += translateLocal('reportActionsView.askConcierge'); + } + + welcomeMessage.messageHtml = messageHtml; return welcomeMessage; } From 0bcd546851093d9cb01a932697b4ebce748cbcf6 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 29 Nov 2025 12:34:26 +0300 Subject: [PATCH 4/5] fix: LHN regression by setting messageText from messageHtml --- src/libs/SidebarUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3a171a255921..e78b6d9b37a3 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1089,6 +1089,7 @@ function getWelcomeMessage( } welcomeMessage.messageHtml = messageHtml; + welcomeMessage.messageText = Parser.htmlToText(welcomeMessage.messageHtml); return welcomeMessage; } From d01ce59a0dddb32266d5105401f1de742f8e281f Mon Sep 17 00:00:00 2001 From: TaduJR Date: Mon, 1 Dec 2025 05:18:37 +0300 Subject: [PATCH 5/5] fix: LHN regression by setting messageText from messageHtml --- tests/unit/SidebarUtilsTest.ts | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/unit/SidebarUtilsTest.ts b/tests/unit/SidebarUtilsTest.ts index 979b5c36f991..2cc28a51b533 100644 --- a/tests/unit/SidebarUtilsTest.ts +++ b/tests/unit/SidebarUtilsTest.ts @@ -882,6 +882,77 @@ describe('SidebarUtils', () => { ); }); + it('returns correct messageText for a single user DM chat', async () => { + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + chatType: undefined, + type: 'chat', + }; + const participantPersonalDetailList: PersonalDetails[] = [ + {accountID: 1, displayName: 'Email One', avatar: 'https://example.com/one.png', login: 'email1@test.com'} as unknown as PersonalDetails, + ]; + + await waitForBatchedUpdates(); + await act(async () => { + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + }); + }); + + const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, participantPersonalDetailList, localeCompare); + expect(result.messageText).toBe('This chat is with Email One.'); + expect(result.messageHtml).toContain('Email One'); + }); + + it('returns correct messageText for two users in a group chat', async () => { + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + chatType: 'group', + type: 'chat', + }; + const participantPersonalDetailList: PersonalDetails[] = [ + {accountID: 1, displayName: 'Email One', avatar: 'https://example.com/one.png', login: 'email1@test.com'} as unknown as PersonalDetails, + {accountID: 2, displayName: 'Email Two', avatar: 'https://example.com/two.png', login: 'email2@test.com'} as unknown as PersonalDetails, + ]; + + await waitForBatchedUpdates(); + await act(async () => { + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + }); + }); + + const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, participantPersonalDetailList, localeCompare); + expect(result.messageText).toMatch(/^This chat is with .+ and .+\.$/); + expect(result.messageText).toContain(' and '); + expect(result.messageText).not.toContain(' { + const MOCK_REPORT: Report = { + ...LHNTestUtils.getFakeReport(), + chatType: 'group', + type: 'chat', + }; + const participantPersonalDetailList: PersonalDetails[] = [ + {accountID: 1, displayName: 'Email One', avatar: 'https://example.com/one.png', login: 'email1@test.com'} as unknown as PersonalDetails, + {accountID: 2, displayName: 'Email Two', avatar: 'https://example.com/two.png', login: 'email2@test.com'} as unknown as PersonalDetails, + {accountID: 3, displayName: 'Email Three', avatar: 'https://example.com/three.png', login: 'email3@test.com'} as unknown as PersonalDetails, + ]; + + await waitForBatchedUpdates(); + await act(async () => { + await Onyx.multiSet({ + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + }); + }); + + const result = SidebarUtils.getWelcomeMessage(MOCK_REPORT, undefined, participantPersonalDetailList, localeCompare); + expect(result.messageText).toMatch(/^This chat is with .+, .+, and .+\.$/); + expect(result.messageText).toContain(', and '); + expect(result.messageText).not.toContain(' { const MOCK_REPORT: Report = { ...LHNTestUtils.getFakeReport(),