├── .babelrc ├── .eslintrc ├── .gitignore ├── data.js ├── index.html ├── package.json ├── server.js ├── src ├── js │ ├── components │ │ ├── App │ │ │ ├── App.js │ │ │ └── index.js │ │ ├── Header │ │ │ ├── Header.js │ │ │ ├── Header.scss │ │ │ ├── SubHeader.js │ │ │ └── index.js │ │ └── Widgets │ │ │ ├── Widget.js │ │ │ ├── Widgets.js │ │ │ ├── Widgets.scss │ │ │ └── index.js │ └── index.js ├── styles │ └── base.scss └── utils │ └── translator.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-0"], 3 | "plugins": [ 4 | "transform-object-rest-spread", 5 | "transform-runtime", 6 | [ 7 | "react-intl", { 8 | "messagesDir": "./build/messages", 9 | "enforceDescriptions": false 10 | } 11 | ] 12 | ], 13 | "env": { 14 | "development": { 15 | "presets": ["react-hmre"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "mocha": true 8 | }, 9 | "ecmaFeatures": { 10 | "forOf": true, 11 | "jsx": true, 12 | "es6": true 13 | }, 14 | "rules": { 15 | "react/prefer-stateless-function": 0, 16 | "react/jsx-curly-spacing": 0, 17 | }, 18 | "plugins": [ 19 | "jsx-a11y" 20 | ], 21 | "settings": { 22 | "import/resolver": "webpack", 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### node etc ### 2 | 3 | # Logs 4 | logs 5 | *.log 6 | *.rdb 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Compiled Dirs (http://nodejs.org/api/addons.html) 20 | build/ 21 | 22 | # Dependency directories 23 | node_modules/ 24 | 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /data.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'Preethi', 3 | lastLogin: 'Thu Aug 04 2016 19:58:19 GMT-0700 (PDT)', 4 | notifications: [ 5 | { 6 | id: 1, 7 | read: false, 8 | createdAt: 1469336316043, 9 | }, 10 | { 11 | id: 2, 12 | read: false, 13 | createdAt: 1469336288833, 14 | }, 15 | { 16 | id: 3, 17 | read: true, 18 | createdAt: 1469332088829, 19 | }, 20 | { 21 | id: 4, 22 | read: false, 23 | createdAt: 1469332088732, 24 | }, 25 | ], 26 | }; 27 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Inbox Internationlized 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inbox-react-intl", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=development node server.js", 8 | "lint": "./node_modules/.bin/eslint src --fix", 9 | "build:langs": "./node_modules/.bin/babel-node src/utils/translator.js" 10 | }, 11 | "author": "Preethi Kasireddy", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "babel-cli": "^6.24.1", 15 | "babel-core": "^6.11.4", 16 | "babel-eslint": "^6.1.0", 17 | "babel-loader": "^6.2.4", 18 | "babel-plugin-react-intl": "^2.1.3", 19 | "babel-plugin-react-transform": "^2.0.2", 20 | "babel-plugin-transform-object-rest-spread": "^6.8.0", 21 | "babel-plugin-transform-runtime": "^6.9.0", 22 | "babel-preset-es2015": "^6.9.0", 23 | "babel-preset-react": "^6.11.1", 24 | "babel-preset-react-hmre": "^1.1.1", 25 | "babel-preset-stage-0": "^6.5.0", 26 | "babel-register": "^6.11.5", 27 | "cross-env": "^2.0.0", 28 | "css-loader": "^0.23.1", 29 | "eslint": "2.13.1", 30 | "eslint-config-airbnb": "^9.0.1", 31 | "eslint-import-resolver-webpack": "0.3.2", 32 | "eslint-loader": "^1.4.1", 33 | "eslint-plugin-import": "^1.11.1", 34 | "eslint-plugin-jsx-a11y": "1.2.0", 35 | "eslint-plugin-react": "^5.2.2", 36 | "exports-loader": "^0.6.3", 37 | "extract-text-webpack-plugin": "^1.0.1", 38 | "file-loader": "^0.9.0", 39 | "html-webpack-plugin": "^2.22.0", 40 | "imports-loader": "^0.6.5", 41 | "json-loader": "^0.5.4", 42 | "node-sass": "^3.8.0", 43 | "react-json-tree": "^0.10.0", 44 | "react-transform-hmr": "^1.0.4", 45 | "resolve-url": "^0.2.1", 46 | "resolve-url-loader": "^1.6.0", 47 | "sass-loader": "^4.0.0", 48 | "style-loader": "^0.13.1", 49 | "url-loader": "^0.5.7", 50 | "webpack": "^1.13.1", 51 | "webpack-dev-middleware": "^1.6.1", 52 | "webpack-dev-server": "^1.14.1", 53 | "webpack-hot-middleware": "^2.12.1" 54 | }, 55 | "dependencies": { 56 | "classnames": "^2.2.5", 57 | "fs": "0.0.2", 58 | "glob": "^7.0.5", 59 | "intl": "^1.2.4", 60 | "lodash": "^4.13.1", 61 | "mkdirp": "^0.5.1", 62 | "path": "^0.12.7", 63 | "react": "^15.2.1", 64 | "react-dom": "^15.2.1", 65 | "react-intl": "^2.1.3", 66 | "sass": "^0.5.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const WebpackDevServer = require('webpack-dev-server'); 3 | const config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { // Start a server 6 | publicPath: config.output.publicPath, 7 | hot: true, // With hot reloading 8 | inline: false, 9 | historyApiFallback: true, 10 | headers: { 'Access-Control-Allow-Origin': '*' }, // Allow CORS requests 11 | quiet: true // Without logging 12 | }).listen(3000, 'localhost', function(err) { 13 | if (err) { 14 | console.error(err); 15 | } else { 16 | console.info('==> Server started. Listening at http://localhost:3000'); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/js/components/App/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import filter from 'lodash/filter'; 3 | import Header from './../Header/index'; 4 | import Widgets from './../Widgets/index'; 5 | import Data from './../../../../data'; 6 | 7 | class App extends Component { 8 | static propTypes = { 9 | children: PropTypes.node, 10 | } 11 | 12 | state = { 13 | name: Data.name, 14 | notifications: Data.notifications, 15 | lastLogin: Data.lastLogin, 16 | unreadCount: filter(Data.notifications, ['read', false]).length, 17 | } 18 | 19 | render() { 20 | const { name, unreadCount, lastLogin } = this.state; 21 | return ( 22 |
23 |
28 | 29 |
30 | ); 31 | } 32 | } 33 | 34 | export default App; 35 | -------------------------------------------------------------------------------- /src/js/components/App/index.js: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | export default App; 3 | -------------------------------------------------------------------------------- /src/js/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | import SubHeader from './SubHeader'; 4 | import styles from './Header.scss'; 5 | import classNames from 'classnames/bind'; 6 | const cx = classNames.bind(styles); 7 | 8 | class Header extends Component { 9 | static propTypes = { 10 | name: PropTypes.string.isRequired, 11 | lastLogin: PropTypes.string.isRequired, 12 | unreadCount: PropTypes.number.isRequired, 13 | } 14 | 15 | render() { 16 | const { name, unreadCount, lastLogin } = this.props; 17 | 18 | return ( 19 |
20 |
21 | 26 |
27 | 31 |
32 | ); 33 | } 34 | } 35 | 36 | export default Header; 37 | -------------------------------------------------------------------------------- /src/js/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/base.scss'; 2 | 3 | .container { 4 | width: 100vw; 5 | } 6 | 7 | .text { 8 | text-align: center; 9 | vertical-align: middle; 10 | margin: 1em 0 0.5em 0; 11 | } 12 | 13 | .header-text { 14 | font-size: 32px; 15 | } 16 | 17 | .subheader-text { 18 | font-size: 24px; 19 | } 20 | 21 | .subheader-lastlogin { 22 | font-size: 16px; 23 | } 24 | -------------------------------------------------------------------------------- /src/js/components/Header/SubHeader.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { FormattedMessage, FormattedPlural, FormattedNumber, FormattedRelative } from 'react-intl'; 3 | import styles from './Header.scss'; 4 | import classNames from 'classnames/bind'; 5 | const cx = classNames.bind(styles); 6 | 7 | class SubHeader extends Component { 8 | static propTypes = { 9 | lastLogin: PropTypes.string.isRequired, 10 | unreadCount: PropTypes.number.isRequired, 11 | } 12 | 13 | render() { 14 | const { unreadCount, lastLogin } = this.props; 15 | 16 | return ( 17 |
18 |
19 | 25 | 28 | 29 | ), 30 | notifications: ( 31 | 36 | ), 37 | }} 38 | /> 39 |
40 |
41 | }} 45 | /> 46 |
47 |
48 | ); 49 | } 50 | } 51 | 52 | export default SubHeader; 53 | -------------------------------------------------------------------------------- /src/js/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | export default Header; 3 | -------------------------------------------------------------------------------- /src/js/components/Widgets/Widget.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import styles from './Widgets.scss'; 3 | import classNames from 'classnames/bind'; 4 | const cx = classNames.bind(styles); 5 | 6 | class Widget extends Component { 7 | static propTypes = { 8 | header: PropTypes.string.isRequired, 9 | body: PropTypes.string.isRequired, 10 | } 11 | 12 | render() { 13 | const { header, body } = this.props; 14 | 15 | return ( 16 |
17 |
18 | {header} 19 |
20 |
21 | {body} 22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | export default Widget; 29 | -------------------------------------------------------------------------------- /src/js/components/Widgets/Widgets.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { injectIntl, defineMessages } from 'react-intl'; 3 | import Widget from './Widget'; 4 | import styles from './Widgets.scss'; 5 | import classNames from 'classnames/bind'; 6 | const cx = classNames.bind(styles); 7 | 8 | const messages = defineMessages({ 9 | widget1Header: { 10 | id: 'Widgets.widget1.header', 11 | defaultMessage: 'Creative header', 12 | }, 13 | widget1Body: { 14 | id: 'Widgets.widget1.body', 15 | defaultMessage: 'Mark todays date: {date}', 16 | }, 17 | widget2Header: { 18 | id: 'Widgets.widget2.header', 19 | defaultMessage: 'Here is another widget', 20 | }, 21 | widget2Body: { 22 | id: 'Widgets.widget2.body', 23 | defaultMessage: 'Hello. How is your day going?', 24 | }, 25 | widget3Header: { 26 | id: 'Widgets.widget3.header', 27 | defaultMessage: 'Yet another widget', 28 | }, 29 | widget3Body: { 30 | id: 'Widgets.widget3.body', 31 | defaultMessage: 'What is the meaning of life, my friend?', 32 | }, 33 | widget4Header: { 34 | id: 'Widgets.widget4.header', 35 | defaultMessage: 'This is the last widget', 36 | }, 37 | widget4Body: { 38 | id: 'Widgets.widget4.body', 39 | defaultMessage: 'I love React so much!', 40 | }, 41 | }); 42 | 43 | class Widgets extends Component { 44 | static propTypes = { 45 | intl: PropTypes.object.isRequired, 46 | } 47 | 48 | render() { 49 | const { formatMessage, formatDate } = this.props.intl; 50 | const currentDate = Date.now(); 51 | return ( 52 |
53 |
54 | 64 |
65 |
66 | 70 |
71 |
72 | 76 |
77 |
78 | 82 |
83 |
84 | ); 85 | } 86 | } 87 | 88 | export default injectIntl(Widgets); 89 | -------------------------------------------------------------------------------- /src/js/components/Widgets/Widgets.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/base.scss'; 2 | 3 | .widgets-container { 4 | padding: 0; 5 | margin: 40px; 6 | display: flex; 7 | flex-flow: row wrap; 8 | justify-content: space-around; 9 | color: #666A86; 10 | } 11 | 12 | .widget-header { 13 | font-size: 18px; 14 | line-height: 22px; 15 | font-weight: 500; 16 | border-bottom: 1px solid #666A86; 17 | } 18 | 19 | .widget-body { 20 | padding-top: 20px; 21 | font-size: 14px; 22 | } 23 | 24 | .widget-item-container { 25 | padding: 15px; 26 | margin: 20px; 27 | background-color: #DCEDFF; 28 | border: 2px solid; 29 | border-radius: 5px; 30 | text-align: center; 31 | height: 250px; 32 | width: 350px; 33 | } -------------------------------------------------------------------------------- /src/js/components/Widgets/index.js: -------------------------------------------------------------------------------- 1 | import Widgets from './Widgets'; 2 | export default Widgets; 3 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './components/App/index'; 4 | import { IntlProvider, addLocaleData } from 'react-intl'; 5 | import en from 'react-intl/locale-data/en'; 6 | import es from 'react-intl/locale-data/es'; 7 | import fr from 'react-intl/locale-data/fr'; 8 | import it from 'react-intl/locale-data/it'; 9 | import localeData from './../../build/locales/data.json'; 10 | 11 | addLocaleData([...en, ...es, ...fr, ...it]); 12 | 13 | // Define user's language. Different browsers have the user locale defined 14 | // on different fields on the `navigator` object, so we make sure to account 15 | // for these different by checking all of them 16 | const language = (navigator.languages && navigator.languages[0]) || 17 | navigator.language || 18 | navigator.userLanguage; 19 | 20 | // Split locales with a region code 21 | const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0]; 22 | 23 | // Try full locale, fallback to locale without region code, fallback to en 24 | const messages = localeData[language] || localeData[languageWithoutRegionCode] || localeData.en; 25 | 26 | // Render our root component into the div with id "root" 27 | 28 | // If browser doesn't support Intl (i.e. Safari), then we manually import 29 | // the intl polyfill and locale data. 30 | if (!window.Intl) { 31 | require.ensure([ 32 | 'intl', 33 | 'intl/locale-data/jsonp/en.js', 34 | 'intl/locale-data/jsonp/es.js', 35 | 'intl/locale-data/jsonp/fr.js', 36 | 'intl/locale-data/jsonp/it.js', 37 | ], (require) => { 38 | require('intl'); 39 | require('intl/locale-data/jsonp/en.js'); 40 | require('intl/locale-data/jsonp/es.js'); 41 | require('intl/locale-data/jsonp/fr.js'); 42 | require('intl/locale-data/jsonp/it.js'); 43 | render( 44 | 45 | 46 | , 47 | document.getElementById('root') 48 | ); 49 | }); 50 | } else { 51 | render( 52 | 53 | 54 | , 55 | document.getElementById('root') 56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/styles/base.scss: -------------------------------------------------------------------------------- 1 | :global { 2 | html { 3 | height: 100%; 4 | } 5 | body { 6 | width: 100%; 7 | float: left; 8 | background-color: #666A86; 9 | font-family: 'Roboto', Arial, 'Helvetica Neue', Helvetica, sans-serif; 10 | color: #EEEEFF; 11 | } 12 | a { 13 | text-decoration: none; 14 | cursor: pointer; 15 | } 16 | 17 | #root { 18 | display: flex; 19 | height: 100%; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/translator.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import * as fs from 'fs'; 3 | import { sync as globSync } from 'glob'; 4 | import { sync as mkdirpSync } from 'mkdirp'; 5 | 6 | const filePattern = './build/messages/**/*.json'; 7 | const outputLanguageDataDir = './build/locales/'; 8 | 9 | // Aggregates the default messages that were extracted from the example app's 10 | // React components via the React Intl Babel plugin. An error will be thrown if 11 | // there are messages in different components that use the same `id`. The result 12 | // is a flat collection of `id: message` pairs for the app's default locale. 13 | const defaultMessages = globSync(filePattern) 14 | .map((filename) => fs.readFileSync(filename, 'utf8')) 15 | .map((file) => JSON.parse(file)) 16 | .reduce((collection, descriptors) => { 17 | descriptors.forEach(({ id, defaultMessage }) => { 18 | if (collection.hasOwnProperty(id)) { 19 | throw new Error(`Duplicate message id: ${id}`); 20 | } 21 | collection[id] = defaultMessage; 22 | }); 23 | 24 | return collection; 25 | }, {}); 26 | 27 | mkdirpSync(outputLanguageDataDir); 28 | 29 | fs.writeFileSync(outputLanguageDataDir + 'data.json', `{ "en": ${JSON.stringify(defaultMessages, null, 2)} }`); 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | const entry = [ 6 | 'babel-polyfill', 7 | 'webpack-dev-server/client?http://localhost:3000', // Needed for hot reloading 8 | path.join(__dirname, 'src/js/index.js') // Start with js/index.js 9 | ]; 10 | // Output 11 | const output = { // Compile into build/ directory 12 | path: path.join(__dirname, 'build'), 13 | filename: 'js/bundle.js', // in js folder as bundle.js 14 | publicPath: '/build/', 15 | }; 16 | 17 | // Hot module replacement plugin 18 | const plugins = [ 19 | new webpack.HotModuleReplacementPlugin(), // Make hot loading work 20 | new webpack.optimize.OccurenceOrderPlugin(), 21 | new HtmlWebpackPlugin({ 22 | template: 'index.html', // Move the index.html file 23 | inject: true 24 | }), 25 | ]; 26 | 27 | const loaders = [ 28 | { test: /\.js$/, loader: 'babel', exclude: path.join(__dirname, '/node_modules/') }, 29 | { test: /\.scss$/, loaders: ["style", "css", "sass"] }, 30 | { test: /\.css$/, loader: 'style-loader!css-loader?modules&importLoaders=1&localIdentName=[local]!resolve-url?outputStyle=expanded' }, 31 | { test: /\.jpe?g$|\.gif$|\.png$/i, loader: 'url-loader?limit=8000' }, 32 | { test: /\.json$/, loader: 'json' }, 33 | ]; 34 | 35 | const preLoaders = [ 36 | { 37 | test: /\.js$/, 38 | loaders: ['eslint-loader'], 39 | exclude: [path.join(__dirname, '/node_modules/'), path.join(__dirname, '/build/')] 40 | } 41 | ]; 42 | 43 | const resolve = { 44 | extensions: ['', '.js', '.json'], 45 | root: path.resolve(__dirname, './src'), 46 | }; 47 | 48 | module.exports = { 49 | entry: entry, 50 | output: output, 51 | module: { 52 | preLoaders: preLoaders, 53 | loaders: loaders 54 | }, 55 | resolve: resolve, 56 | plugins: plugins, 57 | target: 'web', // Make web variables accessible to webpack, e.g. window 58 | stats: false, // Don't show stats in the console 59 | progress: true 60 | }; 61 | --------------------------------------------------------------------------------