├── .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 |
--------------------------------------------------------------------------------