├── .gitignore ├── demo ├── index.html └── index.js ├── webpack.config.js ├── devServer.js ├── .eslintrc ├── dist └── ReactLanguage.js ├── package.json ├── README.md └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | coverage.html 3 | /coverage 4 | .DS_Store 5 | node_modules 6 | publish 7 | *.sock 8 | *.swp 9 | *.bat 10 | *.sh 11 | *.log 12 | .idea 13 | /components 14 | typings 15 | jsconfig.json 16 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 |
13 | loading... 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | 'ReactLanguage': "./src/index.js" 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | library: 'ReactLanguage', 11 | libraryTarget: 'umd', 12 | filename: '[name].js' 13 | }, 14 | externals: {'react': 'React', 'react-dom': 'ReactDOM'}, 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.optimize.UglifyJsPlugin({ 18 | compressor: { 19 | warnings: false 20 | } 21 | }) 22 | ], 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.jsx?$/, 27 | loader: 'babel', 28 | query: { 29 | presets: ["react", "es2015"] 30 | } 31 | }, 32 | ] 33 | } 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /devServer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var express = require('express'); 5 | var webpack = require('webpack'); 6 | 7 | var config = require("./webpack.config.js"); 8 | 9 | var app = express(); 10 | var compiler = webpack(config); 11 | 12 | config.entry = { 13 | demo: [ 14 | 'webpack-hot-middleware/client', 15 | './demo/index.js' 16 | ] 17 | }; 18 | config.module.loaders.push({ 19 | test: /\.jsx?$/, 20 | loader: 'babel', 21 | query: { 22 | presets: ["react", "es2015", "react-hmre"] 23 | } 24 | }); 25 | config.plugins = [new webpack.HotModuleReplacementPlugin()]; 26 | var compiler = webpack(config); 27 | 28 | app.use(require('webpack-dev-middleware')(compiler, { 29 | noInfo: true, 30 | publicPath: '/' 31 | })); 32 | 33 | app.use(require('webpack-hot-middleware')(compiler)); 34 | 35 | app.get('/', function(req, res) { 36 | res.sendFile(path.join(__dirname, 'demo/index.html')); 37 | }); 38 | 39 | app.listen(3000, 'localhost', function(err) { 40 | if (err) { 41 | console.log(err); 42 | return; 43 | } 44 | 45 | console.log('Listening at http://localhost:3000'); 46 | }); 47 | 48 | 49 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import * as ReactLanguage from '../src'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | 5 | var Ja = ReactLanguage.create('ja'); 6 | var Cn = ReactLanguage.create(function (lang) { 7 | return lang.toLowerCase().indexOf('zh-') === 0; 8 | }); 9 | var En = ReactLanguage.create(true); 10 | 11 | var Demo = React.createClass({ 12 | handleClick: function (lang) { 13 | ReactLanguage.setLanguage(lang); 14 | this.forceUpdate(); 15 | }, 16 | 17 | render: function () { 18 | return ( 19 |
20 | 你好,世界 21 | Hello world. 22 | こんにちは世界 23 | 24 |
25 | English 26 | 中文 27 | 日本語 28 | Français 29 |
30 |
31 | ); 32 | } 33 | }) 34 | 35 | ReactDOM.render( 36 | , 37 | document.querySelector('#demo') 38 | ); 39 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "ecmascript": 6, 4 | "jsx": true, 5 | "modules": true 6 | }, 7 | "env": { 8 | "es6": true, 9 | "browser": true, 10 | "node": true 11 | }, 12 | "parser": "babel-eslint", 13 | "rules": { 14 | "jsx-quotes": 1, 15 | "quotes": [2, "single"], 16 | "no-undef": [2], 17 | "no-unused-vars": [2, {vars: local}], 18 | "babel/generator-star-spacing": 1, 19 | "babel/object-shorthand": 1, 20 | "babel/arrow-parens": 1, 21 | "babel/no-await-in-loop": 1, 22 | "react/jsx-boolean-value": 1, 23 | "react/jsx-key": 1, 24 | "react/jsx-no-duplicate-props": 1, 25 | "react/jsx-no-undef": 1, 26 | "react/jsx-pascal-case": 1, 27 | "react/jsx-uses-react": 1, 28 | "react/jsx-uses-vars": 1, 29 | "react/no-deprecated": 1, 30 | "react/no-did-mount-set-state": 1, 31 | "react/no-did-update-set-state": 1, 32 | "react/no-direct-mutation-state": 1, 33 | "react/no-is-mounted": 1, 34 | "react/no-unknown-property": 1, 35 | "react/prefer-es6-class": 1, 36 | "react/prop-types": 1, 37 | "react/self-closing-comp": 1, 38 | "react/sort-comp": 1, 39 | "react/sort-prop-types": 1, 40 | "react/wrap-multilines": 1 41 | }, 42 | "plugins": [ 43 | "babel", 44 | "react" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /dist/ReactLanguage.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("React")):"function"==typeof define&&define.amd?define(["React"],t):"object"==typeof exports?exports.ReactLanguage=t(require("React")):e.ReactLanguage=t(e.React)}(this,function(e){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={exports:{},id:o,loaded:!1};return e[o].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function o(){return p||(s&&(p=s.getItem(f)),p||(p=(window.navigator.language||window.navigator.userLanguage||"").toLowerCase()),r()),p}function r(){g=!0,d.forEach(function(e){e(o())&&(g=!1)})}function a(e){return function(t){return e.cache=e.cache||{},t in e.cache?e.cache[t]:e.cache[t]=e(t)}}function c(e){p=e,r(),s&&s.setItem(f,p)}function u(e){function t(e){var t=e.children,r=e.tag,a=n(o());return a?(0,i.isValidElement)(t)?i.Children.only(t):(0,i.createElement)(r,{},t):i.DOM.noscript()}var n=void 0;return e===!0?n=function(){return g}:(n=a("function"==typeof e?e:function(t){return e===t.toLowerCase()}),d.push(n),r()),t.propTypes={children:i.PropTypes.any,tag:i.PropTypes.string},t.defaultProps={tag:"span"},t}Object.defineProperty(t,"__esModule",{value:!0}),t.getLanguage=o,t.setLanguage=c,t.create=u;var i=n(1),f="17tee190yt8gs",s=window.localStorage||null,p=void 0,d=[],g=!0},function(t,n){t.exports=e}])}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-language", 3 | "author": "lobos841@gmail.com", 4 | "version": "0.1.3", 5 | "description": "a React component for dev i18n webpage", 6 | "main": "./src/index.js", 7 | "scripts": { 8 | "dev": "node devServer.js", 9 | "build": "env NODE_ENV=production webpack --config webpack.config.prod.js", 10 | "lint": "./node_modules/.bin/eslint src" 11 | }, 12 | "keywords": [ 13 | "react i18n", 14 | "react-language" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/Lobos/react-language.git" 19 | }, 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/lobos/react-language/issues" 23 | }, 24 | "devDependencies": { 25 | "babel": "^6.5.2", 26 | "babel-core": "~6.7.2", 27 | "babel-eslint": "^6.0.4", 28 | "babel-loader": "~6.2.4", 29 | "babel-plugin-react-transform": "~2.0.2", 30 | "babel-preset-es2015": "~6.6.0", 31 | "babel-preset-es2015-loose": "~7.0.0", 32 | "babel-preset-react": "~6.5.0", 33 | "babel-preset-react-hmre": "~1.1.1", 34 | "eslint": "~2.4.0", 35 | "eslint-plugin-babel": "~3.1.0", 36 | "eslint-plugin-react": "~4.2.3", 37 | "express": "~4.13.4", 38 | "react": "^15.0.2", 39 | "react-dom": "^15.0.2", 40 | "webpack": "~1.12.14", 41 | "webpack-dev-middleware": "~1.5.1", 42 | "webpack-hot-middleware": "~2.10.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReactLanguage 2 | This is a React component for build i18n website more effortless. 3 | [online example](https://jsfiddle.net/lobos/hma00jf2/) 4 | 5 | # Install 6 | ``` 7 | npm install react-language 8 | ``` 9 | 10 | # Usage 11 | ```javascript 12 | var ReactLanguage = require('react-language'); 13 | 14 | /* set currentLanguage, if currentLanguage not set, 15 | * use window.navigator.language || window.navigator.userLanguage 16 | * setLanguage also store the language to localStorage, reopen the web page 17 | * will getItem from localStorage before get from window.navigator 18 | * notice: ReactLanguage converted navigator.language to lower case 19 | */ 20 | ReactLanguage.setLanguage('xxx'); 21 | 22 | 23 | /* create method return a ReactComponent, it must and only pass one argument. 24 | * if argument === true, this component will be the default component, 25 | * if no other created component match currentLanguge, this content(children) will show. 26 | * Ther is only one default component in a project. 27 | */ 28 | const En = ReactLanguage.create(true); 29 | 30 | 31 | // string argument, if argument === currentLanguage, the content(children) will show. 32 | const Ja = ReactLanguage.create('ja'); 33 | 34 | 35 | // function argument, if return true, the content(children) will show. 36 | const Cn = ReactLanguage.create(function (currentLanguage) { 37 | // notice: currentLanguage was converted to lower case 38 | return currentLanguage.indexOf('zh-') === 0; 39 | }); 40 | 41 | 42 | .... 43 | 44 |
45 | 你好,世界 46 | Hello world. 47 | こんにちは世界 48 |
49 | 50 | ====================================================== 51 | rendered result: 52 | currentLanguage === 'en-US' =>
Hello world.
53 | currentLanguage === 'zh-CN' =>
你好,世界
54 | currentLanguage === 'ja' =>
こんにちは世界
55 | currentLanguage === 'other language' =>
Hello world.
56 | ``` 57 | 58 | # props 59 | created component only has two props 60 | - children: content, anything 61 | - tag: ReactElement type string, like 'div', 'a', 'span'... if children is an Array or a string, render a new ReactElement wrapped by the tag. default value is 'span'. 62 | 63 | #license 64 | 65 | The MIT License (MIT) 66 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { PropTypes, isValidElement, Children, createElement, DOM } from 'react'; 2 | 3 | const STORAGE_KEY = '17tee190yt8gs'; 4 | const storage = window.localStorage || null; 5 | 6 | let currentLanguage; 7 | let createdLanguageList = []; 8 | let noMatchedLanguage = true; 9 | 10 | export function getLanguage () { 11 | if (!currentLanguage) { 12 | if (storage) { 13 | currentLanguage = storage.getItem(STORAGE_KEY); 14 | } 15 | 16 | if (!currentLanguage) { 17 | currentLanguage = (window.navigator.language || window.navigator.userLanguage || '').toLowerCase(); 18 | } 19 | 20 | checkLanguageList() 21 | } 22 | 23 | return currentLanguage; 24 | } 25 | 26 | // If created Language List has Component match currentLanguage, set noMatchedLanguage to false. 27 | function checkLanguageList () { 28 | noMatchedLanguage = true; 29 | createdLanguageList.forEach((f) => { 30 | if (f(getLanguage())) { 31 | noMatchedLanguage = false; 32 | } 33 | }); 34 | } 35 | 36 | // performance optimize 37 | function memoize(fn) { 38 | return function (lang) { 39 | fn.cache = fn.cache || {}; 40 | 41 | return (lang in fn.cache) ? 42 | fn.cache[lang] : 43 | fn.cache[lang] = fn(lang); 44 | }; 45 | } 46 | 47 | // set currentLanguage, store language to LocalStorage for next visit. 48 | export function setLanguage (lang) { 49 | currentLanguage = lang; 50 | checkLanguageList(); 51 | if (storage) { 52 | storage.setItem(STORAGE_KEY, currentLanguage); 53 | } 54 | } 55 | 56 | export function create (lang) { 57 | let detect; 58 | 59 | // if lang === true, create default Language Component. 60 | if (lang === true) { 61 | // if currentLanguage has no other matched Language, show default Language. 62 | detect = () => noMatchedLanguage; 63 | } else { 64 | detect = memoize(typeof lang === 'function' ? lang : (l) => lang === l.toLowerCase()); 65 | createdLanguageList.push(detect); 66 | checkLanguageList(); 67 | } 68 | 69 | // Component ====================================== 70 | function Language (props) { 71 | const { children, tag } = props; 72 | 73 | let isShow = detect(getLanguage()); 74 | if (!isShow) { 75 | return DOM.noscript(); 76 | } 77 | 78 | if (isValidElement(children)) { 79 | return Children.only(children); 80 | } else { 81 | return createElement(tag, {}, children); 82 | } 83 | } 84 | 85 | Language.propTypes = { 86 | children: PropTypes.any, 87 | tag: PropTypes.string 88 | }; 89 | 90 | Language.defaultProps = { 91 | tag: 'span' 92 | }; 93 | 94 | return Language; 95 | } 96 | 97 | --------------------------------------------------------------------------------