You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-09-28] Rename "Download as PDF" to "Download report" and add singular/plural support #101055
Per the naming spec in Expensify/Expensify#653740, the Download report label should read:
Individual report page: always singular Download report (the page only ever acts on one report).
Spend/Reports/Expenses page (bulk actions): singular Download report when exactly one report is selected, plural Download reports when more than one is selected.
Today both places use common.downloadAsPDF ("Download as PDF") with no singular/plural handling:
src/hooks/useExportActions.ts (individual report)
src/hooks/useSearchBulkActions.ts (bulk actions)
PR #96947 added Download receipts to the report page but left Download as PDF unchanged, since renaming it was out of scope for #95356.
Solution
Add new translation keys, across all languages: common.downloadReport (singular), common.downloadReports (plural).
In src/hooks/useExportActions.ts, change the DOWNLOAD_PDF action's text from common.downloadAsPDF to common.downloadReport (always singular).
In src/hooks/useSearchBulkActions.ts, change the DOWNLOAD_PDF option's text to pick common.downloadReport or common.downloadReports based on selectedReportIDs.length.
⚠️@Dip-G-Git Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.
Please re-state the problem that we are trying to solve in this issue.
The "Download report" action is currently labelled "Download as PDF" in both places it appears, and it has no singular/plural handling. Per the naming spec, the individual report page should always read "Download report" (it only ever acts on one report), while the Spend/Reports/Expenses bulk-action menu should read "Download report" when exactly one report is selected and "Download reports" when more than one is selected.
What is the root cause of that problem?
Both call sites read the same single, fixed translation key common.downloadAsPDF, which resolves to the literal string "Download as PDF". There is no separate singular/plural key and no count-based selection anywhere in the path.
On the individual report page, the DOWNLOAD_PDF secondary action hardcodes that key:
In the bulk-action menu, the DOWNLOAD_PDF option uses the same key, even though the count of selected reports is already available in scope as selectedReportIDs.length (the very next lines branch on selectedReportIDs.length === 1 for the download behaviour itself):
So the label is wrong in wording and cannot vary by count, because the only thing driving it is a constant key.
What changes do you think we should make in order to solve the problem?
Add two new keys to the common section of every language file (en, es, de, el, fr, it, ja, nl, pl, pt-BR, zh-hans): downloadReport for the singular label and downloadReports for the plural one. In English these read "Download report" and "Download reports"; the other languages get their correctly pluralised equivalents. Placing them in common keeps them next to the existing downloadAsPDF entry and available to both hooks without any new import.
In useExportActions.ts, change the text of the DOWNLOAD_PDF secondary action from the old key to the new singular key. This page always operates on exactly one report, so it is a plain constant swap with no count logic.
In useSearchBulkActions.ts, change the text of the DOWNLOAD_PDF bulk option so it selects between the plural and singular key based on whether selectedReportIDs.length is greater than one. That value is already in scope and already used a few lines below to choose between the single-report and multi-report download call, so the label now derives from exactly the same source of truth as the behaviour, which keeps the two from drifting apart. The option is only pushed when selectedReportIDs.length > 0, so the count is always at least one and no empty-state wording is needed.
Remove the now-unused downloadAsPDF key from every language file. After the two swaps above, it has no remaining consumer anywhere in src outside the language definitions themselves, so leaving it in would be dead translation copy that future contributors could reintroduce by mistake.
The result is that the individual report page reads "Download report" unconditionally, and the bulk menu reads "Download report" for a single selection and "Download reports" for two or more, with all languages covered.
What alternative solutions did you explore? (Optional)
An alternative is a single count-parameterised key, for example common.downloadReport taking a count parameter and resolving the plural form inside the translation function. That is more flexible for languages with several plural forms, but it is heavier than this issue needs: only two forms are ever required here, the count is already available at both call sites, and the parameterised variant would add a params type entry plus a function-valued translation in all eleven language files for no behavioural gain. I therefore kept the two flat keys, matching how similar singular/plural label pairs are already handled elsewhere in the codebase.
🤖 ProposalPolice™ is tracking duplicate proposals for this issue using an OpenAI Conversation. This comment stores that Conversation's ID and can be safely ignored.
Please re-state the problem that we are trying to solve in this issue.
Per the naming spec in Expensify/Expensify#653740, the PDF download action should no longer read "Download as PDF". Instead it should read:
Individual report page — always the singular Download report (the page only ever exports one report).
Spend / Reports / Expenses page (bulk actions) — Download report (singular) when exactly one report is selected, and Download reports (plural) when more than one is selected.
Today both entry points render the static common.downloadAsPDF ("Download as PDF") label with no singular/plural handling.
What is the root cause of that problem?
There is no bug in behavior — the label was simply never updated after the naming spec, and neither call site has singular/plural handling. Two places hardcode common.downloadAsPDF:
The individual report page's DOWNLOAD_PDF secondary action sets its text to translate('common.downloadAsPDF'). It always acts on a single report, so it should always read the singular Download report.
The Search bulk-action option for DOWNLOAD_PDF also sets its text to translate('common.downloadAsPDF'). This code already branches on selectedReportIDs.length inside onSelected (one report vs. many), but the visible label is a constant regardless of how many reports are selected, so it can never say "Download reports".
// Clear the selection now that the export has started. The ExportDownloadStatusManager shows the modal.
selectAllMatchingItems(false);
clearSelectedTransactions(undefined,true);
},
});
}
The underlying reason no singular/plural variant is possible: the common namespace only defines the fixed downloadAsPDF string (no downloadReport/downloadReports keys exist), mirroring the sibling downloadReceipts key added in feat: add download receipts option to report #96947.
The same two keys must be added to the other language files that define common.downloadReceipts (de.ts, el.ts, fr.ts, it.ts, ja.ts, nl.ts, pl.ts, pt-BR.ts, zh-hans.ts) with the appropriate translation, so the TranslationPaths type derived from en.ts resolves for every locale. The downloadAsPDF key can be kept if referenced elsewhere, or removed if these two call sites are its only consumers.
2. Individual report page — always singular. In useExportActions.ts, change the DOWNLOAD_PDF entry's text:
3. Search bulk actions — singular/plural based on the number of selected reports. In useSearchBulkActions.ts, change the bulk DOWNLOAD_PDF option's text to pick the key from selectedReportIDs.length (this block is only pushed when selectedReportIDs.length > 0, and onSelected already treats length === 1 as the single-report case, so the label stays in sync with the actual export):
This reuses existing translation infrastructure and the already-computed selectedReportIDs, adds no new helpers, and keeps the change minimal and consistent with how the neighboring downloadReceipts label is wired.
Please re-state the problem that we are trying to solve in this issue.
The PDF download action is labeled "Download as PDF" in two places: the report's More menu and the Spend > Reports "Selected" bulk menu. On the report page it should say "Download report". In the bulk menu it should say "Download report" when one report is selected and "Download reports" when more than one is selected.
What is the root cause of that problem?
In the bulk menu, the option's label and its onSelected handler use different logic. The handler already picks a flow based on how many reports are selected. With exactly one (selectedReportIDs.length === 1) it calls the single-report exportReportToPDF and opens the PDF modal. With more than one it calls the batch exportReportsToPDF. The text field ignores that choice and always uses common.downloadAsPDF, so the same label covers two different flows:
// Clear the selection now that the export has started. The ExportDownloadStatusManager shows the modal.
selectAllMatchingItems(false);
clearSelectedTransactions(undefined,true);
},
The count this needs is already in the hook. selectedReportIDs is rebuilt from selectedReports on every render, and the option is only added when it has at least one entry. So the label never has to handle zero, only 1 or more than 1:
On the report page, the DOWNLOAD_PDF secondary action always exports moneyRequestReport.reportID. It can never act on more than one report, so it only needs the singular label and no count check. It still uses the old key:
There are two more things a label-only change would leave behind:
Unused key:common.downloadAsPDF has no other users. No .tsx file reads it and no test checks its text. After both sites switch keys, it would sit unused in all 11 locale files.
Help-site docs: the help articles tell users to click "Download as PDF" in exactly these two flows, so the steps would no longer match the UI.
What changes do you think we should make in order to solve the problem?
1. Bulk menu: base the label on the same check as the handler. In src/hooks/useSearchBulkActions.ts, change the text at L2502 to use the same === 1 test as L2510. If someone changes one branch later, the mismatch is easy to spot in review:
In es.ts, add downloadReport: 'Descargar informe' and downloadReports: 'Descargar informes'. The other locales (de, fr, it, ja, nl, pl, pt-BR, zh-hans, el) start with the "automatically generated" header, so regenerate them with scripts/generateTranslations.ts rather than editing them by hand. Then remove downloadAsPDF from all 11 files so the old string can't be used again.
4. Tests: check the label in each branch.tests/unit/hooks/useSearchBulkActionsDownloadPDFTest.ts already has one test for the single-report flow and one for the multi-report flow, and it mocks translate as (key) => key. Add expect(pdfOption.text).toBe('common.downloadReport') to the single-report case (L257) and 'common.downloadReports' to the multi-report case (L361), and rename both test titles to match. If the label and handler ever drift apart, these tests will fail.
5. Help site: update the steps. In How-to-Export-Reports.md, step 4 of the single-report steps becomes Download report and step 4 of the multiple-reports steps becomes Download reports:
1. In the navigation tabs (on the left on web, on the bottom on mobile), go to **Spend > Reports**.
2. Open the report you want to download.
3. Select **More**.
4. Select **Download as PDF**.
The PDF includes all expenses, attached receipts, and report notes.
For reports on a Submit workspace, the PDF also includes an **Approve or Pay** button and QR code that let your approver open and approve the report online.
## How to download multiple reports as PDFs
1. In the navigation tabs (on the left on web, on the bottom on mobile), go to **Spend > Reports**.
2. Select the checkbox next to each report you want to download, or use the top checkbox to select all.
3. Select **Selected** at the top.
4. Select **Download as PDF**.
In the same article, the modal notes at lines 131 and 136 describe the batch and single-report flows. Change them to Download reports (more than one report selected) and Download report (single report):
This applies to the downloads that show a preparing status window, whether you start them from the **Reports** page or from a report's **More** menu: **Export** with any template other than **Basic export**, **Download receipts**, and **Download as PDF** when you selected more than one report.
It does not apply to:
-**Basic export**, which downloads straight to your device without a status window.
-**Download as PDF** for a single report, which uses its own window that does not reappear after a refresh. Start the download again to get a new PDF.
Search-and-Download-Expenses.md line 88 also becomes Download report.
Out of scope: the "Export as PDF" option for Expensify Card settlement groups is a separate action with its own key, and it stays as it is.
@mollfpr in the future, please add Bug, that'll trigger triage from Melvin then a C+ will be assigned to review. Also.. if/when needed, you can set the price of a job by prefacing the title with [$100] and so on. The standard price is currently $175 as of Sept 19th
@mallenexpensify Thanks! Unfortunately the payment still hasn't come through. The Upwork contract was ended without any payment being released. Could you please send a new offer or a bonus payment so I can receive it? 🙏
@ahmdshrif apologies, something fishy is happening, this happened twice the past few days where a contract was closed with $0 (and I don't know why). I'll be more vigilant. Paid now
Activity
melvin-bot commentedon Sep 14, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @abdulrahuman5196 (
External)melvin-bot commentedon Sep 14, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
melvin-bot commentedon Sep 14, 2026
Unable to auto-create job on Upwork. The BZ team member should create it manually for this issue.
Dip-G-Git commentedon Sep 14, 2026
github-actions commentedon Sep 14, 2026
wildan-m commentedon Sep 14, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
The "Download report" action is currently labelled "Download as PDF" in both places it appears, and it has no singular/plural handling. Per the naming spec, the individual report page should always read "Download report" (it only ever acts on one report), while the Spend/Reports/Expenses bulk-action menu should read "Download report" when exactly one report is selected and "Download reports" when more than one is selected.
What is the root cause of that problem?
Both call sites read the same single, fixed translation key
common.downloadAsPDF, which resolves to the literal string "Download as PDF". There is no separate singular/plural key and no count-based selection anywhere in the path.On the individual report page, the
DOWNLOAD_PDFsecondary action hardcodes that key:App/src/hooks/useExportActions.ts
Lines 273 to 277 in 07dca73
In the bulk-action menu, the
DOWNLOAD_PDFoption uses the same key, even though the count of selected reports is already available in scope asselectedReportIDs.length(the very next lines branch onselectedReportIDs.length === 1for the download behaviour itself):App/src/hooks/useSearchBulkActions.ts
Lines 2499 to 2511 in 07dca73
And the key itself is defined once per language with the old PDF-centric wording:
App/src/languages/en.ts
Line 465 in 07dca73
So the label is wrong in wording and cannot vary by count, because the only thing driving it is a constant key.
What changes do you think we should make in order to solve the problem?
Add two new keys to the
commonsection of every language file (en,es,de,el,fr,it,ja,nl,pl,pt-BR,zh-hans):downloadReportfor the singular label anddownloadReportsfor the plural one. In English these read "Download report" and "Download reports"; the other languages get their correctly pluralised equivalents. Placing them incommonkeeps them next to the existingdownloadAsPDFentry and available to both hooks without any new import.In
useExportActions.ts, change thetextof theDOWNLOAD_PDFsecondary action from the old key to the new singular key. This page always operates on exactly one report, so it is a plain constant swap with no count logic.In
useSearchBulkActions.ts, change thetextof theDOWNLOAD_PDFbulk option so it selects between the plural and singular key based on whetherselectedReportIDs.lengthis greater than one. That value is already in scope and already used a few lines below to choose between the single-report and multi-report download call, so the label now derives from exactly the same source of truth as the behaviour, which keeps the two from drifting apart. The option is only pushed whenselectedReportIDs.length > 0, so the count is always at least one and no empty-state wording is needed.Remove the now-unused
downloadAsPDFkey from every language file. After the two swaps above, it has no remaining consumer anywhere insrcoutside the language definitions themselves, so leaving it in would be dead translation copy that future contributors could reintroduce by mistake.The result is that the individual report page reads "Download report" unconditionally, and the bulk menu reads "Download report" for a single selection and "Download reports" for two or more, with all languages covered.
Compare branch: https://github.com/Expensify/App/compare/main...wildan-m:App:wildan/101055-download-report-label?expand=1
What alternative solutions did you explore? (Optional)
An alternative is a single count-parameterised key, for example
common.downloadReporttaking a count parameter and resolving the plural form inside the translation function. That is more flexible for languages with several plural forms, but it is heavier than this issue needs: only two forms are ever required here, the count is already available at both call sites, and the parameterised variant would add a params type entry plus a function-valued translation in all eleven language files for no behavioural gain. I therefore kept the two flat keys, matching how similar singular/plural label pairs are already handled elsewhere in the codebase.github-actions commentedon Sep 14, 2026
🤖 ProposalPolice™ is tracking duplicate proposals for this issue using an OpenAI Conversation. This comment stores that Conversation's ID and can be safely ignored.
abbasifaizan70 commentedon Sep 14, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
Per the naming spec in Expensify/Expensify#653740, the PDF download action should no longer read "Download as PDF". Instead it should read:
Download report(the page only ever exports one report).Download report(singular) when exactly one report is selected, andDownload reports(plural) when more than one is selected.Today both entry points render the static
common.downloadAsPDF("Download as PDF") label with no singular/plural handling.What is the root cause of that problem?
There is no bug in behavior — the label was simply never updated after the naming spec, and neither call site has singular/plural handling. Two places hardcode
common.downloadAsPDF:DOWNLOAD_PDFsecondary action sets itstexttotranslate('common.downloadAsPDF'). It always acts on a single report, so it should always read the singularDownload report.App/src/hooks/useExportActions.ts
Lines 273 to 289 in 07dca73
DOWNLOAD_PDFalso sets itstexttotranslate('common.downloadAsPDF'). This code already branches onselectedReportIDs.lengthinsideonSelected(one report vs. many), but the visible label is a constant regardless of how many reports are selected, so it can never say "Download reports".App/src/hooks/useSearchBulkActions.ts
Lines 2499 to 2527 in 07dca73
commonnamespace only defines the fixeddownloadAsPDFstring (nodownloadReport/downloadReportskeys exist), mirroring the siblingdownloadReceiptskey added in feat: add download receipts option to report #96947.App/src/languages/en.ts
Lines 465 to 466 in 07dca73
What changes do you think we should make in order to solve the problem?
1. Add the two new translation keys to every language file, alongside the existing
downloadReceiptskey in thecommonnamespace.en.ts(src/languages/en.ts), afterdownloadAsPDF:App/src/languages/en.ts
Lines 465 to 466 in 07dca73
es.ts(src/languages/es.ts):App/src/languages/es.ts
Lines 407 to 408 in 07dca73
The same two keys must be added to the other language files that define
common.downloadReceipts(de.ts,el.ts,fr.ts,it.ts,ja.ts,nl.ts,pl.ts,pt-BR.ts,zh-hans.ts) with the appropriate translation, so theTranslationPathstype derived fromen.tsresolves for every locale. ThedownloadAsPDFkey can be kept if referenced elsewhere, or removed if these two call sites are its only consumers.2. Individual report page — always singular. In
useExportActions.ts, change theDOWNLOAD_PDFentry'stext:App/src/hooks/useExportActions.ts
Line 275 in 07dca73
3. Search bulk actions — singular/plural based on the number of selected reports. In
useSearchBulkActions.ts, change the bulkDOWNLOAD_PDFoption'stextto pick the key fromselectedReportIDs.length(this block is only pushed whenselectedReportIDs.length > 0, andonSelectedalready treatslength === 1as the single-report case, so the label stays in sync with the actual export):App/src/hooks/useSearchBulkActions.ts
Line 2502 in 07dca73
This reuses existing translation infrastructure and the already-computed
selectedReportIDs, adds no new helpers, and keeps the change minimal and consistent with how the neighboringdownloadReceiptslabel is wired.trasnake87 commentedon Sep 14, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
The PDF download action is labeled "Download as PDF" in two places: the report's More menu and the Spend > Reports "Selected" bulk menu. On the report page it should say "Download report". In the bulk menu it should say "Download report" when one report is selected and "Download reports" when more than one is selected.
What is the root cause of that problem?
In the bulk menu, the option's label and its
onSelectedhandler use different logic. The handler already picks a flow based on how many reports are selected. With exactly one (selectedReportIDs.length === 1) it calls the single-reportexportReportToPDFand opens the PDF modal. With more than one it calls the batchexportReportsToPDF. Thetextfield ignores that choice and always usescommon.downloadAsPDF, so the same label covers two different flows:App/src/hooks/useSearchBulkActions.ts
Lines 2499 to 2525 in f777fab
The count this needs is already in the hook.
selectedReportIDsis rebuilt fromselectedReportson every render, and the option is only added when it has at least one entry. So the label never has to handle zero, only 1 or more than 1:App/src/hooks/useSearchBulkActions.ts
Lines 578 to 580 in f777fab
On the report page, the
DOWNLOAD_PDFsecondary action always exportsmoneyRequestReport.reportID. It can never act on more than one report, so it only needs the singular label and no count check. It still uses the old key:App/src/hooks/useExportActions.ts
Lines 273 to 288 in f777fab
Only one string is defined, with the PDF wording. It sits next to
downloadReceipts, which #96947 added without touching this key:App/src/languages/en.ts
Lines 465 to 466 in f777fab
There are two more things a label-only change would leave behind:
common.downloadAsPDFhas no other users. No.tsxfile reads it and no test checks its text. After both sites switch keys, it would sit unused in all 11 locale files.What changes do you think we should make in order to solve the problem?
1. Bulk menu: base the label on the same check as the handler. In
src/hooks/useSearchBulkActions.ts, change thetextat L2502 to use the same=== 1test as L2510. If someone changes one branch later, the mismatch is easy to spot in review:This two-key
length === 1 ? singular : pluralpattern is already used for fixed-text labels elsewhere:App/src/pages/settings/Rules/ExpenseRulesPage.tsx
Line 165 in f777fab
App/src/pages/workspace/fields/FieldsListValuesPage.tsx
Line 222 in f777fab
I'd skip the
{one, other}plural form here. That form is meant for strings that show a count, and the spec asks for two fixed labels with no number.2. Report page. In
src/hooks/useExportActions.tsL275:3. Translations: replace the key instead of adding next to it. In
src/languages/en.ts, replacedownloadAsPDFwith:In
es.ts, adddownloadReport: 'Descargar informe'anddownloadReports: 'Descargar informes'. The other locales (de, fr, it, ja, nl, pl, pt-BR, zh-hans, el) start with the "automatically generated" header, so regenerate them withscripts/generateTranslations.tsrather than editing them by hand. Then removedownloadAsPDFfrom all 11 files so the old string can't be used again.4. Tests: check the label in each branch.
tests/unit/hooks/useSearchBulkActionsDownloadPDFTest.tsalready has one test for the single-report flow and one for the multi-report flow, and it mockstranslateas(key) => key. Addexpect(pdfOption.text).toBe('common.downloadReport')to the single-report case (L257) and'common.downloadReports'to the multi-report case (L361), and rename both test titles to match. If the label and handler ever drift apart, these tests will fail.5. Help site: update the steps. In
How-to-Export-Reports.md, step 4 of the single-report steps becomes Download report and step 4 of the multiple-reports steps becomes Download reports:App/docs/articles/new-expensify/reports-and-expenses/How-to-Export-Reports.md
Lines 70 to 86 in f777fab
In the same article, the modal notes at lines 131 and 136 describe the batch and single-report flows. Change them to Download reports (more than one report selected) and Download report (single report):
App/docs/articles/new-expensify/reports-and-expenses/How-to-Export-Reports.md
Lines 131 to 136 in f777fab
Search-and-Download-Expenses.mdline 88 also becomes Download report.Out of scope: the "Export as PDF" option for Expensify Card settlement groups is a separate action with its own key, and it stays as it is.
53 remaining items
melvin-bot commentedon Sep 27, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)melvin-bot commentedon Sep 27, 2026
Payment Summary
Resolving PRs:
Rename "Download as PDF" to "Download report" with singular/plural support #101181
[No QA] Docs updates for E/A#101181 name singular Download receipt label #101376
Assignee: @ahmdshrif requires payment (Needs manual offer from BZ)
Reviewer: @abdulrahuman5196 owed $175 via NewDot
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Sep 28, 2026
Payment Summary
Contributor: @ahmdshrif paid $250 via Upwork
Contributor+: @abdulrahuman5196 due $125 via NewDot
@ahmdshrif can you please accept the job below? Please reply here and tag me once you have.
https://www.upwork.com/jobs/~022104705448339657192
@abdulrahuman5196 due $125 cuz PR review wasn't needed.
@mollfpr in the future, please add
Bug, that'll trigger triage from Melvin then a C+ will be assigned to review. Also.. if/when needed, you can set the price of a job by prefacing the title with[$100]and so on. The standard price is currently $175 as of Sept 19thahmdshrif commentedon Sep 28, 2026
@mallenexpensify, I haven’t revived the payment yet.
mallenexpensify commentedon Sep 28, 2026
@ahmdshrif paid, summary updated above. Thx. (sorry, mixed up my keyboard shortcuts)
ahmdshrif commentedon Sep 29, 2026
@mallenexpensify Thanks! Unfortunately the payment still hasn't come through. The Upwork contract was ended without any payment being released. Could you please send a new offer or a bonus payment so I can receive it? 🙏
mallenexpensify commentedon Oct 1, 2026
@ahmdshrif apologies, something fishy is happening, this happened twice the past few days where a contract was closed with $0 (and I don't know why). I'll be more vigilant. Paid now
laurenreidexpensify commentedon Oct 6, 2026
Approved $125 @abdulrahuman5196 New Expensify