Skip to content

Commit 68346b6

Browse files
[i18n] - Add react-i18n package support (#61)
* [i18n] - Add react-i18n package support * [i18n] - add 4 tab spacing for lang JSON files * Create cuddly-plums-beg.md SOV-787 - Add react-i18n package support * feat: lang switcher/second lang examples * chore: fixes from comments * chore: resolve final review comment * chore: rebuild yarn.lock Co-authored-by: soulBit <its.soulBit@gmail.com>
1 parent 68b5b4d commit 68346b6

14 files changed

Lines changed: 178 additions & 20 deletions

File tree

.changeset/cuddly-plums-beg.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"frontend": patch
3+
---
4+
5+
SOV-787 - Add react-i18n package support

.prettierrc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,13 @@
1414
"^app/",
1515
"^[./|~/]"
1616
],
17-
"importOrderSeparation": true
17+
"importOrderSeparation": true,
18+
"overrides": [
19+
{
20+
"files": ["apps/frontend/src/locales/**/*.json"],
21+
"options": {
22+
"tabWidth": 4
23+
}
24+
}
25+
]
1826
}

apps/frontend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
"@sovryn/ui": "*",
1414
"classnames": "2.3.2",
1515
"ethers": "5.7.1",
16+
"i18next": "22.0.4",
17+
"i18next-browser-languagedetector": "7.0.0",
1618
"react": "18.2.0",
1719
"react-dom": "18.2.0",
20+
"react-i18next": "12.0.0",
1821
"react-router-dom": "6.4.2",
1922
"react-scripts": "5.0.1"
2023
},

apps/frontend/src/app/5_pages/App/App.tsx

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
1-
import React, { useReducer } from 'react';
1+
import React, { useCallback, useReducer, useState } from 'react';
22

3-
import { Button, Dialog, Dropdown, noop } from '@sovryn/ui';
3+
import i18next from 'i18next';
4+
import { useTranslation } from 'react-i18next';
5+
6+
import { Button, Dialog, Dropdown, Menu, MenuItem } from '@sovryn/ui';
47

58
import { ConnectWalletButton } from '../../2_molecules/ConnectWalletButton/ConnectWalletButton';
69
import { useTheme } from '../../../hooks/useTheme';
710
import { useWalletConnect } from '../../../hooks/useWalletConnect';
11+
import { translations, languages } from '../../../locales/i18n';
812
import { AppTheme } from '../../../types/tailwind';
913
import styles from './App.module.css';
1014

1115
function App() {
1216
const { handleThemeChange } = useTheme();
13-
17+
const { t } = useTranslation();
1418
const [isOpen, toggle] = useReducer(p => !p, false);
1519
const { connectWallet, disconnectWallet, wallets, pending } =
1620
useWalletConnect();
1721

22+
const [currentLang, setCurrentLang] = useState(i18next.language);
23+
24+
const changeLanguage = useCallback(
25+
(lng: string) => () => {
26+
i18next.changeLanguage(lng);
27+
setCurrentLang(lng);
28+
},
29+
[],
30+
);
31+
1832
return (
1933
<div className="my-2 px-4">
2034
<header>
@@ -31,18 +45,15 @@ function App() {
3145
<div className="p-4">Hello.</div>
3246
</Dialog>
3347

34-
<Dropdown text="test">
35-
<div>
36-
<div className="my-2" onClick={noop}>
37-
Dropdown Item 1
38-
</div>
39-
<div className="my-2" onClick={noop}>
40-
Dropdown Item 2
41-
</div>
42-
<div className="my-2" onClick={noop}>
43-
Dropdown Item 3
44-
</div>
45-
</div>
48+
<Dropdown text={currentLang.toUpperCase()} className="my-4">
49+
<Menu>
50+
{languages.map(lng => (
51+
<MenuItem
52+
text={lng.toUpperCase()}
53+
onClick={changeLanguage(lng)}
54+
/>
55+
))}
56+
</Menu>
4657
</Dropdown>
4758

4859
<div className="flex items-center gap-4">
@@ -93,6 +104,8 @@ function App() {
93104

94105
<br />
95106
<br />
107+
108+
<p>{t(translations.wallet)}</p>
96109
</main>
97110
</div>
98111
);

apps/frontend/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { OnboardProvider } from '@sovryn/onboard-react';
77

88
import App from './app/5_pages/App/App';
99
import { onboard } from './lib/connector';
10+
import './locales/i18n';
1011
import './styles/tailwindcss/index.css';
1112

1213
const root = ReactDOM.createRoot(

apps/frontend/src/locales/.gitkeep

Whitespace-only changes.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"wallet": "wallet"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"wallet": "cartera"
3+
}

apps/frontend/src/locales/i18n.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import i18next from 'i18next';
2+
import LanguageDetector from 'i18next-browser-languagedetector';
3+
import { initReactI18next } from 'react-i18next';
4+
5+
import en from './en/translations.json';
6+
import es from './es/translations.json';
7+
import { ConvertedToObjectType } from './types';
8+
9+
const translationsJson = {
10+
en: {
11+
translation: en,
12+
},
13+
es: {
14+
translation: es,
15+
},
16+
};
17+
18+
export const languages = Object.keys(translationsJson);
19+
20+
export const languageLocalStorageKey = 'i18nextLng_dapp';
21+
export const walletLanguageLocalStorageKey = 'i18nextLng'; // language key for @sovryn/react-wallet
22+
23+
export type TranslationResource = typeof en;
24+
export type LanguageKey = keyof TranslationResource;
25+
26+
export const translations: ConvertedToObjectType<TranslationResource> =
27+
{} as ConvertedToObjectType<TranslationResource>;
28+
29+
/*
30+
* Converts the static JSON file into an object where keys are identical
31+
* but values are strings concatenated according to syntax.
32+
* This is helpful when using the JSON file keys and still have the intellisense support
33+
* along with type-safety
34+
*/
35+
const convertLanguageJsonToObject = (obj: {}, dict: {}, current?: string) => {
36+
Object.keys(obj).forEach(key => {
37+
const currentLookupKey = current ? `${current}.${key}` : key;
38+
if (typeof obj[key] === 'object') {
39+
dict[key] = {};
40+
convertLanguageJsonToObject(obj[key], dict[key], currentLookupKey);
41+
} else {
42+
dict[key] = currentLookupKey;
43+
}
44+
});
45+
};
46+
47+
export const i18n = i18next
48+
.use(initReactI18next)
49+
.use(LanguageDetector)
50+
.init(
51+
{
52+
resources: translationsJson,
53+
react: {
54+
useSuspense: true,
55+
},
56+
fallbackLng: 'en',
57+
supportedLngs: languages,
58+
detection: {
59+
order: ['localStorage', 'navigator'],
60+
// needs to be different from default to prevent overwrite by @sovryn/react-wallet
61+
lookupLocalStorage: languageLocalStorageKey,
62+
// don't cache automatically into localStorage only on manual language change
63+
caches: [],
64+
excludeCacheFor: ['cimode'],
65+
},
66+
},
67+
error => {
68+
if (error) {
69+
console.error(error);
70+
}
71+
convertLanguageJsonToObject(en, translations);
72+
},
73+
);

apps/frontend/src/locales/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type ConvertedToObjectType<T> = {
2+
[P in keyof T]: T[P] extends string ? string : ConvertedToObjectType<T[P]>;
3+
};

0 commit comments

Comments
 (0)