Skip to content

Commit

Permalink
fix: i18n tree shaking (#1646)
Browse files Browse the repository at this point in the history
* fix: type import

* chore: refactor I18nContext usage

* Revert "chore: refactor I18nContext usage"

This reverts commit b9f4536.

* chore: refactor useTranslation hook

* feat: locale tree shaking

* fix: i18nprovider

* fix: default import en-US and en

* revert: i18n.t style changes

* revert: I18nProvider, useTranslation

* fix: i18n test files

* fix: typo

* fix: missing locale bug & small cleanups

* fix: type import

* chore: refactor I18nContext usage

* Revert "chore: refactor I18nContext usage"

This reverts commit b9f4536.

* chore: refactor useTranslation hook

* feat: locale tree shaking

* fix: i18nprovider

* fix: default import en-US and en

* revert: i18n.t style changes

* revert: I18nProvider, useTranslation

* revert: misc changes

* fix: i18n test files

* fix: typo

* fix: missing locale bug & small cleanups

* fix: create custom json loader for vitest

* fix: waitFor instead of fake timers

* fix: style tweaks

* chore: changeset

---------

Co-authored-by: Magomed Khamidov <53529533+KosmosKey@users.noreply.github.com>
  • Loading branch information
DanielSinclair and magiziz committed Dec 21, 2023
1 parent 34572bb commit 7ba94f4
Show file tree
Hide file tree
Showing 19 changed files with 171 additions and 108 deletions.
5 changes: 5 additions & 0 deletions .changeset/popular-actors-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rainbow-me/rainbowkit": patch
---

Optimized bundle size for localization feature
1 change: 1 addition & 0 deletions packages/rainbowkit/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const baseBuildConfig = {
loader: {
'.png': 'dataurl',
'.svg': 'dataurl',
'.json': 'text',
},
platform: 'browser',
plugins: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function ChainModal({ onClose, open }: ChainModalProps) {
},
});

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

const { disconnect } = useDisconnect();
const titleId = 'rk_chain_modal_title';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { screen, waitFor } from '@testing-library/react';
import React from 'react';
import { describe, expect, it } from 'vitest';
import { mainnet } from 'wagmi/chains';
Expand All @@ -8,37 +9,36 @@ import { ConnectButton } from './ConnectButton';
describe('<ConnectButton />', () => {
const renderTextButton = (locale?: Locale) => {
const options = {
mock: true,
props: {
chains: [mainnet],
...(locale ? { locale } : {}),
},
};

const { getByTestId } = renderWithProviders(<ConnectButton />, options);
renderWithProviders(<ConnectButton />, options);

const button = getByTestId('rk-connect-button');

return button.textContent;
return screen.getByTestId('rk-connect-button');
};

it('Defaults to English without a `locale` prop', () => {
const text = renderTextButton();
expect(text).toBe('Connect Wallet');
it('Defaults to English without a `locale` prop', async () => {
const button = renderTextButton();
await waitFor(() => expect(button.textContent).toBe('Connect Wallet'));
});

it("Displays in English for 'en-US'", () => {
const text = renderTextButton('en-US');
expect(text).toBe('Connect Wallet');
it("Displays in English for 'en-US'", async () => {
const button = renderTextButton('en-US');
await waitFor(() => expect(button.textContent).toBe('Connect Wallet'));
});

it("Displays in Spanish for 'es-419'", () => {
const text = renderTextButton('es-419');
expect(text).toBe('Conectar la billetera');
it("Displays in Spanish for 'es-419'", async () => {
const button = renderTextButton('es-419');
await waitFor(() =>
expect(button.textContent).toBe('Conectar la billetera'),
);
});

it("Displays in Russian for 'ru-RU'", () => {
const text = renderTextButton('ru-RU');
expect(text).toBe('Подключить кошелек');
it("Displays in Russian for 'ru-RU'", async () => {
const button = renderTextButton('ru-RU');
await waitFor(() => expect(button.textContent).toBe('Подключить кошелек'));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function ConnectButton({
const chains = useRainbowKitChains();
const connectionStatus = useConnectionStatus();

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<ConnectButtonRenderer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { screen, waitFor } from '@testing-library/react';
import React from 'react';
import { describe, expect, it } from 'vitest';
import { mainnet } from 'wagmi/chains';
Expand All @@ -6,42 +7,41 @@ import { Locale } from '../../locales';
import { ConnectModal } from './ConnectModal';

describe('<ConnectModal />', () => {
const renderHeaderLabelModal = (locale?: Locale) => {
const renderHeaderLabelModal = async (locale?: Locale) => {
const options = {
mock: true,
props: {
chains: [mainnet],
...(locale ? { locale } : {}),
},
};

const { getByTestId } = renderWithProviders(
renderWithProviders(
<ConnectModal onClose={() => {}} open={true} />,
options,
);

const modal = getByTestId('rk-connect-header-label');

return modal.textContent;
return screen.getByTestId('rk-connect-header-label');
};

it('Defaults to English without a `locale` prop', () => {
const label = renderHeaderLabelModal();
expect(label).toBe('Connect a Wallet');
it('Defaults to English without a `locale` prop', async () => {
const modal = await renderHeaderLabelModal();
await waitFor(() => expect(modal.textContent).toBe('Connect a Wallet'));
});

it("Displays in English for 'en-US'", () => {
const label = renderHeaderLabelModal('en-US');
expect(label).toBe('Connect a Wallet');
it("Displays in English for 'en-US'", async () => {
const modal = await renderHeaderLabelModal('en-US');
await waitFor(() => expect(modal.textContent).toBe('Connect a Wallet'));
});

it("Displays in Spanish for 'es-419'", () => {
const label = renderHeaderLabelModal('es-419');
expect(label).toBe('Conectar una billetera');
it("Displays in Spanish for 'es-419'", async () => {
const modal = await renderHeaderLabelModal('es-419');
await waitFor(() =>
expect(modal.textContent).toBe('Conectar una billetera'),
);
});

it("Displays in Russian for 'ru-RU'", () => {
const label = renderHeaderLabelModal('ru-RU');
expect(label).toBe('Подключить кошелек');
it("Displays in Russian for 'ru-RU'", async () => {
const modal = await renderHeaderLabelModal('ru-RU');
await waitFor(() => expect(modal.textContent).toBe('Подключить кошелек'));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function ConnectModalIntro({
getWallet: () => void;
}) {
const { disclaimer: Disclaimer, learnMoreUrl } = useContext(AppContext);
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function GetDetail({
const wallets = useWalletConnectors();
const shownWallets = wallets.splice(0, 5);

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<Box
Expand Down Expand Up @@ -214,7 +214,7 @@ export function ConnectDetail({
const getDesktopDeepLink = wallet.desktop?.getUri;
const safari = isSafari();

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

const hasExtension = !!wallet.extensionDownloadUrl;
const hasQrCodeAndExtension = downloadUrls?.qrCode && hasExtension;
Expand Down Expand Up @@ -625,7 +625,7 @@ export function DownloadOptionsDetail({
mobileDownloadUrl,
} = wallet;

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

useEffect(() => {
// Preload icons used on next screen
Expand Down Expand Up @@ -732,7 +732,7 @@ export function DownloadDetail({
}) {
const { downloadUrls, qrCode } = wallet;

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

useEffect(() => {
// Preload icons used on next screen
Expand Down Expand Up @@ -813,7 +813,7 @@ export function InstructionMobileDetail({
connectWallet: (wallet: WalletConnector) => void;
wallet: WalletConnector;
}) {
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<Box
Expand Down Expand Up @@ -900,7 +900,7 @@ export function InstructionExtensionDetail({
}: {
wallet: WalletConnector;
}) {
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<Box
Expand Down Expand Up @@ -989,7 +989,7 @@ export function InstructionDesktopDetail({
connectWallet: (wallet: WalletConnector) => void;
wallet: WalletConnector;
}) {
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<Box
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function DesktopOptions({ onClose }: { onClose: () => void }) {
const modalSize = useContext(ModalSizeContext);
const compactModeEnabled = modalSize === ModalSizeOptions.COMPACT;
const { disclaimer: Disclaimer } = useContext(AppContext);
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

const initialized = useRef(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function WalletButton({
const coolModeRef = useCoolMode(iconUrl);
const initialized = useRef(false);

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

const onConnect = useCallback(async () => {
if (id === 'walletConnect') onClose?.();
Expand Down Expand Up @@ -226,7 +226,7 @@ export function MobileOptions({ onClose }: { onClose: () => void }) {
MobileWalletStep.Connect,
);

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

const ios = isIOS();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { WalletButton } from './MobileOptions';

export const MobileStatus = ({ onClose }: { onClose: () => void }) => {
const { connector } = useContext(WalletButtonContext);
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);
const connectorName = connector?.name || '';

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const ModalSelection = ({
const coolModeRef = useCoolMode(iconUrl);
const [isMouseOver, setIsMouseOver] = useState<boolean>(false);

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<Box
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function ProfileDetails({
const showRecentTransactions = useContext(ShowRecentTransactionsContext);
const [copiedAddress, setCopiedAddress] = useState(false);

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

const copyAddressAction = useCallback(() => {
if (address) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,53 @@
import React, { ReactNode, createContext, useMemo } from 'react';
import React, {
ReactNode,
createContext,
useEffect,
useMemo,
useState,
} from 'react';

import { Locale, i18n as _i18n } from '../../locales';
import { Locale, i18n, setLocale } from '../../locales';
import { detectedBrowserLocale } from '../../utils/locale';

export const I18nContext = createContext<typeof _i18n>(_i18n);
export const I18nContext = createContext<{ i18n: typeof i18n }>({ i18n });

interface I18nProviderProps {
children: ReactNode;
locale?: Locale;
}

export const I18nProvider = ({ children, locale }: I18nProviderProps) => {
const [updateCount, setUpdateCount] = useState(0);

const browserLocale: Locale = useMemo(
() => detectedBrowserLocale() as Locale,
[],
);

const i18n = useMemo(() => {
if (locale) {
_i18n.locale = locale;
} else if (!locale && browserLocale) {
_i18n.locale = browserLocale;
}
useEffect(() => {
const unsubscribe = i18n.onChange(() => {
setUpdateCount((count) => count + 1);
});
return unsubscribe;
}, []);

return _i18n;
useEffect(() => {
if (locale && locale !== i18n.locale) {
setLocale(locale);
} else if (!locale && browserLocale && browserLocale !== i18n.locale) {
setLocale(browserLocale);
}
}, [locale, browserLocale]);

return <I18nContext.Provider value={i18n}>{children}</I18nContext.Provider>;
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
const memoizedValue = useMemo(() => {
const t = (key: string, options?: any) => i18n.t(key, options);
return { t, i18n };
}, [updateCount]);

return (
<I18nContext.Provider value={memoizedValue}>
{children}
</I18nContext.Provider>
);
};
4 changes: 2 additions & 2 deletions packages/rainbowkit/src/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { Text } from '../Text/Text';
export const signInIcon = async () => (await import('./sign.png')).default;

export function SignIn({ onClose }: { onClose: () => void }) {
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);
const [{ status, ...state }, setState] = React.useState<{
status: 'idle' | 'signing' | 'verifying';
errorMessage?: string;
Expand All @@ -35,7 +35,7 @@ export function SignIn({ onClose }: { onClose: () => void }) {
status: 'idle',
}));
}
}, [authAdapter, i18n]);
}, [authAdapter, i18n.t]);

// Pre-fetch nonce when screen is rendered
// to ensure deep linking works for WalletConnect
Expand Down
3 changes: 1 addition & 2 deletions packages/rainbowkit/src/components/Txs/TxList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { isMobile } from '../../utils/isMobile';
import { Box } from '../Box/Box';
import { ExternalLinkIcon } from '../Icons/ExternalLink';
import { AppContext } from '../RainbowKitProvider/AppContext';

import { I18nContext } from '../RainbowKitProvider/I18nContext';
import { Text } from '../Text/Text';
import { TxItem } from './TxItem';
Expand All @@ -29,7 +28,7 @@ export function TxList({ address }: TxListProps) {
const mobile = isMobile();
const { appName } = useContext(AppContext);

const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const WalletButton = ({ wallet }: { wallet?: string }) => {
<WalletButtonRenderer wallet={wallet}>
{({ ready, connect, connected, mounted, connector, loading }) => {
const isDisabled = !ready || loading;
const i18n = useContext(I18nContext);
const { i18n } = useContext(I18nContext);
const connectorName = connector?.name || '';

// SSR mismatch issue in next.js:
Expand Down

2 comments on commit 7ba94f4

@vercel
Copy link

@vercel vercel bot commented on 7ba94f4 Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 7ba94f4 Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.