From 62f7780e3ce9e0b52010548841619f32ae005ee5 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
---
api/server.mjs | 3 +
locales/en.json | 5 +
package.json | 4 +
src/core/app/actions.js | 5 +-
src/core/i18n/actions.js | 10 +
src/core/i18n/index.js | 3 +
src/core/i18n/reducer.js | 17 ++
src/core/i18n/sagas.js | 37 ++++
src/core/reducers.js | 4 +-
src/core/sagas.js | 4 +-
src/i18n.js | 20 ++
src/index.js | 1 +
src/views/components/app/app.js | 3 +-
.../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 | 128 ++++++------
webpack/webpack.dev.babel.mjs | 7 +
yarn.lock | 197 +++++++++++++++++-
19 files changed, 420 insertions(+), 77 deletions(-)
create mode 100644 locales/en.json
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.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/api/server.mjs b/api/server.mjs
index 4a4d1ab4..92269ca3 100644
--- a/api/server.mjs
+++ b/api/server.mjs
@@ -70,6 +70,9 @@ api.use((req, res, next) => {
const resourcesPath = path.join(__dirname, '..', 'resources')
api.use('/resources', serveStatic(resourcesPath))
+const localesPath = path.join(__dirname, '..', 'locales')
+api.use('/locales', serveStatic(localesPath))
+
const dataPath = path.join(__dirname, '..', 'data')
api.use('/data', serveStatic(dataPath))
diff --git a/locales/en.json b/locales/en.json
new file mode 100644
index 00000000..321c1a50
--- /dev/null
+++ b/locales/en.json
@@ -0,0 +1,5 @@
+{
+ "menu": {
+ "introduction": "Introduction"
+ }
+}
diff --git a/package.json b/package.json
index 28b85565..fb880f86 100644
--- a/package.json
+++ b/package.json
@@ -96,6 +96,8 @@
"fetch-cheerio-object": "^1.3.0",
"front-matter": "^4.0.2",
"fs-extra": "^11.1.1",
+ "i18next": "^23.8.2",
+ "i18next-http-backend": "^2.4.3",
"jsonwebtoken": "^9.0.1",
"knex": "^0.95.15",
"markdown-it": "^12.3.2",
@@ -114,6 +116,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",
@@ -140,6 +143,7 @@
"compression-webpack-plugin": "^10.0.0",
"concurrently": "^8.2.0",
"copy-text-to-clipboard": "^3.2.0",
+ "copy-webpack-plugin": "^12.0.2",
"cross-env": "^7.0.3",
"css-loader": "6.8.1",
"deepmerge": "4.3.1",
diff --git a/src/core/app/actions.js b/src/core/app/actions.js
index e178832a..c671f3ee 100644
--- a/src/core/app/actions.js
+++ b/src/core/app/actions.js
@@ -1,11 +1,12 @@
export const appActions = {
INIT_APP: 'INIT_APP',
- init: ({ token, key }) => ({
+ init: ({ token, key, locale }) => ({
type: appActions.INIT_APP,
payload: {
token,
- key
+ key,
+ locale
}
})
}
diff --git a/src/core/i18n/actions.js b/src/core/i18n/actions.js
new file mode 100644
index 00000000..4e968253
--- /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..1891169f
--- /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_LOCALE:
+ 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..06135e04
--- /dev/null
+++ b/src/core/i18n/sagas.js
@@ -0,0 +1,37 @@
+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({ payload }) {
+ if (payload.locale) {
+ yield put(i18nActions.change_locale(payload.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.js b/src/i18n.js
new file mode 100644
index 00000000..3d1c3f70
--- /dev/null
+++ b/src/i18n.js
@@ -0,0 +1,20 @@
+import { initReactI18next } from 'react-i18next'
+import i18n from 'i18next'
+import HttpBackend from 'i18next-http-backend'
+
+i18n
+ .use(HttpBackend)
+ .use(initReactI18next)
+ .init({
+ // detection
+ debug: true,
+ backend: {
+ // Configuration options for the backend plugin
+ loadPath: '/locales/{{lng}}.json' // Path to the translation files
+ },
+ lng: 'en',
+ fallbackLng: 'en'
+ // supportedLngs
+ })
+
+export default i18n
diff --git a/src/index.js b/src/index.js
index a587c113..5dad6c68 100644
--- a/src/index.js
+++ b/src/index.js
@@ -4,6 +4,7 @@ import '@babel/polyfill'
import React from 'react'
import { render } from 'react-dom'
+import './i18n'
import Root from '@views/root'
document.addEventListener('DOMContentLoaded', () => {
diff --git a/src/views/components/app/app.js b/src/views/components/app/app.js
index 5ae1193f..61685dc5 100644
--- a/src/views/components/app/app.js
+++ b/src/views/components/app/app.js
@@ -15,7 +15,8 @@ export default class App extends React.Component {
async componentDidMount() {
const token = await localStorageAdapter.getItem('token')
const key = await localStorageAdapter.getItem('key')
- this.props.init({ token, key })
+ const locale = await localStorageAdapter.getItem('locale')
+ this.props.init({ token, key, locale })
this.props.getRepresentatives()
this.props.getNetworkStats()
this.props.getGithubEvents()
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..7ef291c5 100644
--- a/src/views/components/menu/menu.js
+++ b/src/views/components/menu/menu.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { useState } from 'react'
import { NavLink } from 'react-router-dom'
import PropTypes from 'prop-types'
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer'
@@ -6,19 +6,22 @@ import CloseIcon from '@material-ui/icons/Close'
import SpeedDial from '@material-ui/lab/SpeedDial'
import SpeedDialAction from '@material-ui/lab/SpeedDialAction'
import HomeIcon from '@material-ui/icons/Home'
+import { useTranslation } from 'react-i18next'
import SearchBar from '@components/search-bar'
import history from '@core/history'
+import ChangeLocale from '@components/change-locale'
import './menu.styl'
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent)
function MenuSections() {
+ const { t } = useTranslation()
return (
-
Introduction
+
{t('menu.introduction')}
Overview
Advantages
@@ -115,74 +118,69 @@ function MenuSections() {
)
}
-export default class Menu extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- open: false
- }
- }
+export default function Menu(props) {
+ const [open, setOpen] = useState(false)
- handleOpen = () => this.setState({ open: true })
- handleClose = () => this.setState({ open: false })
- handleClick = () => this.setState({ open: !this.state.open })
- handleHomeClick = () => history.push('/')
+ const handleOpen = () => setOpen(true)
+ const handleClose = () => setOpen(false)
+ const handleClick = () => setOpen(!open)
+ const handleHomeClick = () => history.push('/')
- render() {
- const { hide, hideSearch, hide_speed_dial } = this.props
- const isHome = history.location.pathname === '/'
- const isMobile = window.innerWidth < 750
+ const { hide, hideSearch, hide_speed_dial } = props
+ const isHome = history.location.pathname === '/'
+ const isMobile = window.innerWidth < 750
- return (
-
-
-
-
- {!hide_speed_dial && (
-
- }
- openIcon={
}>
- {!isHome && (
-
}
- tooltipTitle='Home'
- tooltipPlacement={isMobile ? 'left' : 'right'}
- onClick={this.handleHomeClick}
- />
- )}
-
- )}
-
- {isHome ? (
-
NANO
- ) : (
-
- NANO
-
+ return (
+
+
+
+
+
+ {!hide_speed_dial && (
+
+ }
+ openIcon={}>
+ {!isHome && (
+ }
+ tooltipTitle='Home'
+ tooltipPlacement={isMobile ? 'left' : 'right'}
+ onClick={handleHomeClick}
+ />
)}
- {!hideSearch && }
- {!hide && }
-
+
+ )}
+
+ {isHome ? (
+
NANO
+ ) : (
+
+ NANO
+
+ )}
+ {!hideSearch &&
}
+ {!hide &&
}
+
- )
- }
+
+ )
}
Menu.propTypes = {
diff --git a/webpack/webpack.dev.babel.mjs b/webpack/webpack.dev.babel.mjs
index 737b25dc..cf883f04 100644
--- a/webpack/webpack.dev.babel.mjs
+++ b/webpack/webpack.dev.babel.mjs
@@ -6,6 +6,7 @@ import path from 'path'
import webpack from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import CircularDependencyPlugin from 'circular-dependency-plugin'
+import CopyWebpackPlugin from 'copy-webpack-plugin'
import base from './webpack.base.babel.mjs'
@@ -44,6 +45,12 @@ export default base({
new CircularDependencyPlugin({
exclude: /a\.js|node_modules/, // exclude node_modules
failOnError: false // show a warning when there is a circular dependency
+ }),
+ new CopyWebpackPlugin({
+ patterns: [
+ { from: 'locales', to: 'locales' },
+ { from: 'resources', to: 'resources' }
+ ]
})
],
diff --git a/yarn.lock b/yarn.lock
index 4be8228a..dff92bb2 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"
@@ -3267,6 +3276,13 @@ __metadata:
languageName: node
linkType: hard
+"@sindresorhus/merge-streams@npm:^2.1.0":
+ version: 2.2.1
+ resolution: "@sindresorhus/merge-streams@npm:2.2.1"
+ checksum: edb3d7b8fd9cdf4976c32483f073bb903f8ee94a2f1e93b47cc7d9205ac77cf64fce751ea45b1b39036a8de19d980cb942fff596c0d200232f3e1d4835c8ca4b
+ languageName: node
+ linkType: hard
+
"@smithy/abort-controller@npm:^2.1.1":
version: 2.1.1
resolution: "@smithy/abort-controller@npm:2.1.1"
@@ -4694,7 +4710,7 @@ __metadata:
languageName: node
linkType: hard
-"ajv-keywords@npm:^5.0.0":
+"ajv-keywords@npm:^5.0.0, ajv-keywords@npm:^5.1.0":
version: 5.1.0
resolution: "ajv-keywords@npm:5.1.0"
dependencies:
@@ -4717,7 +4733,7 @@ __metadata:
languageName: node
linkType: hard
-"ajv@npm:^8.0.0, ajv@npm:^8.8.0":
+"ajv@npm:^8.0.0, ajv@npm:^8.8.0, ajv@npm:^8.9.0":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
dependencies:
@@ -5414,7 +5430,7 @@ __metadata:
languageName: node
linkType: hard
-"braces@npm:^3.0.1, braces@npm:~3.0.2":
+"braces@npm:^3.0.1, braces@npm:^3.0.2, braces@npm:~3.0.2":
version: 3.0.2
resolution: "braces@npm:3.0.2"
dependencies:
@@ -6419,6 +6435,22 @@ __metadata:
languageName: node
linkType: hard
+"copy-webpack-plugin@npm:^12.0.2":
+ version: 12.0.2
+ resolution: "copy-webpack-plugin@npm:12.0.2"
+ dependencies:
+ fast-glob: ^3.3.2
+ glob-parent: ^6.0.1
+ globby: ^14.0.0
+ normalize-path: ^3.0.0
+ schema-utils: ^4.2.0
+ serialize-javascript: ^6.0.2
+ peerDependencies:
+ webpack: ^5.1.0
+ checksum: 98127735336c6db5924688486d3a1854a41835963d0c0b81695b2e3d58c6675164be7d23dee7090b84a56d3c9923175d3d0863ac1942bcc3317d2efc1962b927
+ languageName: node
+ linkType: hard
+
"core-js-compat@npm:^3.31.0":
version: 3.31.1
resolution: "core-js-compat@npm:3.31.1"
@@ -6473,6 +6505,15 @@ __metadata:
languageName: node
linkType: hard
+"cross-fetch@npm:4.0.0":
+ version: 4.0.0
+ resolution: "cross-fetch@npm:4.0.0"
+ dependencies:
+ node-fetch: ^2.6.12
+ checksum: ecca4f37ffa0e8283e7a8a590926b66713a7ef7892757aa36c2d20ffa27b0ac5c60dcf453119c809abe5923fc0bae3702a4d896bfb406ef1077b0d0018213e24
+ languageName: node
+ linkType: hard
+
"cross-spawn@npm:^5.0.1":
version: 5.1.0
resolution: "cross-spawn@npm:5.1.0"
@@ -8365,6 +8406,19 @@ __metadata:
languageName: node
linkType: hard
+"fast-glob@npm:^3.3.2":
+ version: 3.3.2
+ resolution: "fast-glob@npm:3.3.2"
+ dependencies:
+ "@nodelib/fs.stat": ^2.0.2
+ "@nodelib/fs.walk": ^1.2.3
+ glob-parent: ^5.1.2
+ merge2: ^1.3.0
+ micromatch: ^4.0.4
+ checksum: 900e4979f4dbc3313840078419245621259f349950411ca2fa445a2f9a1a6d98c3b5e7e0660c5ccd563aa61abe133a21765c6c0dec8e57da1ba71d8000b05ec1
+ languageName: node
+ linkType: hard
+
"fast-json-stable-stringify@npm:^2.0.0":
version: 2.1.0
resolution: "fast-json-stable-stringify@npm:2.1.0"
@@ -9073,7 +9127,7 @@ __metadata:
languageName: node
linkType: hard
-"glob-parent@npm:^5.1.0, glob-parent@npm:~5.1.2":
+"glob-parent@npm:^5.1.0, glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
version: 5.1.2
resolution: "glob-parent@npm:5.1.2"
dependencies:
@@ -9082,7 +9136,7 @@ __metadata:
languageName: node
linkType: hard
-"glob-parent@npm:^6.0.2":
+"glob-parent@npm:^6.0.1, glob-parent@npm:^6.0.2":
version: 6.0.2
resolution: "glob-parent@npm:6.0.2"
dependencies:
@@ -9202,6 +9256,20 @@ __metadata:
languageName: node
linkType: hard
+"globby@npm:^14.0.0":
+ version: 14.0.1
+ resolution: "globby@npm:14.0.1"
+ dependencies:
+ "@sindresorhus/merge-streams": ^2.1.0
+ fast-glob: ^3.3.2
+ ignore: ^5.2.4
+ path-type: ^5.0.0
+ slash: ^5.1.0
+ unicorn-magic: ^0.1.0
+ checksum: 33568444289afb1135ad62d52d5e8412900cec620e3b6ece533afa46d004066f14b97052b643833d7cf4ee03e7fac571430130cde44c333df91a45d313105170
+ languageName: node
+ linkType: hard
+
"gopd@npm:^1.0.1":
version: 1.0.1
resolution: "gopd@npm:1.0.1"
@@ -9646,6 +9714,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 +9943,24 @@ __metadata:
languageName: node
linkType: hard
+"i18next-http-backend@npm:^2.4.3":
+ version: 2.4.3
+ resolution: "i18next-http-backend@npm:2.4.3"
+ dependencies:
+ cross-fetch: 4.0.0
+ checksum: 8abd3966475828d677f5f5351eee93ba6412a2d3e4e663966af0762ea98a605d4b8dce6966650d28acf5411e39fd44cc8aae87ed94b39b627791eeba22a1e73e
+ 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"
@@ -12479,6 +12574,16 @@ __metadata:
languageName: node
linkType: hard
+"micromatch@npm:^4.0.4":
+ version: 4.0.5
+ resolution: "micromatch@npm:4.0.5"
+ dependencies:
+ braces: ^3.0.2
+ picomatch: ^2.3.1
+ checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc
+ languageName: node
+ linkType: hard
+
"mime-db@npm:1.47.0, mime-db@npm:>= 1.43.0 < 2, mime-db@npm:^1.28.0":
version: 1.47.0
resolution: "mime-db@npm:1.47.0"
@@ -13139,7 +13244,7 @@ __metadata:
languageName: node
linkType: hard
-"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.8":
+"node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.8":
version: 2.7.0
resolution: "node-fetch@npm:2.7.0"
dependencies:
@@ -14027,6 +14132,13 @@ __metadata:
languageName: node
linkType: hard
+"path-type@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "path-type@npm:5.0.0"
+ checksum: 15ec24050e8932c2c98d085b72cfa0d6b4eeb4cbde151a0a05726d8afae85784fc5544f733d8dfc68536587d5143d29c0bd793623fad03d7e61cc00067291cd5
+ languageName: node
+ linkType: hard
+
"pend@npm:~1.2.0":
version: 1.2.0
resolution: "pend@npm:1.2.0"
@@ -14055,7 +14167,7 @@ __metadata:
languageName: node
linkType: hard
-"picomatch@npm:^2.0.4":
+"picomatch@npm:^2.0.4, picomatch@npm:^2.3.1":
version: 2.3.1
resolution: "picomatch@npm:2.3.1"
checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf
@@ -14641,6 +14753,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 +15143,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"
@@ -15391,6 +15528,7 @@ __metadata:
concurrently: ^8.2.0
connected-react-router: ^6.9.3
copy-text-to-clipboard: ^3.2.0
+ copy-webpack-plugin: ^12.0.2
cors: ^2.8.5
cross-env: ^7.0.3
css-loader: 6.8.1
@@ -15421,6 +15559,8 @@ __metadata:
html-inline-script-webpack-plugin: ^2.0.3
html-loader: ^2.1.2
html-webpack-plugin: ^5.5.3
+ i18next: ^23.8.2
+ i18next-http-backend: ^2.4.3
image-webpack-loader: ^7.0.1
ipfs-deploy: ^12.0.1
jsonwebtoken: ^9.0.1
@@ -15446,6 +15586,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
@@ -15581,6 +15722,18 @@ __metadata:
languageName: node
linkType: hard
+"schema-utils@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "schema-utils@npm:4.2.0"
+ dependencies:
+ "@types/json-schema": ^7.0.9
+ ajv: ^8.9.0
+ ajv-formats: ^2.1.1
+ ajv-keywords: ^5.1.0
+ checksum: 26a0463d47683258106e6652e9aeb0823bf0b85843039e068b57da1892f7ae6b6b1094d48e9ed5ba5cbe9f7166469d880858b9d91abe8bd249421eb813850cde
+ languageName: node
+ linkType: hard
+
"seamless-immutable@npm:^7.1.3":
version: 7.1.4
resolution: "seamless-immutable@npm:7.1.4"
@@ -15771,6 +15924,15 @@ __metadata:
languageName: node
linkType: hard
+"serialize-javascript@npm:^6.0.2":
+ version: 6.0.2
+ resolution: "serialize-javascript@npm:6.0.2"
+ dependencies:
+ randombytes: ^2.1.0
+ checksum: c4839c6206c1d143c0f80763997a361310305751171dd95e4b57efee69b8f6edd8960a0b7fbfc45042aadff98b206d55428aee0dc276efe54f100899c7fa8ab7
+ languageName: node
+ linkType: hard
+
"serve-index@npm:^1.9.1":
version: 1.9.1
resolution: "serve-index@npm:1.9.1"
@@ -15939,6 +16101,13 @@ __metadata:
languageName: node
linkType: hard
+"slash@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "slash@npm:5.1.0"
+ checksum: 70434b34c50eb21b741d37d455110258c42d2cf18c01e6518aeb7299f3c6e626330c889c0c552b5ca2ef54a8f5a74213ab48895f0640717cacefeef6830a1ba4
+ languageName: node
+ linkType: hard
+
"smart-buffer@npm:^4.2.0":
version: 4.2.0
resolution: "smart-buffer@npm:4.2.0"
@@ -17250,6 +17419,13 @@ __metadata:
languageName: node
linkType: hard
+"unicorn-magic@npm:^0.1.0":
+ version: 0.1.0
+ resolution: "unicorn-magic@npm:0.1.0"
+ checksum: 48c5882ca3378f380318c0b4eb1d73b7e3c5b728859b060276e0a490051d4180966beeb48962d850fd0c6816543bcdfc28629dcd030bb62a286a2ae2acb5acb6
+ languageName: node
+ linkType: hard
+
"uniq@npm:^1.0.1":
version: 1.0.1
resolution: "uniq@npm:1.0.1"
@@ -17529,6 +17705,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"