Попробовав несколько решений, я думаю, что нашел тот, который хорошо работает и должен быть идиоматическим решением для 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
его своими собственными средствами.
[Edit: 31/03/2016]: недавно я работал над Retrospective Board (для Agile Retrospectives), созданным с помощью React и Redux, и многоязычным. Поскольку довольно много людей просили в комментариях привести пример из реальной жизни, вот он:
Вы можете найти код здесь: https://github.com/antoinejaussoin/retro-board/tree/master