From bcf17bb34e53bc56d16e7b8a96158db1224c21dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20G=C5=82ownia?= Date: Wed, 29 Nov 2023 15:16:17 +0100 Subject: [PATCH] feat(trading): i18n language switcher (#5320) Co-authored-by: Matthew Russell --- .../trading/components/navbar/navbar.spec.tsx | 16 ++++++ apps/trading/components/navbar/navbar.tsx | 21 +++++++- apps/trading/lib/i18n/index.ts | 5 +- apps/trading/lib/use-t.ts | 1 + apps/trading/pages/_app.page.tsx | 1 - libs/i18n/src/locales/en/environment.json | 2 + .../icon/vega-icons/svg-icons/icon-globe.tsx | 21 +++++++- .../icon/vega-icons/svg-icons/icon-moon.tsx | 7 ++- .../icon/vega-icons/svg-icons/icon-sun.tsx | 17 +++++++ .../icon/vega-icons/vega-icon-record.ts | 3 ++ .../components/icon/vega-icons/vega-icon.tsx | 2 +- libs/ui-toolkit/src/components/index.ts | 1 + .../src/components/language-selector/index.ts | 1 + .../language-selector/language-selector.tsx | 50 +++++++++++++++++++ .../src/components/theme-switcher/icons.tsx | 27 ---------- .../src/components/theme-switcher/index.tsx | 1 - .../theme-switcher/theme-switcher.tsx | 12 +++-- 17 files changed, 145 insertions(+), 43 deletions(-) create mode 100644 libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-sun.tsx create mode 100644 libs/ui-toolkit/src/components/language-selector/index.ts create mode 100644 libs/ui-toolkit/src/components/language-selector/language-selector.tsx delete mode 100644 libs/ui-toolkit/src/components/theme-switcher/icons.tsx diff --git a/apps/trading/components/navbar/navbar.spec.tsx b/apps/trading/components/navbar/navbar.spec.tsx index 3f043ce7b..167d99dd5 100644 --- a/apps/trading/components/navbar/navbar.spec.tsx +++ b/apps/trading/components/navbar/navbar.spec.tsx @@ -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(); + }); }); diff --git a/apps/trading/components/navbar/navbar.tsx b/apps/trading/components/navbar/navbar.tsx index bcc5d0b36..86ad4a37a 100644 --- a/apps/trading/components/navbar/navbar.tsx +++ b/apps/trading/components/navbar/navbar.tsx @@ -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(null); @@ -77,6 +85,15 @@ export const Navbar = ({ {/* Right section */}
+
+ + {supportedLngs.length > 1 ? ( + i18n.changeLanguage(language)} + /> + ) : null} +
{ if (isConnected) { diff --git a/apps/trading/lib/i18n/index.ts b/apps/trading/lib/i18n/index.ts index f905a306e..b637151b9 100644 --- a/apps/trading/lib/i18n/index.ts +++ b/apps/trading/lib/i18n/index.ts @@ -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: [ diff --git a/apps/trading/lib/use-t.ts b/apps/trading/lib/use-t.ts index 4e9ef8ea0..b9fdf6ce0 100644 --- a/apps/trading/lib/use-t.ts +++ b/apps/trading/lib/use-t.ts @@ -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; diff --git a/apps/trading/pages/_app.page.tsx b/apps/trading/pages/_app.page.tsx index 054670549..e2273fc90 100644 --- a/apps/trading/pages/_app.page.tsx +++ b/apps/trading/pages/_app.page.tsx @@ -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 = () => { diff --git a/libs/i18n/src/locales/en/environment.json b/libs/i18n/src/locales/en/environment.json index 3f1528246..2b9573e5d 100644 --- a/libs/i18n/src/locales/en/environment.json +++ b/libs/i18n/src/locales/en/environment.json @@ -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", diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-globe.tsx b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-globe.tsx index d6fa7c953..de48bbeec 100644 --- a/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-globe.tsx +++ b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-globe.tsx @@ -1,7 +1,26 @@ export const IconGlobe = ({ size = 24 }: { size: number }) => { return ( - + + + + ); }; diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-moon.tsx b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-moon.tsx index 52e735a3f..15fa5680d 100644 --- a/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-moon.tsx +++ b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-moon.tsx @@ -1,9 +1,8 @@ export const IconMoon = ({ 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 + + ); }; diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-sun.tsx b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-sun.tsx new file mode 100644 index 000000000..4ecf5c5d6 --- /dev/null +++ b/libs/ui-toolkit/src/components/icon/vega-icons/svg-icons/icon-sun.tsx @@ -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 + + + + + ); +}; diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts b/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts index 03910bc34..df902fba9 100644 --- a/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts +++ b/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon-record.ts @@ -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, diff --git a/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon.tsx b/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon.tsx index 3a240d6ab..b673b9042 100644 --- a/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon.tsx +++ b/libs/ui-toolkit/src/components/icon/vega-icons/vega-icon.tsx @@ -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) => { diff --git a/libs/ui-toolkit/src/components/index.ts b/libs/ui-toolkit/src/components/index.ts index 96fe2b9b8..32c2c1847 100644 --- a/libs/ui-toolkit/src/components/index.ts +++ b/libs/ui-toolkit/src/components/index.ts @@ -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'; diff --git a/libs/ui-toolkit/src/components/language-selector/index.ts b/libs/ui-toolkit/src/components/language-selector/index.ts new file mode 100644 index 000000000..d38957b55 --- /dev/null +++ b/libs/ui-toolkit/src/components/language-selector/index.ts @@ -0,0 +1 @@ +export * from './language-selector'; diff --git a/libs/ui-toolkit/src/components/language-selector/language-selector.tsx b/libs/ui-toolkit/src/components/language-selector/language-selector.tsx new file mode 100644 index 000000000..841cfa810 --- /dev/null +++ b/libs/ui-toolkit/src/components/language-selector/language-selector.tsx @@ -0,0 +1,50 @@ +import { VegaIcon, VegaIconNames } from '../icon'; +import { + TradingDropdown, + TradingDropdownContent, + TradingDropdownItem, + TradingDropdownTrigger, +} from '../trading-dropdown'; + +const labels: Record = { + 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 ( + + + + } + > + + {languages.map((language) => { + return ( + onSelect(language)} + > + {labels[language] || language} + + ); + })} + + + ); +}; diff --git a/libs/ui-toolkit/src/components/theme-switcher/icons.tsx b/libs/ui-toolkit/src/components/theme-switcher/icons.tsx deleted file mode 100644 index 481b506aa..000000000 --- a/libs/ui-toolkit/src/components/theme-switcher/icons.tsx +++ /dev/null @@ -1,27 +0,0 @@ -type IconProps = { - className?: string; -}; - -export const SunIcon = ({ className }: IconProps) => ( - - - - -); - -export const MoonIcon = ({ className }: IconProps) => ( - - - -); diff --git a/libs/ui-toolkit/src/components/theme-switcher/index.tsx b/libs/ui-toolkit/src/components/theme-switcher/index.tsx index 36f7c3b32..92502cb07 100644 --- a/libs/ui-toolkit/src/components/theme-switcher/index.tsx +++ b/libs/ui-toolkit/src/components/theme-switcher/index.tsx @@ -1,2 +1 @@ export * from './theme-switcher'; -export * from './icons'; diff --git a/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx b/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx index b0dc093e6..27805294b 100644 --- a/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx +++ b/libs/ui-toolkit/src/components/theme-switcher/theme-switcher.tsx @@ -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 = ({ ); const toggles = [