├── .babelrc ├── server.js ├── src ├── client.jsx ├── components │ └── App.jsx └── server.jsx ├── README.md ├── .gitignore ├── LICENSE ├── package.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('babel-core/register'); 2 | require('babel-polyfill'); 3 | require('server'); 4 | -------------------------------------------------------------------------------- /src/client.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/App'; 4 | 5 | ReactDOM.render(, document.getElementById('react-view')); 6 | -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class extends Component { 4 | render() { 5 | return ( 6 |
7 |

Hello World!

8 |
9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Internationalizing React App Boilerplate For SmashingMagazine 2 | This is a boilerplate template for the [Internationalizing React Apps article](https://www.smashingmagazine.com/2017/01/internationalizing-react-apps/) 3 | 4 | You may also check for the [solution branch](https://github.com/yury-dymov/smashing-react-i18n/tree/solution) 5 | 6 | # Under The Hood 7 | 8 | * Universal / Isomorphic React App 9 | * Babel 10 | * webpack, webpack-dev-server, and configuration 11 | * express 12 | 13 | # Tutorial Steps 14 | 15 | 1. Implement user's locale detection with `accept-language` library 16 | 2. Translate messages with `react-intl` 17 | 3. Support locale-specific content like numbers and dates 18 | 19 | # License 20 | MIT (c) Yury Dymov 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | public 39 | build 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yury Dymov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/server.jsx: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import React from 'react'; 3 | import ReactDom from 'react-dom/server'; 4 | import App from './components/App'; 5 | 6 | const assetUrl = process.env.NODE_ENV !== 'production' ? 'http://localhost:8050' : '/'; 7 | 8 | function renderHTML(componentHTML) { 9 | return ` 10 | 11 | 12 | 13 | 14 | 15 | Hello React 16 | 17 | 18 |
${componentHTML}
19 | 20 | 21 | 22 | `; 23 | } 24 | 25 | const app = express(); 26 | 27 | app.use((req, res) => { 28 | const componentHTML = ReactDom.renderToString(); 29 | 30 | return res.end(renderHTML(componentHTML)); 31 | }); 32 | 33 | const PORT = process.env.PORT || 3001; 34 | 35 | app.listen(PORT, () => { 36 | console.log(`Server listening on: ${PORT}`); // eslint-disable-line no-console 37 | }); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smashing-react-i18n", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "dist/bundle.js", 6 | "scripts": { 7 | "build": "better-npm-run build", 8 | "webpack-devserver": "webpack-dev-server --debug --hot --devtool eval-source-map --output-pathinfo --watch --colors --inline --content-base public --port 8050 --host 0.0.0.0", 9 | "nodemon": "better-npm-run nodemon" 10 | }, 11 | "betterScripts": { 12 | "build": { 13 | "command": "webpack -p", 14 | "env": { 15 | "NODE_ENV": "production" 16 | } 17 | }, 18 | "nodemon": { 19 | "command": "nodemon server.js", 20 | "env": { 21 | "NODE_PATH": "src" 22 | } 23 | } 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/yury-dymov/smashing-react-i18n.git" 28 | }, 29 | "author": "Yury Dymov", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/yury-dymov/smashing-react-i18n/issues" 33 | }, 34 | "homepage": "https://github.com/yury-dymov/smashing-react-i18n#readme", 35 | "devDependencies": { 36 | "babel-cli": "^6.18.0", 37 | "babel-core": "^6.18.2", 38 | "babel-loader": "^6.2.8", 39 | "babel-polyfill": "^6.16.0", 40 | "babel-preset-es2015": "^6.18.0", 41 | "babel-preset-react": "^6.16.0", 42 | "babel-preset-stage-0": "^6.16.0", 43 | "better-npm-run": "0.0.13", 44 | "clean-webpack-plugin": "^0.1.14", 45 | "nodemon": "1.8.1", 46 | "react-hot-loader": "^1.2.9", 47 | "webpack": "^1.13.3", 48 | "webpack-dev-server": "^1.16.2" 49 | }, 50 | "dependencies": { 51 | "bluebird": "^3.4.7", 52 | "express": "^4.14.0", 53 | "react": "^15.4.0", 54 | "react-dom": "^15.4.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | global.Promise = require('bluebird'); 2 | 3 | var webpack = require('webpack'); 4 | var path = require('path'); 5 | var CleanWebpackPlugin = require('clean-webpack-plugin'); 6 | 7 | var publicPath = process.env.NODE_ENV === 'production' ? '/' : 'http://localhost:8050/public/assets'; 8 | var jsName = process.env.NODE_ENV === 'production' ? 'bundle-[hash].js' : 'bundle.js'; 9 | 10 | var plugins = [ 11 | new webpack.DefinePlugin({ 12 | 'process.env': { 13 | BROWSER: JSON.stringify(true), 14 | NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development') 15 | } 16 | }) 17 | ]; 18 | 19 | if (process.env.NODE_ENV === 'production') { 20 | plugins.push( 21 | new CleanWebpackPlugin([ 'public/assets/' ], { 22 | root: __dirname, 23 | verbose: true, 24 | dry: false 25 | }) 26 | ); 27 | plugins.push(new webpack.optimize.DedupePlugin()); 28 | plugins.push(new webpack.optimize.OccurenceOrderPlugin()); 29 | } 30 | 31 | module.exports = { 32 | entry: ['babel-polyfill', './src/client'], 33 | debug: process.env.NODE_ENV !== 'production', 34 | resolve: { 35 | alias: { 'react/lib/ReactMount': 'react-dom/lib/ReactMount' }, 36 | root: path.join(__dirname, 'src'), 37 | modulesDirectories: ['node_modules'], 38 | extensions: ['', '.js', '.jsx'] 39 | }, 40 | plugins, 41 | output: { 42 | path: `${__dirname}/public/assets/`, 43 | filename: jsName, 44 | publicPath 45 | }, 46 | module: { 47 | loaders: [ 48 | { test: /\.jsx?$/, loader: 'react-hot!babel', exclude: [/node_modules/, /public/] } 49 | ] 50 | }, 51 | devtool: process.env.NODE_ENV !== 'production' ? 'source-map' : null, 52 | devServer: { 53 | headers: { 'Access-Control-Allow-Origin': '*' } 54 | } 55 | }; 56 | --------------------------------------------------------------------------------