From ef369ed9d922aea5116ca7e50208169fd7831389 Mon Sep 17 00:00:00 2001 From: Jason Huang Date: Thu, 2 Mar 2017 21:55:11 +0100 Subject: [PATCH] Add an i18n functionality to change language using a dropdown in real time. (#340) Demonstrates use of i18n (internationalization) with React on Rails. Docs: https://github.com/shakacode/react_on_rails/blob/master/docs/basics/i18n.md Relevant PRs from React on Rails: * https://github.com/shakacode/react_on_rails/pull/642 * https://github.com/shakacode/react_on_rails/pull/717 --- .gitignore | 4 + .travis.yml | 3 +- Gemfile | 2 +- Gemfile.lock | 95 +++++++++---------- Procfile.dev | 2 +- Procfile.hot | 2 +- Procfile.spec | 2 +- Procfile.static | 2 +- Procfile.static.trace | 2 +- README.md | 1 + client/.eslintignore | 3 + .../actions/commentsActionCreators.js | 7 ++ .../components/CommentBox/CommentBox.jsx | 44 +++++---- .../CommentBox/CommentForm/CommentForm.jsx | 59 +++++++----- .../SimpleCommentScreen.jsx | 62 ++++++++++-- .../comments/constants/commentsConstants.js | 2 + .../containers/NonRouterCommentsContainer.jsx | 14 ++- .../containers/RouterCommentsContainer.jsx | 14 ++- .../comments/reducers/commentsReducer.js | 9 +- .../comments/startup/clientRegistration.jsx | 9 ++ client/app/libs/i18n/selectLanguage.jsx | 13 +++ client/npm-shrinkwrap.json | 30 ++++++ client/package.json | 2 + client/webpack.client.base.config.js | 1 + config/initializers/react_on_rails.rb | 6 ++ config/locales/de.yml | 23 +++++ config/locales/en.yml | 44 ++++----- config/locales/ja.yml | 23 +++++ config/locales/zh-CN.yml | 23 +++++ config/locales/zh-TW.yml | 23 +++++ 30 files changed, 392 insertions(+), 134 deletions(-) create mode 100644 client/app/libs/i18n/selectLanguage.jsx create mode 100644 config/locales/de.yml create mode 100644 config/locales/ja.yml create mode 100644 config/locales/zh-CN.yml create mode 100644 config/locales/zh-TW.yml diff --git a/.gitignore b/.gitignore index 93c476de7..3c81b6a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ vendor/ruby .idea spec/examples.txt + +# Ignore i18n-js +client/app/libs/i18n/translations.js +client/app/libs/i18n/default.js diff --git a/.travis.yml b/.travis.yml index 4e92b4763..dcbeb15ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,8 @@ install: - npm install npm@latest -g - npm --version - npm install - - npm run build:client && npm run build:server + - rake react_on_rails:locale + - cd client && npm run build:test - rake db:setup # Tip: No need to run xvfb if running headless testing. However, we're going to start with diff --git a/Gemfile b/Gemfile index 338c45e1d..8a6a9d8ea 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ gem "sdoc", group: :doc # Use Rails Html Sanitizer for HTML sanitization gem "rails-html-sanitizer" -gem "react_on_rails", "~> 6.1" +gem "react_on_rails", "~> 6.7.1" # See https://github.com/sstephenson/execjs#readme for more supported runtimes # mini_racer is probably faster than therubyracer diff --git a/Gemfile.lock b/Gemfile.lock index 233be69f4..251d1bb5a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,39 +1,39 @@ GEM remote: https://rubygems.org/ specs: - actioncable (5.0.0.1) - actionpack (= 5.0.0.1) + actioncable (5.0.1) + actionpack (= 5.0.1) nio4r (~> 1.2) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) + actionmailer (5.0.1) + actionpack (= 5.0.1) + actionview (= 5.0.1) + activejob (= 5.0.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.0.1) - actionview (= 5.0.0.1) - activesupport (= 5.0.0.1) + actionpack (5.0.1) + actionview (= 5.0.1) + activesupport (= 5.0.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.1) - activesupport (= 5.0.0.1) + actionview (5.0.1) + activesupport (= 5.0.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.1) - activesupport (= 5.0.0.1) + activejob (5.0.1) + activesupport (= 5.0.1) globalid (>= 0.3.6) - activemodel (5.0.0.1) - activesupport (= 5.0.0.1) - activerecord (5.0.0.1) - activemodel (= 5.0.0.1) - activesupport (= 5.0.0.1) + activemodel (5.0.1) + activesupport (= 5.0.1) + activerecord (5.0.1) + activemodel (= 5.0.1) + activesupport (= 5.0.1) arel (~> 7.0) - activesupport (5.0.0.1) + activesupport (5.0.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (~> 0.7) minitest (~> 5.1) @@ -50,7 +50,7 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) brakeman (3.4.1) - builder (3.2.2) + builder (3.2.3) bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) @@ -82,7 +82,7 @@ GEM coffee-script-source execjs coffee-script-source (1.10.0) - concurrent-ruby (1.0.2) + concurrent-ruby (1.0.5) connection_pool (2.2.1) coveralls (0.8.15) json (>= 1.8, < 3) @@ -109,7 +109,7 @@ GEM railties (>= 3.0.0) globalid (0.3.7) activesupport (>= 4.1.0) - i18n (0.7.0) + i18n (0.8.1) interception (0.5) io-like (0.3.0) jbuilder (2.6.0) @@ -134,10 +134,10 @@ GEM mini_portile2 (2.1.0) mini_racer (0.1.7) libv8 (~> 5.3) - minitest (5.9.1) + minitest (5.10.1) multi_json (1.12.1) nio4r (1.2.1) - nokogiri (1.6.8.1) + nokogiri (1.7.0.1) mini_portile2 (~> 2.1.0) parser (2.3.2.0) ast (~> 2.2) @@ -165,45 +165,44 @@ GEM pry-stack_explorer (0.4.9.2) binding_of_caller (>= 0.7) pry (>= 0.9.11) - public_suffix (2.0.4) + public_suffix (2.0.5) puma (3.6.2) rack (2.0.1) rack-test (0.6.3) rack (>= 1.0) - rails (5.0.0.1) - actioncable (= 5.0.0.1) - actionmailer (= 5.0.0.1) - actionpack (= 5.0.0.1) - actionview (= 5.0.0.1) - activejob (= 5.0.0.1) - activemodel (= 5.0.0.1) - activerecord (= 5.0.0.1) - activesupport (= 5.0.0.1) + rails (5.0.1) + actioncable (= 5.0.1) + actionmailer (= 5.0.1) + actionpack (= 5.0.1) + actionview (= 5.0.1) + activejob (= 5.0.1) + activemodel (= 5.0.1) + activerecord (= 5.0.1) + activesupport (= 5.0.1) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.1) + railties (= 5.0.1) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.1) + rails-dom-testing (2.0.2) activesupport (>= 4.2.0, < 6.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (5.0.0.1) - actionpack (= 5.0.0.1) - activesupport (= 5.0.0.1) + railties (5.0.1) + actionpack (= 5.0.1) + activesupport (= 5.0.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.1.0) + rainbow (2.2.1) rake (11.3.0) rb-fsevent (0.9.8) rb-inotify (0.9.7) ffi (>= 0.5.0) rdoc (4.3.0) - react_on_rails (6.1.2) + react_on_rails (6.7.1) addressable connection_pool execjs (~> 2.5) - foreman rails (>= 3.2) rainbow (~> 2.1) redis (3.3.0) @@ -265,7 +264,7 @@ GEM activesupport (>= 4.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.7.0) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.0) @@ -274,8 +273,8 @@ GEM sprockets (>= 3.0.0) term-ansicolor (1.4.0) tins (~> 1.0) - thor (0.19.1) - thread_safe (0.3.5) + thor (0.19.4) + thread_safe (0.3.6) tilt (2.0.5) tins (1.12.0) tzinfo (1.2.2) @@ -289,7 +288,7 @@ GEM debug_inspector railties (>= 5.0) websocket (1.2.3) - websocket-driver (0.6.4) + websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) xpath (2.0.0) @@ -330,7 +329,7 @@ DEPENDENCIES rails rails-html-sanitizer rainbow - react_on_rails (~> 6.1) + react_on_rails (~> 6.7.1) redis rspec-rails (~> 3) rspec-retry diff --git a/Procfile.dev b/Procfile.dev index 0404e3c1b..669571946 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -7,7 +7,7 @@ rails: REACT_ON_RAILS_ENV=HOT rails s -b 0.0.0.0 # Run the hot reload server for client development -hot-assets: sh -c 'rm app/assets/webpack/* || true && HOT_RAILS_PORT=3500 npm run hot-assets' +hot-assets: sh -c 'rm app/assets/webpack/* || true && bundle exec rake react_on_rails:locale && HOT_RAILS_PORT=3500 npm run hot-assets' # Render static client assets rails-static-client-assets: sh -c 'npm run build:dev:client' diff --git a/Procfile.hot b/Procfile.hot index fe4dd92e1..2e6c49324 100644 --- a/Procfile.hot +++ b/Procfile.hot @@ -6,7 +6,7 @@ rails: REACT_ON_RAILS_ENV=HOT rails s -b 0.0.0.0 # Run the hot reload server for client development -hot-assets: sh -c 'rm app/assets/webpack/* || true && HOT_RAILS_PORT=3500 npm run hot-assets' +hot-assets: sh -c 'rm app/assets/webpack/* || true && bundle exec rake react_on_rails:locale && HOT_RAILS_PORT=3500 npm run hot-assets' # Keep the JS fresh for server rendering. Remove if not server rendering rails-server-assets: sh -c 'npm run build:dev:server' diff --git a/Procfile.spec b/Procfile.spec index d26f93be4..7fad064e6 100644 --- a/Procfile.spec +++ b/Procfile.spec @@ -3,7 +3,7 @@ # in rails_helper.rb. # Build client assets, watching for changes. -rails-client-assets: sh -c 'npm run build:dev:client' +rails-client-assets: sh -c 'bundle exec rake react_on_rails:locale && npm run build:dev:client' # Build server assets, watching for changes. Remove if not server rendering. rails-server-assets: sh -c 'npm run build:dev:server' diff --git a/Procfile.static b/Procfile.static index f3508d4bc..ac635a72a 100644 --- a/Procfile.static +++ b/Procfile.static @@ -2,7 +2,7 @@ rails: REACT_ON_RAILS_ENV= rails s -b 0.0.0.0 # Build client assets, watching for changes. -rails-client-assets: rm app/assets/webpack/* || true && npm run build:dev:client +rails-client-assets: rm app/assets/webpack/* || true && bundle exec rake react_on_rails:locale && npm run build:dev:client # Build server assets, watching for changes. Remove if not server rendering. rails-server-assets: npm run build:dev:server diff --git a/Procfile.static.trace b/Procfile.static.trace index c4b58a753..d73acfe5f 100644 --- a/Procfile.static.trace +++ b/Procfile.static.trace @@ -2,7 +2,7 @@ rails: TRACE_REACT_ON_RAILS=TRUE rails s -b 0.0.0.0 # Build client assets, watching for changes. -rails-client-assets: npm run build:dev:client +rails-client-assets: bundle exec rake react_on_rails:locale && npm run build:dev:client # Build server assets, watching for changes. Remove if not server rendering. rails-server-assets: npm run build:dev:server diff --git a/README.md b/README.md index fbf1b24b2..6620ee2c3 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ You can see this tutorial live here: [http://reactrails.com/](http://reactrails. - Enabling the use of npm modules and [Babel](https://babeljs.io/) with a Rails application using [Webpack](https://webpack.github.io/). - Easily enable retrofitting such a JS framework into an existing Rails app. You don't need a brand new single page app! - Example setting up Ruby and JavaScript linting in a real project, with corresponding CI rake tasks. +- Enabling the i18n functionality with [react-intl](https://github.com/yahoo/react-intl). ### Technologies involved diff --git a/client/.eslintignore b/client/.eslintignore index 3c3629e64..369ecb079 100644 --- a/client/.eslintignore +++ b/client/.eslintignore @@ -1 +1,4 @@ node_modules + +app/libs/i18n/translations.js +app/libs/i18n/default.js diff --git a/client/app/bundles/comments/actions/commentsActionCreators.js b/client/app/bundles/comments/actions/commentsActionCreators.js index b02788a2d..418a8675b 100644 --- a/client/app/bundles/comments/actions/commentsActionCreators.js +++ b/client/app/bundles/comments/actions/commentsActionCreators.js @@ -71,3 +71,10 @@ export function submitComment(comment) { ); }; } + +export function setLocale(locale) { + return { + type: actionTypes.SET_LOCALE, + locale, + }; +} diff --git a/client/app/bundles/comments/components/CommentBox/CommentBox.jsx b/client/app/bundles/comments/components/CommentBox/CommentBox.jsx index 5d8b54b48..bf3cd4bbf 100644 --- a/client/app/bundles/comments/components/CommentBox/CommentBox.jsx +++ b/client/app/bundles/comments/components/CommentBox/CommentBox.jsx @@ -1,13 +1,15 @@ import BaseComponent from 'libs/components/BaseComponent'; import React, { PropTypes } from 'react'; - +import { FormattedMessage, injectIntl, intlShape } from 'react-intl'; import CommentForm from './CommentForm/CommentForm'; import CommentList, { CommentPropTypes } from './CommentList/CommentList'; import css from './CommentBox.scss'; import Immutable from 'immutable'; import ActionCable from 'actioncable'; +import { SelectLanguage } from 'libs/i18n/selectLanguage'; +import { defaultMessages, defaultLocale } from 'libs/i18n/default'; -export default class CommentBox extends BaseComponent { +class CommentBox extends BaseComponent { static propTypes = { pollInterval: PropTypes.number.isRequired, actions: PropTypes.shape({ @@ -19,6 +21,7 @@ export default class CommentBox extends BaseComponent { submitCommentError: React.PropTypes.string, $$comments: React.PropTypes.arrayOf(CommentPropTypes), }).isRequired, + intl: intlShape.isRequired, }; constructor() { @@ -26,13 +29,14 @@ export default class CommentBox extends BaseComponent { _.bindAll(this, [ 'refreshComments', ]); + this.cable = null; } subscribeChannel() { const { messageReceived } = this.props.actions; const protocol = window.location.protocol === "https:" ? "wss://" : "ws://" - const cable = ActionCable.createConsumer(protocol+window.location.hostname+":"+window.location.port+"/cable"); - cable.subscriptions.create({channel: "CommentsChannel"}, { + this.cable = ActionCable.createConsumer(protocol+window.location.hostname+":"+window.location.port+"/cable"); + this.cable.subscriptions.create({channel: "CommentsChannel"}, { connected: () => { console.log("connected") }, @@ -52,7 +56,7 @@ export default class CommentBox extends BaseComponent { } componentWillUnmount() { - App.cable.subscriptions.remove({ channel: "CommentsChannel" }); + this.cable.subscriptions.remove({ channel: "CommentsChannel" }); } refreshComments() { @@ -61,37 +65,35 @@ export default class CommentBox extends BaseComponent { } render() { - const { actions, data } = this.props; + const { actions, data, intl } = this.props; + const { formatMessage } = intl; const cssTransitionGroupClassNames = { enter: css.elementEnter, enterActive: css.elementEnterActive, leave: css.elementLeave, leaveActive: css.elementLeaveActive, }; + const locale = data.get('locale') || defaultLocale; return (

- Comments {data.get('isFetching') && 'Loading...'} + {formatMessage(defaultMessages.comments)} + {data.get('isFetching') && formatMessage(defaultMessages.loading)}

+ { SelectLanguage(actions.setLocale, locale) }
- Name + {formatMessage(defaultMessages.inputNameLabel)} - Text + {formatMessage(defaultMessages.inputTextLabel)} - {this.props.isSaving ? 'Saving...' : 'Post'} + {this.props.isSaving + ? `${formatMessage(defaultMessages.inputSaving)}...` + : formatMessage(defaultMessages.inputPost)} @@ -177,15 +181,16 @@ export default class CommentForm extends BaseComponent { } formStacked() { + const { formatMessage } = this.props.intl; return (

- Name + {formatMessage(defaultMessages.inputNameLabel)} - Text + {formatMessage(defaultMessages.inputTextLabel)} - {this.props.isSaving ? 'Saving...' : 'Post'} + {this.props.isSaving + ? `${formatMessage(defaultMessages.inputSaving)}...` + : formatMessage(defaultMessages.inputPost)} @@ -224,17 +231,18 @@ export default class CommentForm extends BaseComponent { // Head up! We have some CSS modules going on here with the className props below. formInline() { + const { formatMessage } = this.props.intl; return (

- Name + {formatMessage(defaultMessages.inputNameLabel)} - Text + {formatMessage(defaultMessages.inputTextLabel)} - {this.props.isSaving ? 'Saving...' : 'Post'} + {this.props.isSaving + ? `${formatMessage(defaultMessages.inputSaving)}...` + : formatMessage(defaultMessages.inputPost)}
@@ -284,7 +294,7 @@ export default class CommentForm extends BaseComponent { }, []); return ( - + Your comment was not saved!
    {errorElements} @@ -310,6 +320,7 @@ export default class CommentForm extends BaseComponent { } const { cssTransitionGroupClassNames } = this.props; + const { formatMessage } = this.props.intl; // For animation with ReactCSSTransitionGroup // https://facebook.github.io/react/docs/animation.html @@ -325,13 +336,15 @@ export default class CommentForm extends BaseComponent { {this.errorWarning()} -
); } } + +export default injectIntl(CommentForm); diff --git a/client/app/bundles/comments/components/SimpleCommentScreen/SimpleCommentScreen.jsx b/client/app/bundles/comments/components/SimpleCommentScreen/SimpleCommentScreen.jsx index 39e83108c..95d716e27 100644 --- a/client/app/bundles/comments/components/SimpleCommentScreen/SimpleCommentScreen.jsx +++ b/client/app/bundles/comments/components/SimpleCommentScreen/SimpleCommentScreen.jsx @@ -3,16 +3,52 @@ import Immutable from 'immutable'; import _ from 'lodash'; import React from 'react'; import ReactOnRails from 'react-on-rails'; - +import { IntlProvider, injectIntl, intlShape } from 'react-intl'; import BaseComponent from 'libs/components/BaseComponent'; import CommentForm from '../CommentBox/CommentForm/CommentForm'; import CommentList from '../CommentBox/CommentList/CommentList'; import css from './SimpleCommentScreen.scss'; +import { SelectLanguage } from 'libs/i18n/selectLanguage'; +import { defaultMessages, defaultLocale } from 'libs/i18n/default'; +import { translations } from 'libs/i18n/translations'; + +export default class I18nWrapper extends BaseComponent { + constructor(props) { + super(props); + + this.state = { + locale: defaultLocale, + }; + + _.bindAll(this, 'handleSetLocale'); + } + + handleSetLocale(locale) { + this.setState({ locale: locale }); + } + + render() { + const { locale } = this.state; + const messages = translations[locale]; + const InjectedSimpleCommentScreen = injectIntl(SimpleCommentScreen); + + return ( + + + + ); + } +} + +class SimpleCommentScreen extends BaseComponent { + constructor(props) { + super(props); -export default class SimpleCommentScreen extends BaseComponent { - constructor(props, context) { - super(props, context); this.state = { $$comments: Immutable.fromJS([]), isSaving: false, @@ -67,6 +103,8 @@ export default class SimpleCommentScreen extends BaseComponent { } render() { + const { handleSetLocale, locale, intl } = this.props; + const { formatMessage } = intl; const cssTransitionGroupClassNames = { enter: css.elementEnter, enterActive: css.elementEnterActive, @@ -75,12 +113,16 @@ export default class SimpleCommentScreen extends BaseComponent { }; return ( -
-

Comments

-

- Text take Github Flavored Markdown. Comments older than 24 hours are deleted.
- Name is preserved. Text is reset, between submits. -

+
+

+ {formatMessage(defaultMessages.comments)} +

+ { SelectLanguage(handleSetLocale, locale) } +
    +
  • {formatMessage(defaultMessages.descriptionSupportMarkdown)}
  • +
  • {formatMessage(defaultMessages.descriptionDeleteRule)}
  • +
  • {formatMessage(defaultMessages.descriptionSubmitRule)}
  • +
+ + + ); } } diff --git a/client/app/bundles/comments/containers/RouterCommentsContainer.jsx b/client/app/bundles/comments/containers/RouterCommentsContainer.jsx index d7828df20..86e8e06aa 100644 --- a/client/app/bundles/comments/containers/RouterCommentsContainer.jsx +++ b/client/app/bundles/comments/containers/RouterCommentsContainer.jsx @@ -3,9 +3,15 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import BaseComponent from 'libs/components/BaseComponent'; - +import { IntlProvider } from 'react-intl'; import CommentScreen from '../components/CommentScreen/CommentScreen'; import * as commentsActionCreators from '../actions/commentsActionCreators'; +import { translations } from 'libs/i18n/translations'; +import { defaultLocale } from 'libs/i18n/default'; + +// polyfill for server-side rendering, required by react-intl +import Intl from 'intl'; +global.Intl = Intl; function select(state) { // Which part of the Redux global state does our component want to receive as props? @@ -25,9 +31,13 @@ class RouterCommentsContainer extends BaseComponent { const { dispatch, data } = this.props; const actions = bindActionCreators(commentsActionCreators, dispatch); const locationState = this.props.location.state; + const locale = data.get('locale') || defaultLocale; + const messages = translations[locale]; return ( - + + + ); } } diff --git a/client/app/bundles/comments/reducers/commentsReducer.js b/client/app/bundles/comments/reducers/commentsReducer.js index 91cbc5a62..e6bb784fd 100644 --- a/client/app/bundles/comments/reducers/commentsReducer.js +++ b/client/app/bundles/comments/reducers/commentsReducer.js @@ -10,10 +10,11 @@ export const $$initialState = Immutable.fromJS({ submitCommentError: null, isFetching: false, isSaving: false, + locale: null, }); export default function commentsReducer($$state = $$initialState, action = null) { - const { type, comment, comments, error } = action; + const { type, comment, comments, error, locale } = action; switch (type) { @@ -75,6 +76,12 @@ export default function commentsReducer($$state = $$initialState, action = null) }); } + case actionTypes.SET_LOCALE: { + return $$state.merge({ + locale, + }); + } + default: { return $$state; } diff --git a/client/app/bundles/comments/startup/clientRegistration.jsx b/client/app/bundles/comments/startup/clientRegistration.jsx index c39cfdb9c..9b09e9e24 100644 --- a/client/app/bundles/comments/startup/clientRegistration.jsx +++ b/client/app/bundles/comments/startup/clientRegistration.jsx @@ -7,6 +7,15 @@ import routerCommentsStore from '../store/routerCommentsStore'; import commentsStore from '../store/commentsStore'; import NavigationBarApp from './NavigationBarApp'; +import { addLocaleData } from 'react-intl'; +import en from 'react-intl/locale-data/en'; +import de from 'react-intl/locale-data/de'; +import ja from 'react-intl/locale-data/ja'; +import zh from 'react-intl/locale-data/zh'; + +// Initizalize all locales for react-intl. +addLocaleData([...en, ...de, ...ja, ...zh]); + ReactOnRails.setOptions({ traceTurbolinks: TRACE_TURBOLINKS, // eslint-disable-line no-undef }); diff --git a/client/app/libs/i18n/selectLanguage.jsx b/client/app/libs/i18n/selectLanguage.jsx new file mode 100644 index 000000000..4c9f9e979 --- /dev/null +++ b/client/app/libs/i18n/selectLanguage.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const SelectLanguage = (onChange, locale = defaultLocale) => ( + +); + +export { SelectLanguage }; diff --git a/client/npm-shrinkwrap.json b/client/npm-shrinkwrap.json index 9db0f9cbd..3f85d44c5 100644 --- a/client/npm-shrinkwrap.json +++ b/client/npm-shrinkwrap.json @@ -3179,6 +3179,31 @@ "from": "interpret@>=0.6.4 <0.7.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz" }, + "intl": { + "version": "1.2.5", + "from": "intl@>=1.2.5 <2.0.0", + "resolved": "https://registry.npmjs.org/intl/-/intl-1.2.5.tgz" + }, + "intl-format-cache": { + "version": "2.0.5", + "from": "intl-format-cache@>=2.0.5 <3.0.0", + "resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.0.5.tgz" + }, + "intl-messageformat": { + "version": "1.3.0", + "from": "intl-messageformat@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-1.3.0.tgz" + }, + "intl-messageformat-parser": { + "version": "1.2.0", + "from": "intl-messageformat-parser@1.2.0", + "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz" + }, + "intl-relativeformat": { + "version": "1.3.0", + "from": "intl-relativeformat@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-1.3.0.tgz" + }, "invariant": { "version": "2.2.2", "from": "invariant@>=2.2.0 <3.0.0", @@ -4646,6 +4671,11 @@ "from": "react-dom@>=15.4.1 <16.0.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.4.1.tgz" }, + "react-intl": { + "version": "2.2.2", + "from": "react-intl@>=2.1.5 <3.0.0", + "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.2.2.tgz" + }, "react-on-rails": { "version": "6.2.1", "from": "react-on-rails@>=6.2.1 <7.0.0", diff --git a/client/package.json b/client/package.json index 253b253c0..b525a0cc6 100644 --- a/client/package.json +++ b/client/package.json @@ -62,6 +62,7 @@ "file-loader": "^0.9.0", "immutable": "^3.8.1", "imports-loader": "^0.6.5", + "intl": "^1.2.5", "jquery": "^3.1.1", "jquery-ujs": "^1.2.2", "loader-utils": "^0.2.16", @@ -74,6 +75,7 @@ "react-addons-css-transition-group": "^15.4.1", "react-bootstrap": "^0.30.7", "react-dom": "^15.4.1", + "react-intl": "^2.2.2", "react-on-rails": "^6.2.1", "react-redux": "^4.4.6", "react-router": "^3.0.0", diff --git a/client/webpack.client.base.config.js b/client/webpack.client.base.config.js index 6d29af922..444a1cabe 100644 --- a/client/webpack.client.base.config.js +++ b/client/webpack.client.base.config.js @@ -38,6 +38,7 @@ module.exports = { 'react-on-rails', 'react-router-redux', 'redux-thunk', + 'react-intl', ], // This will contain the app entry points defined by webpack.hot.config and webpack.rails.config diff --git a/config/initializers/react_on_rails.rb b/config/initializers/react_on_rails.rb index 4d3adeec8..b704cd189 100644 --- a/config/initializers/react_on_rails.rb +++ b/config/initializers/react_on_rails.rb @@ -66,6 +66,12 @@ config.server_renderer_pool_size = 1 # increase if you're on JRuby config.server_renderer_timeout = 20 # seconds + ################################################################################ + # I18N OPTIONS + ################################################################################ + # Replace the following line to the location where you keep translation.js & default.js. + config.i18n_dir = Rails.root.join("client", "app", "libs", "i18n") + ################################################################################ # MISCELLANEOUS OPTIONS ################################################################################ diff --git a/config/locales/de.yml b/config/locales/de.yml new file mode 100644 index 000000000..3dffb5a7d --- /dev/null +++ b/config/locales/de.yml @@ -0,0 +1,23 @@ +de: + type: "Deutsch" + comments: "Kommentare " + loading: "Laden..." + description: + force_refrech: "Erzwingt die Aktualisierung aller Kommentare." + support_markdown: "Der Text unterstützt Github Flavored Markdown." + delete_rule: "Kommentare, die aelter als 24 Stunden sind, werden gelöscht." + submit_rule: "Der Name wird beibehalten. Der Text wird während des Postens zurücksetzt." + see_action_cable: "So sehen Action Cable sofort aktualisieren zwei Browser, öffnen Sie zwei Browser und senden Sie einen Kommentar!" + form: + horizontal: "Horizontale Form" + stacked: "Gestapelte Form" + inline: "Inline Form" + input: + name: + label: "Name" + placeholder: "Dein Name" + text: + label: "Text" + placeholder: "Sagen Sie etwas mit markdown..." + saving: "Speichern" + post: "Schicken" diff --git a/config/locales/en.yml b/config/locales/en.yml index f1d42ae52..9ff110cc4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,23 +1,23 @@ -# Files in the config/locales directory are used for internationalization -# and are automatically loaded by Rails. If you want to use locales other -# than English, add the necessary files in this directory. -# -# To use the locales, use `I18n.t`: -# -# I18n.t "hello" -# -# In views, this is aliased to just `t`: -# -# <%= t("hello") %> -# -# To use a different locale, set it with `I18n.locale`: -# -# I18n.locale = :es -# -# This would use the information in config/locales/es.yml. -# -# To learn more, please read the Rails Internationalization guide -# available at http://guides.rubyonrails.org/i18n.html. - en: - hello: "Hello world" + type: "English" + comments: "Comments " + loading: "Loading..." + description: + force_refrech: "Force Refresh of All Comments." + support_markdown: "Text supports Github Flavored Markdown." + delete_rule: "Comments older than 24 hours are deleted." + submit_rule: "Name is preserved. Text is reset, between submits." + see_action_cable: "To see Action Cable instantly update two browsers, open two browsers and submit a comment!" + form: + horizontal: "Horizontal Form" + stacked: "Stacked Form" + inline: "Inline Form" + input: + name: + label: "Name" + placeholder: "Your Name" + text: + label: "Text" + placeholder: "Say something using markdown..." + saving: "Saving" + post: "Post" diff --git a/config/locales/ja.yml b/config/locales/ja.yml new file mode 100644 index 000000000..17c3b350f --- /dev/null +++ b/config/locales/ja.yml @@ -0,0 +1,23 @@ +ja: + type: "日本語" + comments: "コメント " + loading: "読み込んでいます..." + description: + force_refrech: "すべてのコメントを強制的にリフレッシュします。" + support_markdown: "テキストは Github Flavored Markdown をサポートしています。" + delete_rule: "コメントは24時間後削除されます。" + submit_rule: "送信の間に名前は保存され、テキストがリセットされます。" + see_action_cable: "Action Cableが即座に2つのブラウザを更新することを確認するには、2つのブラウザを開いてコメントを送信してください!" + form: + horizontal: "水平フォーム" + stacked: "積み上げフォーム" + inline: "インラインフォーム" + input: + name: + label: "名前" + placeholder: "あなたの名前" + text: + label: "本文" + placeholder: "何かが Markdown を使用して言います..." + saving: "保存" + post: "サブミット" diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml new file mode 100644 index 000000000..e5222edbb --- /dev/null +++ b/config/locales/zh-CN.yml @@ -0,0 +1,23 @@ +zh-CN: + type: "简体中文" + comments: "评论 " + loading: "载入中..." + description: + force_refrech: "强制更新所有评论。" + support_markdown: "支援 Github Flavored Markdown。" + delete_rule: "评论于24小时后移除。" + submit_rule: "送出评论后, 名字保留并清除内容。" + see_action_cable: "要查看 Action Cable 立即更新两个浏览器,打开两个浏览器并提交评论!" + form: + horizontal: "水平格式" + stacked: "堆叠格式" + inline: "内嵌格式" + input: + name: + label: "名字" + placeholder: "你的名字" + text: + label: "内容" + placeholder: "使用markdown说些什么..." + saving: "储存" + post: "发表" diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml new file mode 100644 index 000000000..3c93fcf9d --- /dev/null +++ b/config/locales/zh-TW.yml @@ -0,0 +1,23 @@ +zh-TW: + type: "正體中文" + comments: "評論 " + loading: "載入中..." + description: + force_refrech: "強制更新所有評論。" + support_markdown: "支援 Github Flavored Markdown。" + delete_rule: "評論於24小時後移除。" + submit_rule: "送出評論後, 名字保留並清除內容。" + see_action_cable: "要查看 Action Cable 立即更新兩個瀏覽器,打開兩個瀏覽器並提交評論!" + form: + horizontal: "水平格式" + stacked: "堆疊格式" + inline: "內嵌格式" + input: + name: + label: "名字" + placeholder: "你的名字" + text: + label: "內容" + placeholder: "使用markdown說些什麼..." + saving: "儲存" + post: "發表"