Skip to content

Commit 9d9ec3a

Browse files
mchappellljagiela
andcommitted
Feat/add language selection es (#2016)
* feat: add initI18n to use selected language with fallback * feat: add analytics types for language selection * feat: add background services language change function * feat: add language selection in app * fix: remove hardcoded search placeholder * feat: add storage listener for language change * feat: add missing translation keys * fix: add missing bitcoin-mode translation keys * fix: use globalThis instead of window object * fix: add missing translations * fix: remove state flicker from translation preference loading * feat: add spanish switch * fix: add misspelt english translation for stake certificate * feat: add spanish translations * fix: missing translations --------- Co-authored-by: Lukasz Jagiela <[email protected]>
1 parent b7ced4f commit 9d9ec3a

File tree

56 files changed

+2614
-165
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2614
-165
lines changed

apps/browser-extension-wallet/src/components/MainLoader/MainLoader.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,17 @@ import { useTranslation } from 'react-i18next';
55
import styles from './MainLoader.module.scss';
66

77
export interface MainLoaderProps {
8-
text?: string;
98
overlay?: boolean;
109
}
1110

12-
export const MainLoader = ({ text, overlay = false }: MainLoaderProps): React.ReactElement => {
11+
export const MainLoader = ({ overlay = false }: MainLoaderProps): React.ReactElement => {
1312
const { t } = useTranslation();
1413

1514
return (
1615
<div className={classNames([styles.loaderContainer, { [styles.overlay]: overlay }])} data-testid="main-loader">
1716
<Loader className={styles.loader} data-testid="main-loader-image" />
1817
<p className={styles.loaderText} data-testid="main-loader-text">
19-
{text ?? t('general.loading')}
18+
{t('general.loading')}
2019
</p>
2120
</div>
2221
);

apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/DropdownMenuOverlay.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
Links,
99
LockWallet,
1010
NetworkChoise,
11+
LanguageChoice,
12+
LanguageInfo,
1113
RenameWalletDrawer,
1214
Separator,
1315
SettingsLink,
@@ -98,6 +100,11 @@ export const DropdownMenuOverlay: VFC<Props> = ({
98100
sendAnalyticsEvent(PostHogAction.UserWalletProfileNetworkClick);
99101
};
100102

103+
const handleLanguageChoice = () => {
104+
setCurrentSection(Sections.Language);
105+
sendAnalyticsEvent(PostHogAction.UserWalletProfileLanguageClick);
106+
};
107+
101108
const goBackToMainSection = useCallback(() => setCurrentSection(Sections.Main), []);
102109

103110
useEffect(() => {
@@ -209,6 +216,7 @@ export const DropdownMenuOverlay: VFC<Props> = ({
209216
/>
210217
</div>
211218
)}
219+
<LanguageChoice onClick={handleLanguageChoice} />
212220
<NetworkChoise onClick={handleNetworkChoise} />
213221
{lockWalletButton && (
214222
<>
@@ -219,6 +227,7 @@ export const DropdownMenuOverlay: VFC<Props> = ({
219227
</div>
220228
)}
221229
{currentSection === Sections.NetworkInfo && <NetworkInfo onBack={goBackToMainSection} />}
230+
{currentSection === Sections.Language && <LanguageInfo onBack={goBackToMainSection} />}
222231
{currentSection === Sections.WalletAccounts && <WalletAccounts onBack={goBackToMainSection} isPopup={isPopup} />}
223232
{isRenamingWallet && (
224233
<RenameWalletDrawer open={isRenamingWallet} popupView={isPopup} onClose={() => setIsRenamingWallet(false)} />
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useTranslation } from 'react-i18next';
3+
import cn from 'classnames';
4+
import styles from '../DropdownMenuOverlay.module.scss';
5+
import { useBackgroundServiceAPIContext } from '@providers';
6+
import { storage as webStorage } from 'webextension-polyfill';
7+
8+
type LanguageChoiceProps = {
9+
onClick: () => void;
10+
};
11+
12+
export const LanguageChoice = ({ onClick }: LanguageChoiceProps): React.ReactElement => {
13+
const { t } = useTranslation();
14+
const { getBackgroundStorage } = useBackgroundServiceAPIContext();
15+
const [language, setLanguage] = useState<string>('en');
16+
const [loadingLanguage, setLoadingLanguage] = useState(true);
17+
18+
useEffect(() => {
19+
const getLanguage = async () => {
20+
const { languageChoice } = await getBackgroundStorage();
21+
if (languageChoice) setLanguage(languageChoice);
22+
setLoadingLanguage(false);
23+
};
24+
getLanguage();
25+
webStorage.onChanged.addListener(getLanguage);
26+
}, [getBackgroundStorage, setLoadingLanguage]);
27+
28+
return (
29+
<div
30+
data-testid="header-menu-language-choice-container"
31+
className={cn(styles.menuItem, styles.cta)}
32+
onClick={() => onClick()}
33+
>
34+
<div className={styles.networkChoise}>
35+
<span data-testid="header-menu-language-choice-label">{t('browserView.topNavigationBar.links.language')}</span>
36+
{!loadingLanguage && (
37+
<span data-testid="header-menu-language-choice-value" className={styles.value}>
38+
{language || 'en'}
39+
</span>
40+
)}
41+
</div>
42+
</div>
43+
);
44+
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/* eslint-disable unicorn/no-null */
2+
import React, { useCallback, useEffect, useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
import { NavigationButton, PostHogAction } from '@lace/common';
5+
import styles from './NetworkInfo.module.scss';
6+
import { useBackgroundServiceAPIContext } from '@providers';
7+
import { Radio, RadioChangeEvent } from 'antd';
8+
import { usePostHogClientContext } from '@providers/PostHogClientProvider';
9+
import { storage as webStorage } from 'webextension-polyfill';
10+
import type { Language } from '@lace/translation';
11+
12+
type LanguageChoiceProps = {
13+
onBack: () => void;
14+
};
15+
16+
const LANG_CHOICES = ['en', 'es']; // hardcoding for v1 only
17+
18+
export const LanguageInfo = ({ onBack }: LanguageChoiceProps): React.ReactElement => {
19+
const { t } = useTranslation();
20+
const [language, setLanguage] = useState<Language | null>(null);
21+
const posthog = usePostHogClientContext();
22+
23+
const { getBackgroundStorage, handleChangeLanguage } = useBackgroundServiceAPIContext();
24+
25+
useEffect(() => {
26+
const getLanguage = async () => {
27+
const { languageChoice } = await getBackgroundStorage();
28+
if (languageChoice) setLanguage(languageChoice);
29+
};
30+
webStorage.onChanged.addListener(getLanguage);
31+
getLanguage();
32+
}, [getBackgroundStorage]);
33+
34+
const handleLanguageChangeRequest = useCallback(
35+
(e: RadioChangeEvent) => {
36+
handleChangeLanguage(e.target.value);
37+
posthog.sendEvent(PostHogAction.UserWalletProfileLanguageSelectClick, { language: e.target.value });
38+
},
39+
[handleChangeLanguage, posthog]
40+
);
41+
42+
return (
43+
<div data-testid="user-dropdown-language-info-section" className={styles.container}>
44+
<div className={styles.navigation} data-testid="drawer-navigation">
45+
<NavigationButton iconClassName={styles.iconClassName} icon="arrow" onClick={onBack} />
46+
</div>
47+
<div className={styles.titleSection}>
48+
<div data-testid="user-dropdown-language-title" className={styles.title}>
49+
{t('browserView.settings.wallet.language.title')}
50+
</div>
51+
<div data-testid="user-dropdown-language-description" className={styles.subTitle}>
52+
{t('browserView.settings.wallet.language.drawerDescription')}
53+
</div>
54+
</div>
55+
<div className={styles.content} data-testid="user-dropdown-language-choice">
56+
<Radio.Group
57+
className={styles.radioGroup}
58+
onChange={handleLanguageChangeRequest}
59+
value={language}
60+
data-testid={'language-choice-radio-group'}
61+
>
62+
{LANG_CHOICES.map((choice) => (
63+
<Radio key={`language-choice-${choice}`} value={choice} className={styles.radioLabel}>
64+
{choice}
65+
</Radio>
66+
))}
67+
</Radio.Group>
68+
</div>
69+
</div>
70+
);
71+
};

apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/NetworkInfo.module.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,10 @@
3535
font-size: 14px !important;
3636
}
3737
}
38+
39+
.radioGroup {
40+
color: var(--text-color-primary) !important;
41+
label {
42+
color: var(--text-color-primary) !important;
43+
}
44+
}

apps/browser-extension-wallet/src/components/MainMenu/DropdownMenuOverlay/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ export * from './NetworkInfo';
1212
export * from './AddNewWalletLink';
1313
export * from './AddNewBitcoinWalletLink';
1414
export * from './RenameWalletDrawer';
15+
export * from './LanguageInfo';
16+
export * from './LanguageChoice';
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export enum Sections {
22
Main = 'main',
33
NetworkInfo = 'network_info',
4-
WalletAccounts = 'wallet_accounts'
4+
WalletAccounts = 'wallet_accounts',
5+
Language = 'language'
56
}

apps/browser-extension-wallet/src/hooks/useAppInit.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { setBackgroundStorage } from '@lib/scripts/background/storage';
99
import { useCustomSubmitApi } from '@hooks/useCustomSubmitApi';
1010
import { bitcoinWalletManager } from '@lib/wallet-api-ui';
1111
import { useCurrentBlockchain } from '@src/multichain';
12+
import { useBackgroundServiceAPIContext } from '@providers';
13+
import { initI18n } from '@lace/translation';
14+
import { Message, MessageTypes } from '@lib/scripts/types';
1215

1316
export const useAppInit = (): void => {
1417
const {
@@ -26,6 +29,28 @@ export const useAppInit = (): void => {
2629
const walletState = useWalletState();
2730
const { environmentName, currentChain } = useWalletStore();
2831
const { getCustomSubmitApiForNetwork } = useCustomSubmitApi();
32+
const backgroundServices = useBackgroundServiceAPIContext();
33+
34+
useEffect(() => {
35+
const subscription = backgroundServices.requestMessage$?.subscribe(({ type, data }: Message): void => {
36+
if (type === MessageTypes.CHANGE_LANGUAGE) {
37+
initI18n(data);
38+
backgroundServices.setBackgroundStorage({ languageChoice: data });
39+
}
40+
});
41+
42+
backgroundServices
43+
.getBackgroundStorage()
44+
.then((bs) => {
45+
initI18n(bs.languageChoice ?? globalThis.navigator.language ?? 'en');
46+
})
47+
.catch((error) => {
48+
// eslint-disable-next-line no-console
49+
console.log(error);
50+
});
51+
52+
return () => subscription.unsubscribe();
53+
}, [backgroundServices]);
2954

3055
useEffect(() => {
3156
setWalletState(walletState);

apps/browser-extension-wallet/src/lib/scripts/background/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const backgroundServiceProperties: RemoteApiProperties<BackgroundService>
2323
handleOpenNamiBrowser: RemoteApiPropertyType.MethodReturningPromise,
2424
closeAllTabsAndOpenPopup: RemoteApiPropertyType.MethodReturningPromise,
2525
handleChangeTheme: RemoteApiPropertyType.MethodReturningPromise,
26+
handleChangeLanguage: RemoteApiPropertyType.MethodReturningPromise,
2627
handleChangeMode: RemoteApiPropertyType.MethodReturningPromise,
2728
clearBackgroundStorage: RemoteApiPropertyType.MethodReturningPromise,
2829
getBackgroundStorage: RemoteApiPropertyType.MethodReturningPromise,

apps/browser-extension-wallet/src/lib/scripts/background/services/utilityServices.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { POPUP_WINDOW_NAMI_TITLE } from '@utils/constants';
4242
import { catchAndBrandExtensionApiError } from '@utils/catch-and-brand-extension-api-error';
4343
import { initCardanoTokenPrices } from './cardanoTokenPrices';
4444
import { pollController$ } from '../session/poll-controller';
45+
import { Language } from '@lace/translation';
4546

4647
export const requestMessage$ = new Subject<Message>();
4748
export const backendFailures$ = new BehaviorSubject(0);
@@ -181,6 +182,8 @@ const closeAllTabsAndOpenPopup = async () => {
181182

182183
const handleChangeTheme = (data: ChangeThemeData) => requestMessage$.next({ type: MessageTypes.CHANGE_THEME, data });
183184

185+
const handleChangeLanguage = (data: Language) => requestMessage$.next({ type: MessageTypes.CHANGE_LANGUAGE, data });
186+
184187
const handleChangeMode = (data: ChangeModeData) => requestMessage$.next({ type: MessageTypes.CHANGE_MODE, data });
185188

186189
const { ADA_PRICE_CHECK_INTERVAL, SAVED_PRICE_DURATION } = config();
@@ -352,6 +355,7 @@ export const exposeBackgroundService = (wallet$: Observable<ActiveWallet>): void
352355
migrationState$,
353356
coinPrices,
354357
handleChangeTheme,
358+
handleChangeLanguage,
355359
handleChangeMode,
356360
clearBackgroundStorage,
357361
getBackgroundStorage,

0 commit comments

Comments
 (0)