├── .babelrc ├── .gitignore ├── Readme.md ├── example ├── .babelrc ├── App.js ├── index.html ├── index.js ├── package.json ├── server.js ├── translations.js └── webpack.config.js ├── package.json └── src ├── actions.js ├── context.js ├── index.js ├── provider.js ├── reducer.js ├── utils.js └── withTranslate.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | lib -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # React Multilingual with redux 2 | 3 | A simple and slim (Only 6KB) multi-lingual component for React with Redux without react-intl or react-i18n 4 | 5 | ## Install 6 | 7 | ``` 8 | npm i react-redux-multilingual --save 9 | ``` 10 | 11 | ## Wiring it up 12 | 13 | ```js 14 | import translations from './translations' 15 | import { IntlReducer as Intl, IntlProvider } from 'react-redux-multilingual' 16 | import { createStore, combineReducers } from 'redux' 17 | import { Provider } from 'react-redux' 18 | import App from './App' 19 | 20 | let reducers = combineReducers(Object.assign({}, { Intl })) 21 | let store = createStore(reducers) 22 | 23 | ReactDOM.render( 24 | 25 | 26 | 27 | 28 | 29 | , document.getElementById('root')) 30 | ``` 31 | 32 | ## Translations format 33 | The translations props accepts object in this format 34 | 35 | ```js 36 | { 37 | en: { 38 | locale: 'en-US', 39 | messages: { 40 | hello: 'how are you {name}' 41 | } 42 | }, 43 | zh: { 44 | locale: 'zh', 45 | messages: { 46 | hello: '你好!你好吗 {name}' 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | ## Translate using hook 53 | 54 | ```js 55 | import { useTranslate } from 'react-redux-multilingual' 56 | 57 | function App () { 58 | const t = useTranslate() 59 | 60 | return ( 61 |
62 | {t('hello', { name: 'Foo Bar'})} 63 |
64 | ) 65 | } 66 | ``` 67 | 68 | ## Translate using higher-order component (HOC) 69 | 70 | ```js 71 | import { withTranslate } from 'react-redux-multilingual' 72 | 73 | const App = ({ translate }) = { 74 | return ( 75 |
76 | {translate('hello', { name: 'John Doe' })} 77 |
78 | ) 79 | } 80 | 81 | module.exports = withTranslate(App) 82 | ``` 83 | 84 | ## Translate using Context 85 | 86 | ```js 87 | const App = (props, context) => { 88 | return ( 89 |
90 | {context.translate('hello', { name: 'John Doe' })} 91 |
92 | ) 93 | } 94 | 95 | App.contextTypes = { 96 | translate: React.propTypes.func 97 | } 98 | 99 | module.exports = App 100 | ``` 101 | 102 | ## Setting the initial locale 103 | 104 | Option 1: Pass your locale to initial state of your reducer 105 | ```js 106 | let store = createStore(reducers, { Intl: { locale: 'zh'}}) 107 | ``` 108 | 109 | Option 2: Dispatch an action to change locale 110 | ```js 111 | import { IntlActions } from 'react-redux-multilingual' 112 | let store = createStore(reducers) 113 | store.dispatch(IntlActions.setLocale('zh')) 114 | ``` 115 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import { withTranslate, IntlActions, useTranslate } from 'react-redux-multilingual' 4 | 5 | const App = ({ dispatch }) => { 6 | 7 | const translate = useTranslate() 8 | return ( 9 |
10 |

Hey there

11 | {translate('hello')} 12 | 13 |

14 | 18 | 22 |

23 |
24 | ) 25 | } 26 | 27 | module.exports = connect()(App) -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import translations from './translations' 4 | import { IntlReducer as Intl, IntlProvider } from 'react-redux-multilingual' 5 | import { createStore, combineReducers } from 'redux' 6 | import { Provider } from 'react-redux' 7 | import App from './App' 8 | 9 | const defaultLocale = 'zh' 10 | const reducers = combineReducers(Object.assign({}, { Intl })) 11 | const store = createStore(reducers, { Intl: { locale: defaultLocale }}) 12 | 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | 19 | , document.getElementById('root')) -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "react": "^16.4.1", 13 | "react-dom": "^16.4.1", 14 | "react-redux": "^4.4.5", 15 | "react-redux-multilingual": "^1.0.4", 16 | "redux": "^3.5.2" 17 | }, 18 | "devDependencies": { 19 | "babel-cli": "^6.10.1", 20 | "babel-core": "^6.9.1", 21 | "babel-eslint": "^6.0.4", 22 | "babel-loader": "^6.2.4", 23 | "babel-preset-env": "^1.6.1", 24 | "babel-preset-es2015": "^6.9.0", 25 | "babel-preset-react": "^6.5.0", 26 | "babel-preset-stage-0": "^6.5.0", 27 | "express": "^4.14.0", 28 | "webpack": "^1.13.1", 29 | "webpack-dev-middleware": "^1.6.1", 30 | "webpack-dev-server": ">=3.1.11", 31 | "webpack-hot-middleware": "^2.10.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.get('*', function(req, res) { 17 | res.sendFile(path.join(__dirname, 'index.html')); 18 | }); 19 | 20 | app.listen(3009, 'localhost', function(err) { 21 | if (err) { 22 | console.log(err); 23 | return; 24 | } 25 | 26 | console.log('Listening at http://localhost:3009'); 27 | }); -------------------------------------------------------------------------------- /example/translations.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | en: { 3 | locale: 'en-US', 4 | messages: { 5 | hello: 'Hello! How are you?' 6 | } 7 | }, 8 | zh: { 9 | locale: 'zh', 10 | messages: { 11 | hello: '你好!你好吗' 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: [ 6 | './index' 7 | ], 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: 'bundle.js' 11 | }, 12 | resolve: { 13 | alias: { 14 | 'react-redux-multilingual': path.join(__dirname, './../lib') 15 | }, 16 | fallback: path.resolve(__dirname, './node_modules') 17 | }, 18 | module: { 19 | loaders: [{ 20 | test: /\.js$/, 21 | loaders: ['babel'], 22 | include: path.join(__dirname, './'), 23 | exclude: /(node_modules|bower_components)/ 24 | }] 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-multilingual", 3 | "version": "2.0.4", 4 | "description": "A simple multilingual translate component and HOC for react and redux", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "prettier": "prettier --single-quote --no-semi 'src/*.js' --write", 8 | "release": "npm run build && npm publish", 9 | "build": "BABEL_ENV=production babel src --out-dir lib", 10 | "watch": "BABEL_ENV=production babel --watch src --out-dir lib" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/rmdort/react-redux-multilingual.git" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "translate", 19 | "multilingual" 20 | ], 21 | "author": "Vinay M ", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/rmdort/react-redux-multilingual/issues" 25 | }, 26 | "homepage": "https://github.com/rmdort/react-redux-multilingual#readme", 27 | "dependencies": { 28 | "hoist-non-react-statics": "^1.2.0", 29 | "prettier-standard": "^8.0.1", 30 | "prop-types": "^15.5.8" 31 | }, 32 | "peerDependencies": { 33 | "react": ">16.3.0", 34 | "react-redux": ">4.4.0", 35 | "redux": ">3.3.1" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.10.1", 39 | "babel-core": "^6.9.1", 40 | "babel-eslint": "^6.0.4", 41 | "babel-preset-env": "^1.6.1", 42 | "babel-preset-react": "^6.5.0", 43 | "babel-preset-stage-0": "^6.5.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/actions.js: -------------------------------------------------------------------------------- 1 | export function setLocale(locale) { 2 | return { 3 | type: 'SET_LOCALE', 4 | locale 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | 3 | const TranslateContext = React.createContext(null) 4 | const { 5 | Provider: TranslateProvider, 6 | Consumer: TranslateConsumer 7 | } = TranslateContext 8 | const useTranslate = () => useContext(TranslateContext) 9 | 10 | export { TranslateProvider, TranslateConsumer, useTranslate } 11 | export default TranslateContext 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import IntlReducer from './reducer' 2 | import withTranslate from './withTranslate' 3 | import IntlProvider from './provider' 4 | import * as IntlActions from './actions' 5 | import { translateUtil } from "./utils" 6 | import { useTranslate } from './context' 7 | 8 | export { IntlReducer, withTranslate, IntlProvider, IntlActions, translateUtil, useTranslate } 9 | -------------------------------------------------------------------------------- /src/provider.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { translateKey, translate } from './utils' 5 | import { TranslateProvider } from './context' 6 | 7 | class IntlProvider extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | if (!props.translations || !props.locale) { 11 | let namePart = this.constructor.displayName 12 | ? ' of ' + this.constructor.displayName 13 | : '' 14 | throw new Error( 15 | 'Could not find translations or locale on this.props ' + namePart 16 | ) 17 | } 18 | } 19 | 20 | static propTypes = { 21 | translations: PropTypes.object 22 | } 23 | static defaultProps = { 24 | translations: {} 25 | } 26 | translate = (key, placeholders, isHTML, options = {}) => { 27 | /** 28 | * Accept user defined translate 29 | */ 30 | const translateFn = this.props.translate || translateKey 31 | return translate(translateFn, 32 | this.props.translations, 33 | this.props.locale, 34 | key, 35 | placeholders, 36 | isHTML, 37 | options); 38 | } 39 | render() { 40 | return ( 41 | 42 | {this.props.children} 43 | 44 | ) 45 | } 46 | } 47 | 48 | function mapPropsToState(state) { 49 | const { Intl } = state 50 | return { 51 | ...Intl, 52 | key: Intl.locale 53 | } 54 | } 55 | 56 | export default connect(mapPropsToState)(IntlProvider) 57 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | var initialState = { 2 | locale: 'en' 3 | } 4 | 5 | export default function(state = initialState, action) { 6 | switch (action.type) { 7 | case 'SET_LOCALE': 8 | return { 9 | ...state, 10 | locale: action.locale 11 | } 12 | default: 13 | return state 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export function supplant(s, d) { 4 | for (var p in d) { 5 | s = s.replace(new RegExp('{' + p + '}', 'g'), d[p]) 6 | } 7 | return s 8 | } 9 | 10 | export function translateKey(path, obj, safe) { 11 | return path.split('.').reduce((prev, curr) => { 12 | return !safe ? prev[curr] : prev ? prev[curr] : undefined 13 | }, obj) 14 | } 15 | 16 | export function createHTMLMarkup(html) { 17 | return { __html: html } 18 | } 19 | 20 | export function translate(translateFn, translations, locale, key, placeholders, isHTML, options = {}) { 21 | const result = translateFn( 22 | key, 23 | translations[locale]['messages'] 24 | ); 25 | const tagName = options.tagName || 'div'; 26 | if (typeof placeholders === 'undefined') { 27 | return result 28 | } 29 | const finalResult = supplant(result, placeholders); 30 | return isHTML 31 | ? React.createElement( 32 | tagName, 33 | { dangerouslySetInnerHTML: createHTMLMarkup(finalResult) }, 34 | null 35 | ) 36 | : finalResult 37 | } 38 | 39 | export function translateUtil(translations, locale, key, placeholders, isHTML, options = {}) { 40 | return translate(translateKey, translations,locale,key,placeholders,isHTML,options) 41 | } 42 | -------------------------------------------------------------------------------- /src/withTranslate.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TranslateConsumer } from './context' 3 | 4 | /** 5 | * Access translate function 6 | * @param {Object} WrappedComponent 7 | * @return {Object} 8 | */ 9 | function withTranslate(WrappedComponent) { 10 | return React.forwardRef((props, ref) => ( 11 | 12 | {translate => ( 13 | 14 | )} 15 | 16 | )) 17 | } 18 | 19 | export default withTranslate 20 | --------------------------------------------------------------------------------