Después de probar bastantes soluciones, creo que encontré una que funciona bien y debería ser una solución idiomática para React 0.14 (es decir, no usa mixins, sino componentes de orden superior) ( editar : ¡también perfectamente bien con React 15, por supuesto! ).
Entonces aquí la solución, comenzando por la parte inferior (los componentes individuales):
El componente
Lo único que necesitaría su componente (por convención) es un strings
accesorio. Debe ser un objeto que contenga las diversas cadenas que necesita su componente, pero realmente la forma depende de usted.
Contiene las traducciones predeterminadas, por lo que puede usar el componente en otro lugar sin la necesidad de proporcionar ninguna traducción (funcionaría de inmediato con el idioma predeterminado, inglés en este ejemplo)
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);
El componente de orden superior
En el fragmento anterior, es posible que haya notado esto en la última línea:
translate('MyComponent')(MyComponent)
translate
en este caso, es un componente de orden superior que envuelve su componente y proporciona alguna funcionalidad adicional (esta construcción reemplaza los mixins de versiones anteriores de React).
El primer argumento es una clave que se usará para buscar las traducciones en el archivo de traducción (usé el nombre del componente aquí, pero podría ser cualquier cosa). El segundo (observe que la función está modificada, para permitir que los decoradores de ES7) sea el componente en sí para envolver.
Aquí está el código para el componente de traducción:
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;
};
}
No es mágico: simplemente leerá el idioma actual del contexto (y ese contexto no se desangra en toda la base del código, solo se usa aquí en este contenedor), y luego obtendrá el objeto de cadenas relevante de los archivos cargados. Esta pieza de lógica es bastante ingenua en este ejemplo, podría hacerse de la manera que realmente desee.
La pieza importante es que toma el lenguaje actual del contexto y lo convierte en cadenas, dada la clave proporcionada.
En lo más alto de la jerarquía
En el componente raíz, solo necesita establecer el idioma actual desde su estado actual. El siguiente ejemplo usa Redux como implementación similar a Flux, pero se puede convertir fácilmente usando cualquier otro marco / patrón / biblioteca.
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);
Y para terminar, los archivos de traducción:
Archivos de traducción
// 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'
}
};
¿Qué piensan ustedes?
Creo que esto resuelve todo el problema que estaba tratando de evitar en mi pregunta: la lógica de traducción no sangra en todo el código fuente, está bastante aislada y permite reutilizar los componentes sin ella.
Por ejemplo, MyComponent no necesita ser envuelto por translate () y podría estar separado, permitiendo su reutilización por cualquier otra persona que desee proporcionar el strings
por sus propios medios.
[Edición: 31/03/2016]: Recientemente trabajé en un tablero de retrospectiva (para retrospectivas ágiles), construido con React & Redux, y es multilingüe. Dado que muchas personas pidieron un ejemplo de la vida real en los comentarios, aquí está:
Puede encontrar el código aquí: https://github.com/antoinejaussoin/retro-board/tree/master