fix: localization dates not being localized correctly - #91569
Conversation
|
Hey! I see that you made changes to our Form component. Make sure to update the docs in FORMS.md accordingly. Cheers! |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
…ation-dates-not-being-localized-correctly # Conflicts: # src/pages/Travel/FlightTripDetails.tsx
PR ReviewWell-executed migration from date-fns format strings to Issues to fix1. Mock type mismatch — 2. 3. Suggestions (non-blocking)4. 5. CalendarPicker missing 6. What's done well
|
|
@TaduJR you're missing native platform tests. Please test various timezones (US timezone UTC-X, Asia timezone UTC+X) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e56f6b81c3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…ation-dates-not-being-localized-correctly # Conflicts: # src/components/FrozenCardHeader.tsx # src/pages/Travel/TrainTripDetails.tsx
…calDate hardening
|
@codex review |
|
Codex Review: Didn't find any major issues. You're on a roll. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
…ation-dates-not-being-localized-correctly
|
Addressed many inconsistencies after merge main. It's hard to keep up. Would it be possible to finish reviewing this weekend. Thanks! |
|
Can we split into smaller PRs to avoid being reverted? |
|
Codex Review: Didn't find any major issues. 👍 Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Code reviewVerdict: Strong, carefully-built PR. The date/locale refactor is well-reasoned, the risky timezone paths are documented and defended, and the test coverage is substantial. I found no correctness blockers — two minor cleanups and one pre-existing edge to note. CI is green (the only red check, What it doesMoves display-date formatting to Strengths
Findings1. Dead code — 2. 3. Pre-existing (not a regression), flagging for a follow-up. Can't verify hereThe testing session couldn't sign in, so I reviewed statically only. Two things warrant the manual/visual QA you documented: (a) whether the 3-char short weekday labels (e.g. fr |
Regression huntBottom line: No functional regressions found — no day-shifts, crashes, broken selection, clock, or locale-loading regressions. The behavioral rewrites (shared minute clock, Behavioral / lifecycle — all cleanDetails
Visible English format changes (intended, but confirm the design sign-off covers en)These are correct-per-the-localization-goal and mostly documented in your Tests section, but they change what an English user sees today:
All high-traffic surfaces (search/transaction date cells, expense rows, travel ranges, EReceipt, chat timestamps, wallet, frozen card, distance rates) render byte-identical to before in en — verified against the Worth an eyeball before merge
|
|
Please fix conflict. |
…ation-dates-not-being-localized-correctly # Conflicts: # src/components/AutoUpdateTime.tsx # src/components/DateIcon.tsx # src/components/DatePicker/CalendarPicker/MonthPickerModal.tsx # src/components/DatePicker/CalendarPicker/index.tsx # src/components/EReceipt.tsx # src/components/FrozenCardHeader.tsx # src/components/LHNOptionsList/OptionRowLHN/OptionRow/Status.tsx # src/components/LocaleContextProvider.tsx # src/components/MoneyRequestConfirmationList/sections/PerDiemFields.tsx # src/components/PaymentCardDetails.tsx # src/components/ReportActionItem/ChronosOOOListActions.tsx # src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx # src/components/ReportActionItem/TripDetailsView.tsx # src/components/ReportActionItem/TripRoomPreview.tsx # src/components/Search/FilterComponents/DateFilterBase.tsx # src/components/Search/FilterComponents/DatePresetFilterBase.tsx # src/components/Search/FilterDropdowns/DateSelectPopup/index.tsx # src/components/Search/SearchList/ListItem/DateCell.tsx # src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx # src/components/Search/SearchList/ListItem/ExpenseReportListItemRow/ExpenseReportListItemRowNarrow.tsx # src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx # src/components/Search/SearchList/ListItem/getExpenseReportRowAccessibilityLabel.ts # src/components/Search/SearchList/ListItem/getExpenseReportRowDisplayValues.ts # src/components/Tables/WorkspaceExpensifyCardsTable/WorkspaceExpensifyCardsTableRow.tsx # src/languages/IntlStore.ts # src/libs/CardUtils.ts # src/libs/DateUtils.ts # src/libs/LocaleDigitUtils.ts # src/libs/PerDiemRequestUtils.ts # src/libs/ReportActionsUtils.ts # src/libs/SearchQueryUtils.ts # src/libs/SearchUIUtils.ts # src/libs/TransactionPreviewUtils.ts # src/libs/actions/IOU/PerDiem.ts # src/libs/actions/OnyxDerived/configs/reportAttributes.ts # src/pages/MultifactorAuthentication/AuthorizeTransactionPage/AuthorizeCardTransactionPreview.tsx # src/pages/ScheduleCall/ScheduleCallConfirmationPage.tsx # src/pages/ScheduleCall/ScheduleCallPage.tsx # src/pages/Travel/CarTripDetails.tsx # src/pages/Travel/FlightTripDetails.tsx # src/pages/Travel/HotelTripDetails.tsx # src/pages/Travel/TrainTripDetails.tsx # src/pages/home/RecentlyAddedSection/RecentlyAddedRow.tsx # src/pages/inbox/report/ParticipantLocalTime.tsx # src/pages/inbox/report/ReportActionItemSingle.tsx # src/pages/iou/SplitExpensePage.tsx # src/pages/settings/Profile/CustomStatus/StatusPage.tsx # src/pages/settings/Subscription/CardSection/CardSection.tsx # src/pages/settings/Subscription/CardSection/utils.ts # src/pages/wallet/WalletStatementPage.tsx # src/pages/workspace/distanceRates/PolicyDistanceRateDetailsPage.tsx # tests/unit/CalendarPickerTest.tsx # tests/unit/DateUtilsTest.ts # tests/unit/Search/SearchQueryUtilsTest.ts # tests/unit/ViolationUtilsTest.ts # tests/unit/getExpenseReportRowAccessibilityLabelTest.ts # tests/unit/getExpenseReportRowDisplayValuesTest.ts
…ation-dates-not-being-localized-correctly # Conflicts: # src/components/Search/hooks/useSearchSnapshot.ts # src/pages/iou/DynamicSplitBillDetailsPage.tsx # src/pages/iou/request/step/IOURequestStepAmount.tsx
…LDR, lint cleanup
…names, NowStore first-paint freshness
…re timer, active-mock IntlStore
… localized per-diem merchant
…ale-load failure, Hermes-safe parsing
…compute, date-fns relative-time parity
…ation-dates-not-being-localized-correctly # Conflicts: # src/libs/NextStepUtils.ts # src/libs/OptionsListUtils/index.ts # src/pages/settings/Subscription/CardSection/utils.ts
No we can't they are tightly coupled. |
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx bun ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
|
|
Explanation of Change
Intl.DateTimeFormat(dateStyle/timeStyle) across CalendarPicker, DateIcon, DOB step, receipts, travel, search, per-diem, status, schedule-call, and chat travel notifications.getWeekStartsOn/getWeekEndsOnare locale-aware viaIntl.Locale.getWeekInfo(); calendar shows the correct first day per locale.MM/DD/YYYY, de →DD.MM.YYYY, ja →YYYY/MM/DD).Enero, fr →Janvier, de →Januar).weekday: 'short'(fr →lun. mar. mer.) — narrow labels were ambiguous in fr/it/pt-BR.formatTravelDate(chat travel notifications) now respects locale; per-diem same-day "Time" row keeps the date.Architectural improvements:
Localeend-to-end —IntlStoreeager-seeds EN at module load sogetCurrentLocale(): Locale(neverundefined); cascades throughuseLocalize().preferredLocaleto every formatter signature. No?? CONST.LOCALES.DEFAULTfallbacks left.LocaleContextProviderreadsIntlStoreviauseSyncExternalStore— one re-render per locale change instead of the prioruseState+useEffect-on-Onyx-flag two-tick dance.useNowhook (new) —AutoUpdateTimeandParticipantLocalTimepreviously ran their own per-secondsetIntervals; both now share one ref-counted minute-tick viauseSyncExternalStore.Fixed Issues
$ #69562
PROPOSAL: #69562 (comment)
Tests
Setup
A. Language
B. Device timezone (OS-level — outside the app)
Set the device's timezone in the OS settings before each pass:
After changing OS timezone, fully close and reopen the App so it picks up the new zone.
C. In-app timezone (separate from device timezone)
In-app timezone is configured at: Settings → Profile → Timezone
America/Los_Angeles,Asia/Tokyo,Europe/Berlin).Tests 10, 11, 9, and R7-A explicitly require manipulating the in-app timezone.
Test 1 — Date Picker placeholder + selected value
Where: Settings → Profile → Personal Details → Date of birth.
Expected:
MM/DD/YYYY, saved value01/05/2026.DD/MM/AAAA(orDD/MM/YYYY), saved value05/01/2026.Test 2 — Calendar weekday headers and month names
Where: Any date picker.
Expected:
S M T W T F SorM T W T F S S; months read "January…".Test 3 — Calendar first day of week
Expected:
Test 4 — Travel reservations (Hotel / Flight / Car / Train)
Where: A chat with a travel booking → trip → individual reservation.
Expected:
2026-04-02T19:00-05:00must show consistent date+time, not "Apr 3 at 9:00 AM" vs "Apr 2 at 9:00 AM" split across the two halves of the row.Test 5 — Travel chat preview card
Where: Chat list showing a travel-room conversation.
Expected: Range uses the active language's month abbreviations. Single-day trips show one date.
Test 6 — Travel update messages in chat
Where: A travel chat with system notifications (ticket issued, flight changed, cancelled, etc.).
Expected: Both render in active language and describe the same moment regardless of device timezone.
Test 7 — Receipt full view (EReceipt)
Where: A transaction with a receipt → tap thumbnail → view receipt.
Expected: Date in active language (English: "January 5, 2026"; Spanish: "5 de enero de 2026"). Date does not shift across UTC−8 vs UTC+9.
Test 8 — Per-diem expense Time row
Where: Workspace with per-diem enabled.
Expected:
9:00 AM – 5:00 PM, Jan 5, 2026.9:00 AM, Jan 5, 2026 – 5:00 PM, Jan 6, 2026.Test 9 — Custom status (Clear after) — in-app timezone sensitive
Where: Settings → Profile → Status.
Run in two sub-passes:
9a. Device TZ = UTC−8, In-app TZ = Automatic (so in-app = UTC−8)
9b. Device TZ = UTC−8, In-app TZ = Manual →
Asia/Tokyo(UTC+9)Expected (both sub-passes):
Test 10 — Profile "Local time" row — in-app timezone sensitive
Where: Open your own profile (avatar → details) → find Local time row.
Run in two sub-passes:
10a. In-app Timezone = Automatic
Expected: Time matches the device clock. Auto-updates within ~60 seconds (e.g. "2:34 PM" → "2:35 PM"). 12-hour AM/PM in English, 24-hour in Spanish.
10b. In-app Timezone = Manual
Asia/TokyoExpected: Time now shows Tokyo wall-clock time (≈17 hours ahead of US Pacific), formatted in the active language. The displayed timezone abbreviation (e.g.
JST) reflects Tokyo. Auto-update at the minute boundary still works.Test 11 — Participant local time (chat header) — in-app timezone sensitive
Where: A 1:1 chat with a participant whose in-app timezone differs from yours.
Setup: You should be in (say)
America/Los_Angeles. The other participant should be inAsia/Tokyo. Both timezones set via Settings → Profile → Timezone → Manual in each account.Expected:
Test 12 — Search results date column
Where: Search → any search with dated results.
Expected: Dates use active language's month abbreviations. Past-year rows include year, current-year typically omit it. No row shows the wrong calendar day after changing device timezone.
Test 13 — Wallet statements
Where: Settings → Wallet → View statement → pick a month.
Expected: Month name fully localized.
Test 14 — Workspace distance rates start/end date
Where: Workspace with Date-bound mileage rate beta enabled. Workspace → Distance rates → tap a rate.
Expected: Rows in active language. Editor calendar shows correct localized placeholder. Saving a date and re-opening shows the same date even after switching device timezone.
Test 15 — Frozen card header
Where: Settings → Wallet → Expensify Card → A frozen card.
Expected: Frozen date follows the active language.
Test 16 — Schedule a call
Where: Any chat with the Schedule a call action.
Expected: Calendar and confirmation line both in active language. Selected date+time consistent across device timezones.
Test 17 — Out-of-office (Chronos) summary
Where: A chat with Chronos / OOO bot messages.
Expected: Both in active language, consistent date/time formatting.
Test 18 — Split expense date column
Where: An expense that has been split.
Expected: Dates in active language. No day-shift across device timezones.
Regression Test Cases (must pass before merge)
Each below reproduces a real bug from prior reverted attempts. They must all pass.
R1 — Merge expense: no crash on confirmation page (#80013)
Expected: Merge confirmation page opens normally. No crash, no white screen.
R2 — Merge expense: opening merged expense does not crash (PR #69945)
Expected: Detail page opens. Date row renders. No crash.
R3 — Date picker: picked date matches saved date (#76646)
Run on iOS App, Windows Chrome, and macOS Chrome/Safari.
Expected: Saved date is exactly Jan 15, 2000. Not Jan 14, not Jan 16.
Run this also under UTC+9 device timezone. The result must still be Jan 15.
R4 — Date picker: same-day select on Monday-start calendar (PR #69945 deploy-blocker)
Active language: English or Spanish.
Expected: Saved date is today. Every column in the grid saves the date its header letter says (M-cell → Monday, T-cell → Tuesday, etc.). Repeat for several weekdays.
R5 — Bank account DOB placeholder shows locale format (#80011)
Active language: English, Windows Chrome.
Expected: Placeholder reads
MM/DD/YYYY. NeverYYYY-MM-DD. Switch to Spanish and re-check.R6 — Split expense: opening a split does not crash on Android (#76643)
Run on Android (original repro: Redmi Note 10s, Android 13).
Expected: Detail page opens. Dates render in active language. No crash.
R7 — No day-shift across the app (#80024) — timezone-critical
Setup: Device language English. Run all three parts twice: once with device timezone US Pacific (UTC−08:00), then again with device timezone Asia/Tokyo (UTC+09:00). Test on iOS App and Windows Chrome.
R7-A — Status:
This also tests in-app timezone interaction.
Expected (both Pacific and Tokyo passes): Shows the date and time you picked. Never Jan 24 in one pass and Jan 25 in the other.
Then repeat R7-A with in-app Timezone set manually to
Europe/Berlin(UTC+01:00) — the saved value should now reflect Berlin wall-clock, not device wall-clock.R7-B — Date of birth:
Expected (both Pacific and Tokyo passes): Shows March 10, 1995 (or
03/10/1995). Not March 9, not March 11.R7-C — Company cards transaction start date:
Expected (both passes): Saved date is Feb 1, 2026.
Offline tests
Same as tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Mac-Chrome.mp4