Skip to content
52 changes: 43 additions & 9 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { StoreProvider } from '@/hooks/useStore';
import CallbackPage from '@/pages/callback';
import Endpoint from '@/pages/endpoint';
import { TAuthData } from '@/types/api-types';
import { initializeI18n, localize, TranslationProvider } from '@deriv-com/translations';
import { FILTERED_LANGUAGES } from '@/utils/languages';
import { initializeI18n, localize, TranslationProvider, useTranslations } from '@deriv-com/translations';
import CoreStoreProvider from './CoreStoreProvider';
import './app-root.scss';

Expand All @@ -22,6 +23,37 @@ const i18nInstance = initializeI18n({
cdnUrl: `${TRANSLATIONS_CDN_URL || 'https://translations.deriv.com'}/${R2_PROJECT_NAME}/${CROWDIN_BRANCH_NAME}`,
});

// Component to handle language URL parameter
const LanguageHandler = ({ children }: { children: React.ReactNode }) => {
const { switchLanguage } = useTranslations();

React.useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const langParam = urlParams.get('lang');

if (langParam) {
// Convert to uppercase to match our language codes
const langCode = langParam.toUpperCase() as (typeof FILTERED_LANGUAGES)[number]['code'];
// Use FILTERED_LANGUAGES instead of hard-coded array
const supportedLangCodes = FILTERED_LANGUAGES.map(lang => lang.code);

if (supportedLangCodes.includes(langCode)) {
try {
switchLanguage(langCode);
// Remove lang parameter after processing to avoid URL pollution
const url = new URL(window.location.href);
url.searchParams.delete('lang');
window.history.replaceState({}, '', url.toString());
} catch (error) {
console.error('Failed to switch language:', error);
}
}
}
}, [switchLanguage]);

return <>{children}</>;
};

const router = createBrowserRouter(
createRoutesFromElements(
<Route
Expand All @@ -31,14 +63,16 @@ const router = createBrowserRouter(
fallback={<ChunkLoader message={localize('Please wait while we connect to the server...')} />}
>
<TranslationProvider defaultLang='EN' i18nInstance={i18nInstance}>
<StoreProvider>
<LocalStorageSyncWrapper>
<RoutePromptDialog />
<CoreStoreProvider>
<Layout />
</CoreStoreProvider>
</LocalStorageSyncWrapper>
</StoreProvider>
<LanguageHandler>
<StoreProvider>
<LocalStorageSyncWrapper>
<RoutePromptDialog />
<CoreStoreProvider>
<Layout />
</CoreStoreProvider>
</LocalStorageSyncWrapper>
</StoreProvider>
</LanguageHandler>
</TranslationProvider>
</Suspense>
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/journal/journal-components/format-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ const FormatMessage = ({ logType, className, extra }: TFormatMessageProps) => {
return localize('Resale of this contract is not offered.');
}
case LogTypes.PURCHASE: {
const { longcode, transaction_id } = extra;
const { transaction_id } = extra;
return (
<Localize
i18n_default_text='<0>Bought</0>: {{longcode}} (ID: {{transaction_id}})'
values={{ longcode, transaction_id }}
i18n_default_text='<0>Bought</0>: Contract purchased (ID: {{transaction_id}})'
values={{ transaction_id }}
components={[<Text key={0} size='xxs' styles={{ color: 'var(--status-info)' }} />]}
options={{ interpolation: { escapeValue: false } }}
/>
Expand Down
7 changes: 5 additions & 2 deletions src/components/layout/footer/LanguageSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import Text from '@/components/shared_ui/text';
import { LANGUAGES } from '@/utils/languages';
import { FILTERED_LANGUAGES } from '@/utils/languages';
import { useTranslations } from '@deriv-com/translations';
import { Tooltip } from '@deriv-com/ui';

Expand All @@ -12,7 +12,7 @@ const LanguageSettings = ({ openLanguageSettingModal }: TLanguageSettings) => {
const { currentLang, localize } = useTranslations();

const countryIcon = useMemo(
() => LANGUAGES.find(({ code }: { code: string }) => code == currentLang)?.placeholderIcon,
() => FILTERED_LANGUAGES.find(({ code }: { code: string }) => code == currentLang)?.placeholderIcon,
[currentLang]
);

Expand All @@ -22,6 +22,9 @@ const LanguageSettings = ({ openLanguageSettingModal }: TLanguageSettings) => {
className='app-footer__language'
onClick={openLanguageSettingModal}
tooltipContent={localize('Language')}
aria-label={`${localize('Change language')} - ${localize('Current language')}: ${currentLang}`}
aria-expanded='false'
aria-haspopup='dialog'
>
{countryIcon}
<Text size='xs' weight='bold'>
Expand Down
43 changes: 25 additions & 18 deletions src/components/layout/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,55 @@
// import useModalManager from '@/hooks/useModalManager';
// import { getActiveTabUrl } from '@/utils/getActiveTabUrl';
// import { LANGUAGES } from '@/utils/languages';
// import { useTranslations } from '@deriv-com/translations';
// import { DesktopLanguagesModal } from '@deriv-com/ui';
import useModalManager from '@/hooks/useModalManager';
import { getActiveTabUrl } from '@/utils/getActiveTabUrl';
import { FILTERED_LANGUAGES } from '@/utils/languages';
import { useTranslations } from '@deriv-com/translations';
import { DesktopLanguagesModal } from '@deriv-com/ui';
import ChangeTheme from './ChangeTheme';
import Endpoint from './Endpoint';
import FullScreen from './FullScreen';
// import LanguageSettings from './LanguageSettings';
import LanguageSettings from './LanguageSettings';
import NetworkStatus from './NetworkStatus';
import ServerTime from './ServerTime';
import './footer.scss';

const Footer = () => {
// const { currentLang = 'EN', localize, switchLanguage } = useTranslations();
// const { hideModal, isModalOpenFor, showModal } = useModalManager();
const { currentLang = 'EN', localize, switchLanguage } = useTranslations();
const { hideModal, isModalOpenFor, showModal } = useModalManager();

// const openLanguageSettingModal = () => showModal('DesktopLanguagesModal');
const openLanguageSettingModal = () => showModal('DesktopLanguagesModal');
return (
<footer className='app-footer'>
<FullScreen />
{/* <LanguageSettings openLanguageSettingModal={openLanguageSettingModal} />
<div className='app-footer__vertical-line' /> */}
<LanguageSettings openLanguageSettingModal={openLanguageSettingModal} />
<div className='app-footer__vertical-line' />
<ChangeTheme />
<div className='app-footer__vertical-line' />
<ServerTime />
<div className='app-footer__vertical-line' />
<NetworkStatus />
<Endpoint />

{/* {isModalOpenFor('DesktopLanguagesModal') && (
{isModalOpenFor('DesktopLanguagesModal') && (
<DesktopLanguagesModal
headerTitle={localize('Select Language')}
isModalOpen
languages={LANGUAGES}
languages={FILTERED_LANGUAGES}
onClose={hideModal}
onLanguageSwitch={code => {
switchLanguage(code);
hideModal();
window.location.replace(getActiveTabUrl());
window.location.reload();
try {
switchLanguage(code);
hideModal();
// Page reload is necessary because Blockly is outside React lifecycle
// and won't re-render with new language without full page refresh
window.location.replace(getActiveTabUrl());
window.location.reload();
} catch (error) {
console.error('Failed to switch language:', error);
hideModal();
}
}}
selectedLanguage={currentLang}
/>
)} */}
)}
</footer>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/header/account-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import classNames from 'classnames';
import { observer } from 'mobx-react-lite';
import { getCurrencyDisplayCode } from '@/components/shared';
import Text from '@/components/shared_ui/text';
import { Localize, localize } from '@deriv-com/translations';
import { Localize } from '@deriv-com/translations';
import { useDevice } from '@deriv-com/ui';
import { TAccountSwitcher } from './common/types';
import AccountInfoIcon from './account-info-icon';
Expand Down Expand Up @@ -43,7 +43,7 @@ const AccountSwitcher = observer(({ activeAccount }: TAccountSwitcher) => {
<div className='acc-info__content'>
<div className='acc-info__account-type-header'>
<Text as='p' size='xxxs' className='acc-info__account-type'>
{isVirtual ? localize('Demo') : localize('Real')}
{isVirtual ? 'Demo' : 'Real'}
</Text>
</div>
{(typeof balance !== 'undefined' || !currency) && (
Expand Down
12 changes: 9 additions & 3 deletions src/components/layout/header/mobile-menu/menu-header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ComponentProps, useMemo } from 'react';
import { LANGUAGES } from '@/utils/languages';
import { FILTERED_LANGUAGES } from '@/utils/languages';
import { useTranslations } from '@deriv-com/translations';
import { Text, useDevice } from '@deriv-com/ui';

Expand All @@ -13,7 +13,7 @@ const MenuHeader = ({ hideLanguageSetting, openLanguageSetting }: TMenuHeader) =
const { isDesktop } = useDevice();

const countryIcon = useMemo(
() => LANGUAGES.find(({ code }) => code === currentLang)?.placeholderIconInMobile,
() => FILTERED_LANGUAGES.find(({ code }) => code === currentLang)?.placeholderIconInMobile,
[currentLang]
);

Expand All @@ -24,7 +24,13 @@ const MenuHeader = ({ hideLanguageSetting, openLanguageSetting }: TMenuHeader) =
</Text>

{!hideLanguageSetting && (
<button className='mobile-menu__header__language items-center' onClick={openLanguageSetting}>
<button
className='mobile-menu__header__language items-center'
onClick={openLanguageSetting}
aria-label={`${localize('Change language')} - ${localize('Current language')}: ${currentLang}`}
aria-expanded='false'
aria-haspopup='menu'
>
{countryIcon}
<Text className='ml-[0.4rem]' size={isDesktop ? 'xs' : 'sm'} weight='bold'>
{currentLang}
Expand Down
44 changes: 40 additions & 4 deletions src/components/layout/header/mobile-menu/mobile-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useState } from 'react';
import useModalManager from '@/hooks/useModalManager';
import { getActiveTabUrl } from '@/utils/getActiveTabUrl';
import { FILTERED_LANGUAGES } from '@/utils/languages';
import { useTranslations } from '@deriv-com/translations';
import { Drawer, useDevice } from '@deriv-com/ui';
import { Drawer, MobileLanguagesDrawer, useDevice } from '@deriv-com/ui';
import NetworkStatus from './../../footer/NetworkStatus';
import ServerTime from './../../footer/ServerTime';
import BackButton from './back-button';
Expand All @@ -17,7 +20,8 @@ type TMobileMenuProps = {
const MobileMenu = ({ onLogout }: TMobileMenuProps) => {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [activeSubmenu, setActiveSubmenu] = useState<string | null>(null);
const { localize } = useTranslations();
const { currentLang = 'EN', localize, switchLanguage } = useTranslations();
const { hideModal, isModalOpenFor, showModal } = useModalManager();
const { isDesktop } = useDevice();

const openDrawer = () => setIsDrawerOpen(true);
Expand All @@ -28,6 +32,8 @@ const MobileMenu = ({ onLogout }: TMobileMenuProps) => {

const openSubmenu = (submenu: string) => setActiveSubmenu(submenu);
const closeSubmenu = () => setActiveSubmenu(null);
const openLanguageSetting = () => showModal('MobileLanguagesDrawer');
const isLanguageSettingVisible = Boolean(isModalOpenFor('MobileLanguagesDrawer'));

if (isDesktop) return null;
return (
Expand All @@ -38,11 +44,41 @@ const MobileMenu = ({ onLogout }: TMobileMenuProps) => {

<Drawer isOpen={isDrawerOpen} onCloseDrawer={closeDrawer} width='29.5rem'>
<Drawer.Header onCloseDrawer={closeDrawer}>
<MenuHeader hideLanguageSetting={true} openLanguageSetting={() => {}} />
<MenuHeader
hideLanguageSetting={isLanguageSettingVisible}
openLanguageSetting={openLanguageSetting}
/>
</Drawer.Header>

<Drawer.Content>
{activeSubmenu === 'reports' ? (
{isLanguageSettingVisible ? (
<>
<div className='mobile-menu__back-btn'>
<BackButton buttonText={localize('Language')} onClick={hideModal} />
</div>

<MobileLanguagesDrawer
isOpen
languages={FILTERED_LANGUAGES}
onClose={hideModal}
onLanguageSwitch={code => {
try {
switchLanguage(code);
hideModal();
// Page reload is necessary because Blockly is outside React lifecycle
// and won't re-render with new language without full page refresh
window.location.replace(getActiveTabUrl());
window.location.reload();
} catch (error) {
console.error('Failed to switch language:', error);
hideModal();
}
}}
selectedLanguage={currentLang}
wrapperClassName='mobile-menu__language-drawer'
/>
</>
) : activeSubmenu === 'reports' ? (
<>
<div className='mobile-menu__back-btn'>
<BackButton buttonText={localize('Reports')} onClick={closeSubmenu} />
Expand Down
3 changes: 1 addition & 2 deletions src/components/layout/header/wallets/wallet-badge.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import Badge from '@/components/shared_ui/badge';
import { localize } from '@deriv-com/translations';

type TWalletBadge = {
is_demo: boolean;
Expand All @@ -9,7 +8,7 @@ type TWalletBadge = {

const WalletBadge = ({ is_demo, label }: TWalletBadge) => {
return is_demo ? (
<Badge type='contained' background_color='blue' label={localize('Demo')} custom_color='colored-background' />
<Badge type='contained' background_color='blue' label='Demo' custom_color='colored-background' />
) : (
<Badge type='bordered' label={label?.toUpperCase() ?? ''} />
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/shared_ui/autocomplete/autocomplete.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

&__trailing-icon {
position: absolute;
right: 0;
inset-inline-end: 0;
pointer-events: none;
margin-right: 1.1rem;
margin-inline-end: 1.1rem;
cursor: text;
transition: transform 0.2s ease;
transform: rotate(0deg);
Expand Down
10 changes: 5 additions & 5 deletions src/components/shared_ui/input/input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,10 @@
}

&__leading-icon {
margin-left: 1rem;
margin-inline-start: 1rem;
top: 1rem;
position: absolute;
left: 0;
inset-inline-start: 0;
pointer-events: none;
cursor: text;
font-size: var(--text-size-xs);
Expand All @@ -229,18 +229,18 @@

+ .dc-input__field {
// default padding for three letter currency symbols
padding-left: calc(1.6rem + 2.4rem);
padding-inline-start: calc(1.6rem + 2.4rem);
}

&--usdc + .dc-input__field,
&--ust + .dc-input__field {
padding-left: calc(1.6rem + 3.2rem);
padding-inline-start: calc(1.6rem + 3.2rem);
}
}
}

&__trailing-icon {
right: 0;
// right: 0;
font-size: var(--text-size-xs);

&.symbols {
Expand Down
4 changes: 1 addition & 3 deletions src/components/shared_ui/select-native/select-native.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@
}

&__arrow {
position: absolute;
right: 1.3rem;
fill: var(--text-general);

--fill-color1: var(--text-less-prominent);
Expand All @@ -63,7 +61,7 @@
display: flex;
align-items: center;
justify-content: flex-start;
padding-left: 1.2rem;
padding-inline-start: 1.2rem;

&-text {
color: var(--text-prominent);
Expand Down
Loading