かなりの数のソリューションを試した後、私はうまく機能し、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の以前のバージョンのミックスインを置き換えます)。
最初の引数は、翻訳ファイル内の翻訳を検索するために使用されるキーです(ここではコンポーネントの名前を使用しましたが、何でもかまいません)。2つ目(ES7デコレーターを許可するために関数がカレー化されていることに注意)は、ラップするコンポーネント自体です。
以下は、translateコンポーネントのコードです。
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;
};
}
それは魔法ではありません:コンテキストから現在の言語を読み取るだけで(そのコンテキストはコードベース全体に流れ出ることはなく、このラッパーで使用されるだけです)、ロードされたファイルから関連する文字列オブジェクトを取得します。このロジックの一部は、この例では非常にナイーブであり、実際に望む方法で実行できます。
重要な部分は、提供されたキーを指定して、コンテキストから現在の言語を取得し、それを文字列に変換することです。
階層の最上位
ルートコンポーネントでは、現在の状態から現在の言語を設定するだけです。次の例では、Fluxのような実装としてReduxを使用していますが、他のフレームワーク/パターン/ライブラリを使用して簡単に変換できます。
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
独自の方法でを提供したい他の人が再利用できます。
[編集:2016年3月31日]:最近、React&Reduxで構築された多言語対応のRetrospective Board(Agile Retrospectives用)に取り組みました。かなり多くの人々がコメントで実際の例を求めたので、それはここにあります:
ここでコードを見つけることができます:https : //github.com/antoinejaussoin/retro-board/tree/master