Uso corretto della traslazione angolare nei controller


121

Sto usando angular-translate per i18n in un'applicazione AngularJS.

Per ogni visualizzazione dell'applicazione, c'è un controller dedicato. Nei controller di seguito, ho impostato il valore da mostrare come titolo della pagina.

Codice

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

Sto caricando i file di traduzione utilizzando l' estensione angular-translate-loader-url .

Problema

Al caricamento della pagina iniziale, viene mostrata la chiave di traduzione invece della traduzione per quella chiave. La traduzione è Hello, World!, ma sto vedendo HELLO_WORLD.

La seconda volta che vado sulla pagina, va tutto bene e viene mostrata la versione tradotta.

Presumo che il problema abbia a che fare con il fatto che forse il file di traduzione non è ancora caricato quando il controller sta assegnando il valore a $scope.pageTitle.

osservazione

Quando si utilizza <h1>{{ pageTitle | translate }}</h1>e $scope.pageTitle = 'HELLO_WORLD';, la traduzione funziona perfettamente dalla prima volta. Il problema con questo è che non voglio sempre usare le traduzioni (es. Per il secondo controller voglio solo passare una stringa grezza).

Domanda

Si tratta di un problema / limitazione noto? Come risolverlo?

Risposte:


69

EDIT : Si prega di vedere la risposta di PascalPrecht (l'autore di angular-translate) per una soluzione migliore.


La natura asincrona del caricamento causa il problema. Vedi, con {{ pageTitle | translate }}Angular guarderà l'espressione; quando vengono caricati i dati di localizzazione, il valore dell'espressione cambia e lo schermo viene aggiornato.

Quindi, puoi farlo da solo:

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

Tuttavia, questo eseguirà l'espressione osservata su ogni ciclo digest. Questo non è ottimale e può o non può causare un degrado visibile delle prestazioni. Comunque è quello che fa Angular, quindi non può essere così male ...


Grazie! Mi aspetto che l'utilizzo di un filtro nella vista o in un controller si comporti esattamente allo stesso modo. Non sembra essere il caso qui.
ndequeker

Direi che l'uso di a $scope.$watchè piuttosto eccessivo poiché Angular Translate offre un servizio da utilizzare nei controller. Vedi la mia risposta di seguito.
Robin van Baalen

1
Il filtro Angular Translate non è richiesto, poiché $translate.instant()offre lo stesso servizio. Oltre a questo, per favore presta attenzione alla risposta di Pascal.
knalli

Sono d'accordo, usare $ watch è eccessivo. Di seguito le risposte sono un uso più corretto.
jpblancoder

141

Consigliato: non tradurre nel controller, tradurre nella tua visualizzazione

Ti consiglio di mantenere il tuo controller libero dalla logica di traduzione e tradurre le tue stringhe direttamente all'interno della tua vista in questo modo:

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

Utilizzando il servizio fornito

Angular Translate fornisce il $translateservizio che puoi utilizzare nei tuoi controller.

Un esempio di utilizzo del $translateservizio può essere:

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

Il servizio di traduzione ha anche un metodo per tradurre direttamente le stringhe senza la necessità di gestire una promessa, utilizzando $translate.instant():

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

Lo svantaggio dell'utilizzo $translate.instant()potrebbe essere che il file della lingua non è ancora caricato se lo stai caricando in modo asincrono.

Utilizzando il filtro fornito

Questo è il mio modo preferito poiché non devo gestire le promesse in questo modo. L'output del filtro può essere impostato direttamente su una variabile di ambito.

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

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

Utilizzando la direttiva fornita

Poiché @PascalPrecht è il creatore di questa fantastica libreria, consiglierei di seguire il suo consiglio (vedi la sua risposta di seguito) e di utilizzare la direttiva fornita che sembra gestire le traduzioni in modo molto intelligente.

La direttiva si occupa dell'esecuzione asincrona ed è anche abbastanza intelligente da annullare l'osservazione degli ID di traduzione sull'ambito se la traduzione non ha valori dinamici.


Se lo avessi provato invece di scrivere quel commento non correlato, avresti già saputo la risposta. Risposta breve: sì. È possibile.
Robin van Baalen

1
nel tuo esempio con il filtro nel controller: come con instant (), se il file della lingua non è caricato, non funzionerà bene? Non dovremmo usare un orologio in quel caso? O intendi dire 'usa il filtro solo se sai che le traduzioni vengono caricate?
Bombinosh

@Bombinosh Direi di utilizzare il metodo di filtro se sai che le traduzioni vengono caricate. Personalmente consiglierei anche di non caricare le traduzioni in modo dinamico se non è necessario. È una parte obbligatoria della tua applicazione, quindi è meglio che tu non voglia che l'utente lo stia aspettando. Ma questa è un'opinione personale.
Robin van Baalen

Lo scopo delle traduzioni è che possono cambiare in base alle preferenze dell'utente o anche all'azione dell'utente. Quindi è necessario, in generale, caricarli dinamicamente. Almeno se il numero di stringhe da tradurre è importante e / o se hai molte traduzioni.
PhiLho

4
Quando la traduzione viene eseguita in HTML, il ciclo digest viene eseguito due volte, ma solo una volta nel controller. Nel 99% dei casi questo probabilmente non avrà importanza, ma ho avuto un problema con prestazioni terribili in una griglia ui angolare con traduzioni in molte celle. Un caso limite di sicuro, solo qualcosa di cui essere consapevoli
tykowale

123

In realtà, dovresti invece usare la direttiva translate per queste cose.

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

La direttiva si occupa dell'esecuzione asincrona ed è anche abbastanza intelligente da annullare l'osservazione degli ID di traduzione sull'ambito se la traduzione non ha valori dinamici.

Tuttavia, se non c'è modo intorno e realmente necessario l'uso $translatedi servizio nel controllore, si dovrebbe avvolgere la chiamata in un $translateChangeSuccessevento utilizzando $rootScopein combinazione con $translate.instant()in questo modo:

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

Allora perché $rootScopee no $scope? La ragione di ciò è che in angolare traducono di eventi sono $emitEd il $rootScopepiù che $broadcastEd il $scopeperché non abbiamo bisogno per trasmettere attraverso l'intera gerarchia ambito.

Perché $translate.instant()e non solo asincrono $translate()? Quando l' $translateChangeSuccessevento viene attivato, è sicuro che i dati di traduzione necessari siano presenti e che non sia in corso alcuna esecuzione asincrona (ad esempio, l'esecuzione del caricatore asincrono), quindi possiamo solo usare $translate.instant()che è sincrono e presuppone solo che le traduzioni siano disponibili.

Dalla versione 2.8.0 c'è anche $translate.onReady(), che restituisce una promessa che viene risolta non appena le traduzioni sono pronte. Vedi il changelog .


Potrebbero esserci problemi di prestazioni se utilizzo la direttiva translate invece del filtro? Inoltre credo che internamente, guardi il valore di ritorno di instant (). Quindi rimuove gli orologi quando l'ambito corrente viene distrutto?
Nilesh

Ho provato a utilizzare il tuo suggerimento ma non funziona quando il valore della variabile di ambito cambia dinamicamente.
Nilesh,

10
In realtà è sempre meglio evitare i filtri dove possibile, poiché rallentano la tua app perché impostano sempre nuovi orologi. La direttiva, tuttavia, va un po 'oltre. Controlla se deve controllare o meno il valore di un id di traduzione. Ciò consente di eseguire meglio la tua app. Potresti fare un plunk e collegarmi ad esso, così posso dare un'altra occhiata?
Pascal Precht

Plunk : plnkr.co/edit/j53xL1EdJ6bT20ldlhxr Probabilmente nel mio esempio, la direttiva sta decidendo di non guardare il valore. Inoltre, come problema separato, il mio gestore di errori personalizzato viene chiamato se la chiave non viene trovata, ma non visualizza la stringa restituita. Farò un altro colpo per questo.
Nilesh

2
@PascalPrecht Solo una domanda, è una buona pratica usare il binding una volta con la traduzione? In questo modo {{::'HELLO_WORLD | translate}}'.
Zunair Zubair

5

Per effettuare una traduzione nel controller è possibile utilizzare il $translateservizio:

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

Questa istruzione esegue solo la traduzione all'attivazione del controller ma non rileva il cambiamento di lingua in runtime. Per ottenere quel comportamento, potresti ascoltare l' $rootScopeevento: $translateChangeSuccesse fare la stessa traduzione lì:

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

Ovviamente, potresti incapsulare il $translateservizio in un metodo e chiamarlo nel controller e nel $translateChangeSucesslistener.


1

Quello che sta accadendo è che Angular-translate sta guardando l'espressione con un sistema basato su eventi e, proprio come in qualsiasi altro caso di associazione o associazione a due vie, viene generato un evento quando i dati vengono recuperati e il valore viene modificato, il che ovviamente non funziona per la traduzione. I dati di traduzione, a differenza di altri dati dinamici sulla pagina, devono, ovviamente, essere immediatamente mostrati all'utente. Non può comparire dopo il caricamento della pagina.

Anche se riesci a eseguire correttamente il debug di questo problema, il problema più grande è che il lavoro di sviluppo coinvolto è enorme. Uno sviluppatore deve estrarre manualmente ogni stringa sul sito, inserirla in un file .json, referenziarla manualmente tramite codice stringa (ad esempio "pageTitle" in questo caso). La maggior parte dei siti commerciali ha migliaia di stringhe per le quali ciò deve avvenire. E questo è solo l'inizio. Ora hai bisogno di un sistema per mantenere sincronizzate le traduzioni quando il testo sottostante cambia in alcune di esse, un sistema per inviare i file di traduzione ai vari traduttori, per reintegrarli nella build, per ridistribuire il sito in modo che i traduttori possano vedere i loro cambiamenti nel contesto, e così via.

Inoltre, poiché si tratta di un sistema basato su eventi 'vincolante', viene generato un evento per ogni singola stringa sulla pagina, che non solo è un modo più lento per trasformare la pagina, ma può rallentare tutte le azioni sulla pagina, se inizi ad aggiungere un gran numero di eventi.

Ad ogni modo, utilizzare una piattaforma di traduzione post-elaborazione ha più senso per me. Usando GlobalizeIt, ad esempio, un traduttore può semplicemente andare su una pagina del sito e iniziare a modificare il testo direttamente sulla pagina per la propria lingua, e il gioco è fatto: https://www.globalizeit.com/HowItWorks . Non necessita di programmazione (sebbene possa essere estensibile programmaticamente), si integra facilmente con Angular: https://www.globalizeit.com/Translate/Angular , la trasformazione della pagina avviene in una volta e visualizza sempre il testo tradotto con il rendering iniziale della pagina.

Divulgazione completa: sono un co-fondatore :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.