Prawidłowe użycie translacji kątowej w kontrolerach

121

Używam translacji kątowej dla i18n w aplikacji AngularJS.

Dla każdego widoku aplikacji jest dedykowany kontroler. W kontrolerach poniżej ustawiłem wartość, która ma być wyświetlana jako tytuł strony.

Kod

HTML

<h1>{{ pageTitle }}</h1>

JavaScript

.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
        $scope.pageTitle = $filter('translate')('HELLO_WORLD');
    }])

.controller('SecondPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
        $scope.pageTitle = 'Second page title';
    }])

Ładuję pliki tłumaczeń za pomocą rozszerzenia angular-translate-loader-url .

Problem

Przy początkowym ładowaniu strony klucz tłumaczenia jest wyświetlany zamiast tłumaczenia tego klucza. Tłumaczenie jest Hello, World!, ale widzę HELLO_WORLD.

Za drugim razem, gdy wchodzę na stronę, wszystko jest w porządku i wyświetla się przetłumaczona wersja.

Zakładam, że problem polega na tym, że być może plik tłumaczenia nie jest jeszcze ładowany, gdy kontroler przypisuje wartość $scope.pageTitle.

Uwaga

Podczas korzystania z <h1>{{ pageTitle | translate }}</h1>i $scope.pageTitle = 'HELLO_WORLD';tłumaczenie działa idealnie od pierwszego razu. Problem polega na tym, że nie zawsze chcę używać tłumaczeń (np. Dla drugiego kontrolera chcę tylko przekazać nieprzetworzony ciąg znaków).

Pytanie

Czy to znany problem / ograniczenie? Jak można to rozwiązać?

ndequeker
źródło

Odpowiedzi:

69

EDYCJA : Proszę zapoznać się z odpowiedzią PascalPrechta (autora angular-translate), aby uzyskać lepsze rozwiązanie.


Asynchroniczny charakter ładowania powoduje problem. Widzisz, {{ pageTitle | translate }}Angular będzie obserwował wyraz twarzy; po załadowaniu danych lokalizacyjnych wartość wyrażenia zmienia się i ekran jest aktualizowany.

Możesz więc zrobić to sam:

.controller('FirstPageCtrl', ['$scope', '$filter', function ($scope, $filter) {
    $scope.$watch(
        function() { return $filter('translate')('HELLO_WORLD'); },
        function(newval) { $scope.pageTitle = newval; }
    );
});

Jednak spowoduje to uruchomienie obserwowanego wyrażenia w każdym cyklu podsumowania. Jest to nieoptymalne i może, ale nie musi, powodować widoczne pogorszenie wydajności. W każdym razie to właśnie robi Angular, więc nie może być tak źle ...

Nikos Paraskevopoulos
źródło
Dziękuję Ci! Spodziewałbym się, że użycie filtru w widoku lub w kontrolerze będzie zachowywać się dokładnie tak samo. Wydaje się, że tak nie jest w tym przypadku.
ndequeker
Powiedziałbym, że używanie a $scope.$watchjest raczej przesadą, ponieważ Angular Translate oferuje usługę do użycia w kontrolerach. Zobacz moją odpowiedź poniżej.
Robin van Baalen
1
Filtr Tłumacz kątowy nie jest wymagany, ponieważ $translate.instant()oferuje to samo, co usługa. Poza tym proszę zwróć uwagę na odpowiedź Pascala.
knalli
Zgadzam się, używanie $ watch to przesada. Poniżej odpowiedzi są bardziej właściwe.
jpblancoder
141

Zalecane: nie tłumacz w kontrolerze, tłumacz w swoim widoku

Zalecałbym, aby twój kontroler był wolny od logiki tłumaczenia i tłumaczył ciągi bezpośrednio w widoku w następujący sposób:

<h1>{{ 'TITLE.HELLO_WORLD' | translate }}</h1>

Korzystanie ze świadczonej usługi

Angular Translate zapewnia $translateusługę, z której możesz korzystać w swoich Kontrolerach.

Przykładowym wykorzystaniem $translateusługi może być:

.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
    $translate('PAGE.TITLE')
        .then(function (translatedValue) {
            $scope.pageTitle = translatedValue;
        });
});

Usługa tłumaczenia ma również metodę bezpośredniego tłumaczenia ciągów znaków bez konieczności obsługi obietnicy, używając $translate.instant():

.controller('TranslateMe', ['$scope', '$translate', function ($scope, $translate) {
    $scope.pageTitle = $translate.instant('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});

Wadą używania $translate.instant()może być to, że plik językowy nie jest jeszcze załadowany, jeśli ładujesz go asynchronicznie.

Korzystanie z dostarczonego filtra

To mój ulubiony sposób, ponieważ nie muszę w ten sposób obsługiwać obietnic. Dane wyjściowe filtru można bezpośrednio ustawić na zmienną zakresu.

.controller('TranslateMe', ['$scope', '$filter', function ($scope, $filter) {
    var $translate = $filter('translate');

    $scope.pageTitle = $translate('TITLE.DASHBOARD'); // Assuming TITLE.DASHBOARD is defined
});

Korzystanie z podanej dyrektywy

Ponieważ @PascalPrecht jest twórcą tej niesamowitej biblioteki, polecam skorzystać z jego rady (patrz jego odpowiedź poniżej) i skorzystać z dostarczonej dyrektywy, która wydaje się obsługiwać tłumaczenia bardzo inteligentnie.

Dyrektywa zajmuje się wykonywaniem asynchronicznym i jest również wystarczająco sprytna, aby odsłuchać identyfikatory translacji w zakresie, jeśli tłumaczenie nie ma wartości dynamicznych.

Robin van Baalen
źródło
Gdybyś spróbował zamiast napisać ten niezwiązany komentarz, znałbyś już odpowiedź. Krótka odpowiedź: tak. To jest możliwe.
Robin van Baalen
1
w twoim przykładzie z filtrem w kontrolerze: jak z instant (), jeśli plik językowy nie jest załadowany, to nie zadziała, prawda? Czy w takim razie nie powinniśmy używać zegarka? A może chcesz powiedzieć „użyj filtru tylko wtedy, gdy wiesz, że tłumaczenia są załadowane?
Bombinosh
@Bombinosh Powiedziałbym, że użyj metody filtrującej, jeśli wiesz, że tłumaczenia są załadowane. Osobiście radziłbym nawet nie ładować tłumaczeń dynamicznie, jeśli nie musisz. Jest to obowiązkowa część Twojej aplikacji, więc lepiej nie chcesz, aby użytkownik na nią czekał. Ale to osobista opinia.
Robin van Baalen
Chodzi o to, że tłumaczenia mogą zmieniać się w zależności od preferencji użytkownika, a nawet działań użytkownika. Więc generalnie musisz ładować je dynamicznie. Przynajmniej jeśli liczba ciągów do przetłumaczenia jest ważna i / lub jeśli masz dużo tłumaczeń.
PhiLho
4
Kiedy tłumaczenie jest wykonywane w HTML, cykl podsumowania jest uruchamiany dwukrotnie, ale tylko raz w kontrolerze. W 99% przypadków to prawdopodobnie nie będzie miało znaczenia, ale miałem problem ze straszną wydajnością w kątowej siatce interfejsu użytkownika z tłumaczeniami w wielu komórkach. To na pewno wyjątkowa sprawa, po prostu coś, o czym należy pamiętać
Kwestia tykowale
123

Właściwie do takich rzeczy powinieneś użyć dyrektywy translate.

<h1 translate="{{pageTitle}}"></h1>

Dyrektywa zajmuje się wykonywaniem asynchronicznym i jest również wystarczająco sprytna, aby odsłuchać identyfikatory translacji w zakresie, jeśli tłumaczenie nie ma wartości dynamicznych.

Jeśli jednak nie ma sposobu, a ty naprawdę musisz użyć $translateusługi w kontrolerze, powinieneś zawinąć wywołanie w $translateChangeSuccesszdarzeniu, używając $rootScopew połączeniu z $translate.instant()następującymi:

.controller('foo', function ($rootScope, $scope, $translate) {
  $rootScope.$on('$translateChangeSuccess', function () {
    $scope.pageTitle = $translate.instant('PAGE.TITLE');
  });
})

Więc dlaczego $rootScope i nie $scope? Powodem tego jest to, że w kanciasty-translate wydarzeń są $emited na $rootScopezamiast $broadcasted na $scopeponieważ nie musimy nadawać przez całą hierarchią zakres.

Dlaczego $translate.instant()i nie tylko asynchronicznie $translate()? Kiedy $translateChangeSuccesszdarzenie jest uruchamiane, jest pewne, że potrzebne są dane translacji i nie ma wykonania asynchronicznego wykonania (na przykład asynchroniczne wykonanie programu ładującego), dlatego możemy po prostu użyć tego, $translate.instant()który jest synchroniczny i po prostu zakłada, że ​​tłumaczenia są dostępne.

Od wersji 2.8.0 istnieje również $translate.onReady(), który zwraca obietnicę, która jest rozpatrywana, gdy tylko tłumaczenia są gotowe. Zobacz dziennik zmian .

Pascal Precht
źródło
Czy mogą wystąpić problemy z wydajnością, jeśli użyję dyrektywy translate zamiast filtra? Uważam również, że wewnętrznie obserwuje zwracaną wartość instant (). Czy więc usuwa zegarki, gdy obecny zakres zostanie zniszczony?
Nilesh
Próbowałem skorzystać z twojej sugestii, ale nie działa, gdy wartość zmiennej zakresu zmienia się dynamicznie.
Nilesh,
10
Właściwie zawsze lepiej jest unikać filtrów, jeśli to możliwe, ponieważ spowalniają one Twoją aplikację, ponieważ zawsze konfigurują nowe zegarki. Dyrektywa idzie jednak nieco dalej. Sprawdza, czy musi obserwować wartość identyfikatora tłumaczenia, czy nie. Dzięki temu Twoja aplikacja będzie działać lepiej. Czy mógłbyś zrobić kawałek i połączyć mnie z nim, abym mógł przyjrzeć się dokładniej?
Pascal Precht
Plunk : plnkr.co/edit/j53xL1EdJ6bT20ldlhxr Prawdopodobnie w moim przykładzie dyrektywa decyduje się nie obserwować wartości. Również jako osobny problem, mój niestandardowy program obsługi błędów jest wywoływany, jeśli klucz nie zostanie znaleziony, ale nie wyświetla zwróconego ciągu. Zrobię za to kolejny kawałek.
Nilesh
2
@PascalPrecht Pytanie tylko, czy warto używać funkcji bind-once z tłumaczeniem? W ten sposób {{::'HELLO_WORLD | translate}}'.
Zunair Zubair
5

Aby dokonać tłumaczenia w kontrolerze możesz skorzystać z $translateusługi:

$translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
    vm.si = translations['COMMON.SI'];
    vm.no = translations['COMMON.NO'];
});

Ta instrukcja tłumaczy tylko podczas aktywacji kontrolera, ale nie wykrywa zmiany języka w czasie wykonywania. Aby osiągnąć takie zachowanie, możesz posłuchać $rootScopezdarzenia: $translateChangeSuccessi tam wykonać to samo tłumaczenie:

    $rootScope.$on('$translateChangeSuccess', function () {
        $translate(['COMMON.SI', 'COMMON.NO']).then(function (translations) {
            vm.si = translations['COMMON.SI'];
            vm.no = translations['COMMON.NO'];
        });
    });

Oczywiście możesz hermetyzować $translateusługę w metodzie i wywołać ją w kontrolerze i $translateChangeSucessodbiorniku.

MacLeod
źródło
1

Dzieje się tak, że Angular-translate obserwuje wyrażenie za pomocą systemu opartego na zdarzeniach i tak jak w każdym innym przypadku wiązania lub wiązania dwukierunkowego, zdarzenie jest wyzwalane, gdy dane są pobierane, a wartość zmienia się, co oczywiście nie działa w przypadku tłumaczenia. Dane tłumaczenia, w przeciwieństwie do innych dynamicznych danych na stronie, muszą oczywiście natychmiast pojawić się użytkownikowi. Nie może pojawić się po załadowaniu strony.

Nawet jeśli uda Ci się pomyślnie debugować ten problem, większym problemem jest to, że praca programistyczna jest ogromna. Programista musi ręcznie wyodrębnić każdy ciąg w witrynie, umieścić go w pliku .json, ręcznie odwołać się do niego za pomocą kodu ciągu (tj. W tym przypadku „pageTitle”). Większość witryn komercyjnych ma tysiące ciągów, w przypadku których musi to nastąpić. A to dopiero początek. Potrzebujesz teraz systemu utrzymywania synchronizacji tłumaczeń, gdy tekst będący podstawą zmiany w niektórych z nich, systemu wysyłania plików tłumaczeń do różnych tłumaczy, ponownego włączania ich do kompilacji, ponownego wdrażania witryny, aby tłumacze mogli zobaczyć ich zmiany w kontekście i tak dalej.

Ponadto, ponieważ jest to „wiążący” system oparty na zdarzeniach, zdarzenie jest wywoływane dla każdego pojedynczego ciągu znaków na stronie, co nie tylko jest wolniejszym sposobem transformacji strony, ale może spowolnić wszystkie działania na stronie, jeśli zaczniesz dodawać do niego dużą liczbę wydarzeń.

W każdym razie korzystanie z platformy tłumaczeniowej do przetwarzania końcowego ma dla mnie większy sens. Korzystając na przykład z GlobalizeIt, tłumacz może po prostu przejść do strony w witrynie i rozpocząć edycję tekstu bezpośrednio na stronie w swoim języku i to wszystko: https://www.globalizeit.com/HowItWorks . Nie jest potrzebne programowanie (choć może być programowo rozszerzalne), łatwo integruje się z Angular: https://www.globalizeit.com/Translate/Angular , transformacja strony odbywa się za jednym razem i zawsze wyświetla przetłumaczony tekst z początkowe renderowanie strony.

Pełne ujawnienie: jestem współzałożycielem :)

Jeff W.
źródło