From 0bb60e46aa987c9b60220ee98d8a45a5ed4ca21d Mon Sep 17 00:00:00 2001
From: mistakia <1823355+mistakia@users.noreply.github.com>
Date: Sat, 17 Feb 2024 08:29:35 -0500
Subject: [PATCH] feat: add internationalization support
---
babel.config.js | 3 +-
package.json | 2 +
src/core/i18n/actions.js | 10 +++
src/core/i18n/index.js | 3 +
src/core/i18n/reducer.js | 17 ++++++
src/core/i18n/sagas.js | 38 ++++++++++++
src/core/reducers.js | 4 +-
src/core/sagas.js | 4 +-
src/i18n/index.js | 19 ++++++
src/index.js | 1 +
.../components/change-locale/change-locale.js | 32 ++++++++++
.../change-locale/change-locale.styl | 0
src/views/components/change-locale/index.js | 17 ++++++
src/views/components/menu/menu.js | 3 +
yarn.lock | 61 +++++++++++++++++++
15 files changed, 211 insertions(+), 3 deletions(-)
create mode 100644 src/core/i18n/actions.js
create mode 100644 src/core/i18n/index.js
create mode 100644 src/core/i18n/reducer.js
create mode 100644 src/core/i18n/sagas.js
create mode 100644 src/i18n/index.js
create mode 100644 src/views/components/change-locale/change-locale.js
create mode 100644 src/views/components/change-locale/change-locale.styl
create mode 100644 src/views/components/change-locale/index.js
diff --git a/babel.config.js b/babel.config.js
index fbe35bd0..a1ebc1b4 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -19,7 +19,8 @@ module.exports = {
'@pages': './src/views/pages',
'@core': './src/core',
'@components': './src/views/components',
- '@styles': './src/styles'
+ '@styles': './src/styles',
+ '@i18n': './src/i18n'
}
}
],
diff --git a/package.json b/package.json
index 28b85565..897d0435 100644
--- a/package.json
+++ b/package.json
@@ -96,6 +96,7 @@
"fetch-cheerio-object": "^1.3.0",
"front-matter": "^4.0.2",
"fs-extra": "^11.1.1",
+ "i18next": "^23.8.2",
"jsonwebtoken": "^9.0.1",
"knex": "^0.95.15",
"markdown-it": "^12.3.2",
@@ -114,6 +115,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
+ "react-i18next": "^14.0.5",
"react-redux": "^7.2.9",
"react-router": "^5.3.4",
"redux-saga": "^1.2.3",
diff --git a/src/core/i18n/actions.js b/src/core/i18n/actions.js
new file mode 100644
index 00000000..aa2ad688
--- /dev/null
+++ b/src/core/i18n/actions.js
@@ -0,0 +1,10 @@
+export const i18nActions = {
+ CHANGE_LOCALE: 'CHANGE_LOCALE',
+
+ change_locale: (locale) => ({
+ type: i18nActions.change_locale,
+ payload: {
+ locale
+ }
+ })
+}
diff --git a/src/core/i18n/index.js b/src/core/i18n/index.js
new file mode 100644
index 00000000..9cb917b1
--- /dev/null
+++ b/src/core/i18n/index.js
@@ -0,0 +1,3 @@
+export { i18nActions } from './actions'
+export { i18nReducer } from './reducer'
+export { i18nSagas } from './sagas'
diff --git a/src/core/i18n/reducer.js b/src/core/i18n/reducer.js
new file mode 100644
index 00000000..adf89774
--- /dev/null
+++ b/src/core/i18n/reducer.js
@@ -0,0 +1,17 @@
+import { Record } from 'immutable'
+
+import { i18nActions } from './actions'
+
+const initialState = new Record({
+ locale: 'en'
+})
+
+export function i18nReducer(state = initialState(), { payload, type }) {
+ switch (type) {
+ case i18nActions.CHANGE_LANGUAGE:
+ return state.set('locale', payload.locale)
+
+ default:
+ return state
+ }
+}
diff --git a/src/core/i18n/sagas.js b/src/core/i18n/sagas.js
new file mode 100644
index 00000000..e1d52703
--- /dev/null
+++ b/src/core/i18n/sagas.js
@@ -0,0 +1,38 @@
+import { takeLatest, put, fork } from 'redux-saga/effects'
+import i18n from 'i18next'
+
+import { localStorageAdapter } from '@core/utils'
+import { appActions } from '@core/app/actions'
+import { i18nActions } from './actions'
+
+export function* init() {
+ const locale = localStorageAdapter.getItem('locale')
+ if (locale) {
+ yield put(i18nActions.change_locale(locale))
+ }
+
+ // TODO detect user locale
+}
+
+export function ChangeLocale({ payload }) {
+ localStorageAdapter.setItem('locale', payload.locale)
+ i18n.changeLanguage(payload.locale)
+}
+
+//= ====================================
+// WATCHERS
+// -------------------------------------
+
+export function* watchInitApp() {
+ yield takeLatest(appActions.INIT_APP, init)
+}
+
+export function* watchChangeLocale() {
+ yield takeLatest(i18nActions.CHANGE_LOCALE, ChangeLocale)
+}
+
+//= ====================================
+// ROOT
+// -------------------------------------
+
+export const i18nSagas = [fork(watchInitApp), fork(watchChangeLocale)]
diff --git a/src/core/reducers.js b/src/core/reducers.js
index 6c7a6b4e..6304fc70 100644
--- a/src/core/reducers.js
+++ b/src/core/reducers.js
@@ -13,6 +13,7 @@ import { networkReducer } from './network'
import { notificationReducer } from './notifications'
import { postsReducer } from './posts'
import { postlistsReducer } from './postlists'
+import { i18nReducer } from './i18n'
const rootReducer = (history) =>
combineReducers({
@@ -28,7 +29,8 @@ const rootReducer = (history) =>
network: networkReducer,
notification: notificationReducer,
posts: postsReducer,
- postlists: postlistsReducer
+ postlists: postlistsReducer,
+ i18n: i18nReducer
})
export default rootReducer
diff --git a/src/core/sagas.js b/src/core/sagas.js
index 237db53f..ebaf63c7 100644
--- a/src/core/sagas.js
+++ b/src/core/sagas.js
@@ -10,6 +10,7 @@ import { githubIssuesSagas } from './github-issues'
import { ledgerSagas } from './ledger'
import { networkSagas } from './network'
import { postlistSagas } from './postlists'
+import { i18nSagas } from './i18n'
export default function* rootSage() {
yield all([
@@ -22,6 +23,7 @@ export default function* rootSage() {
...githubIssuesSagas,
...ledgerSagas,
...networkSagas,
- ...postlistSagas
+ ...postlistSagas,
+ ...i18nSagas
])
}
diff --git a/src/i18n/index.js b/src/i18n/index.js
new file mode 100644
index 00000000..c43e8286
--- /dev/null
+++ b/src/i18n/index.js
@@ -0,0 +1,19 @@
+import { initReactI18next } from 'react-i18next'
+import i18n from 'i18next'
+
+i18n.use(initReactI18next).init({
+ // detection
+ debug: true,
+ resources: {
+ en: {
+ translation: {
+ 'Welcome to React': 'Welcome to React and react-i18next'
+ }
+ }
+ },
+ lng: 'en',
+ fallbackLng: 'en'
+ // supportedLngs
+})
+
+export default i18n
diff --git a/src/index.js b/src/index.js
index a587c113..28baef2f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,7 @@
// Needed for redux-saga es6 generator support
import '@babel/polyfill'
+import '@i18n'
import React from 'react'
import { render } from 'react-dom'
diff --git a/src/views/components/change-locale/change-locale.js b/src/views/components/change-locale/change-locale.js
new file mode 100644
index 00000000..cf9b3f29
--- /dev/null
+++ b/src/views/components/change-locale/change-locale.js
@@ -0,0 +1,32 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import FormControl from '@material-ui/core/FormControl'
+import Select from '@material-ui/core/Select'
+import MenuItem from '@material-ui/core/MenuItem'
+
+import './change-locale.styl'
+
+export default function ChangeLocale({ change_locale, locale }) {
+ return (
+
+
+
+ )
+}
+
+ChangeLocale.propTypes = {
+ change_locale: PropTypes.func.isRequired,
+ locale: PropTypes.string.isRequired
+}
diff --git a/src/views/components/change-locale/change-locale.styl b/src/views/components/change-locale/change-locale.styl
new file mode 100644
index 00000000..e69de29b
diff --git a/src/views/components/change-locale/index.js b/src/views/components/change-locale/index.js
new file mode 100644
index 00000000..5d78615d
--- /dev/null
+++ b/src/views/components/change-locale/index.js
@@ -0,0 +1,17 @@
+import { connect } from 'react-redux'
+import { createSelector } from 'reselect'
+
+import { i18nActions } from '@core/i18n'
+
+import ChangeLocale from './change-locale'
+
+const mapStateToProps = createSelector(
+ (state) => state.getIn(['i18n', 'locale']),
+ (locale) => ({ locale })
+)
+
+const mapDispatchToProps = {
+ change_locale: i18nActions.change_locale
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ChangeLocale)
diff --git a/src/views/components/menu/menu.js b/src/views/components/menu/menu.js
index d597a19d..f4d8e945 100644
--- a/src/views/components/menu/menu.js
+++ b/src/views/components/menu/menu.js
@@ -9,6 +9,7 @@ import HomeIcon from '@material-ui/icons/Home'
import SearchBar from '@components/search-bar'
import history from '@core/history'
+import ChangeLocale from '@components/change-locale'
import './menu.styl'
@@ -143,6 +144,7 @@ export default class Menu extends React.Component {
disableDiscovery={iOS}
anchor='top'>
+
{!hide_speed_dial && (
}
{!hide && }
+
)
diff --git a/yarn.lock b/yarn.lock
index 4be8228a..c52b47b6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2451,6 +2451,15 @@ __metadata:
languageName: node
linkType: hard
+"@babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9":
+ version: 7.23.9
+ resolution: "@babel/runtime@npm:7.23.9"
+ dependencies:
+ regenerator-runtime: ^0.14.0
+ checksum: 6bbebe8d27c0c2dd275d1ac197fc1a6c00e18dab68cc7aaff0adc3195b45862bae9c4cc58975629004b0213955b2ed91e99eccb3d9b39cabea246c657323d667
+ languageName: node
+ linkType: hard
+
"@babel/template@npm:^7.22.15, @babel/template@npm:^7.23.9":
version: 7.23.9
resolution: "@babel/template@npm:7.23.9"
@@ -9646,6 +9655,15 @@ __metadata:
languageName: node
linkType: hard
+"html-parse-stringify@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "html-parse-stringify@npm:3.0.1"
+ dependencies:
+ void-elements: 3.1.0
+ checksum: 334fdebd4b5c355dba8e95284cead6f62bf865a2359da2759b039db58c805646350016d2017875718bc3c4b9bf81a0d11be5ee0cf4774a3a5a7b97cde21cfd67
+ languageName: node
+ linkType: hard
+
"html-webpack-plugin@npm:^5.5.3":
version: 5.5.3
resolution: "html-webpack-plugin@npm:5.5.3"
@@ -9866,6 +9884,15 @@ __metadata:
languageName: node
linkType: hard
+"i18next@npm:^23.8.2":
+ version: 23.8.2
+ resolution: "i18next@npm:23.8.2"
+ dependencies:
+ "@babel/runtime": ^7.23.2
+ checksum: c20e68c6c216bfcedc16d8d8b1ee545423e26e84ace36b699f936ec8cf1b4df8ee2ae093e7a3e444a9cb5931ca76698ae1a80d31691aa4153bcc804394e0019e
+ languageName: node
+ linkType: hard
+
"iconv-lite@npm:0.4.24":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
@@ -14641,6 +14668,24 @@ __metadata:
languageName: node
linkType: hard
+"react-i18next@npm:^14.0.5":
+ version: 14.0.5
+ resolution: "react-i18next@npm:14.0.5"
+ dependencies:
+ "@babel/runtime": ^7.23.9
+ html-parse-stringify: ^3.0.1
+ peerDependencies:
+ i18next: ">= 23.2.3"
+ react: ">= 16.8.0"
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+ checksum: 54fe5ffd887d633852ea7e82e98b6ef057facf45dec512469dd0e43ae64d9450d2bafe7c85c87fd650cf6dad1bf81727a89fba23af0508b7a806153d459fc5cc
+ languageName: node
+ linkType: hard
+
"react-immutable-proptypes@npm:^2.2.0":
version: 2.2.0
resolution: "react-immutable-proptypes@npm:2.2.0"
@@ -15013,6 +15058,13 @@ __metadata:
languageName: node
linkType: hard
+"regenerator-runtime@npm:^0.14.0":
+ version: 0.14.1
+ resolution: "regenerator-runtime@npm:0.14.1"
+ checksum: 9f57c93277b5585d3c83b0cf76be47b473ae8c6d9142a46ce8b0291a04bb2cf902059f0f8445dcabb3fb7378e5fe4bb4ea1e008876343d42e46d3b484534ce38
+ languageName: node
+ linkType: hard
+
"regenerator-transform@npm:^0.15.2":
version: 0.15.2
resolution: "regenerator-transform@npm:0.15.2"
@@ -15421,6 +15473,7 @@ __metadata:
html-inline-script-webpack-plugin: ^2.0.3
html-loader: ^2.1.2
html-webpack-plugin: ^5.5.3
+ i18next: ^23.8.2
image-webpack-loader: ^7.0.1
ipfs-deploy: ^12.0.1
jsonwebtoken: ^9.0.1
@@ -15446,6 +15499,7 @@ __metadata:
react: ^17.0.2
react-dom: ^17.0.2
react-helmet: ^6.1.0
+ react-i18next: ^14.0.5
react-immutable-proptypes: ^2.2.0
react-redux: ^7.2.9
react-router: ^5.3.4
@@ -17529,6 +17583,13 @@ __metadata:
languageName: node
linkType: hard
+"void-elements@npm:3.1.0":
+ version: 3.1.0
+ resolution: "void-elements@npm:3.1.0"
+ checksum: 0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f
+ languageName: node
+ linkType: hard
+
"watchpack@npm:^2.4.0":
version: 2.4.0
resolution: "watchpack@npm:2.4.0"