From 3985f49d64c619a26ad99685fa5bdaaeb7fb4ad6 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Tue, 30 Apr 2024 17:48:35 +1000 Subject: [PATCH 1/7] Add initial LanguagePicker component --- src/components/user-settings.tsx | 54 +++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/components/user-settings.tsx b/src/components/user-settings.tsx index 64b573e0..5f2d0969 100644 --- a/src/components/user-settings.tsx +++ b/src/components/user-settings.tsx @@ -1,4 +1,11 @@ -import { DropdownMenu, Icon, MenuGroup, MenuItem, Spinner } from '@wordpress/components'; +import { + DropdownMenu, + Icon, + MenuGroup, + MenuItem, + SelectControl, + Spinner, +} from '@wordpress/components'; import { sprintf } from '@wordpress/i18n'; import { moreVertical, trash } from '@wordpress/icons'; import { useI18n } from '@wordpress/react-i18n'; @@ -12,6 +19,7 @@ import { useSiteDetails } from '../hooks/use-site-details'; import { useSiteUsage } from '../hooks/use-site-usage'; import { cx } from '../lib/cx'; import { getIpcApi } from '../lib/get-ipc-api'; +// import { namedLocales } from '../lib/locale'; import Button from './button'; import { Gravatar } from './gravatar'; import Modal from './modal'; @@ -20,6 +28,49 @@ import ProgressBar from './progress-bar'; import Tooltip from './tooltip'; import { WordPressLogo } from './wordpress-logo'; +// TODO - Move to locales.ts +export const namedLocales = [ + { name: 'Arabic', locale: 'ar' }, + { name: 'German', locale: 'de' }, + { name: 'English', locale: 'en' }, + { name: 'Spanish', locale: 'es' }, + { name: 'French', locale: 'fr' }, + { name: 'Hebrew', locale: 'he' }, + { name: 'Indonesian', locale: 'id' }, + { name: 'Italian', locale: 'it' }, + { name: 'Japanese', locale: 'ja' }, + { name: 'Korean', locale: 'ko' }, + { name: 'Dutch', locale: 'nl' }, + { name: 'Polish', locale: 'pl' }, + { name: 'Portuguese (Brazil)', locale: 'pt-br' }, + { name: 'Russian', locale: 'ru' }, + { name: 'Swedish', locale: 'sv' }, + { name: 'Turkish', locale: 'tr' }, + { name: 'Chinese (Simplified)', locale: 'zh-cn' }, + { name: 'Chinese (Traditional)', locale: 'zh-tw' }, +]; + +const LanguagePicker = () => { + const { __ } = useI18n(); + const [ locale, setLocale ] = useState( '' ); + + return ( +
+

{ __( 'Language' ) }

+ { + setLocale( value ); + } } + options={ namedLocales.map( ( { name, locale } ) => ( { + value: locale, + label: name, + } ) ) } + /> +
+ ); +}; + const UserInfo = ( { user, onLogout, @@ -196,6 +247,7 @@ export default function UserSettings() {
+ Date: Tue, 30 Apr 2024 18:05:37 +1000 Subject: [PATCH 2/7] Move LanguagePicker component to dedicated file --- src/components/language-picker.tsx | 44 +++++++++++++++++++++++++ src/components/user-settings.tsx | 53 ++---------------------------- 2 files changed, 46 insertions(+), 51 deletions(-) create mode 100644 src/components/language-picker.tsx diff --git a/src/components/language-picker.tsx b/src/components/language-picker.tsx new file mode 100644 index 00000000..0039e476 --- /dev/null +++ b/src/components/language-picker.tsx @@ -0,0 +1,44 @@ +import { SelectControl } from '@wordpress/components'; +import { useState } from 'react'; + +// TODO - Move to locales.ts +export const namedLocales = [ + { name: 'Arabic', locale: 'ar' }, + { name: 'German', locale: 'de' }, + { name: 'English', locale: 'en' }, + { name: 'Spanish', locale: 'es' }, + { name: 'French', locale: 'fr' }, + { name: 'Hebrew', locale: 'he' }, + { name: 'Indonesian', locale: 'id' }, + { name: 'Italian', locale: 'it' }, + { name: 'Japanese', locale: 'ja' }, + { name: 'Korean', locale: 'ko' }, + { name: 'Dutch', locale: 'nl' }, + { name: 'Polish', locale: 'pl' }, + { name: 'Portuguese (Brazil)', locale: 'pt-br' }, + { name: 'Russian', locale: 'ru' }, + { name: 'Swedish', locale: 'sv' }, + { name: 'Turkish', locale: 'tr' }, + { name: 'Chinese (Simplified)', locale: 'zh-cn' }, + { name: 'Chinese (Traditional)', locale: 'zh-tw' }, +]; + +export const LanguagePicker = () => { + const [ locale, setLocale ] = useState( '' ); + + return ( +
+

Language

+ { + setLocale( value ); + } } + options={ namedLocales.map( ( { name, locale } ) => ( { + value: locale, + label: name, + } ) ) } + /> +
+ ); +}; diff --git a/src/components/user-settings.tsx b/src/components/user-settings.tsx index 5f2d0969..fdeb8bd8 100644 --- a/src/components/user-settings.tsx +++ b/src/components/user-settings.tsx @@ -1,11 +1,4 @@ -import { - DropdownMenu, - Icon, - MenuGroup, - MenuItem, - SelectControl, - Spinner, -} from '@wordpress/components'; +import { DropdownMenu, Icon, MenuGroup, MenuItem, Spinner } from '@wordpress/components'; import { sprintf } from '@wordpress/i18n'; import { moreVertical, trash } from '@wordpress/icons'; import { useI18n } from '@wordpress/react-i18n'; @@ -22,55 +15,13 @@ import { getIpcApi } from '../lib/get-ipc-api'; // import { namedLocales } from '../lib/locale'; import Button from './button'; import { Gravatar } from './gravatar'; +import { LanguagePicker } from './language-picker'; import Modal from './modal'; import offlineIcon from './offline-icon'; import ProgressBar from './progress-bar'; import Tooltip from './tooltip'; import { WordPressLogo } from './wordpress-logo'; -// TODO - Move to locales.ts -export const namedLocales = [ - { name: 'Arabic', locale: 'ar' }, - { name: 'German', locale: 'de' }, - { name: 'English', locale: 'en' }, - { name: 'Spanish', locale: 'es' }, - { name: 'French', locale: 'fr' }, - { name: 'Hebrew', locale: 'he' }, - { name: 'Indonesian', locale: 'id' }, - { name: 'Italian', locale: 'it' }, - { name: 'Japanese', locale: 'ja' }, - { name: 'Korean', locale: 'ko' }, - { name: 'Dutch', locale: 'nl' }, - { name: 'Polish', locale: 'pl' }, - { name: 'Portuguese (Brazil)', locale: 'pt-br' }, - { name: 'Russian', locale: 'ru' }, - { name: 'Swedish', locale: 'sv' }, - { name: 'Turkish', locale: 'tr' }, - { name: 'Chinese (Simplified)', locale: 'zh-cn' }, - { name: 'Chinese (Traditional)', locale: 'zh-tw' }, -]; - -const LanguagePicker = () => { - const { __ } = useI18n(); - const [ locale, setLocale ] = useState( '' ); - - return ( -
-

{ __( 'Language' ) }

- { - setLocale( value ); - } } - options={ namedLocales.map( ( { name, locale } ) => ( { - value: locale, - label: name, - } ) ) } - /> -
- ); -}; - const UserInfo = ( { user, onLogout, From 3097bd8f69d2d27a1d10f987dadbd0a18da15ff5 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Wed, 1 May 2024 18:30:55 +1000 Subject: [PATCH 3/7] Update user-settings tests and imports --- src/components/tests/user-settings.test.tsx | 7 +++++++ src/components/user-settings.tsx | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/tests/user-settings.test.tsx b/src/components/tests/user-settings.test.tsx index 3775ebde..50b93a61 100644 --- a/src/components/tests/user-settings.test.tsx +++ b/src/components/tests/user-settings.test.tsx @@ -20,6 +20,13 @@ describe( 'UserSettings', () => { callback(); } } ); + + // Mock window.matchMedia + window.matchMedia = jest.fn().mockReturnValue( { + matches: false, + addListener: jest.fn(), + removeListener: jest.fn(), + } ); } ); it( 'logs in when not authenticated', async () => { diff --git a/src/components/user-settings.tsx b/src/components/user-settings.tsx index fdeb8bd8..d4b705d2 100644 --- a/src/components/user-settings.tsx +++ b/src/components/user-settings.tsx @@ -12,7 +12,6 @@ import { useSiteDetails } from '../hooks/use-site-details'; import { useSiteUsage } from '../hooks/use-site-usage'; import { cx } from '../lib/cx'; import { getIpcApi } from '../lib/get-ipc-api'; -// import { namedLocales } from '../lib/locale'; import Button from './button'; import { Gravatar } from './gravatar'; import { LanguagePicker } from './language-picker'; From c623b0f269c71c7a1d333fb66fd408f690eebafc Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Fri, 3 May 2024 15:54:50 +1000 Subject: [PATCH 4/7] Update LanguagePicker selection --- src/components/language-picker.tsx | 46 +++++++++++++-------------- src/lib/locale.ts | 50 ++++++++++++++---------------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/src/components/language-picker.tsx b/src/components/language-picker.tsx index 0039e476..6174094c 100644 --- a/src/components/language-picker.tsx +++ b/src/components/language-picker.tsx @@ -1,27 +1,27 @@ import { SelectControl } from '@wordpress/components'; import { useState } from 'react'; -// TODO - Move to locales.ts -export const namedLocales = [ - { name: 'Arabic', locale: 'ar' }, - { name: 'German', locale: 'de' }, - { name: 'English', locale: 'en' }, - { name: 'Spanish', locale: 'es' }, - { name: 'French', locale: 'fr' }, - { name: 'Hebrew', locale: 'he' }, - { name: 'Indonesian', locale: 'id' }, - { name: 'Italian', locale: 'it' }, - { name: 'Japanese', locale: 'ja' }, - { name: 'Korean', locale: 'ko' }, - { name: 'Dutch', locale: 'nl' }, - { name: 'Polish', locale: 'pl' }, - { name: 'Portuguese (Brazil)', locale: 'pt-br' }, - { name: 'Russian', locale: 'ru' }, - { name: 'Swedish', locale: 'sv' }, - { name: 'Turkish', locale: 'tr' }, - { name: 'Chinese (Simplified)', locale: 'zh-cn' }, - { name: 'Chinese (Traditional)', locale: 'zh-tw' }, -]; +// TODO: use supportedLocales from locale.ts +export const supportedLocales = { + ar: 'العربية', + de: 'Deutsch', + en: 'English', + es: 'Español', + fr: 'Français', + he: 'עברית', + id: 'Bahasa Indonesia', + it: 'Italiano', + ja: '日本語', + ko: '한국어', + nl: 'Nederlands', + pl: 'Polski', + 'pt-br': 'Português (Brasil)', + ru: 'Русский', + sv: 'Svenska', + tr: 'Türkçe', + 'zh-cn': '简体中文', + 'zh-tw': '繁體中文', +}; export const LanguagePicker = () => { const [ locale, setLocale ] = useState( '' ); @@ -34,9 +34,9 @@ export const LanguagePicker = () => { onChange={ ( value ) => { setLocale( value ); } } - options={ namedLocales.map( ( { name, locale } ) => ( { + options={ Object.entries( supportedLocales ).map( ( [ locale, name ] ) => ( { value: locale, - label: name, + label: name as string, // Cast 'name' to string } ) ) } />
diff --git a/src/lib/locale.ts b/src/lib/locale.ts index 29139910..42a2aa33 100644 --- a/src/lib/locale.ts +++ b/src/lib/locale.ts @@ -5,47 +5,43 @@ import type { LocaleData } from '@wordpress/i18n'; export const DEFAULT_LOCALE = 'en'; -const supportedLocales = [ - 'ar', - 'de', - 'en', - 'es', - 'fr', - 'he', - 'id', - 'it', - 'ja', - 'ko', - 'nl', - 'pl', - 'pt-br', - 'ru', - 'sv', - 'tr', - 'zh-cn', - 'zh-tw', -]; +export const supportedLocales = { + ar: 'العربية', + de: 'Deutsch', + en: 'English', + es: 'Español', + fr: 'Français', + he: 'עברית', + id: 'Bahasa Indonesia', + it: 'Italiano', + ja: '日本語', + ko: '한국어', + nl: 'Nederlands', + pl: 'Polski', + 'pt-br': 'Português (Brasil)', + ru: 'Русский', + sv: 'Svenska', + tr: 'Türkçe', + 'zh-cn': '简体中文', + 'zh-tw': '繁體中文', +}; export function getPreferredSystemLanguages() { if ( process.platform === 'linux' && process.env.NODE_ENV !== 'test' ) { - // app.getPreferredSystemLanguages() is implemented by g_get_language_names on Linux. - // See: https://developer-old.gnome.org/glib/unstable/glib-I18N.html#g-get-language-names - // The language tags returned by this system function are in a format like "en_US" or "en_US.utf8". - // When these sorts of tags are passed to Intl.getCanonicalLocales() it throws an error. return app .getPreferredSystemLanguages() - .filter( ( lang ) => supportedLocales.includes( lang ) ); + .filter( ( lang ) => Object.keys( supportedLocales ).includes( lang ) ); } return app.getPreferredSystemLanguages(); } export function getSupportedLocale(): string { - return match( getPreferredSystemLanguages(), supportedLocales, DEFAULT_LOCALE ); + return match( getPreferredSystemLanguages(), Object.keys( supportedLocales ), DEFAULT_LOCALE ); } export function getLocaleData( locale: string ): LocaleData | null { - if ( locale === DEFAULT_LOCALE || ! supportedLocales.includes( locale ) ) { + if ( locale === DEFAULT_LOCALE || ! Object.keys( supportedLocales ).includes( locale ) ) { return null; } From 1201c48d0e6cd26951633a7f9760c6c4e0a3dc9c Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Tue, 7 May 2024 21:42:03 +1000 Subject: [PATCH 5/7] Use named and unnamed supportedLocales for testing --- src/lib/locale.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/lib/locale.ts b/src/lib/locale.ts index 39a8465a..eda44a89 100644 --- a/src/lib/locale.ts +++ b/src/lib/locale.ts @@ -5,7 +5,7 @@ import type { LocaleData } from '@wordpress/i18n'; export const DEFAULT_LOCALE = 'en'; -export const supportedLocales = { +export const namedLocales = { ar: 'العربية', de: 'Deutsch', en: 'English', @@ -26,6 +26,27 @@ export const supportedLocales = { 'zh-tw': '繁體中文', }; +const supportedLocales = [ + 'ar', + 'de', + 'en', + 'es', + 'fr', + 'he', + 'id', + 'it', + 'ja', + 'ko', + 'nl', + 'pl', + 'pt-br', + 'ru', + 'sv', + 'tr', + 'zh-cn', + 'zh-tw', +]; + export function getSupportedLocale(): string { // `app.getLocale` returns the current application locale, acquired using // Chromium's `l10n_util` library. This value is utilized to determine @@ -34,7 +55,7 @@ export function getSupportedLocale(): string { } export function getLocaleData( locale: string ): LocaleData | null { - if ( locale === DEFAULT_LOCALE || ! Object.keys( supportedLocales ).includes( locale ) ) { + if ( locale === DEFAULT_LOCALE || ! supportedLocales.includes( locale ) ) { return null; } @@ -45,4 +66,4 @@ export function getLocaleData( locale: string ): LocaleData | null { Sentry.captureException( err ); return null; } -} +} \ No newline at end of file From b7a8aca49e47dbaf5b10d2f51f411d2c2b73dac9 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Fri, 10 May 2024 17:17:58 +1000 Subject: [PATCH 6/7] Update linting --- src/lib/locale.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/locale.ts b/src/lib/locale.ts index eda44a89..41e1dc4c 100644 --- a/src/lib/locale.ts +++ b/src/lib/locale.ts @@ -66,4 +66,4 @@ export function getLocaleData( locale: string ): LocaleData | null { Sentry.captureException( err ); return null; } -} \ No newline at end of file +} From a1b9569fd4978076328ad603dbb348b175bb6a85 Mon Sep 17 00:00:00 2001 From: Derek Blank Date: Fri, 17 May 2024 09:48:58 +1000 Subject: [PATCH 7/7] Add saveUserData IpcApi handler --- src/components/language-picker.tsx | 15 +++++++++++---- src/ipc-handlers.ts | 8 ++++++++ src/preload.ts | 1 + src/storage/storage-types.ts | 1 + 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/components/language-picker.tsx b/src/components/language-picker.tsx index 6174094c..21beb2f8 100644 --- a/src/components/language-picker.tsx +++ b/src/components/language-picker.tsx @@ -1,5 +1,6 @@ import { SelectControl } from '@wordpress/components'; import { useState } from 'react'; +import { getIpcApi } from '../lib/get-ipc-api'; // TODO: use supportedLocales from locale.ts export const supportedLocales = { @@ -26,17 +27,23 @@ export const supportedLocales = { export const LanguagePicker = () => { const [ locale, setLocale ] = useState( '' ); + const handleLocaleChange = ( value: string ) => { + console.log( `Switching to locale: ${ value }` ); + setLocale( value ); + + // Save locale string to saveUserLocale + getIpcApi().saveUserLocale( value ); + }; + return (

Language

{ - setLocale( value ); - } } + onChange={ handleLocaleChange } options={ Object.entries( supportedLocales ).map( ( [ locale, name ] ) => ( { value: locale, - label: name as string, // Cast 'name' to string + label: name as string, } ) ) } />
diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index 916cdf34..340e4f6a 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -542,6 +542,14 @@ export async function saveOnboarding( } ); } +export async function saveUserLocale( _event: IpcMainInvokeEvent, userLocale: string ) { + const userData = await loadUserData(); + await saveUserData( { + ...userData, + userLocale, + } ); +} + export async function getThumbnailData( _event: IpcMainInvokeEvent, id: string ) { const path = getSiteThumbnailPath( id ); return getImageData( path ); diff --git a/src/preload.ts b/src/preload.ts index f1386683..90cb5f40 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -39,6 +39,7 @@ const api: IpcApi = { getOnboardingData: () => ipcRenderer.invoke( 'getOnboardingData' ), saveOnboarding: ( onboardingCompleted: boolean ) => ipcRenderer.invoke( 'saveOnboarding', onboardingCompleted ), + saveUserLocale: ( locale: string ) => ipcRenderer.invoke( 'saveUserLocale', locale ), openTerminalAtPath: ( targetPath: string ) => ipcRenderer.invoke( 'openTerminalAtPath', targetPath ), showMessageBox: ( options: Electron.MessageBoxOptions ) => diff --git a/src/storage/storage-types.ts b/src/storage/storage-types.ts index d6e4b80f..686221f0 100644 --- a/src/storage/storage-types.ts +++ b/src/storage/storage-types.ts @@ -6,6 +6,7 @@ export interface UserData { devToolsOpen?: boolean; authToken?: StoredToken; onboardingCompleted?: boolean; + userLocale?: string; lastBumpStats?: { [ group: string ]: { [ stat: string ]: number;