Skip to content

Commit 857140d

Browse files
author
ryanjohnson
committed
Merge branch 'feature/enhance-translate-function'
2 parents cc54813 + bff32d2 commit 857140d

File tree

3 files changed

+144
-36
lines changed

3 files changed

+144
-36
lines changed

README.md

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ const mapStateToProps = state => ({
131131
export default connect(mapStateToProps)(Greeting);
132132
```
133133

134-
For components not already using `connect` instead use [localize](#localizecomponent). This will automatically connect your component with the `translate` function and `currentLanguage` prop.
134+
For components not already using `connect` instead use [localize](#localizecomponent-slice). This will automatically connect your component with the `translate` function and `currentLanguage` prop.
135135

136136
```javascript
137137
import { localize } from 'react-localize-redux';
@@ -204,6 +204,38 @@ Then pass in the data you want to swap in for placeholders to the `translate` fu
204204
<h1>{ translate('info.greeting') }</h1>
205205
```
206206

207+
### Pass multiple translations to child components
208+
209+
A parent component that has added the `translate` function by using [getTranslate](#gettranslatestate) or [localize](#localizecomponent-slice) can easily pass multiple translations down to it's child components. Just pass the `translate` function an array of translation keys instead of a single key.
210+
211+
```json
212+
{
213+
"heading": ["Heading", "Heading French"],
214+
"article": {
215+
"title": ["Title", "Title French"],
216+
"author": ["By ${ name }", "By French ${ name }"],
217+
"desc": ["Description", "Description French"]
218+
}
219+
}
220+
```
221+
222+
```javascript
223+
const Article = props => (
224+
<div>
225+
<h2>{ props['article.title'] }</h2>
226+
<h3>{ props['article.author'] }</h3>
227+
<p>{ props['article.desc'] }</p>
228+
</div>
229+
);
230+
231+
const Page = ({ translate }) => (
232+
<div>
233+
<h1>{ translate('heading') }</h1>
234+
<Article { ...translate(['article.title', 'article.author', 'article.desc'], { name: 'Ted' }) } />
235+
</div>
236+
);
237+
```
238+
207239
### Load translation data on demand
208240

209241
If you have a larger app you may want to break your translation data up into multiple files, or maybe your translation data is being loaded from a service. Either way you can call [addTranslation](#addtranslationdata) for each new translation file/service, and the new translation data will be merged with any existing data.
@@ -216,12 +248,12 @@ Also If you are using a tool like [webpack](https://webpack.js.org) for bundling
216248

217249
A selector that takes the localeReducer slice of your `state` and returns the translate function. This function will have access to any and all translations that were added with [addTranslation](#addtranslationdata).
218250

219-
returns `(key, data) => LocalizedElement`
251+
returns `(key | key[], data) => LocalizedElement | { [key: string]: LocalizedElement }`
220252

221-
* `key: string` = The key for the transaltion string e.g. 'greeting'
253+
* `key: string|array` = A translation key or an array of translation keys.
222254
* `data: object` = Pass data to your [dynamic translation](#add-dynamic-content-to-translations) string.
223255

224-
#### Usage:
256+
#### Usage (single translation):
225257

226258
```javascript
227259
const Greeting = ({ translate }) => <h1>{ translate('greeting', { name: 'Testy McTest' }) }</h1>
@@ -230,6 +262,10 @@ const mapStateToProps = state => ({ translate: getTranslate(state.locale) });
230262
export default connect(mapStateToProps)(Greeting);
231263
```
232264

265+
#### Usage (multiple translations):
266+
267+
See [Pass multiple translations to child components](#pass-multiple-translations-to-child-components).
268+
233269
### `getActiveLanguage(state)`
234270

235271
A selector that takes the localeReducer slice of your `state` and returns the currently active language object.
@@ -266,7 +302,32 @@ const mapStateToProps = state => ({ languages: getLanguages(state.locale) });
266302
export default connect(mapStateToProps)(Greeting);
267303
```
268304

269-
### `localize(Component, slice)`
305+
### `getLanguages(state)`
306+
307+
A selector that takes your redux `state` and returns the languages you set.
308+
309+
returns `[{ code: 'en', active: true }, { code: 'fr', active: false }]`;
310+
311+
#### Usage:
312+
313+
```javascript
314+
const LanguageSelector = ({ languages }) => (
315+
<ul>
316+
{ languages.map(language =>
317+
<a href={ `/${ language.code }` }>{ language.code }</a>
318+
)}
319+
</ul>
320+
)
321+
322+
const mapStateToProps = state => ({ languages: getLanguages(state) });
323+
export default connect(mapStateToProps)(Greeting);
324+
```
325+
326+
### `getTranslations(state)`
327+
328+
A selector that takes your redux `state` and returns the raw translation data.
329+
330+
### `localize(Component)`
270331

271332
If you have a Component that is not using `connect` you can wrap it with `localize` to automatically add the `translate` function and `currentLanguage` prop. When using `combineReducers` to add `localeReducer` you must pass the `slice` param to `localize`, where `slice` is the name of the prop you used with `combineReducers` (e.g. locale).
272333

src/modules/locale.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,18 @@ export const getTranslationsForActiveLanguage = customeEqualSelector(
111111

112112
export const getTranslate = (state) => {
113113
const translations = getTranslationsForActiveLanguage(state);
114-
return (key, data) => getLocalizedElement(key, translations, data);
114+
return (value, data) => {
115+
if (typeof value === 'string') {
116+
return getLocalizedElement(value, translations, data);
117+
} else if (Array.isArray(value)) {
118+
return value.reduce((prev, cur) => {
119+
return {
120+
...prev,
121+
[cur]: getLocalizedElement(cur, translations, data)
122+
};
123+
}, {});
124+
} else {
125+
throw new Error('react-localize-redux: invalid key passed to translate.');
126+
}
127+
}
115128
};

tests/modules.test.js

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,10 @@
11
import * as actions from 'modules/locale';
2-
import { languages, translations, getActiveLanguage, getTranslationsForActiveLanguage, customeEqualSelector, setLanguages } from 'modules/locale';
2+
import { shallow } from 'enzyme';
3+
import { languages, translations, getActiveLanguage, getTranslationsForActiveLanguage, customeEqualSelector, setLanguages, getTranslate } from 'modules/locale';
34
import { SET_LANGUAGES, SET_ACTIVE_LANGUAGE, ADD_TRANSLATION } from 'modules/locale';
45

56
describe('locale module', () => {
67

7-
let mainState = {};
8-
9-
beforeEach(() => {
10-
mainState = {
11-
locale: {
12-
currentLanguage: 'en',
13-
translations: {
14-
global: {
15-
en: {
16-
title: 'Global Title'
17-
},
18-
fr: {
19-
title: 'FR Global Title'
20-
}
21-
},
22-
local: {
23-
en: {
24-
greeting: 'Greeting'
25-
},
26-
fr: {
27-
greeting: 'FR Greeting'
28-
}
29-
}
30-
}
31-
}
32-
};
33-
});
34-
35-
368
describe('reducer: languages', () => {
379
let initialState = [];
3810

@@ -226,6 +198,68 @@ describe('locale module', () => {
226198
});
227199
});
228200

201+
describe('getTranslate', () => {
202+
let state = {};
203+
204+
beforeEach(() => {
205+
state = {
206+
languages: [{ code: 'en', active: false }, { code: 'fr', active: true }],
207+
translations: {
208+
hi: ['hi-en', 'hi-fr'],
209+
bye: ['bye-en', 'bye-fr'],
210+
yo: ['yo ${ name }', 'yo-fr ${ name }'],
211+
foo: ['foo ${ bar }', 'foo-fr ${ bar }']
212+
}
213+
};
214+
});
215+
216+
it('should throw an error when invalid key provided to translate function', () => {
217+
const translate = getTranslate(state);
218+
expect(() => translate(23)).toThrow();
219+
});
220+
221+
it('should return single translated element when valid key provided', () => {
222+
const translate = getTranslate(state);
223+
const result = translate('hi');
224+
const wrapper = shallow(result);
225+
expect(wrapper.text()).toBe('hi-fr');
226+
expect(wrapper.type()).toBe('span');
227+
});
228+
229+
it('should return an object of translation keys matched with translated element', () => {
230+
const translate = getTranslate(state);
231+
const result = translate(['hi', 'bye']);
232+
233+
Object.keys(result).map((key, index) => {
234+
const element = result[key];
235+
const wrapper = shallow(element);
236+
expect(wrapper.text()).toBe(state.translations[key][1]);
237+
});
238+
});
239+
240+
it('should insert dynamic data for single translation', () => {
241+
const translate = getTranslate(state);
242+
const element = translate('yo', { name: 'ted' });
243+
const wrapper = shallow(element);
244+
expect(wrapper.text()).toBe('yo-fr ted');
245+
});
246+
247+
it('should insert dynamic data for multiple translations', () => {
248+
const translate = getTranslate(state);
249+
const result = translate(['yo', 'foo'], { name: 'ted', bar: 'bar' });
250+
const results = [
251+
'yo-fr ted',
252+
'foo-fr bar'
253+
];
254+
255+
Object.keys(result).map((key, index) => {
256+
const element = result[key];
257+
const wrapper = shallow(element);
258+
expect(wrapper.text()).toBe(results[index]);
259+
});
260+
});
261+
});
262+
229263
describe('createSelectorCreator', () => {
230264
let languages = [];
231265
let activeLanguage = {};

0 commit comments

Comments
 (0)