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?
źródło
Odpowiedzi:
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)
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:
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.
Na koniec pliki tłumaczeń:
Pliki tłumaczeń
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
źródło
dangerouslySetInnerHTML
rekwizytu, pamiętaj tylko o konsekwencjach (ręcznie wyczyść dane wejściowe). Zobacz facebook.github.io/react/tips/dangerously-set-inner-html.htmlconst formStrings = { cancel, create, required }; export default { fooForm: { ...formStrings, foo: 'foo' }, barForm: { ...formStrings, bar: 'bar' } }
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ęzykaMoja sugestia dotycząca stanu początkowego i18n to:
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:
2- ludzki format ten moduł pozwoli ci przekonwertować liczbę na / z ciągu czytelnego dla człowieka, na przykład:
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:
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
źródło
Rozwiązanie Antoine działa dobrze, ale należy mieć pewne zastrzeżenia:
Dlatego budowane redux-Polyglot na szczycie zarówno Redux i Airbnb za Polyglot .
(Jestem jednym z autorów)
To zapewnia :
setLanguage(lang, messages)
getP(state)
selektor, który pobiera sięP
obiekt, który odsłania 4 metody:t(key)
: oryginalna funkcja Polyglot T.tc(key)
: tłumaczenie pisane wielką literątu(key)
: tłumaczenie wielkimi literamitm(morphism)(key)
: niestandardowe tłumaczenie przekształconegetLocale(state)
wybierak, aby uzyskać aktualny języktranslate
komponent wyższa aby zwiększyć swoje React komponentów przez wstrzykiwaniep
obiekt w rekwizytówProsty przykład użycia:
wyślij nowy język:
w komponencie:
Proszę, powiedz mi, jeśli masz jakieś pytania / sugestie!
źródło
_()
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.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:
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
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 :
Zgrubne porównanie
gettext z sprintf:
messageformat.js (moje najlepsze przypuszczenie z lektury przewodnika ):
źródło
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:
W zestawie próbki do:
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
źródło
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:
Dodaj zmienną środowiskową języka do pliku package.json
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
lib / lang.js
src / App.jsx
źródło