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