Po wypróbowaniu kilku rozwiązań, myślę, że znalazłem takie, które działa dobrze i powinno być idiomatycznym rozwiązaniem dla React 0.14 (tj. Nie używa on mixinów, ale komponentów wyższego rzędu) ( edytuj : również doskonale pasuje do React 15 oczywiście! ).
Więc tutaj rozwiązanie, zaczynając od dołu (poszczególne składniki):
Składnik
Jedyne, czego potrzebuje twój komponent (zgodnie z konwencją), to plik strings
rekwizyty. Powinien to być obiekt zawierający różne ciągi znaków potrzebne Twojemu komponentowi, ale tak naprawdę jego kształt zależy od Ciebie.
Zawiera domyślne tłumaczenia, więc możesz użyć komponentu w innym miejscu bez konieczności dostarczania jakiegokolwiek tłumaczenia (działałoby po wyjęciu z pudełka z domyślnym językiem, angielskim w tym przykładzie)
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);
Komponent wyższego rzędu
W poprzednim fragmencie mogłeś zauważyć to w ostatnim wierszu:
translate('MyComponent')(MyComponent)
translate
w tym przypadku jest to komponent wyższego rzędu, który otacza twój komponent i zapewnia dodatkową funkcjonalność (ta konstrukcja zastępuje miksy poprzednich wersji React).
Pierwszy argument to klucz, który zostanie użyty do wyszukania tłumaczeń w pliku tłumaczeń (użyłem tutaj nazwy komponentu, ale może to być wszystko). Drugi (zauważ, że funkcja jest curry, aby umożliwić dekoratorom ES7) sam komponent do zawijania.
Oto kod komponentu tłumaczenia:
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;
};
}
To nie jest magia: po prostu odczyta bieżący język z kontekstu (a ten kontekst nie rozleje się po całej bazie kodu, po prostu użyty w tym opakowaniu), a następnie pobierze odpowiedni obiekt ciągów z załadowanych plików. Ten kawałek logiki jest w tym przykładzie dość naiwny, można go zrobić tak, jak chcesz.
Ważnym elementem jest to, że pobiera bieżący język z kontekstu i konwertuje go na łańcuchy, biorąc pod uwagę dostarczony klucz.
Na samym szczycie hierarchii
W komponencie głównym wystarczy ustawić bieżący język z bieżącego stanu. Poniższy przykład używa Redux jako implementacji podobnej do Flux, ale można ją łatwo przekonwertować za pomocą dowolnego innego środowiska / wzorca / biblioteki.
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);
Na koniec pliki tłumaczeń:
Pliki tłumaczeń
// 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'
}
};
Co o tym myślicie?
Myślę, że rozwiązuje to cały problem, którego starałem się uniknąć w swoim pytaniu: logika tłumaczenia nie rozlewa się po całym kodzie źródłowym, jest dość odizolowana i umożliwia ponowne użycie komponentów bez niej.
Na przykład element MyComponent nie musi być opakowywany przez translate () i może być oddzielny, umożliwiając jego ponowne wykorzystanie przez każdego, kto chce udostępnić strings
go we własnym zakresie.
[Edycja: 31/03/2016]: Niedawno pracowałem nad tablicą retrospektywną (dla Agile Retrospectives), zbudowaną za pomocą React & Redux i jest wielojęzyczna. Ponieważ całkiem sporo osób prosiło w komentarzach o przykład z życia wzięty, oto on:
Możesz znaleźć kod tutaj: https://github.com/antoinejaussoin/retro-board/tree/master