Skip to content

MSD: Server-render the interim omnibar in the user's language#109904

Open
StevenDufresne wants to merge 2 commits intotrunkfrom
fix/interim-omnibar-ssr-locale
Open

MSD: Server-render the interim omnibar in the user's language#109904
StevenDufresne wants to merge 2 commits intotrunkfrom
fix/interim-omnibar-ssr-locale

Conversation

@StevenDufresne
Copy link
Copy Markdown
Contributor

@StevenDufresne StevenDufresne commented Apr 9, 2026

Part of DOTMSD-1199

Proposed Changes

  • New `loadDashboardLocaleData` Express middleware populates `defaultI18n` with the bootstrapped user's locale during the document render, so the server-side render of the interim omnibar emits translated strings.
  • Wired into all dashboard section paths (dotcom, ciab, start-store).
  • New `shared-locale-loader.ts` exposes `getUserLanguage` (mirrors the server's `setUpLoggedInRoute` derivation so both sides agree on the effective slug, including the `use_fallback_for_incomplete_languages` gate) and `loadUserLocaleData` (cache-aware CDN fetch).
  • `loadOmnibar` fetches the same locale on the client and calls `resetLocaleData` before `hydrateRoot` so the first client render matches the SSR-translated HTML.
  • `I18nProvider`'s `useLocaleSlug` switches to the shared `getUserLanguage` so the main dashboard tree agrees with the omnibar on the effective locale.

Why are these changes being made?

The interim omnibar currently renders in English regardless of the user's account language. It's rendered on the server using the default-English `defaultI18n` singleton, then hydrated on the client the same way. Non-English users never see the masterbar in their own language — they see English, and then (if the main dashboard's `I18nProvider` finishes loading before anything else) the strings flip.

This PR gives the omnibar first-class language support: the server knows the user's locale at render time and applies it, the client applies the same locale before hydration, and both sides share one derivation so they can't disagree.

Requirements

Requires `wpcom-user-bootstrap: true` so the server knows the user's locale at render time. Horizon, stage, and production dashboard environments have this enabled; local dev (`dashboard-development.json`) does not by default.

Testing Instructions

  1. Set your WordPress.com account language to a non-English locale (French, Arabic, etc.).
  2. Hard-reload a dashboard page with the omnibar visible.
  3. Confirm the masterbar renders translated on the first visible frame.
  4. Change your account language in WordPress.com settings, return to the dashboard, reload. Confirm the new language applies (including when switching back to English).

To test locally, flip `wpcom-user-bootstrap: true` in `config/dashboard-development.json` and make sure `wpcom_calypso_rest_api_key` is in your secrets.

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you tested accessibility for your changes? Ensure the feature remains usable with various user agents (e.g., browsers), interfaces (e.g., keyboard navigation), and assistive technologies (e.g., screen readers) (PCYsg-S3g-p2).
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages.
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

Populate the bootstrapped user's locale data in `defaultI18n` during
the server-side document render, so the masterbar HTML ships with
translated strings for non-English users, and have the client apply
the same data before hydration so the first client render matches.
Eliminates the visible flash of English before the client-side
locale fetch completes.

Changes:

  - `client/server/dashboard-i18n.ts` — new Express middleware that
    loads the user's locale JSON from `public/languages/` (falling
    back to the Calypso CDN) and applies it to `defaultI18n` for the
    duration of the request's render. Cached per language.
  - `client/server/pages/index.js` — `handleSectionPath` takes an
    optional `extraMiddleware` that runs after `setUpRoute`; wires
    the new middleware into all dashboard section paths (dotcom,
    ciab, start-store).
  - `client/dashboard/app/shared-locale-loader.ts` — new module with
    `getUserLanguage` (mirrors the server's `setUpLoggedInRoute`
    derivation so both sides agree on the effective locale, including
    the `use_fallback_for_incomplete_languages` gate) and
    `loadUserLocaleData` (a cache-aware pure fetch with no singleton
    mutation — callers apply the result themselves).
  - `client/dashboard/app/interim-omnibar/index.tsx` — loadOmnibar
    fetches the locale in parallel with the auth query and chunk
    import, then applies it to `defaultI18n` via `resetLocaleData`
    before calling `hydrateRoot`.
  - `client/dashboard/app/i18n.tsx` — refactored to use the shared
    loader; removes the duplicate `fetchLocaleData` definition.
    `I18nProvider`'s `useEffect` now applies `resetLocaleData` inside
    a `cancelled`-gated `.then`, so stale completions from a slower
    previous request can't override a newer locale.

Requires `wpcom-user-bootstrap: true` for the server to know the
user's locale at render time. Dashboard environments (horizon, stage,
production) have this enabled; local dev does not by default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@StevenDufresne StevenDufresne force-pushed the fix/interim-omnibar-ssr-locale branch from e918d9c to a0cb8f2 Compare April 9, 2026 03:59
@matticbot
Copy link
Copy Markdown
Contributor

matticbot commented Apr 9, 2026

This PR modifies the release build for the following Calypso Apps:

For info about this notification, see here: PCYsg-OT6-p2

  • help-center
  • notifications
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug fix/interim-omnibar-ssr-locale on your sandbox.

Revert the refactor that replaced `I18nProvider`'s internal
`fetchLocaleData` helper and `AbortController` with the shared
`loadUserLocaleData` and a `cancelled` flag. Keep only the minimum
change needed for server/client agreement on the effective locale:
have `useLocaleSlug` call `getUserLanguage` from the shared loader so
it applies the same `use_fallback_for_incomplete_languages` gate as
the server's `setUpLoggedInRoute`.

Reasons to go minimal here:

- Restores the unmount-time HTTP request cancellation that was added
  in response to review feedback on #103635. The refactor's
  `cancelled` flag prevented setState-after-unmount but did not
  actually abort the in-flight fetch.
- Shrinks the branch's `i18n.tsx` diff from ~50 lines to ~7, making
  review and future history-walking cheaper.
- The shared fetch cache (one network request shared between
  `loadOmnibar` and the main I18nProvider) is a nice-to-have, not
  load-bearing. Both callers hit the same CDN URL with far-future
  cache headers, so the worst case is one extra request once per
  session for non-English users.

The partial-locale hydration correctness is preserved via the
`getUserLanguage` import — both `loadOmnibar` and `I18nProvider`
derive the same slug, matching the server's decision.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@StevenDufresne StevenDufresne marked this pull request as ready for review April 9, 2026 04:51
@StevenDufresne StevenDufresne requested a review from a team as a code owner April 9, 2026 04:51
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Apr 9, 2026
@StevenDufresne StevenDufresne requested a review from jsnajdr April 9, 2026 05:09
// effective locale.
const [ { InterimOmnibarContainer }, localeData ] = await Promise.all( [
import( './interim-omnibar-container' ),
loadUserLocaleData( getUserLanguage( window.currentUser ?? null ) ),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the tradeoff. For non-english, we do wait to download the payload.

export const loadDashboardLocaleData: RequestHandler = ( req, res, next ) => {
const language = ( req as CalypsoRequest ).context?.lang;
if ( ! language || language === 'en' ) {
defaultI18n.resetLocaleData();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can use the global here 🤔 This isn't PHP, node requests don't get their own thread, and setting the global locale could interfere with other requests.

We need some sort of locale provider to make sure each React tree being rendered on the server can have it's own locale data 🤔 Importing __ at the module level means that it will use defaultI18n, but the other way is to do const { __ } = useI18n(); at that means you can provide your own i18n data to each tree.

That's how Calypso's old translate() function worked too. I wonder if I was wrong to migrate the logged in master bar over to core's translation functions 😬 I thought I was making it more compatible but I may have made it worse.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 I was too busy trying to reign in my dragon, this very important details slipped past. hmmmm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants