Skip to content

Commit 1cc26e4

Browse files
authored
[RN] Enable localization for the RN SDK (#1799)
1 parent 99d7ec0 commit 1cc26e4

34 files changed

+387
-203
lines changed

.changeset/silver-donuts-pretend.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"@thirdweb-dev/react-native": patch
3+
---
4+
5+
Adds localization for the React Native SDK
6+
7+
You can now pass a Locale object to the ThirdwebProvider with your translations:
8+
9+
```javascript
10+
import { ThirdwebProvicer, en } from @thirdweb-dev/react-native;
11+
12+
<ThirdwebProvider locale={{
13+
...your-translated-strings
14+
}}>
15+
<App />
16+
</ThirdwebProvider>
17+
```

packages/react-native/.eslintrc.cjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
module.exports = {
22
root: true,
33
extends: ["thirdweb"],
4+
plugins: ["i18next"],
5+
rules: {
6+
"i18next/no-literal-string": 2,
7+
},
48
settings: {
59
// https://github.com/facebook/react-native/issues/28549
610
"import/ignore": ["react-native"],

packages/react-native/package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"eslint": "^8.45.0",
6767
"eslint-config-prettier": "^8.9.0",
6868
"eslint-config-thirdweb": "workspace:*",
69+
"eslint-plugin-i18next": "^6.0.3",
6970
"eslint-plugin-import": "^2.26.0",
7071
"eslint-plugin-inclusive-language": "^2.2.0",
7172
"eslint-plugin-prettier": "^5.0.0",
@@ -85,15 +86,15 @@
8586
"typescript": "^5.1.6"
8687
},
8788
"optionalDependencies": {
89+
"amazon-cognito-identity-js": "^6.3.3",
90+
"react-native-aes-gcm-crypto": "^0.2.2",
8891
"react-native-camera": "^4.2.1",
8992
"react-native-device-info": "10.6.0",
90-
"react-native-safe-area-context": "4.5.3",
91-
"react-native-webview": "12.1.0",
92-
"amazon-cognito-identity-js": "^6.3.3",
93+
"react-native-inappbrowser-reborn": "^3.7.0",
9394
"react-native-quick-base64": "^2.0.7",
9495
"react-native-quick-crypto": "^0.6.1",
95-
"react-native-aes-gcm-crypto": "^0.2.2",
96-
"react-native-inappbrowser-reborn": "^3.7.0"
96+
"react-native-safe-area-context": "4.5.3",
97+
"react-native-webview": "12.1.0"
9798
},
9899
"peerDependencies": {
99100
"ethers": ">=5.5.1 <6",

packages/react-native/src/evm/components/ConnectWallet.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { ConnectWalletButton } from "./ConnectWalletFlow/ConnectWalletButton";
1414
import { ConnectWalletButtonProps } from "./ConnectWalletFlow/ConnectWalletButton";
1515
import BaseButton from "./base/BaseButton";
1616
import Text from "./base/Text";
17-
import { useUIContext } from "../providers/ui-context-provider";
17+
import { useLocale, useUIContext } from "../providers/ui-context-provider";
1818
import { ThemeProvider } from "../styles/ThemeProvider";
1919
import { SupportedTokens, defaultTokens } from "./SendFunds/defaultTokens";
2020

@@ -81,6 +81,7 @@ export const ConnectWallet = ({
8181
privacyPolicyUrl,
8282
supportedTokens,
8383
}: ConnectWalletProps) => {
84+
const l = useLocale();
8485
const fadeAnim = useRef(new Animated.Value(0)).current;
8586
const address = useAddress();
8687
const isNetworkMismatch = useNetworkMismatch();
@@ -142,7 +143,7 @@ export const ConnectWallet = ({
142143
<ActivityIndicator size="small" color="buttonTextColor" />
143144
) : (
144145
<Text variant="bodyLarge" color="buttonTextColor">
145-
Switch Network
146+
{l.common.switch_network}
146147
</Text>
147148
)}
148149
</BaseButton>

packages/react-native/src/evm/components/ConnectWalletDetails/ConnectWalletDetailsModal.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { SendButton } from "../SendFunds/SendButton";
2929
import { SupportedTokens } from "../SendFunds/defaultTokens";
3030
import { ActiveDot } from "../base";
3131
import { EmbeddedWallet } from "../../wallets/wallets/embedded/EmbeddedWallet";
32+
import { useLocale } from "../../providers/ui-context-provider";
3233

3334
const MODAL_HEIGHT = Dimensions.get("window").height * 0.7;
3435
const DEVICE_WIDTH = Dimensions.get("window").width;
@@ -50,6 +51,7 @@ export const ConnectWalletDetailsModal = ({
5051
supportedTokens: SupportedTokens;
5152
displayBalanceToken?: Record<number, string>;
5253
}) => {
54+
const l = useLocale();
5355
const [isExportModalVisible, setIsExportModalVisible] = useState(false);
5456
const activeWallet = useWallet();
5557
const chain = useChain();
@@ -137,7 +139,9 @@ export const ConnectWalletDetailsModal = ({
137139
return (
138140
<>
139141
<View style={styles.currentNetwork}>
140-
<Text variant="bodySmallSecondary">Additional Actions</Text>
142+
<Text variant="bodySmallSecondary">
143+
{l.connect_wallet_details.additional_actions}
144+
</Text>
141145
</View>
142146
<BaseButton
143147
backgroundColor="background"
@@ -152,7 +156,9 @@ export const ConnectWalletDetailsModal = ({
152156
<>
153157
<PocketWalletIcon width={16} height={16} />
154158
<View style={styles.exportWalletInfo}>
155-
<Text variant="bodySmall">Backup wallet</Text>
159+
<Text variant="bodySmall">
160+
{l.connect_wallet_details.backup_wallet}
161+
</Text>
156162
</View>
157163
</>
158164
<RightArrowIcon height={10} width={10} />
@@ -171,7 +177,9 @@ export const ConnectWalletDetailsModal = ({
171177
<>
172178
<PocketWalletIcon width={16} height={16} />
173179
<View style={styles.exportWalletInfo}>
174-
<Text variant="bodySmall">Import wallet</Text>
180+
<Text variant="bodySmall">
181+
{l.connect_wallet_details.import_wallet}
182+
</Text>
175183
</View>
176184
</>
177185
<RightArrowIcon height={10} width={10} />
@@ -185,9 +193,7 @@ export const ConnectWalletDetailsModal = ({
185193

186194
{activeWallet?.walletId === LocalWallet.id ? (
187195
<Text variant="error" textAlign="left" mb="sm">
188-
{
189-
"This is a temporary guest wallet. Download a backup if you don't want to lose access to it."
190-
}
196+
{l.local_wallet.this_is_a_temporary_wallet}
191197
</Text>
192198
) : null}
193199
</>
@@ -201,6 +207,7 @@ export const ConnectWalletDetailsModal = ({
201207
onExportLocalWalletPress,
202208
onWalletImported,
203209
smartWallet,
210+
l,
204211
]);
205212

206213
return (
@@ -234,7 +241,7 @@ export const ConnectWalletDetailsModal = ({
234241
<Box flexDirection="row" alignItems="center">
235242
<ActiveDot width={10} height={10} />
236243
<Text variant="bodySmallSecondary" ml="xxs">
237-
Connected to a Smart Wallet
244+
{l.connect_wallet_details.connected_to_smart_wallet}
238245
</Text>
239246
</Box>
240247
{isSmartWalletDeployed ? (
@@ -260,13 +267,15 @@ export const ConnectWalletDetailsModal = ({
260267
<ReceiveButton />
261268
</Box>
262269
<View style={styles.currentNetwork}>
263-
<Text variant="bodySmallSecondary">Current Network</Text>
270+
<Text variant="bodySmallSecondary">
271+
{l.connect_wallet_details.current_network}
272+
</Text>
264273
</View>
265274
<NetworkButton chain={chain} enableSwitchModal={true} />
266275
{!hideTestnetFaucet && chain?.testnet && chain?.faucets?.length ? (
267276
<IconTextButton
268277
mt="xs"
269-
text="Request Testnet Funds"
278+
text={l.connect_wallet_details.request_testnet_funds}
270279
icon={<MoneyIcon height={16} width={16} />}
271280
onPress={() => {
272281
if (chain?.faucets?.[0]) {
@@ -278,7 +287,7 @@ export const ConnectWalletDetailsModal = ({
278287
{chain?.explorers && chain?.explorers?.[0] && (
279288
<IconTextButton
280289
mt="xs"
281-
text="View Transaction History"
290+
text={l.connect_wallet_details.view_transaction_history}
282291
icon={<TransactionIcon height={16} width={16} />}
283292
onPress={() => {
284293
Linking.openURL(

packages/react-native/src/evm/components/ConnectWalletDetails/ExportLocalWalletModal.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { usePersonalWalletAddress } from "../../wallets/hooks/usePersonalWalletA
1818
import { shortenWalletAddress } from "../../utils/addresses";
1919
import { LocalWallet } from "../../wallets/wallets/LocalWallet";
2020
import { TWModal } from "../base/modal/TWModal";
21+
import { useLocale } from "../../providers/ui-context-provider";
2122

2223
export type ExportLocalWalletModalProps = {
2324
isVisible: boolean;
@@ -28,6 +29,7 @@ export const ExportLocalWalletModal = ({
2829
isVisible,
2930
onClose,
3031
}: ExportLocalWalletModalProps) => {
32+
const l = useLocale();
3133
const [password, setPassword] = useState<string | undefined>();
3234
const [error, setError] = useState<string | undefined>();
3335
const [isExporting, setIsExporting] = useState<boolean>(false);
@@ -154,20 +156,18 @@ export const ExportLocalWalletModal = ({
154156
headerText="Backup your Wallet"
155157
/>
156158
<Text variant="subHeader" mt="md" textAlign="left">
157-
{
158-
"This will download a JSON file containing your wallet information onto your device encrypted with the password."
159-
}
159+
{l.local_wallet.this_will_download_json}
160160
</Text>
161161
<Text variant="bodySmall" textAlign="left" mt="lg" mb="xxs">
162-
Wallet Address
162+
{l.local_wallet.wallet_address}
163163
</Text>
164164
<Text variant="bodySmallSecondary">
165165
{shortenWalletAddress(
166166
personalWalletAddress ? personalWalletAddress : address,
167167
)}
168168
</Text>
169169
<Text variant="bodySmall" textAlign="left" mt="lg" mb="xxs">
170-
Password
170+
{l.common.password}
171171
</Text>
172172
<PasswordInput onChangeText={onChangeText} />
173173
<Text variant="bodySmall" color="red" mt="xs" textAlign="left">
@@ -183,7 +183,7 @@ export const ExportLocalWalletModal = ({
183183
<ActivityIndicator size="small" color="buttonTextColor" />
184184
) : (
185185
<Text variant="bodySmall" color="black">
186-
Backup
186+
{l.connect_wallet_details.backup}
187187
</Text>
188188
)}
189189
</BaseButton>

packages/react-native/src/evm/components/ConnectWalletDetails/SessionProposalModal.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import {
77
CLOSE_MODAL_STATE,
88
WalletConnectSessionProposalModal,
99
} from "../../utils/modalTypes";
10-
import { useModalState } from "../../providers/ui-context-provider";
10+
import { useLocale, useModalState } from "../../providers/ui-context-provider";
1111

1212
export const SessionProposalModal = () => {
13+
const l = useLocale();
1314
const { modalState, setModalState } = useModalState();
1415
const { data: proposalData } =
1516
modalState as WalletConnectSessionProposalModal;
@@ -27,7 +28,7 @@ export const SessionProposalModal = () => {
2728
borderRadius="md"
2829
p="lg"
2930
>
30-
<Text variant="bodyLarge">Connect to App</Text>
31+
<Text variant="bodyLarge">{l.connect_wallet_details.connect_to_app}</Text>
3132
<Text variant="bodyLarge">{proposalData.proposer.metadata.name}</Text>
3233
<Box flexDirection="row" justifyContent="space-evenly" mt="lg">
3334
<BaseButton
@@ -44,7 +45,7 @@ export const SessionProposalModal = () => {
4445
onClose();
4546
}}
4647
>
47-
<Text variant="bodySmall">Reject</Text>
48+
<Text variant="bodySmall">{l.common.reject}</Text>
4849
</BaseButton>
4950
<BaseButton
5051
alignContent="center"
@@ -67,7 +68,7 @@ export const SessionProposalModal = () => {
6768
}}
6869
>
6970
<Text variant="bodySmall" color="black">
70-
Approve
71+
{l.common.approve}
7172
</Text>
7273
</BaseButton>
7374
</Box>

packages/react-native/src/evm/components/ConnectWalletDetails/SessionRequestModal.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
EIP155_SIGNING_METHODS,
77
IWalletConnectReceiver,
88
} from "@thirdweb-dev/wallets";
9-
import { useModalState } from "../../providers/ui-context-provider";
9+
import { useLocale, useModalState } from "../../providers/ui-context-provider";
1010
import {
1111
CLOSE_MODAL_STATE,
1212
WalletConnectSessionRequestModal,
@@ -30,6 +30,7 @@ const getTitle = (method: string) => {
3030
const MODAL_HEIGHT = Dimensions.get("window").height * 0.7;
3131

3232
export const SessionRequestModal = () => {
33+
const l = useLocale();
3334
const { modalState, setModalState } = useModalState();
3435
const { data: requestData } = modalState as WalletConnectSessionRequestModal;
3536
const [approvingRequest, setApprovingRequest] = useState(false);
@@ -60,7 +61,7 @@ export const SessionRequestModal = () => {
6061
case EIP155_SIGNING_METHODS.SWITCH_CHAIN:
6162
return (
6263
<Text variant="bodySmall" textAlign="left">
63-
{`Switch to ${
64+
{`${l.connect_wallet_details.switch_to} ${
6465
requestData.params[0].chainId === chain?.chainId
6566
? chain?.slug
6667
: requestData.params[0].chainId
@@ -85,17 +86,17 @@ export const SessionRequestModal = () => {
8586
return (
8687
<Box>
8788
<Text variant="bodySmall" textAlign="left">
88-
{`from: ${shortenWalletAddress(from)}`}
89+
{`${l.common.from}: ${shortenWalletAddress(from)}`}
8990
</Text>
9091
<Text variant="bodySmall" textAlign="left" mt="sm">
91-
{`to: ${shortenWalletAddress(to)}`}
92+
{`${l.common.to}: ${shortenWalletAddress(to)}`}
9293
</Text>
9394
</Box>
9495
);
9596
default:
9697
throw new Error(`Method not implemented: ${requestData.method}`);
9798
}
98-
}, [chain?.chainId, chain?.slug, requestData]);
99+
}, [chain?.chainId, chain?.slug, requestData, l]);
99100

100101
return (
101102
<Box
@@ -129,7 +130,7 @@ export const SessionRequestModal = () => {
129130
onClose();
130131
}}
131132
>
132-
<Text variant="bodySmall">Reject</Text>
133+
<Text variant="bodySmall">{l.common.reject}</Text>
133134
</BaseButton>
134135
<BaseButton
135136
alignContent="center"
@@ -148,7 +149,7 @@ export const SessionRequestModal = () => {
148149
<ActivityIndicator size="small" />
149150
) : (
150151
<Text variant="bodySmall" color="black">
151-
Approve
152+
{l.common.approve}
152153
</Text>
153154
)}
154155
</BaseButton>

packages/react-native/src/evm/components/ConnectWalletDetails/SmartWalletAdditionalActions.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import RightArrowIcon from "../../assets/right-arrow";
1414
import ConnectAppField from "./ConnectAppField";
1515
import DisconnectIcon from "../../assets/disconnect";
1616
import { IconTextButton } from "../base/IconTextButton";
17-
import { useGlobalTheme } from "../../providers/ui-context-provider";
17+
import { useGlobalTheme, useLocale } from "../../providers/ui-context-provider";
1818

1919
export const SmartWalletAdditionalActions = ({
2020
onExportPress,
2121
}: {
2222
onExportPress: () => void;
2323
}) => {
24+
const l = useLocale();
2425
const { setConnectedWallet } = useWalletContext();
2526
const [smartWallet, setSmartWallet] = useSmartWallet();
2627
const [smartWalletAddress, setSmartWalletAddress] = useState<string>("");
@@ -64,8 +65,8 @@ export const SmartWalletAdditionalActions = ({
6465
mt="xs"
6566
text={
6667
showSmartWallet
67-
? "Switch to Smart Wallet"
68-
: "Switch to Personal Wallet"
68+
? l.smart_wallet.switch_to_smart
69+
: l.smart_wallet.switch_to_personal
6970
}
7071
icon={
7172
<DisconnectIcon
@@ -98,8 +99,8 @@ export const SmartWalletAdditionalActions = ({
9899
<View style={styles.exportWalletInfo}>
99100
<Text variant="bodySmall">
100101
{wallet?.walletId === walletIds.localWallet
101-
? "Backup personal wallet"
102-
: "Backup wallet"}
102+
? l.connect_wallet_details.backup_personal_wallet
103+
: l.connect_wallet_details.backup_wallet}
103104
</Text>
104105
</View>
105106
</>
@@ -110,9 +111,7 @@ export const SmartWalletAdditionalActions = ({
110111
/>
111112
</BaseButton>
112113
<Text variant="error" textAlign="left" mb="sm">
113-
{
114-
"This is a temporary guest wallet. Download a backup if you don't want to loose access to it."
115-
}
114+
{l.local_wallet.this_is_a_temporary_wallet}
116115
</Text>
117116
</>
118117
) : null}

0 commit comments

Comments
 (0)