feat(trading): i18n language switcher (#5320)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Bartłomiej Głownia 2023-11-29 15:16:17 +01:00 committed by GitHub
parent a2b9b0da05
commit bcf17bb34e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 145 additions and 43 deletions

View File

@ -172,4 +172,20 @@ describe('Navbar', () => {
expect(mockDisconnect).toHaveBeenCalled();
expect(screen.queryByTestId(navbarContent)).not.toBeInTheDocument();
});
it('does not render the language selector until we have more languages', () => {
renderComponent();
expect(screen.queryByTestId('icon-globe')).not.toBeInTheDocument();
});
it('renders the theme switcher', async () => {
renderComponent();
await userEvent.click(screen.getByTestId('icon-moon'));
expect(screen.queryByTestId('icon-moon')).not.toBeInTheDocument();
expect(screen.getByTestId('icon-sun')).toBeInTheDocument();
await userEvent.click(screen.getByTestId('icon-sun'));
expect(screen.queryByTestId('icon-sun')).not.toBeInTheDocument();
expect(screen.getByTestId('icon-moon')).toBeInTheDocument();
});
});

View File

@ -11,7 +11,13 @@ import {
} from '@vegaprotocol/environment';
import { useGlobalStore } from '../../stores';
import { VegaWalletConnectButton } from '../vega-wallet-connect-button';
import { VegaIconNames, VegaIcon, VLogo } from '@vegaprotocol/ui-toolkit';
import {
VegaIconNames,
VegaIcon,
VLogo,
LanguageSelector,
ThemeSwitcher,
} from '@vegaprotocol/ui-toolkit';
import * as N from '@radix-ui/react-navigation-menu';
import * as D from '@radix-ui/react-dialog';
import { NavLink } from 'react-router-dom';
@ -22,7 +28,8 @@ import { VegaWalletMenu } from '../vega-wallet';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { WalletIcon } from '../icons/wallet';
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
import { useT } from '../../lib/use-t';
import { useT, useI18n } from '../../lib/use-t';
import { supportedLngs } from '../../lib/i18n';
type MenuState = 'wallet' | 'nav' | null;
type Theme = 'system' | 'yellow';
@ -34,6 +41,7 @@ export const Navbar = ({
children?: ReactNode;
theme?: Theme;
}) => {
const i18n = useI18n();
const t = useT();
// menu state for small screens
const [menu, setMenu] = useState<MenuState>(null);
@ -77,6 +85,15 @@ export const Navbar = ({
{/* Right section */}
<div className="ml-auto flex items-center justify-end gap-2">
<ProtocolUpgradeCountdown />
<div className="flex">
<ThemeSwitcher />
{supportedLngs.length > 1 ? (
<LanguageSelector
languages={supportedLngs}
onSelect={(language) => i18n.changeLanguage(language)}
/>
) : null}
</div>
<NavbarMobileButton
onClick={() => {
if (isConnected) {

View File

@ -6,6 +6,8 @@ import type { HttpBackendOptions, RequestCallback } from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
export const supportedLngs = ['en'];
const isInDev = process.env.NODE_ENV === 'development';
const useLocize = isInDev && !!process.env.NX_USE_LOCIZE;
@ -51,9 +53,8 @@ i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
supportedLngs: ['en'],
supportedLngs,
load: 'languageOnly',
// have a common namespace used around the full app
ns: [

View File

@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
export const ns = 'trading';
export const useT = () => useTranslation('trading').t;
export const useI18n = () => useTranslation('trading').i18n;

View File

@ -32,7 +32,6 @@ import { SSRLoader } from './ssr-loader';
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
import { TransactionHandlers } from './transaction-handlers';
import '../lib/i18n';
import { useT } from '../lib/use-t';
const Title = () => {

View File

@ -8,6 +8,7 @@
"Check": "Check",
"Checking": "Checking",
"Connect to this node": "Connect to this node",
"Connected node": "Connected node",
"current": "current",
"Custom": "Custom",
"Devnet": "Devnet",
@ -33,6 +34,7 @@
"The mainnet-mirror network": "The mainnet-mirror network",
"The validator deployed testnet": "The validator deployed testnet",
"The vega mainnet": "The vega mainnet",
"This app will only work on {{VEGA_ENV}}. Select a node to connect to.": "This app will only work on {{VEGA_ENV}}. Select a node to connect to.",
"VALIDATOR_TESTNET": "VALIDATOR_TESTNET",
"View on Etherscan (opens in a new tab)": "View on Etherscan (opens in a new tab)",
"Warning delay ( >{{warningLatency}} sec): {{blockUpdateLatency}} sec": "Warning delay ( >{{warningLatency}} sec): {{blockUpdateLatency}} sec",

View File

@ -1,7 +1,26 @@
export const IconGlobe = ({ size = 24 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 24 24">
<path d="M11.9946 3C10.2165 3.00106 8.47842 3.52883 6.99987 4.51677C5.51983 5.5057 4.36628 6.91131 3.68509 8.55585C3.0039 10.2004 2.82567 12.01 3.17294 13.7558C3.5202 15.5016 4.37737 17.1053 5.63604 18.364C6.89471 19.6226 8.49836 20.4798 10.2442 20.8271C11.99 21.1743 13.7996 20.9961 15.4442 20.3149C17.0887 19.6337 18.4943 18.4802 19.4832 17.0001C20.4722 15.5201 21 13.78 21 12C21 9.61305 20.0518 7.32386 18.364 5.63604C16.6761 3.94821 14.3869 3 12 3C12.0001 3 11.9999 3 12 3M11.9959 3.936C11.9972 3.936 11.9985 3.936 11.9999 3.936C13.2976 3.93617 14.3772 5.83592 14.9515 8.22998H9.04712C9.62068 5.83664 10.6991 3.93971 11.9959 3.936ZM9.8073 4.24157C9.05208 5.16925 8.44481 6.56185 8.07534 8.22998H4.87438C5.24741 7.52551 5.72602 6.87397 6.3 6.3C7.28288 5.31711 8.49319 4.61388 9.8073 4.24157ZM4.42885 9.22998C4.10667 10.1091 3.93685 11.0458 3.936 12C3.936 12.9499 4.10378 13.8872 4.42669 14.77H7.88922C7.75324 13.8973 7.67969 12.9663 7.67969 12C7.67969 11.0336 7.7527 10.1027 7.8879 9.22998H4.42885ZM4.87153 15.77C5.00006 16.013 5.14133 16.2501 5.29503 16.4801C6.18112 17.8062 7.44054 18.8398 8.91404 19.4502C9.20977 19.5727 9.51146 19.677 9.81744 19.763C9.06048 18.8354 8.44956 17.4409 8.07765 15.77H4.87153ZM14.1834 19.7628C15.5101 19.3896 16.7227 18.6815 17.7021 17.7021C18.2744 17.1298 18.7541 16.4778 19.1285 15.77H15.9224C15.5508 17.4416 14.9402 18.8355 14.1834 19.7628ZM19.5733 14.77C19.7153 14.3819 19.8278 13.9819 19.9091 13.5732C20.1981 12.12 20.0808 10.6174 19.5733 9.22998H16.1106C16.2463 10.1024 16.3197 11.0333 16.3197 12C16.3197 12.9667 16.2463 13.8976 16.1106 14.77H19.5733ZM19.1285 8.22998C18.5047 7.05058 17.596 6.04063 16.4801 5.29503C15.7711 4.82129 14.9955 4.46564 14.1834 4.23723C14.9402 5.16453 15.5508 6.55844 15.9224 8.22998H19.1285ZM8.60129 12C8.60129 11.0806 8.68603 10.1352 8.84194 9.22998H15.1569C15.3132 10.1358 15.3981 11.0814 15.3981 12C15.3981 12.9186 15.314 13.8642 15.1588 14.77H8.84003C8.68519 13.8648 8.60129 12.9194 8.60129 12ZM11.9997 20.064C10.6916 20.064 9.61486 18.1657 9.04394 15.77H14.9547C14.3836 18.1642 13.3072 20.064 11.9997 20.064Z" />
<path
d="M12 1.248C14.1265 1.248 16.2053 1.87859 17.9735 3.06004C19.7417 4.24148 21.1198 5.92072 21.9336 7.88539C22.7474 9.85006 22.9603 12.0119 22.5454 14.0976C22.1305 16.1833 21.1065 18.0991 19.6028 19.6028C18.0991 21.1065 16.1833 22.1305 14.0976 22.5454C12.0119 22.9603 9.85006 22.7473 7.88539 21.9336C5.92072 21.1198 4.24149 19.7416 3.06004 17.9735C1.8786 16.2053 1.248 14.1265 1.248 12C1.25055 9.14917 2.38416 6.41584 4.4 4.39999C6.41584 2.38415 9.14918 1.25054 12 1.248ZM12 0C9.62663 0 7.30655 0.703786 5.33316 2.02236C3.35977 3.34094 1.8217 5.21508 0.91345 7.4078C0.00519871 9.60051 -0.23244 12.0133 0.230582 14.3411C0.693605 16.6689 1.83649 18.807 3.51472 20.4853C5.19295 22.1635 7.33115 23.3064 9.65892 23.7694C11.9867 24.2324 14.3995 23.9948 16.5922 23.0866C18.7849 22.1783 20.6591 20.6402 21.9776 18.6668C23.2962 16.6935 24 14.3734 24 12C24 8.8174 22.7357 5.76515 20.4853 3.51472C18.2349 1.26428 15.1826 0 12 0Z"
fill="currentColor"
/>
<path
d="M12 1.248C14.592 1.248 16.5312 6.9312 16.5312 12C16.5312 17.0688 14.6112 22.752 12 22.752C9.38879 22.752 7.46879 17.0784 7.46879 12C7.46879 6.9216 9.40799 1.248 12 1.248ZM12 0C8.81279 0 6.23999 5.376 6.23999 12C6.23999 18.624 8.83199 24 12 24C15.168 24 17.76 18.6336 17.76 12C17.76 5.3664 15.168 0 12 0Z"
fill="currentColor"
/>
<path
d="M1.229 7.64001H22.7714"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
/>
<path
d="M1.229 16.36H22.7714"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
/>
</svg>
);
};

View File

@ -1,9 +1,8 @@
export const IconMoon = ({ size = 16 }: { size: number }) => {
return (
<svg width={size} height={size} viewBox="0 0 16 16">
<path d="M8 1C4.13 1 1 4.13 1 8C1 11.87 4.13 15 8 15C11.87 15 15 11.87 15 8C15 4.13 11.87 1 8 1ZM8.66 12.44H7.32V11.1H8.66V12.44ZM10.38 6.78C10.29 7.01 10.18 7.2 10.05 7.36C9.92 7.52 9.75 7.7 9.53 7.91C9.3 8.14 9.11 8.34 8.98 8.51C8.85 8.68 8.73 8.89 8.64 9.14C8.55 9.37 8.51 9.65 8.51 9.96V10.04V10.13H7.3V10.04C7.3 9.61 7.36 9.24 7.47 8.92C7.58 8.61 7.71 8.34 7.87 8.13C8.03 7.92 8.22 7.69 8.47 7.43C8.75 7.13 8.96 6.87 9.09 6.66C9.22 6.46 9.28 6.2 9.28 5.88C9.28 5.47 9.16 5.16 8.93 4.93C8.7 4.7 8.38 4.58 7.96 4.58C7.6 4.58 7.28 4.68 7.01 4.89C6.75 5.09 6.56 5.44 6.45 5.96C6.34 6.48 6.43 6.06 6.43 6.06L5.26 5.62L5.28 5.54C5.47 4.87 5.81 4.35 6.29 4.02C6.77 3.69 7.34 3.53 8 3.53C8.75 3.53 9.37 3.75 9.82 4.18C10.28 4.62 10.5 5.22 10.5 5.97C10.5 6.27 10.46 6.53 10.37 6.76L10.38 6.78Z" />
<circle cx="8" cy="8" r="7" />
<path d="M6.15393 5.69232C6.15393 5.10304 6.24054 4.5075 6.46161 4C4.99179 4.63982 4 6.14089 4 7.84607C4 10.1402 5.85982 12 8.15393 12C9.85911 12 11.3602 11.0082 12 9.53839C11.4925 9.75946 10.8964 9.84607 10.3077 9.84607C8.01357 9.84607 6.15393 7.98643 6.15393 5.69232Z" />
// TODO: we need to rescale the icon in an svg editor so the view box is the default 0 0 16 16
<svg width={size} height={size} viewBox="0 0 45 45">
<path d="M28.75 11.69A12.39 12.39 0 0 0 22.5 10a12.5 12.5 0 1 0 0 25c2.196 0 4.353-.583 6.25-1.69A12.46 12.46 0 0 0 35 22.5a12.46 12.46 0 0 0-6.25-10.81Zm-6.25 22a11.21 11.21 0 0 1-11.2-11.2 11.21 11.21 0 0 1 11.2-11.2c1.246 0 2.484.209 3.66.62a13.861 13.861 0 0 0-5 10.58 13.861 13.861 0 0 0 5 10.58 11.078 11.078 0 0 1-3.66.63v-.01Z" />
</svg>
);
};

View File

@ -0,0 +1,17 @@
export const IconSun = ({ size = 16 }: { size: number }) => {
return (
// TODO: we need to rescale the icon in an svg editor so the view box is the default 0 0 16 16
<svg width={size} height={size} viewBox="0 0 45 45">
<path
d="M22.5 27.79a5.29 5.29 0 1 0 0-10.58 5.29 5.29 0 0 0 0 10.58Z"
fill="currentColor"
/>
<path
d="M15.01 22.5H10M35 22.5h-5.01M22.5 29.99V35M22.5 10v5.01M17.21 27.79l-3.55 3.55M31.34 13.66l-3.55 3.55M27.79 27.79l3.55 3.55M13.66 13.66l3.55 3.55"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
/>
</svg>
);
};

View File

@ -31,6 +31,7 @@ import { IconPlus } from './svg-icons/icon-plus';
import { IconQuestionMark } from './svg-icons/icon-question-mark';
import { IconSearch } from './svg-icons/icon-search';
import { IconStar } from './svg-icons/icon-star';
import { IconSun } from './svg-icons/icon-sun';
import { IconTick } from './svg-icons/icon-tick';
import { IconTicket } from './svg-icons/icon-ticket';
import { IconTransfer } from './svg-icons/icon-transfer';
@ -75,6 +76,7 @@ export enum VegaIconNames {
QUESTION_MARK = 'question-mark',
SEARCH = 'search',
STAR = 'star',
SUN = 'sun',
TICK = 'tick',
TICKET = 'ticket',
TRANSFER = 'transfer',
@ -125,6 +127,7 @@ export const VegaIconNameMap: Record<
plus: IconPlus,
search: IconSearch,
star: IconStar,
sun: IconSun,
tick: IconTick,
ticket: IconTicket,
transfer: IconTransfer,

View File

@ -5,7 +5,7 @@ import { VegaIconNameMap } from './vega-icon-record';
export interface VegaIconProps {
name: VegaIconNames;
size?: 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 24 | 32;
size?: 8 | 10 | 12 | 13 | 14 | 16 | 18 | 20 | 24 | 28 | 32;
}
export const VegaIcon = ({ size = 16, name }: VegaIconProps) => {

View File

@ -19,6 +19,7 @@ export * from './indicator';
export * from './input';
export * from './input-error';
export * from './key-value-table';
export * from './language-selector';
export * from './link';
export * from './loader';
export * from './lozenge';

View File

@ -0,0 +1 @@
export * from './language-selector';

View File

@ -0,0 +1,50 @@
import { VegaIcon, VegaIconNames } from '../icon';
import {
TradingDropdown,
TradingDropdownContent,
TradingDropdownItem,
TradingDropdownTrigger,
} from '../trading-dropdown';
const labels: Record<string, string> = {
en: 'English',
es: 'Español',
ru: 'Pусский',
ko: '한국인',
zh: '简体中文',
vi: 'Tiếng Việt',
};
export const LanguageSelector = ({
languages,
onSelect,
}: {
languages: readonly string[];
onSelect: (selectedLanguage: string) => void;
}) => {
return (
<TradingDropdown
trigger={
<TradingDropdownTrigger data-testid="language-selector-trigger">
<button className="flex justify-center items-center hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500 p-1 rounded-full w-7 h-7">
<VegaIcon name={VegaIconNames.GLOBE} size={16} />
</button>
</TradingDropdownTrigger>
}
>
<TradingDropdownContent>
{languages.map((language) => {
return (
<TradingDropdownItem
key={language}
data-testid={`language-selector-trigger-${language}`}
onSelect={() => onSelect(language)}
>
{labels[language] || language}
</TradingDropdownItem>
);
})}
</TradingDropdownContent>
</TradingDropdown>
);
};

View File

@ -1,27 +0,0 @@
type IconProps = {
className?: string;
};
export const SunIcon = ({ className }: IconProps) => (
<svg viewBox="0 0 45 45" className={className || 'w-8 h-8'}>
<path
d="M22.5 27.79a5.29 5.29 0 1 0 0-10.58 5.29 5.29 0 0 0 0 10.58Z"
fill="currentColor"
/>
<path
d="M15.01 22.5H10M35 22.5h-5.01M22.5 29.99V35M22.5 10v5.01M17.21 27.79l-3.55 3.55M31.34 13.66l-3.55 3.55M27.79 27.79l3.55 3.55M13.66 13.66l3.55 3.55"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
/>
</svg>
);
export const MoonIcon = ({ className }: IconProps) => (
<svg viewBox="0 0 45 45" className={className || 'w-8 h-8'}>
<path
d="M28.75 11.69A12.39 12.39 0 0 0 22.5 10a12.5 12.5 0 1 0 0 25c2.196 0 4.353-.583 6.25-1.69A12.46 12.46 0 0 0 35 22.5a12.46 12.46 0 0 0-6.25-10.81Zm-6.25 22a11.21 11.21 0 0 1-11.2-11.2 11.21 11.21 0 0 1 11.2-11.2c1.246 0 2.484.209 3.66.62a13.861 13.861 0 0 0-5 10.58 13.861 13.861 0 0 0 5 10.58 11.078 11.078 0 0 1-3.66.63v-.01Z"
fill="currentColor"
/>
</svg>
);

View File

@ -1,2 +1 @@
export * from './theme-switcher';
export * from './icons';

View File

@ -1,7 +1,8 @@
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { SunIcon, MoonIcon } from './icons';
import { Toggle } from '../toggle';
import { useT } from '../../use-t';
import classNames from 'classnames';
import { VegaIcon, VegaIconNames } from '../icon';
export const ThemeSwitcher = ({
className,
@ -16,12 +17,15 @@ export const ThemeSwitcher = ({
<button
type="button"
onClick={() => setTheme()}
className={className}
className={classNames(
'flex justify-center items-center hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500 rounded-full w-7 h-7',
className
)}
data-testid="theme-switcher"
id="theme-switcher"
>
{theme === 'dark' && <SunIcon />}
{theme === 'light' && <MoonIcon />}
{theme === 'dark' && <VegaIcon name={VegaIconNames.SUN} size={28} />}
{theme === 'light' && <VegaIcon name={VegaIconNames.MOON} size={28} />}
</button>
);
const toggles = [