├── .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 |
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' =>
53 | currentLanguage === 'zh-CN' => 你好,世界
54 | currentLanguage === 'ja' => こんにちは世界
55 | currentLanguage === 'other language' =>
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 |
--------------------------------------------------------------------------------