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"