Skip to content

Commit

Permalink
feat: add internationalization support
Browse files Browse the repository at this point in the history
  • Loading branch information
mistakia committed Feb 17, 2024
1 parent fa11e6e commit 62f7780
Show file tree
Hide file tree
Showing 19 changed files with 420 additions and 77 deletions.
3 changes: 3 additions & 0 deletions api/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
5 changes: 5 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"menu": {
"introduction": "Introduction"
}
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
5 changes: 3 additions & 2 deletions src/core/app/actions.js
Original file line number Diff line number Diff line change
@@ -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
}
})
}
10 changes: 10 additions & 0 deletions src/core/i18n/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const i18nActions = {
CHANGE_LOCALE: 'CHANGE_LOCALE',

change_locale: (locale) => ({
type: i18nActions.CHANGE_LOCALE,
payload: {
locale
}
})
}
3 changes: 3 additions & 0 deletions src/core/i18n/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { i18nActions } from './actions'
export { i18nReducer } from './reducer'
export { i18nSagas } from './sagas'
17 changes: 17 additions & 0 deletions src/core/i18n/reducer.js
Original file line number Diff line number Diff line change
@@ -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
}
}
37 changes: 37 additions & 0 deletions src/core/i18n/sagas.js
Original file line number Diff line number Diff line change
@@ -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)]
4 changes: 3 additions & 1 deletion src/core/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -28,7 +29,8 @@ const rootReducer = (history) =>
network: networkReducer,
notification: notificationReducer,
posts: postsReducer,
postlists: postlistsReducer
postlists: postlistsReducer,
i18n: i18nReducer
})

export default rootReducer
4 changes: 3 additions & 1 deletion src/core/sagas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -22,6 +23,7 @@ export default function* rootSage() {
...githubIssuesSagas,
...ledgerSagas,
...networkSagas,
...postlistSagas
...postlistSagas,
...i18nSagas
])
}
20 changes: 20 additions & 0 deletions src/i18n.js
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
3 changes: 2 additions & 1 deletion src/views/components/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
32 changes: 32 additions & 0 deletions src/views/components/change-locale/change-locale.js
Original file line number Diff line number Diff line change
@@ -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 (
<FormControl className='change-locale'>
<Select
labelId='change-locale'
id='change-locale'
value={locale}
onChange={(event) => change_locale(event.target.value)}>
<MenuItem value='en'>English</MenuItem>
<MenuItem value='es'>Español</MenuItem>
<MenuItem value='fr'>Français</MenuItem>
<MenuItem value='it'>Italiano</MenuItem>
<MenuItem value='de'>Deutsch</MenuItem>
<MenuItem value='nl'>Nederlands</MenuItem>
<MenuItem value='ru'>Русский</MenuItem>
</Select>
</FormControl>
)
}

ChangeLocale.propTypes = {
change_locale: PropTypes.func.isRequired,
locale: PropTypes.string.isRequired
}
Empty file.
17 changes: 17 additions & 0 deletions src/views/components/change-locale/index.js
Original file line number Diff line number Diff line change
@@ -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)
Loading

0 comments on commit 62f7780

Please sign in to comment.