Skip to content

Commit 3c0d60c

Browse files
committed
integrate react-intl
1 parent 9825649 commit 3c0d60c

File tree

8 files changed

+246
-84
lines changed

8 files changed

+246
-84
lines changed
Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,113 @@
11
import React from 'react';
2-
import I18n from 'i18n-js';
2+
import { addLocaleData, defineMessages } from 'react-intl';
3+
import en from 'react-intl/locale-data/en';
4+
import de from 'react-intl/locale-data/de';
5+
import ja from 'react-intl/locale-data/ja';
6+
import zh from 'react-intl/locale-data/zh';
7+
import _ from 'lodash';
38

4-
const InitI18n = (railsContext) => {
5-
I18n.translations = JSON.parse(railsContext.translations);
6-
I18n.defaultLocale = railsContext.i18nDefaultLocale;
7-
I18n.fallbacks = true;
9+
const InitI18nLocale = () => {
10+
addLocaleData([...en, ...de, ...ja, ...zh]);
811
};
912

10-
const SelectLanguage = (onChange) => (
11-
<select onChange={(e) => onChange(e.target.value)} >
12-
<option value="en">English</option>
13-
<option value="de">Deutsch</option>
14-
<option value="ja">日本語</option>
15-
<option value="zh-CN">简体中文</option>
16-
<option value="zh-TW">正體中文</option>
13+
const SelectLanguage = (onChange, locale = defaultLocale) => (
14+
<select onChange={(e) => onChange(e.target.value)} value={locale} >
15+
<option value='en'>English</option>
16+
<option value='de'>Deutsch</option>
17+
<option value='ja'>日本語</option>
18+
<option value='zh-CN'>简体中文</option>
19+
<option value='zh-TW'>正體中文</option>
1720
</select>
1821
);
1922

20-
const SetI18nLocale = (locale) => {
21-
I18n.locale = locale;
23+
const defaultLocale = 'en';
24+
25+
const defaultMessages = defineMessages({
26+
type: {
27+
id: 'type',
28+
defaultMessage: 'English',
29+
},
30+
comments: {
31+
id: 'comments',
32+
defaultMessage: 'Comments',
33+
},
34+
descriptionSupportMarkdown: {
35+
id: 'description.support_markdown',
36+
defaultMessage: 'Text supports Github Flavored Markdown.',
37+
},
38+
descriptionDeleteRule: {
39+
id: 'description.delete_rule',
40+
defaultMessage: 'Comments older than 24 hours are deleted.',
41+
},
42+
descriptionSubmitRule: {
43+
id: 'description.submit_rule',
44+
defaultMessage: 'Name is preserved. Text is reset, between submits.',
45+
},
46+
formHorizontal: {
47+
id: 'form.horizontal',
48+
defaultMessage: 'Horizontal Form',
49+
},
50+
formStacked: {
51+
id: 'form.stacked',
52+
defaultMessage: 'Stacked Form',
53+
},
54+
formInline: {
55+
id: 'form.inline',
56+
defaultMessage: 'Inline Form',
57+
},
58+
inputNameLabel: {
59+
id: 'input.name.label',
60+
defaultMessage: 'Name',
61+
},
62+
inputNamePlaceholder: {
63+
id: 'input.name.placeholder',
64+
defaultMessage: 'Your Name',
65+
},
66+
inputTextLabel: {
67+
id: 'input.text.label',
68+
defaultMessage: 'Text',
69+
},
70+
inputTextPlaceholder: {
71+
id: 'input.text.placeholder',
72+
defaultMessage: 'Say something using markdown...',
73+
},
74+
inputSaving: {
75+
id: 'input.saving',
76+
defaultMessage: 'Saving',
77+
},
78+
inputPost: {
79+
id: 'input.post',
80+
defaultMessage: 'Post',
81+
},
82+
});
83+
84+
const convertTranslations = (trans, locale) => {
85+
const translations = JSON.parse(trans);
86+
const flatMsg = {};
87+
for (const key in translations) {
88+
const tmp = {};
89+
tmp[key] = flattenMessages(translations[key]);
90+
Object.assign(flatMsg, tmp);
91+
}
92+
return _.isEmpty(flatMsg) ? null : flatMsg[`${locale}`];
93+
};
94+
95+
const flattenMessages = (msg, prefix = '') => {
96+
if (!_.isEmpty(msg)) {
97+
return Object.keys(msg).reduce((messages, key) => {
98+
let value = msg[key];
99+
let prefixedKey = prefix ? `${prefix}.${key}` : key;
100+
101+
if (typeof value === 'string') {
102+
messages[prefixedKey] = value;
103+
} else {
104+
Object.assign(messages, flattenMessages(value, prefixedKey));
105+
}
106+
107+
return messages;
108+
}, {});
109+
};
22110
};
23111

24-
export { InitI18n, SelectLanguage, SetI18nLocale };
112+
export { InitI18nLocale, SelectLanguage, convertTranslations,
113+
defaultMessages, defaultLocale };

client/app/bundles/comments/components/CommentBox/CommentBox.jsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import BaseComponent from 'libs/components/BaseComponent';
22
import React, { PropTypes } from 'react';
3-
import I18n from 'i18n-js';
3+
import { FormattedMessage } from 'react-intl';
44
import CommentForm from './CommentForm/CommentForm';
55
import CommentList, { CommentPropTypes } from './CommentList/CommentList';
66
import css from './CommentBox.scss';
7-
import { SelectLanguage, SetI18nLocale } from '../../common/i18nHelper';
7+
import { SelectLanguage, defaultMessages, defaultLocale } from '../../common/i18nHelper';
8+
import { injectIntl, intlShape } from 'react-intl';
89

9-
export default class CommentBox extends BaseComponent {
10+
class CommentBox extends BaseComponent {
1011
static propTypes = {
1112
pollInterval: PropTypes.number.isRequired,
1213
actions: PropTypes.shape({
@@ -18,6 +19,7 @@ export default class CommentBox extends BaseComponent {
1819
submitCommentError: React.PropTypes.string,
1920
$$comments: React.PropTypes.arrayOf(CommentPropTypes),
2021
}).isRequired,
22+
intl: intlShape.isRequired,
2123
};
2224

2325
componentDidMount() {
@@ -31,26 +33,26 @@ export default class CommentBox extends BaseComponent {
3133
}
3234

3335
render() {
34-
const { actions, data } = this.props;
36+
const { actions, data, intl } = this.props;
37+
const { formatMessage } = intl;
3538
const cssTransitionGroupClassNames = {
3639
enter: css.elementEnter,
3740
enterActive: css.elementEnterActive,
3841
leave: css.elementLeave,
3942
leaveActive: css.elementLeaveActive,
4043
};
41-
const locale = data.get('locale');
42-
SetI18nLocale(locale);
44+
const locale = data.get('locale') || defaultLocale;
4345

4446
return (
4547
<div className="commentBox container">
4648
<h2>
47-
{ I18n.t('comments') } {data.get('isFetching') && 'Loading...'}
49+
{formatMessage(defaultMessages.comments)}
4850
</h2>
49-
{ SelectLanguage(actions.setLocale) }
51+
{ SelectLanguage(actions.setLocale, locale) }
5052
<ul>
51-
<li>{ I18n.t('description.support_markdown') }</li>
52-
<li>{ I18n.t('description.delete_rule') }</li>
53-
<li>{ I18n.t('description.submit_rule') }</li>
53+
<li>{formatMessage(defaultMessages.descriptionSupportMarkdown)}</li>
54+
<li>{formatMessage(defaultMessages.descriptionDeleteRule)}</li>
55+
<li>{formatMessage(defaultMessages.descriptionSubmitRule)}</li>
5456
</ul>
5557
<CommentForm
5658
isSaving={data.get('isSaving')}
@@ -67,3 +69,5 @@ export default class CommentBox extends BaseComponent {
6769
);
6870
}
6971
}
72+
73+
export default injectIntl(CommentBox);

client/app/bundles/comments/components/CommentBox/CommentForm/CommentForm.jsx

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import NavItem from 'react-bootstrap/lib/NavItem';
1414
import Alert from 'react-bootstrap/lib/Alert';
1515
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
1616
import _ from 'lodash';
17-
import I18n from 'i18n-js';
18-
17+
import { injectIntl, intlShape } from 'react-intl';
18+
import { defaultMessages } from '../../../common/i18nHelper';
1919
import BaseComponent from 'libs/components/BaseComponent';
2020

2121
import css from './CommentForm.scss';
@@ -31,12 +31,13 @@ function bsStyleFor(propName, error) {
3131
return null;
3232
}
3333

34-
export default class CommentForm extends BaseComponent {
34+
class CommentForm extends BaseComponent {
3535
static propTypes = {
3636
isSaving: PropTypes.bool.isRequired,
3737
actions: PropTypes.object.isRequired,
3838
error: PropTypes.any,
3939
cssTransitionGroupClassNames: PropTypes.object.isRequired,
40+
intl: intlShape.isRequired,
4041
};
4142

4243
constructor(props, context) {
@@ -123,18 +124,19 @@ export default class CommentForm extends BaseComponent {
123124
}
124125

125126
formHorizontal() {
127+
const { formatMessage } = this.props.intl;
126128
return (
127129
<div>
128130
<hr />
129131
<Form horizontal className="commentForm form-horizontal" onSubmit={this.handleSubmit}>
130132
<FormGroup controlId="formHorizontalName">
131133
<Col componentClass={ControlLabel} sm={2}>
132-
{I18n.t('input.name.label')}
134+
{formatMessage(defaultMessages.inputNameLabel)}
133135
</Col>
134136
<Col sm={10}>
135137
<FormControl
136138
type="text"
137-
placeholder={I18n.t('input.name.placeholder')}
139+
placeholder={formatMessage(defaultMessages.inputNamePlaceholder)}
138140
ref="horizontalAuthorNode"
139141
value={this.state.comment.author}
140142
onChange={this.handleChange}
@@ -145,13 +147,13 @@ export default class CommentForm extends BaseComponent {
145147
</FormGroup>
146148
<FormGroup controlId="formHorizontalName">
147149
<Col componentClass={ControlLabel} sm={2}>
148-
{I18n.t('input.text.label')}
150+
{formatMessage(defaultMessages.inputTextLabel)}
149151
</Col>
150152
<Col sm={10}>
151153
<FormControl
152154
type="textarea"
153155
label="Text"
154-
placeholder={I18n.t('input.text.placeholder')}
156+
placeholder={formatMessage(defaultMessages.inputTextPlaceholder)}
155157
ref="horizontalTextNode"
156158
value={this.state.comment.text}
157159
onChange={this.handleChange}
@@ -167,7 +169,9 @@ export default class CommentForm extends BaseComponent {
167169
className="btn btn-primary"
168170
disabled={this.props.isSaving}
169171
>
170-
{this.props.isSaving ? `${I18n.t('input.saving')}...` : I18n.t('input.post')}
172+
{this.props.isSaving
173+
? `${formatMessage(defaultMessages.inputSaving)}...`
174+
: formatMessage(defaultMessages.inputPost)}
171175
</Button>
172176
</Col>
173177
</FormGroup>
@@ -177,15 +181,16 @@ export default class CommentForm extends BaseComponent {
177181
}
178182

179183
formStacked() {
184+
const { formatMessage } = this.props.intl;
180185
return (
181186
<div>
182187
<hr />
183188
<form className="commentForm form form-stacked" onSubmit={this.handleSubmit}>
184189
<FormGroup controlId="formBasicName">
185-
<ControlLabel>{I18n.t('input.name.label')}</ControlLabel>
190+
<ControlLabel>{formatMessage(defaultMessages.inputNameLabel)}</ControlLabel>
186191
<FormControl
187192
type="text"
188-
placeholder={I18n.t('input.name.placeholder')}
193+
placeholder={formatMessage(defaultMessages.inputNamePlaceholder)}
189194
ref="stackedAuthorNode"
190195
value={this.state.comment.author}
191196
onChange={this.handleChange}
@@ -196,11 +201,11 @@ export default class CommentForm extends BaseComponent {
196201
<FormGroup
197202
controlId="formBasicText"
198203
>
199-
<ControlLabel>{I18n.t('input.text.label')}</ControlLabel>
204+
<ControlLabel>{formatMessage(defaultMessages.inputTextLabel)}</ControlLabel>
200205
<FormControl
201206
type="textarea"
202207
label="Text"
203-
placeholder={I18n.t('input.text.placeholder')}
208+
placeholder={formatMessage(defaultMessages.inputTextPlaceholder)}
204209
ref="stackedTextNode"
205210
value={this.state.comment.text}
206211
onChange={this.handleChange}
@@ -214,7 +219,9 @@ export default class CommentForm extends BaseComponent {
214219
className="btn btn-primary"
215220
disabled={this.props.isSaving}
216221
>
217-
{this.props.isSaving ? `${I18n.t('input.saving')}...` : I18n.t('input.post')}
222+
{this.props.isSaving
223+
? `${formatMessage(defaultMessages.inputSaving)}...`
224+
: formatMessage(defaultMessages.inputPost)}
218225
</Button>
219226
</FormGroup>
220227
</form>
@@ -224,17 +231,18 @@ export default class CommentForm extends BaseComponent {
224231

225232
// Head up! We have some CSS modules going on here with the className props below.
226233
formInline() {
234+
const { formatMessage } = this.props.intl;
227235
return (
228236
<div>
229237
<hr />
230238
<Form inline className="commentForm" onSubmit={this.handleSubmit}>
231239
<FormGroup controlId="formInlineName" >
232240
<ControlLabel>
233-
{I18n.t('input.name.label')}
241+
{formatMessage(defaultMessages.inputNameLabel)}
234242
</ControlLabel>
235243
<FormControl
236244
type="text"
237-
placeholder={I18n.t('input.name.placeholder')}
245+
placeholder={formatMessage(defaultMessages.inputNamePlaceholder)}
238246
ref="inlineAuthorNode"
239247
value={this.state.comment.author}
240248
onChange={this.handleChange}
@@ -245,12 +253,12 @@ export default class CommentForm extends BaseComponent {
245253
</FormGroup>
246254
<FormGroup controlId="formInlineName">
247255
<ControlLabel>
248-
{I18n.t('input.text.label')}
256+
{formatMessage(defaultMessages.inputTextLabel)}
249257
</ControlLabel>
250258
<FormControl
251259
type="textarea"
252260
label="Text"
253-
placeholder={I18n.t('input.text.placeholder')}
261+
placeholder={formatMessage(defaultMessages.inputTextPlaceholder)}
254262
ref="inlineTextNode"
255263
value={this.state.comment.text}
256264
onChange={this.handleChange}
@@ -264,7 +272,9 @@ export default class CommentForm extends BaseComponent {
264272
className="btn btn-primary"
265273
disabled={this.props.isSaving}
266274
>
267-
{this.props.isSaving ? `${I18n.t('input.saving')}...` : I18n.t('input.post')}
275+
{this.props.isSaving
276+
? `${formatMessage(defaultMessages.inputSaving)}...`
277+
: formatMessage(defaultMessages.inputPost)}
268278
</Button>
269279
</Form>
270280
</div>
@@ -284,7 +294,7 @@ export default class CommentForm extends BaseComponent {
284294
}, []);
285295

286296
return (
287-
<Alert bsStyle="danger" key="commentSubmissionError">
297+
<Alert bsStyle='danger' key='commentSubmissionError'>
288298
<strong>Your comment was not saved!</strong>
289299
<ul>
290300
{errorElements}
@@ -310,6 +320,7 @@ export default class CommentForm extends BaseComponent {
310320
}
311321

312322
const { cssTransitionGroupClassNames } = this.props;
323+
const { formatMessage } = this.props.intl;
313324

314325
// For animation with ReactCSSTransitionGroup
315326
// https://facebook.github.io/react/docs/animation.html
@@ -325,13 +336,15 @@ export default class CommentForm extends BaseComponent {
325336
{this.errorWarning()}
326337
</ReactCSSTransitionGroup>
327338

328-
<Nav bsStyle="pills" activeKey={this.state.formMode} onSelect={this.handleSelect}>
329-
<NavItem eventKey={0}>{I18n.t('form.horizontal')}</NavItem>
330-
<NavItem eventKey={1}>{I18n.t('form.stacked')}</NavItem>
331-
<NavItem eventKey={2}>{I18n.t('form.inline')}</NavItem>
339+
<Nav bsStyle='pills' activeKey={this.state.formMode} onSelect={this.handleSelect}>
340+
<NavItem eventKey={0}>{formatMessage(defaultMessages.formHorizontal)}</NavItem>
341+
<NavItem eventKey={1}>{formatMessage(defaultMessages.formStacked)}</NavItem>
342+
<NavItem eventKey={2}>{formatMessage(defaultMessages.formInline)}</NavItem>
332343
</Nav>
333344
{inputForm}
334345
</div>
335346
);
336347
}
337348
}
349+
350+
export default injectIntl(CommentForm);

0 commit comments

Comments
 (0)