Aplikacje React / Redux i wielojęzyczne (internacjonalizacja) - architektura

119

Tworzę aplikację, która będzie musiała być dostępna w wielu językach i lokalizacjach.

Moje pytanie nie jest czysto techniczne, ale dotyczy raczej architektury i wzorów, których ludzie używają w produkcji, aby rozwiązać ten problem. Nie mogłem nigdzie znaleźć żadnej "książki kucharskiej", więc przechodzę do mojej ulubionej strony z pytaniami i odpowiedziami :)

Oto moje wymagania (są naprawdę „standardowe”):

  • Użytkownik może wybrać język (trywialne)
  • Po zmianie języka interfejs powinien automatycznie przetłumaczyć na nowy wybrany język
  • W tej chwili nie martwię się zbytnio o formatowanie liczb, dat itp. Chcę prostego rozwiązania, aby po prostu przetłumaczyć ciągi znaków

Oto możliwe rozwiązania, które mógłbym wymyślić:

Każdy komponent zajmuje się tłumaczeniem w izolacji

Oznacza to, że każdy komponent ma na przykład zestaw plików en.json, fr.json itp. Wraz z przetłumaczonymi napisami. I funkcja pomocnicza ułatwiająca odczytywanie wartości z tych zależnych od wybranego języka.

  • Za: bardziej szanujący filozofię React, każdy komponent jest „samodzielny”
  • Wady: nie można scentralizować wszystkich tłumaczeń w pliku (na przykład aby ktoś inny dodał nowy język)
  • Wady: nadal musisz przekazać obecny język jako rekwizyt, w każdym krwawym elemencie i ich dzieciach

Każdy komponent otrzymuje tłumaczenia za pośrednictwem rekwizytów

Więc nie są świadomi obecnego języka, po prostu pobierają listę ciągów jako rekwizytów, które pasują do bieżącego języka

  • Za: skoro te struny przychodzą „z góry”, mogą być gdzieś scentralizowane
  • Wady: Każdy komponent jest teraz powiązany z systemem tłumaczeń, nie możesz po prostu użyć jednego ponownie, za każdym razem musisz określić prawidłowe ciągi

Trochę omijasz rekwizyty i prawdopodobnie używasz kontekstu, aby przekazać bieżący język

  • Pro: jest przeważnie przejrzysty, nie musi przez cały czas przekazywać bieżącego języka i / lub tłumaczeń za pomocą rekwizytów
  • Wady: wygląda na kłopotliwe w użyciu

Jeśli masz inny pomysł, powiedz!

Jak ty to robisz?

Antoine Jaussoin
źródło
2
Wolę ideę obiektu kluczy z ciągami translacji, które są przekazywane jako właściwość, nie musisz przekazywać każdego ciągu jako właściwości osobno. Zmiana tego na najwyższym poziomie powinna spowodować ponowne renderowanie. Nie sądzę, aby używanie kontekstu było do tego dobrym pomysłem, a każdy komponent mający dostęp do pliku tłumaczenia sprawia, że ​​są mniej „głupie” i przenośne, w rzeczywistości imo (i trudniej jest zmusić aplikację do ponownego renderowania po zmianie języka).
Dominic
1
Właściwie według facebook.github.io/react/docs/context.html używanie kontekstu do udostępniania aktualnego języka jest jednym z uzasadnionych przypadków użycia. Podejście, które teraz próbuję, polega na użyciu tego plus komponentu wyższego rzędu, aby poradzić sobie z logiką wyodrębniania ciągów dla tego konkretnego komponentu (prawdopodobnie na podstawie jakiegoś klucza)
Antoine Jaussoin
1
Może możesz też rzucić okiem na Instant . Rozwiązują ten problem w zupełnie inny sposób, rozwiązując go w interfejsie użytkownika ala Optimizely (czyli zmieniając DOM podczas ładowania).
Marcel Panse
1
Tak ogólnie to nieźle! To rzeczywiście zupełnie inna bestia (która wiąże Cię z usługą, za którą będziesz musiał zapłacić, jeśli Twoja witryna będzie się rozwijać), ale podoba mi się ten pomysł i prawdopodobnie jest tego wart w przypadku małej witryny, którą musisz szybko uruchomić!
Antoine Jaussoin
4
Możesz również wspomnieć, że jesteś współzałożycielem Instant, zamiast mówić „Oni”, jakbyś nie miał z nimi nic wspólnego :)
Antoine Jaussoin

Odpowiedzi:

110

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ć stringsgo 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

Antoine Jaussoin
źródło
To fajne rozwiązanie… zastanawiasz się, czy po kilku miesiącach nadal jesteś na pokładzie? Nie znalazłem zbyt wielu porad dotyczących wzorów dla tego w Internecie
Damon
2
Właściwie to stwierdziłem, że działa idealnie (na moje potrzeby). To sprawia, że praca bez tłumaczenia komponentu domyślnie i tłumaczenie po prostu przychodzi na nim bez komponentu wiedząc o tym
Antoine Jaussoin
1
@ l.cetinsoy możesz użyć dangerouslySetInnerHTMLrekwizytu, pamiętaj tylko o konsekwencjach (ręcznie wyczyść dane wejściowe). Zobacz facebook.github.io/react/tips/dangerously-set-inner-html.html
Teodor Sandu
6
Czy jest jakiś powód, dla którego nie próbowałeś react-intl?
SureshCS
1
Naprawdę lubię to rozwiązanie. Jedną rzeczą, którą dodam, która okazała się bardzo przydatna dla spójności i oszczędności czasu, jest to, że jeśli masz wiele komponentów ze wspólnymi ciągami znaków, możesz wykorzystać zmienne i rozłożyć je na obiektach, np.const formStrings = { cancel, create, required }; export default { fooForm: { ...formStrings, foo: 'foo' }, barForm: { ...formStrings, bar: 'bar' } }
Huw Davies
18

Z mojego doświadczenia wynika, że ​​najlepszym podejściem jest utworzenie stanu i18n redux i używanie go z wielu powodów:

1- Umożliwi to przekazanie wartości początkowej z bazy danych, pliku lokalnego lub nawet z silnika szablonów, takiego jak EJS lub jade

2- Gdy użytkownik zmieni język, możesz zmienić cały język aplikacji, nawet bez odświeżania interfejsu użytkownika.

3- Gdy użytkownik zmieni język, umożliwi to również pobranie nowego języka z API, pliku lokalnego lub nawet ze stałych

4- Możesz także zapisać inne ważne rzeczy za pomocą ciągów, takich jak strefa czasowa, waluta, kierunek (RTL / LTR) i lista dostępnych języków

5- Możesz zdefiniować zmianę języka jako normalną akcję redux

6- Możesz mieć ciągi backend i front-end w jednym miejscu, na przykład w moim przypadku używam i18n-node do lokalizacji, a kiedy użytkownik zmienia język interfejsu użytkownika, po prostu wykonuję normalne wywołanie API, a na zapleczu po prostu wracam i18n.getCatalog(req)zwróci to wszystkie ciągi użytkownika tylko dla bieżącego języka

Moja sugestia dotycząca stanu początkowego i18n to:

{
  "language":"ar",
  "availableLanguages":[
    {"code":"en","name": "English"},
    {"code":"ar","name":"عربي"}
  ],
  "catalog":[
     "Hello":"مرحباً",
     "Thank You":"شكراً",
     "You have {count} new messages":"لديك {count} رسائل جديدة"
   ],
  "timezone":"",
  "currency":"",
  "direction":"rtl",
}

Dodatkowe przydatne moduły dla i18n:

1- szablon ciągu, który pozwoli ci wstrzyknąć wartości między ciągami katalogu, na przykład:

import template from "string-template";
const count = 7;
//....
template(i18n.catalog["You have {count} new messages"],{count}) // لديك ٧ رسائل جديدة

2- ludzki format ten moduł pozwoli ci przekonwertować liczbę na / z ciągu czytelnego dla człowieka, na przykład:

import humanFormat from "human-format";
//...
humanFormat(1337); // => '1.34 k'
// you can pass your own translated scale, e.g: humanFormat(1337,MyScale)

3- momentjs najsłynniejsza biblioteka dat i czasów npm, możesz przetłumaczyć moment ale ma już wbudowane tłumaczenie wystarczy podać aktualny język państwowy np:

import moment from "moment";

const umoment = moment().locale(i18n.language);
umoment.format('MMMM Do YYYY, h:mm:ss a'); // أيار مايو ٢ ٢٠١٧، ٥:١٩:٥٥ م

Aktualizacja (14.06.2019)

Obecnie istnieje wiele frameworków implementujących tę samą koncepcję przy użyciu interfejsu API reagowania (bez reduxu), osobiście poleciłem I18next

Fareed Alnamrouti
źródło
Czy takie podejście zadziałałoby również w przypadku więcej niż dwóch języków? Biorąc pod uwagę konfigurację katalogu
tempranova
Głosował w dół. To nie odpowiada na pytanie. OP poprosił o pomysł na architekturę, a nie o sugestię lub porównanie jakiejkolwiek biblioteki i18n.
TrungDQ
9
Zaproponowałem katalog i18n jako stan Redux, wygląda na to, że nie rozumiesz Redux
Fareed Alnamrouti
5

Rozwiązanie Antoine działa dobrze, ale należy mieć pewne zastrzeżenia:

  • Używa bezpośrednio kontekstu Reacta, którego staram się unikać, gdy już używam Redux
  • Importuje bezpośrednio frazy z pliku, co może być problematyczne, jeśli chcesz pobrać potrzebny język w czasie wykonywania, po stronie klienta
  • Nie używa żadnej biblioteki i18n, która jest lekka, ale nie zapewnia dostępu do przydatnych funkcji tłumaczenia, takich jak liczba mnoga i interpolacja

Dlatego budowane redux-Polyglot na szczycie zarówno Redux i Airbnb za Polyglot .
(Jestem jednym z autorów)

To zapewnia :

  • reduktor do przechowywania języka i odpowiednich komunikatów w Twoim sklepie Redux. Możesz dostarczyć oba przez:
    • oprogramowanie pośredniczące, które można skonfigurować w celu przechwytywania określonej akcji, odejmowania bieżącego języka i pobierania / pobierania powiązanych komunikatów.
    • bezpośrednia wysyłka setLanguage(lang, messages)
  • za getP(state)selektor, który pobiera się Pobiekt, który odsłania 4 metody:
    • t(key): oryginalna funkcja Polyglot T.
    • tc(key): tłumaczenie pisane wielką literą
    • tu(key): tłumaczenie wielkimi literami
    • tm(morphism)(key): niestandardowe tłumaczenie przekształcone
  • za getLocale(state)wybierak, aby uzyskać aktualny język
  • translatekomponent wyższa aby zwiększyć swoje React komponentów przez wstrzykiwaniep obiekt w rekwizytów

Prosty przykład użycia:

wyślij nowy język:

import setLanguage from 'redux-polyglot/setLanguage';

store.dispatch(setLanguage('en', {
    common: { hello_world: 'Hello world' } } }
}));

w komponencie:

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);

Proszę, powiedz mi, jeśli masz jakieś pytania / sugestie!

Jalil
źródło
1
Dużo lepsze oryginalne zwroty do przetłumaczenia. I stworzyć narzędzie, które analizuje wszystkie komponenty pod kątem _()funkcji, na przykład aby uzyskać wszystkie te ciągi. Więc możesz w pliku językowym przetłumaczyć to łatwiej i nie zadzieraj z szalonymi zmiennymi. W niektórych przypadkach strony docelowe wymagają odmiennego wyświetlania określonej części układu. Dlatego też powinna być dostępna sprytna funkcja wyboru opcji domyślnych i innych możliwych.
Roman M. Koss
Cześć @Jalil, czy jest gdzieś kompletny przykład z oprogramowaniem pośredniczącym?
ArkadyB,
Cześć @ArkadyB, Używamy go na produkcji w kilku projektach, które nie są open-source. Więcej informacji na temat modułu README: npmjs.com/package/redux-polyglot Masz pytanie / trudności z jego używaniem?
Jalil,
Moim głównym problemem związanym z tym i polyglot.js jest to, że całkowicie od nowa wymyśla koło, zamiast budować na podstawie plików PO. Ta alternatywna biblioteka wygląda obiecująco npmjs.com/package/redux-i18n . Nie wydaje mi się, żeby to działało inaczej - po prostu zapewnia dodatkową warstwę do konwersji do iz plików PO.
icc97
2

Z moich badań wynika, że ​​istnieją dwa główne podejścia do i18n w JavaScript, ICU i gettext .

Używałem tylko gettext, więc jestem stronniczy.

Zdumiewa mnie to, jak słabe jest wsparcie. Pochodzę ze świata PHP, CakePHP lub WordPress. W obu tych sytuacjach podstawowym standardem jest to, że wszystkie struny są po prostu otoczone__('') , a następnie w dalszej części wiersza bardzo łatwo uzyskuje się tłumaczenia przy użyciu plików PO.

gettext

Otrzymasz znajomość sprintf do formatowania ciągów znaków, a pliki PO będą łatwo tłumaczone przez tysiące różnych agencji.

Istnieją dwie popularne opcje:

  1. i18next , z użyciem opisanym w tym poście na blogu arkency.com
  2. Jed , z użyciem opisanym przez post sentry.io i ten post w React + Redux ,

Oba mają obsługę stylu gettext, formatowanie ciągów w stylu sprintf oraz import / eksport do plików PO.

i18next ma opracowane przez siebie rozszerzenie React . Jed nie. Wydaje się, że Sentry.io używa niestandardowej integracji Jed z React. React + Redux słupek , sugeruje użycie

Narzędzia: jed + po2json + jsxgettext

Jednak Jed wydaje się być implementacją bardziej skoncentrowaną na gettext - to jest wyrażona intencja, podczas gdy i18next ma to tylko jako opcję.

OIOM

Ma to większe poparcie dla skrajnych przypadków związanych z tłumaczeniami, np. Zajmowania się płcią. Myślę, że zobaczysz korzyści z tego, jeśli masz bardziej złożone języki do tłumaczenia.

Popularną opcją jest messageformat.js . Krótko omówione w tym samouczku na blogu sentry.io . messageformat.js jest w rzeczywistości rozwijany przez tę samą osobę, która napisała Jed. Dość mocno twierdzi, że używa OIOM-u :

Moim zdaniem Jed jest kompletny. Chętnie naprawiam błędy, ale generalnie nie jestem zainteresowany dodawaniem kolejnych do biblioteki.

Utrzymuję również format messageformat.js. Jeśli nie potrzebujesz konkretnie implementacji gettext, mogę zamiast tego zasugerować użycie MessageFormat, ponieważ ma lepsze wsparcie dla liczby mnogiej / płci i ma wbudowane dane regionalne.

Zgrubne porównanie

gettext z sprintf:

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 (moje najlepsze przypuszczenie z lektury przewodnika ):

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' });
icc97
źródło
Głosował w dół. To nie odpowiada na pytanie. OP poprosił o pomysł na architekturę, a nie o sugestię lub porównanie jakiejkolwiek biblioteki i18n.
TrungDQ
@TrungDQ O to zadał OP: „Moje pytanie nie jest czysto techniczne, ale dotyczy raczej architektury i wzorców, których ludzie używają w produkcji, aby rozwiązać ten problem”. . Są to dwa wzory, które są używane w produkcji.
icc97
Moim zdaniem ta odpowiedź nie dostarcza informacji, których szukam (i innych). Podane przez Ciebie informacje są pomocne, ale może przy innym pytaniu. Chcę tylko złożyć swój głos przeciw, aby właściwa odpowiedź pojawiła się na górze (mam nadzieję).
TrungDQ
@TrungDQ Jeśli to nie jest to, czego szukasz, po prostu zagłosuj na tę, z której korzystałeś, i zignoruj ​​inne, zamiast odrzucać idealnie prawidłowe odpowiedzi, które nie pasują do konkretnej części pytania, które Cię interesuje.
icc97
1

Jeśli jeszcze tego nie zrobiłeś, dobrą radą może być zajrzenie na https://react.i18next.com/ . Opiera się na i18next: ucz się raz - tłumacz wszędzie.

Twój kod będzie wyglądał mniej więcej tak:

<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>

W zestawie próbki do:

  • webpack
  • cra
  • expo.js
  • next.js
  • integracja z bajkami
  • razzle
  • dat
  • ...

https://github.com/i18next/react-i18next/tree/master/example

Poza tym powinieneś również wziąć pod uwagę przepływ pracy podczas programowania i później dla swoich tłumaczy -> https://www.youtube.com/watch?v=9NOzJhgmyQE

jamuhl
źródło
To nie odpowiada na pytanie. OP poprosił o pomysł na architekturę, a nie o sugestię lub porównanie jakiejkolwiek biblioteki i18n.
TrungDQ
@TrungDQ tak jak z komentarzem do mojej odpowiedzi, na którą zgodziłeś się - OP poprosił o aktualne rozwiązania stosowane w produkcji. Jednak zasugerowałem i18next w mojej odpowiedzi z lutego
icc97
0

Chciałbym zaproponować proste rozwiązanie za pomocą aplikacji create-react-app .

Aplikacja zostanie zbudowana dla każdego języka osobno, dlatego cała logika tłumaczenia zostanie przeniesiona z aplikacji.

Serwer sieciowy będzie obsługiwał prawidłowy język automatycznie, w zależności od nagłówka Accept-Language , lub ręcznie, ustawiając plik cookie .

Przeważnie nie zmieniamy języka więcej niż raz, jeśli w ogóle)

Dane tłumaczenia umieszczone w tym samym pliku składowym, który go używa, wraz ze stylami, html i kodem.

I tutaj mamy w pełni niezależny komponent, który odpowiada za własny stan, widok, tłumaczenie:

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',
    }
);

Dodaj zmienną środowiskową języka do pliku 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",

To jest to!

Również moja oryginalna odpowiedź zawierała bardziej monolityczne podejście z pojedynczym plikiem json dla każdego tłumaczenia:

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);
Igor Sukharev
źródło
Czy nie działałoby to tylko w czasie kompilacji? Bez możliwości zmiany języka przez użytkownika w locie? To byłby inny przypadek użycia.
Antoine Jaussoin
Aplikacja zostanie skompilowana dla każdego potrzebnego języka. Serwer WWW będzie automatycznie wyświetlał poprawną wersję, w zależności od nagłówka „Accept-Language” lub przez plik cookie ustawiany w locie przez użytkownika. W ten sposób cała logika tłumaczenia mogłaby zostać przeniesiona z aplikacji.
Igor Sukharev