Спробувавши декілька рішень, я думаю, що я знайшов таке, яке добре працює і повинно бути ідіоматичним рішенням для 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