Skip to content

Commit 3610847

Browse files
committed
feat: add internationalization support
1 parent fa11e6e commit 3610847

15 files changed

+211
-3
lines changed

babel.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ module.exports = {
1919
'@pages': './src/views/pages',
2020
'@core': './src/core',
2121
'@components': './src/views/components',
22-
'@styles': './src/styles'
22+
'@styles': './src/styles',
23+
'@i18n': './src/i18n'
2324
}
2425
}
2526
],

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"fetch-cheerio-object": "^1.3.0",
9797
"front-matter": "^4.0.2",
9898
"fs-extra": "^11.1.1",
99+
"i18next": "^23.8.2",
99100
"jsonwebtoken": "^9.0.1",
100101
"knex": "^0.95.15",
101102
"markdown-it": "^12.3.2",
@@ -114,6 +115,7 @@
114115
"react": "^17.0.2",
115116
"react-dom": "^17.0.2",
116117
"react-helmet": "^6.1.0",
118+
"react-i18next": "^14.0.5",
117119
"react-redux": "^7.2.9",
118120
"react-router": "^5.3.4",
119121
"redux-saga": "^1.2.3",

src/core/i18n/actions.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export const i18nActions = {
2+
CHANGE_LOCALE: 'CHANGE_LOCALE',
3+
4+
change_locale: (locale) => ({
5+
type: i18nActions.change_locale,
6+
payload: {
7+
locale
8+
}
9+
})
10+
}

src/core/i18n/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { i18nActions } from './actions'
2+
export { i18nReducer } from './reducer'
3+
export { i18nSagas } from './sagas'

src/core/i18n/reducer.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Record } from 'immutable'
2+
3+
import { i18nActions } from './actions'
4+
5+
const initialState = new Record({
6+
locale: 'en'
7+
})
8+
9+
export function i18nReducer(state = initialState(), { payload, type }) {
10+
switch (type) {
11+
case i18nActions.CHANGE_LANGUAGE:
12+
return state.set('locale', payload.locale)
13+
14+
default:
15+
return state
16+
}
17+
}

src/core/i18n/sagas.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { takeLatest, put, fork } from 'redux-saga/effects'
2+
import i18n from 'i18next'
3+
4+
import { localStorageAdapter } from '@core/utils'
5+
import { appActions } from '@core/app/actions'
6+
import { i18nActions } from './actions'
7+
8+
export function* init() {
9+
const locale = localStorageAdapter.getItem('locale')
10+
if (locale) {
11+
yield put(i18nActions.change_locale(locale))
12+
}
13+
14+
// TODO detect user locale
15+
}
16+
17+
export function ChangeLocale({ payload }) {
18+
localStorageAdapter.setItem('locale', payload.locale)
19+
i18n.changeLanguage(payload.locale)
20+
}
21+
22+
//= ====================================
23+
// WATCHERS
24+
// -------------------------------------
25+
26+
export function* watchInitApp() {
27+
yield takeLatest(appActions.INIT_APP, init)
28+
}
29+
30+
export function* watchChangeLocale() {
31+
yield takeLatest(i18nActions.CHANGE_LOCALE, ChangeLocale)
32+
}
33+
34+
//= ====================================
35+
// ROOT
36+
// -------------------------------------
37+
38+
export const i18nSagas = [fork(watchInitApp), fork(watchChangeLocale)]

src/core/reducers.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { networkReducer } from './network'
1313
import { notificationReducer } from './notifications'
1414
import { postsReducer } from './posts'
1515
import { postlistsReducer } from './postlists'
16+
import { i18nReducer } from './i18n'
1617

1718
const rootReducer = (history) =>
1819
combineReducers({
@@ -28,7 +29,8 @@ const rootReducer = (history) =>
2829
network: networkReducer,
2930
notification: notificationReducer,
3031
posts: postsReducer,
31-
postlists: postlistsReducer
32+
postlists: postlistsReducer,
33+
i18n: i18nReducer
3234
})
3335

3436
export default rootReducer

src/core/sagas.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { githubIssuesSagas } from './github-issues'
1010
import { ledgerSagas } from './ledger'
1111
import { networkSagas } from './network'
1212
import { postlistSagas } from './postlists'
13+
import { i18nSagas } from './i18n'
1314

1415
export default function* rootSage() {
1516
yield all([
@@ -22,6 +23,7 @@ export default function* rootSage() {
2223
...githubIssuesSagas,
2324
...ledgerSagas,
2425
...networkSagas,
25-
...postlistSagas
26+
...postlistSagas,
27+
...i18nSagas
2628
])
2729
}

src/i18n/index.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { initReactI18next } from 'react-i18next'
2+
import i18n from 'i18next'
3+
4+
i18n.use(initReactI18next).init({
5+
// detection
6+
debug: true,
7+
resources: {
8+
en: {
9+
translation: {
10+
'Welcome to React': 'Welcome to React and react-i18next'
11+
}
12+
}
13+
},
14+
lng: 'en',
15+
fallbackLng: 'en'
16+
// supportedLngs
17+
})
18+
19+
export default i18n

src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Needed for redux-saga es6 generator support
22
import '@babel/polyfill'
33

4+
import '@i18n'
45
import React from 'react'
56
import { render } from 'react-dom'
67

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
import FormControl from '@material-ui/core/FormControl'
4+
import Select from '@material-ui/core/Select'
5+
import MenuItem from '@material-ui/core/MenuItem'
6+
7+
import './change-locale.styl'
8+
9+
export default function ChangeLocale({ change_locale, locale }) {
10+
return (
11+
<FormControl className='change-locale'>
12+
<Select
13+
labelId='change-locale'
14+
id='change-locale'
15+
value={locale}
16+
onChange={(event) => change_locale(event.target.value)}>
17+
<MenuItem value='en'>English</MenuItem>
18+
<MenuItem value='es'>Español</MenuItem>
19+
<MenuItem value='fr'>Français</MenuItem>
20+
<MenuItem value='it'>Italiano</MenuItem>
21+
<MenuItem value='de'>Deutsch</MenuItem>
22+
<MenuItem value='nl'>Nederlands</MenuItem>
23+
<MenuItem value='ru'>Русский</MenuItem>
24+
</Select>
25+
</FormControl>
26+
)
27+
}
28+
29+
ChangeLocale.propTypes = {
30+
change_locale: PropTypes.func.isRequired,
31+
locale: PropTypes.string.isRequired
32+
}

src/views/components/change-locale/change-locale.styl

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { connect } from 'react-redux'
2+
import { createSelector } from 'reselect'
3+
4+
import { i18nActions } from '@core/i18n'
5+
6+
import ChangeLocale from './change-locale'
7+
8+
const mapStateToProps = createSelector(
9+
(state) => state.getIn(['i18n', 'locale']),
10+
(locale) => ({ locale })
11+
)
12+
13+
const mapDispatchToProps = {
14+
change_locale: i18nActions.change_locale
15+
}
16+
17+
export default connect(mapStateToProps, mapDispatchToProps)(ChangeLocale)

src/views/components/menu/menu.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import HomeIcon from '@material-ui/icons/Home'
99

1010
import SearchBar from '@components/search-bar'
1111
import history from '@core/history'
12+
import ChangeLocale from '@components/change-locale'
1213

1314
import './menu.styl'
1415

@@ -143,6 +144,7 @@ export default class Menu extends React.Component {
143144
disableDiscovery={iOS}
144145
anchor='top'>
145146
<MenuSections />
147+
<ChangeLocale />
146148
</SwipeableDrawer>
147149
{!hide_speed_dial && (
148150
<SpeedDial
@@ -179,6 +181,7 @@ export default class Menu extends React.Component {
179181
)}
180182
{!hideSearch && <SearchBar />}
181183
{!hide && <MenuSections />}
184+
<ChangeLocale />
182185
</div>
183186
</div>
184187
)

yarn.lock

+61
Original file line numberDiff line numberDiff line change
@@ -2451,6 +2451,15 @@ __metadata:
24512451
languageName: node
24522452
linkType: hard
24532453

2454+
"@babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9":
2455+
version: 7.23.9
2456+
resolution: "@babel/runtime@npm:7.23.9"
2457+
dependencies:
2458+
regenerator-runtime: ^0.14.0
2459+
checksum: 6bbebe8d27c0c2dd275d1ac197fc1a6c00e18dab68cc7aaff0adc3195b45862bae9c4cc58975629004b0213955b2ed91e99eccb3d9b39cabea246c657323d667
2460+
languageName: node
2461+
linkType: hard
2462+
24542463
"@babel/template@npm:^7.22.15, @babel/template@npm:^7.23.9":
24552464
version: 7.23.9
24562465
resolution: "@babel/template@npm:7.23.9"
@@ -9646,6 +9655,15 @@ __metadata:
96469655
languageName: node
96479656
linkType: hard
96489657

9658+
"html-parse-stringify@npm:^3.0.1":
9659+
version: 3.0.1
9660+
resolution: "html-parse-stringify@npm:3.0.1"
9661+
dependencies:
9662+
void-elements: 3.1.0
9663+
checksum: 334fdebd4b5c355dba8e95284cead6f62bf865a2359da2759b039db58c805646350016d2017875718bc3c4b9bf81a0d11be5ee0cf4774a3a5a7b97cde21cfd67
9664+
languageName: node
9665+
linkType: hard
9666+
96499667
"html-webpack-plugin@npm:^5.5.3":
96509668
version: 5.5.3
96519669
resolution: "html-webpack-plugin@npm:5.5.3"
@@ -9866,6 +9884,15 @@ __metadata:
98669884
languageName: node
98679885
linkType: hard
98689886

9887+
"i18next@npm:^23.8.2":
9888+
version: 23.8.2
9889+
resolution: "i18next@npm:23.8.2"
9890+
dependencies:
9891+
"@babel/runtime": ^7.23.2
9892+
checksum: c20e68c6c216bfcedc16d8d8b1ee545423e26e84ace36b699f936ec8cf1b4df8ee2ae093e7a3e444a9cb5931ca76698ae1a80d31691aa4153bcc804394e0019e
9893+
languageName: node
9894+
linkType: hard
9895+
98699896
"iconv-lite@npm:0.4.24":
98709897
version: 0.4.24
98719898
resolution: "iconv-lite@npm:0.4.24"
@@ -14641,6 +14668,24 @@ __metadata:
1464114668
languageName: node
1464214669
linkType: hard
1464314670

14671+
"react-i18next@npm:^14.0.5":
14672+
version: 14.0.5
14673+
resolution: "react-i18next@npm:14.0.5"
14674+
dependencies:
14675+
"@babel/runtime": ^7.23.9
14676+
html-parse-stringify: ^3.0.1
14677+
peerDependencies:
14678+
i18next: ">= 23.2.3"
14679+
react: ">= 16.8.0"
14680+
peerDependenciesMeta:
14681+
react-dom:
14682+
optional: true
14683+
react-native:
14684+
optional: true
14685+
checksum: 54fe5ffd887d633852ea7e82e98b6ef057facf45dec512469dd0e43ae64d9450d2bafe7c85c87fd650cf6dad1bf81727a89fba23af0508b7a806153d459fc5cc
14686+
languageName: node
14687+
linkType: hard
14688+
1464414689
"react-immutable-proptypes@npm:^2.2.0":
1464514690
version: 2.2.0
1464614691
resolution: "react-immutable-proptypes@npm:2.2.0"
@@ -15013,6 +15058,13 @@ __metadata:
1501315058
languageName: node
1501415059
linkType: hard
1501515060

15061+
"regenerator-runtime@npm:^0.14.0":
15062+
version: 0.14.1
15063+
resolution: "regenerator-runtime@npm:0.14.1"
15064+
checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38
15065+
languageName: node
15066+
linkType: hard
15067+
1501615068
"regenerator-transform@npm:^0.15.2":
1501715069
version: 0.15.2
1501815070
resolution: "regenerator-transform@npm:0.15.2"
@@ -15421,6 +15473,7 @@ __metadata:
1542115473
html-inline-script-webpack-plugin: ^2.0.3
1542215474
html-loader: ^2.1.2
1542315475
html-webpack-plugin: ^5.5.3
15476+
i18next: ^23.8.2
1542415477
image-webpack-loader: ^7.0.1
1542515478
ipfs-deploy: ^12.0.1
1542615479
jsonwebtoken: ^9.0.1
@@ -15446,6 +15499,7 @@ __metadata:
1544615499
react: ^17.0.2
1544715500
react-dom: ^17.0.2
1544815501
react-helmet: ^6.1.0
15502+
react-i18next: ^14.0.5
1544915503
react-immutable-proptypes: ^2.2.0
1545015504
react-redux: ^7.2.9
1545115505
react-router: ^5.3.4
@@ -17529,6 +17583,13 @@ __metadata:
1752917583
languageName: node
1753017584
linkType: hard
1753117585

17586+
"void-elements@npm:3.1.0":
17587+
version: 3.1.0
17588+
resolution: "void-elements@npm:3.1.0"
17589+
checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f
17590+
languageName: node
17591+
linkType: hard
17592+
1753217593
"watchpack@npm:^2.4.0":
1753317594
version: 2.4.0
1753417595
resolution: "watchpack@npm:2.4.0"

0 commit comments

Comments
 (0)