Спробувавши декілька рішень, я думаю, що я знайшов таке, яке добре працює і повинно бути ідіоматичним рішенням для React 0.14 (тобто він не використовує міксини, а компоненти вищого порядку) ( редагувати : також ідеально добре з React 15, звичайно! ).
Отже, тут рішення, починаючи з низу (окремих компонентів):
Компонент
Єдине, що знадобиться вашому компоненту (за умовами) - це stringsреквізит. Це повинен бути об'єкт, що містить різні рядки, потрібні вашому Компоненту, але саме форма його залежить від вас.
Він містить переклади за замовчуванням, тому ви можете використовувати компонент де-небудь ще без необхідності вводити будь-який переклад (він би працював поза кодом з мовою за замовчуванням, англійською в цьому прикладі)
import { default as React, PropTypes } from 'react';
import translate from './translate';
class MyComponent extends React.Component {
render() {
return (
<div>
{ this.props.strings.someTranslatedText }
</div>
);
}
}
MyComponent.propTypes = {
strings: PropTypes.object
};
MyComponent.defaultProps = {
strings: {
someTranslatedText: 'Hello World'
}
};
export default translate('MyComponent')(MyComponent);
Компонент вищого порядку
На попередньому фрагменті ви могли помітити це в останньому рядку:
translate('MyComponent')(MyComponent)
translate у цьому випадку є компонентом вищого порядку, який обгортає ваш компонент і надає деяку додаткову функціональність (ця конструкція замінює міксин попередніх версій React).
Перший аргумент - це ключ, який буде використовуватися для пошуку перекладів у файлі перекладу (я тут використовував ім’я компонента, але це може бути все, що завгодно). Другий (зауважте, що функція завита, щоб дозволити декораторам ES7) - це сам компонент, щоб завертати.
Ось код компонента перекладу:
import { default as React } from 'react';
import en from '../i18n/en';
import fr from '../i18n/fr';
const languages = {
en,
fr
};
export default function translate(key) {
return Component => {
class TranslationComponent extends React.Component {
render() {
console.log('current language: ', this.context.currentLanguage);
var strings = languages[this.context.currentLanguage][key];
return <Component {...this.props} {...this.state} strings={strings} />;
}
}
TranslationComponent.contextTypes = {
currentLanguage: React.PropTypes.string
};
return TranslationComponent;
};
}
Це не магія: він просто зчитує поточну мову з контексту (і цей контекст не кровоточить по всій базі коду, щойно використовується тут, у цій обгортці), а потім отримає відповідний рядковий об'єкт із завантажених файлів. Ця логіка є досить наївною в цьому прикладі, її можна зробити так, як вам справді хочеться.
Важливим елементом є те, що він бере діючу мову з контексту і перетворює її в рядки, враховуючи наданий ключ.
На самій вершині ієрархії
Для кореневого компонента вам просто потрібно встановити поточну мову з вашого поточного стану. У наступному прикладі використовується Redux як Flux-подібна реалізація, але її можна легко перетворити, використовуючи будь-який інший фреймворк / шаблон / бібліотеку.
import { default as React, PropTypes } from 'react';
import Menu from '../components/Menu';
import { connect } from 'react-redux';
import { changeLanguage } from '../state/lang';
class App extends React.Component {
render() {
return (
<div>
<Menu onLanguageChange={this.props.changeLanguage}/>
<div className="">
{this.props.children}
</div>
</div>
);
}
getChildContext() {
return {
currentLanguage: this.props.currentLanguage
};
}
}
App.propTypes = {
children: PropTypes.object.isRequired,
};
App.childContextTypes = {
currentLanguage: PropTypes.string.isRequired
};
function select(state){
return {user: state.auth.user, currentLanguage: state.lang.current};
}
function mapDispatchToProps(dispatch){
return {
changeLanguage: (lang) => dispatch(changeLanguage(lang))
};
}
export default connect(select, mapDispatchToProps)(App);
І на закінчення файли перекладу:
Файли перекладу
// en.js
export default {
MyComponent: {
someTranslatedText: 'Hello World'
},
SomeOtherComponent: {
foo: 'bar'
}
};
// fr.js
export default {
MyComponent: {
someTranslatedText: 'Salut le monde'
},
SomeOtherComponent: {
foo: 'bar mais en français'
}
};
Як ви думаєте, хлопці?
Я думаю, що це вирішує всю проблему, яку я намагався уникнути у своєму питанні: логіка перекладу не кровоточить у всьому вихідному коді, вона досить ізольована і дозволяє повторно використовувати компоненти без неї.
Наприклад, MyComponent не потрібно обгортати translate (), а він може бути окремим, що дозволяє його повторно використовувати будь-хто інший, хто бажає надати stringsїх власними засобами.
[Редагувати: 31.03.2016]: Нещодавно я працював над ретроспективною дошкою (для Agile Retrospectives), побудованою за допомогою React & Redux, і є багатомовною. Оскільки в коментарях досить багато людей запитували приклад із реального життя, ось це:
Ви можете знайти код тут: https://github.com/antoinejaussoin/retro-board/tree/master