Dopo aver provato alcune soluzioni, penso di averne trovata una che funziona bene e dovrebbe essere una soluzione idiomatica per React 0.14 (cioè non usa mixin, ma componenti di ordine superiore) ( modifica : anche perfettamente bene con React 15 ovviamente! ).
Quindi ecco la soluzione, partendo dal basso (i singoli componenti):
Il componente
L'unica cosa di cui il tuo componente avrebbe bisogno (per convenzione), è un file strings
oggetto di scena. Dovrebbe essere un oggetto contenente le varie stringhe di cui il tuo Componente ha bisogno, ma in realtà la forma dipende da te.
Contiene le traduzioni predefinite, quindi puoi utilizzare il componente da qualche altra parte senza la necessità di fornire alcuna traduzione (funzionerebbe immediatamente con la lingua predefinita, l'inglese in questo esempio)
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);
Il componente di ordine superiore
Nello snippet precedente, potresti averlo notato nell'ultima riga:
translate('MyComponent')(MyComponent)
translate
in questo caso è un componente di ordine superiore che avvolge il componente e fornisce alcune funzionalità extra (questa costruzione sostituisce i mixin delle versioni precedenti di React).
Il primo argomento è una chiave che verrà utilizzata per cercare le traduzioni nel file di traduzione (ho usato il nome del componente qui, ma potrebbe essere qualsiasi cosa). Il secondo (notare che la funzione è curry, per consentire ai decoratori ES7) è il Componente stesso di avvolgere.
Ecco il codice per il componente di traduzione:
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;
};
}
Non è magico: leggerà semplicemente la lingua corrente dal contesto (e quel contesto non si spande su tutta la base di codice, usata solo qui in questo wrapper), e quindi otterrà l'oggetto stringhe pertinente dai file caricati. Questo pezzo di logica è abbastanza ingenuo in questo esempio, potrebbe essere fatto nel modo in cui vuoi davvero.
La parte importante è che prende la lingua corrente dal contesto e la converte in stringhe, data la chiave fornita.
Al vertice della gerarchia
Sul componente root, devi solo impostare la lingua corrente dal tuo stato attuale. L'esempio seguente utilizza Redux come implementazione simile a Flux, ma può essere facilmente convertito utilizzando qualsiasi altro framework / pattern / libreria.
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);
E per finire, i file di traduzione:
File di traduzione
// 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'
}
};
Che cosa ne pensate?
Penso che risolva tutto il problema che stavo cercando di evitare nella mia domanda: la logica di traduzione non sanguina in tutto il codice sorgente, è abbastanza isolata e consente di riutilizzare i componenti senza di essa.
Ad esempio, MyComponent non ha bisogno di essere racchiuso da translate () e potrebbe essere separato, consentendo il suo riutilizzo da parte di chiunque altro desideri fornire il strings
con le proprie intenzioni.
[Modifica: 31/03/2016]: Recentemente ho lavorato su una Retrospective Board (per Agile Retrospectives), costruita con React & Redux, ed è multilingue. Poiché molte persone hanno chiesto un esempio di vita reale nei commenti, eccolo qui:
Puoi trovare il codice qui: https://github.com/antoinejaussoin/retro-board/tree/master