複数の言語とロケールで利用できるようにする必要があるアプリを作成しています。
私の質問は純粋に技術的なものではなく、アーキテクチャ、そしてこの問題を解決するために実際に本番環境で使用されているパターンに関するものです。そのための「料理の本」はどこにも見つからなかったので、お気に入りのQ/Aウェブサイトに目を向けています:)
私の要件は次のとおりです(これらは実際には「標準」です)。
考えられる解決策は次のとおりです。
各コンポーネントは単独で翻訳を処理します
つまり、各コンポーネントには、たとえば、en.json、fr.jsonなどのファイルと翻訳された文字列のセットがあります。また、選択した言語に応じた値から値を読み取るのに役立つヘルパー関数。
各コンポーネントはpropsを介して翻訳を受け取ります
したがって、彼らは現在の言語を認識せず、現在の言語に一致する小道具として文字列のリストを取得します
小道具を少しバイパスし、おそらく context thingyを使用して現在の言語を伝えます
他にアイデアがあれば、言ってください!
どうやってやるの?
かなりの数のソリューションを試した後、うまく機能し、React 0.14の慣用的なソリューションになるはずであると思います(つまり、ミックスインを使用せず、高次コンポーネント)(edit:もちろんReact 15でも問題ありません!)。
したがって、ここで解決策は、下から始まります(個々のコンポーネント):
コンポーネント
コンポーネントが(慣例により)必要とする唯一のものは、strings
propsです。コンポーネントに必要なさまざまな文字列を含むオブジェクトである必要がありますが、実際の形状はユーザー次第です。
デフォルトの翻訳が含まれているため、翻訳を提供する必要なく他の場所でコンポーネントを使用できます(デフォルトの言語、この例では英語ですぐに動作します)
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デコレータを許可するために関数がカリー化されていることに注意)は、コンポーネント自体がラップすることです。
翻訳コンポーネントのコードは次のとおりです。
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
を独自の手段で提供したい他のユーザーが再利用できます。
[編集:2016年3月31日]:最近、ReactとReduxで構築された多言語対応のレトロスペクティブボード(アジャイルレトロスペクティブ用)に取り組みました。非常に多くの人がコメントで実際の例を求めたので、ここにあります:
コードはここにあります: https://github.com/antoinejaussoin/retro-board/tree/master
私の経験から、最善のアプローチは、i18n redux stateを作成し、それを使用することです。
1-これにより、データベース、ローカルファイル、またはEJSやjadeなどのテンプレートエンジンから初期値を渡すことができます。
2-ユーザーが言語を変更すると、UIを更新しなくてもアプリケーションの言語全体を変更できます。
3-ユーザーが言語を変更すると、API、ローカルファイル、または定数から新しい言語を取得することもできます
4-また、タイムゾーン、通貨、方向(RTL/LTR)、利用可能な言語のリストなどの文字列で他の重要なものを保存することもできます
5-変更言語を通常のreduxアクションとして定義できます
6-バックエンドとフロントエンドの文字列を1か所に配置できます。たとえば、私の場合、ローカライズに i18n-node を使用し、ユーザーがUI言語を変更するときは、通常のAPI呼び出しとバックエンドでは、i18n.getCatalog(req)
を返すだけです。これは現在の言語のすべてのユーザー文字列のみを返します
国際化の初期状態に対する私の提案は次のとおりです:
{
"language":"ar",
"availableLanguages":[
{"code":"en","name": "English"},
{"code":"ar","name":"عربي"}
],
"catalog":[
"Hello":"مرحباً",
"Thank You":"شكراً",
"You have {count} new messages":"لديك {count} رسائل جديدة"
],
"timezone":"",
"currency":"",
"direction":"rtl",
}
i18nの追加の便利なモジュール:
1- string-template これにより、たとえば次のようにカタログ文字列の間に値を挿入できます。
import template from "string-template";
const count = 7;
//....
template(i18n.catalog["You have {count} new messages"],{count}) // لديك ٧ رسائل جديدة
2- human-format このモジュールを使用すると、人間が読み取り可能な文字列との間で数値を変換できます。たとえば、
import humanFormat from "human-format";
//...
humanFormat(1337); // => '1.34 k'
// you can pass your own translated scale, e.g: humanFormat(1337,MyScale)
3- momentjs 最も有名な日付と時刻のnpmライブラリ。瞬時に翻訳できますが、現在の状態言語を渡すために必要な翻訳が既に組み込まれています。
import moment from "moment";
const umoment = moment().locale(i18n.language);
umoment.format('MMMM Do YYYY, h:mm:ss a'); // أيار مايو ٢ ٢٠١٧، ٥:١٩:٥٥ م
現在、反応コンテキストAPI(reduxなし)を使用して同じ概念を実装するフレームワークが多数あります。個人的に I18next をお勧めします
アントワーヌのソリューションは正常に動作しますが、いくつかの注意事項があります。
そのため、ReduxとAirBNBの両方の Polyglot の上に redux-polyglot を構築しました。
(私は著者の一人です)
setLanguage(lang, messages)
の直接ディスパッチP
オブジェクトを取得するgetP(state)
セレクター:t(key)
:元のポリグロットT関数tc(key)
:大文字の翻訳tu(key)
:大文字変換tm(morphism)(key)
:カスタムモーフィング翻訳getLocale(state)
selectortranslate
オブジェクトをpropsに注入してReactコンポーネントを強化するp
高次コンポーネントimport setLanguage from 'redux-polyglot/setLanguage';
store.dispatch(setLanguage('en', {
common: { hello_world: 'Hello world' } } }
}));
import React, { PropTypes } from 'react';
import translate from 'redux-polyglot/translate';
const MyComponent = props => (
<div className='someId'>
{props.p.t('common.hello_world')}
</div>
);
MyComponent.propTypes = {
p: PropTypes.shape({t: PropTypes.func.isRequired}).isRequired,
}
export default translate(MyComponent);
質問/提案があれば教えてください!
これに関する私の研究から、JavaScriptでi18nに使用されている2つの主なアプローチ、 ICU と gettext があるようです。
私はgettextのみを使用したことがあるため、偏見があります。
私を驚かせるのは、サポートの質の低さです。 CakePHPまたはWordPressのPHPの世界から来ました。これらの状況の両方で、すべての文字列が__('')
で単純に囲まれ、その後、POファイルを使用して非常に簡単に翻訳を取得することが基本的な標準です。
文字列をフォーマットするためのsprintfの親しみが得られ、POファイルは何千もの異なる機関によって簡単に翻訳されます。
2つの一般的なオプションがあります。
どちらもgettextスタイルのサポート、文字列のsprintfスタイルのフォーマット、POファイルへのインポート/エクスポートが可能です。
i18nextには、自分で開発した React拡張 があります。ジェッドはしません。 Sentry.ioは、JedとReactのカスタム統合を使用しているようです。 React + Redux post は、使用を提案します
ツール:jed + po2json + jsxgettext
しかし、Jedはよりgettextに焦点を合わせた実装のように見えます-それは、i18nextがオプションとしてそれを持っているところ、意図を表明したということです。
これにより、翻訳に関するエッジケースのサポートが強化されます。性別を扱うために。より複雑な言語に翻訳する場合は、この利点が得られると思います。
このための一般的なオプションは messageformat.js です。これで簡単に説明します sentry.ioブログチュートリアル 。 messageformat.jsは、実際にはJedを書いた同じ人によって開発されています。 彼はICUの使用について非常に強い主張をしています :
私の意見では、Jedは完全な機能です。バグを修正できてうれしいですが、一般的にライブラリにさらに追加することに興味はありません。
また、messageformat.jsも管理しています。 gettextの実装を特に必要としない場合は、代わりにMessageFormatを使用することをお勧めします。複数形/性別のサポートが改善されており、ロケールデータが組み込まれているためです。
sprintfを使用したgettext:
i18next.t('Hello world!');
i18next.t(
'The first 4 letters of the english alphabet are: %s, %s, %s and %s',
{ postProcess: 'sprintf', sprintf: ['a', 'b', 'c', 'd'] }
);
messageformat.js( ガイド を読んでからの最良の推測):
mf.compile('Hello world!')();
mf.compile(
'The first 4 letters of the english alphabet are: {s1}, {s2}, {s3} and {s4}'
)({ s1: 'a', s2: 'b', s3: 'c', s4: 'd' });
https://react.i18next.com/ をご覧になっていない場合は、良いアドバイスかもしれません。 i18nextに基づいています:一度学ぶ-どこでも翻訳します。
コードは次のようになります。
<div>{t('simpleContent')}</div>
<Trans i18nKey="userMessagesUnread" count={count}>
Hello <strong title={t('nameTitle')}>{{name}}</strong>, you have {{count}} unread message. <Link to="/msgs">Go to messages</Link>.
</Trans>
以下のサンプルが付属しています:
https://github.com/i18next/react-i18next/tree/master/example
それに加えて、開発中および後で翻訳者のためにワークフローも考慮する必要があります-> https://www.youtube.com/watch?v=9NOzJhgmyQE
create-react-appを使用した簡単なソリューションを提案したいと思います。
アプリケーションはすべての言語に対して個別に構築されるため、翻訳ロジック全体がアプリケーションの外に移動されます。
Webサーバーは、Accept-Languageヘッダーに応じて自動的に、またはcookieを設定して手動で正しい言語を提供します。
ほとんどの場合、言語を一度も変更することはありません。
スタイル、html、およびコードに沿って、それを使用する同じコンポーネントファイル内に配置された翻訳データ。
そしてここには、独自の状態、ビュー、翻訳を担当する完全に独立したコンポーネントがあります。
import React from 'react';
import {withStyles} from 'material-ui/styles';
import {languageForm} from './common-language';
const {REACT_APP_LANGUAGE: LANGUAGE} = process.env;
export let language; // define and export language if you wish
class Component extends React.Component {
render() {
return (
<div className={this.props.classes.someStyle}>
<h2>{language.title}</h2>
<p>{language.description}</p>
<p>{language.amount}</p>
<button>{languageForm.save}</button>
</div>
);
}
}
const styles = theme => ({
someStyle: {padding: 10},
});
export default withStyles(styles)(Component);
// sets laguage at build time
language = (
LANGUAGE === 'ru' ? { // Russian
title: 'Транзакции',
description: 'Описание',
amount: 'Сумма',
} :
LANGUAGE === 'ee' ? { // Estonian
title: 'Tehingud',
description: 'Kirjeldus',
amount: 'Summa',
} :
{ // default language // English
title: 'Transactions',
description: 'Description',
amount: 'Sum',
}
);
言語環境変数をpackage.jsonに追加します
"start": "REACT_APP_LANGUAGE=ru npm-run-all -p watch-css start-js",
"build": "REACT_APP_LANGUAGE=ru npm-run-all build-css build-js",
それだけです!
また、私の元の答えには、翻訳ごとに単一のjsonファイルを使用したよりモノリシックなアプローチが含まれていました:
lang/ru.json
{"hello": "Привет"}
lib/lang.js
export default require(`../lang/${process.env.REACT_APP_LANGUAGE}.json`);
src/App.jsx
import lang from '../lib/lang.js';
console.log(lang.hello);