Skip to content

Hosting Dashboard: Introduce I18nProvider to make translation work correctly #103299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 30 additions & 13 deletions client/boot/locale.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function getLocaleFromQueryParam() {
return query.get( 'locale' );
}

export const setupLocale = ( currentUser, reduxStore ) => {
export const setupLocale = ( currentUser, reduxStore = null ) => {
Copy link
Member

Choose a reason for hiding this comment

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

The reduxStore optionality feels a bit suboptimal here 😬 It's not clear what it means: is it because the global Redux store has not been initialized yet, or because we don't intend to have one at all? Definitely feels like a code smell to avoid.

There could be some opportunities for decoupling and reusability, but in general, I agree with what @jsnajdr suggested above, which is, building a simpler version that works with the constraints at hand.

if ( config.isEnabled( 'i18n/empathy-mode' ) && currentUser.i18n_empathy_mode ) {
initLanguageEmpathyMode();
}
Expand All @@ -49,34 +49,51 @@ export const setupLocale = ( currentUser, reduxStore ) => {
i18n.setLocale( localeData );
const localeSlug = i18n.getLocaleSlug();
const localeVariant = i18n.getLocaleVariant();
reduxStore.dispatch( { type: LOCALE_SET, localeSlug, localeVariant } );
reduxStore?.dispatch( { type: LOCALE_SET, localeSlug, localeVariant } );
// Propagate the locale to @automattic/number-formatters
setLocaleNumberFormatters( localeVariant || localeSlug );

if ( localeSlug ) {
loadUserUndeployedTranslations( localeSlug );
}
} else if ( currentUser && currentUser.localeSlug ) {
return localeVariant || localeSlug;
}

if ( currentUser && ( currentUser.localeSlug || currentUser.language ) ) {
if ( shouldUseFallbackLocale ) {
// Use user locale fallback slug
reduxStore.dispatch( setLocale( userLocaleSlug ) );
} else {
// Use the current user's and load traslation data with a fetch request
reduxStore.dispatch( setLocale( currentUser.localeSlug, currentUser.localeVariant ) );
reduxStore?.dispatch( setLocale( userLocaleSlug ) );
return userLocaleSlug;
}
} else if ( bootstrappedLocaleSlug ) {

// Use the current user's and load translation data with a fetch request
reduxStore?.dispatch(
setLocale( currentUser.localeSlug || currentUser.language, currentUser.localeVariant )
);
return currentUser.localeVariant || currentUser.localeSlug || currentUser.language;
}

if ( bootstrappedLocaleSlug ) {
// Use locale slug from bootstrapped language manifest object
reduxStore.dispatch( setLocale( bootstrappedLocaleSlug ) );
} else if ( getLocaleFromQueryParam() ) {
reduxStore?.dispatch( setLocale( bootstrappedLocaleSlug ) );
return bootstrappedLocaleSlug;
}

if ( getLocaleFromQueryParam() ) {
// For logged out Calypso pages, set the locale from query param
const pathLocaleSlug = getLocaleFromQueryParam();
pathLocaleSlug && reduxStore.dispatch( setLocale( pathLocaleSlug, '' ) );
} else if ( ! window.hasOwnProperty( 'localeFromRoute' ) ) {
pathLocaleSlug && reduxStore?.dispatch( setLocale( pathLocaleSlug, '' ) );
return pathLocaleSlug;
}

if ( ! window.hasOwnProperty( 'localeFromRoute' ) ) {
// For logged out Calypso pages, set the locale from path if we cannot get the locale from the route on the server side
const pathLocaleSlug = getLocaleFromPathname();
pathLocaleSlug && reduxStore.dispatch( setLocale( pathLocaleSlug, '' ) );
pathLocaleSlug && reduxStore?.dispatch( setLocale( pathLocaleSlug, '' ) );
recordTracksEvent( 'calypso_locale_set', { path: window.location.pathname } );
return pathLocaleSlug;
}

// If user is logged out and translations are not bootstrapped, we assume default locale
return '';
};
7 changes: 7 additions & 0 deletions client/dashboard/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ module.exports = {
{
group: [
'calypso/*',
'!calypso/boot',
'calypso/boot/*',
'!calypso/boot/locale',
// Allowed: calypso/lib/wp
'!calypso/lib',
'calypso/lib/*',
'!calypso/lib/wp',
'!calypso/lib/i18n-utils',
'calypso/lib/i18n-utils/*',
'!calypso/lib/i18n-utils/switch-locale',
'!calypso/components',
'calypso/components/*',
'!calypso/components/calypso-i18n-provider',
// Allowed: calypso/assets/icons
'!calypso/assets',
'calypso/assets/*',
Expand Down
26 changes: 26 additions & 0 deletions client/dashboard/app/i18n/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import defaultCalypsoI18n from 'i18n-calypso';
import { useEffect } from 'react';
import { setupLocale } from 'calypso/boot/locale';
import CalypsoI18nProvider from 'calypso/components/calypso-i18n-provider';
import switchLocale from 'calypso/lib/i18n-utils/switch-locale';
import { useAuth } from '../auth';

export function I18nProvider( { children }: { children: React.ReactNode } ) {
const { user } = useAuth();
Copy link
Contributor

Choose a reason for hiding this comment

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

I checked out this locally. I found that the new language I set in /me isn't reflected here unless I comment this line out 🥲 are you experiencing the same?

staleTime: 30 * 60 * 1000, // Consider auth valid for 30 minutes

Copy link
Member

Choose a reason for hiding this comment

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

We probably need to invalidate React Query caches for anything that depends on /me information when we know that we've changed that information (i.e. updated the language in the interface).

https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation


useEffect( () => {
if ( ! user.language ) {
return;
}

const locale = setupLocale( user );

// The `switchLocale` function is normally called within the `setLocale` action. However,
// since we don't have access to the Redux store in this context, we need to call it manually.
if ( locale ) {
switchLocale( locale );
}
}, [ user.language ] );

return <CalypsoI18nProvider i18n={ defaultCalypsoI18n }>{ children }</CalypsoI18nProvider>;
}
5 changes: 4 additions & 1 deletion client/dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RouterProvider } from '@tanstack/react-router';
import { useMemo } from 'react';
import { AuthProvider, useAuth } from './auth';
import { AppProvider, type AppConfig } from './context';
import { I18nProvider } from './i18n';
import { queryClient } from './query-client';
import { getRouter } from './router';

Expand All @@ -17,7 +18,9 @@ function Layout( { config }: { config: AppConfig } ) {
<AppProvider config={ config }>
<QueryClientProvider client={ queryClient }>
<AuthProvider>
<RouterProviderWithAuth config={ config } />
<I18nProvider>
<RouterProviderWithAuth config={ config } />
</I18nProvider>
</AuthProvider>
</QueryClientProvider>
</AppProvider>
Expand Down
1 change: 1 addition & 0 deletions client/dashboard/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface User {
username: string;
display_name: string;
avatar_URL?: string;
language: string;
}

export interface SiteDomain {
Expand Down
Loading