Richtige Verwendung für Winkelübersetzung in Steuerungen

121

Ich verwende Angular -Translate für i18n in einer AngularJS-Anwendung.

Für jede Anwendungsansicht gibt es einen dedizierten Controller. In den folgenden Controllern habe ich den Wert festgelegt, der als Seitentitel angezeigt werden soll.

Code

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

Ich lade die Übersetzungsdateien mit der Erweiterung angle-translate-loader-url .

Problem

Beim ersten Laden der Seite wird der Übersetzungsschlüssel anstelle der Übersetzung für diesen Schlüssel angezeigt. Die Übersetzung ist Hello, World!, aber ich sehe HELLO_WORLD.

Wenn ich zum zweiten Mal auf die Seite gehe, ist alles in Ordnung und die übersetzte Version wird angezeigt.

Ich gehe davon aus, dass das Problem damit zusammenhängt, dass die Übersetzungsdatei möglicherweise noch nicht geladen ist, wenn der Controller den Wert zuweist $scope.pageTitle.

Anmerkung

Bei Verwendung von <h1>{{ pageTitle | translate }}</h1>und $scope.pageTitle = 'HELLO_WORLD';funktioniert die Übersetzung vom ersten Mal an perfekt. Das Problem dabei ist, dass ich nicht immer Übersetzungen verwenden möchte (z. B. für den zweiten Controller möchte ich nur eine rohe Zeichenfolge übergeben).

Frage

Ist dies ein bekanntes Problem / eine Einschränkung? Wie kann das gelöst werden?

ndequeker
quelle

Antworten:

69

EDIT : Eine bessere Lösung finden Sie in der Antwort von PascalPrecht (dem Autor von Angular-Translate).


Die asynchrone Art des Ladens verursacht das Problem. Sie sehen, mit {{ pageTitle | translate }}, Angular wird den Ausdruck beobachten; Wenn die Lokalisierungsdaten geladen werden, ändert sich der Wert des Ausdrucks und der Bildschirm wird aktualisiert.

Sie können das also selbst tun:

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

Dadurch wird jedoch der beobachtete Ausdruck bei jedem Digest-Zyklus ausgeführt. Dies ist nicht optimal und kann zu einer sichtbaren Leistungsverschlechterung führen oder auch nicht. Wie auch immer, Angular macht es, also kann es nicht so schlimm sein ...

Nikos Paraskevopoulos
quelle
Danke dir! Ich würde erwarten, dass sich die Verwendung eines Filters in der Ansicht oder in einem Controller genauso verhält. Das scheint hier nicht der Fall zu sein.
Ndequeker
Ich würde sagen, die Verwendung von a $scope.$watchist ziemlich übertrieben, da Angular Translate einen Service anbietet, der in den Controllern verwendet werden kann. Siehe meine Antwort unten.
Robin van Baalen
1
Der Angular Translate-Filter ist nicht erforderlich, da er $translate.instant()das Gleiche wie ein Service bietet. Beachten Sie außerdem die Antwort von Pascal.
Knalli
Ich bin damit einverstanden, dass die Verwendung von $ watch übertrieben ist. Die folgenden Antworten sind zutreffender.
jpblancoder
141

Empfohlen: Nicht im Controller übersetzen, sondern in Ihrer Ansicht übersetzen

Ich würde empfehlen, Ihren Controller frei von Übersetzungslogik zu halten und Ihre Zeichenfolgen wie folgt direkt in Ihre Ansicht zu übersetzen:

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

Nutzung des bereitgestellten Dienstes

Angular Translate bietet den $translateDienst, den Sie in Ihren Controllern verwenden können.

Eine beispielhafte Verwendung des $translateDienstes kann sein:

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

Der Übersetzungsdienst verfügt auch über eine Methode zum direkten Übersetzen von Zeichenfolgen, ohne dass ein Versprechen eingegangen werden muss $translate.instant().

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

Der Nachteil bei der Verwendung $translate.instant()kann sein, dass die Sprachdatei noch nicht geladen ist, wenn Sie sie asynchron laden.

Verwendung des mitgelieferten Filters

Dies ist mein bevorzugter Weg, da ich Versprechen nicht auf diese Weise behandeln muss. Die Ausgabe des Filters kann direkt auf eine Bereichsvariable gesetzt werden.

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

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

Verwendung der bereitgestellten Richtlinie

Da @PascalPrecht der Schöpfer dieser großartigen Bibliothek ist, würde ich empfehlen, seinen Rat zu befolgen (siehe seine Antwort unten) und die bereitgestellte Direktive zu verwenden, die Übersetzungen sehr intelligent zu handhaben scheint.

Die Direktive kümmert sich um die asynchrone Ausführung und ist auch clever genug, um Übersetzungs-IDs im Bereich zu entfernen, wenn die Übersetzung keine dynamischen Werte enthält.

Robin van Baalen
quelle
Wenn Sie es versucht hätten, anstatt diesen nicht verwandten Kommentar zu schreiben, hätten Sie die Antwort inzwischen gewusst. Kurze Antwort: ja. Das ist möglich.
Robin van Baalen
1
In Ihrem Beispiel mit dem Filter im Controller: Wie bei Instant (), wenn die Sprachdatei nicht geladen ist, funktioniert dies nicht richtig? Sollten wir in diesem Fall keine Uhr benutzen? Oder Sie wollen sagen: Verwenden Sie den Filter nur, wenn Sie wissen, dass die Übersetzungen geladen sind?
Bombinosh
@Bombinosh Ich würde sagen, verwenden Sie die Filtermethode, wenn Sie wissen, dass Übersetzungen geladen sind. Persönlich würde ich sogar empfehlen, Übersetzungen nicht dynamisch zu laden, wenn Sie nicht müssen. Es ist ein obligatorischer Bestandteil Ihrer Anwendung, daher möchten Sie besser nicht, dass der Benutzer darauf wartet. Aber das ist eine persönliche Meinung.
Robin van Baalen
Der Punkt von Übersetzungen ist, dass sie sich je nach Benutzereinstellungen oder sogar nach Benutzeraktion ändern können. Sie müssen sie also im Allgemeinen dynamisch laden. Zumindest wenn die Anzahl der zu übersetzenden Zeichenfolgen wichtig ist und / oder wenn Sie viele Übersetzungen haben.
PhiLho
4
Wenn die Übersetzung im HTML erfolgt ist, wird der Digest-Zyklus zweimal ausgeführt, jedoch nur einmal im Controller. In 99% der Fälle spielt dies wahrscheinlich keine Rolle, aber ich hatte ein Problem mit der schrecklichen Leistung in einem eckigen UI-Raster mit Übersetzungen in vielen Zellen. Ein
Randfall
123

Eigentlich sollten Sie stattdessen die Übersetzungsanweisung für solche Dinge verwenden.

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

Die Direktive kümmert sich um die asynchrone Ausführung und ist auch clever genug, um Übersetzungs-IDs im Bereich zu entfernen, wenn die Übersetzung keine dynamischen Werte enthält.

Wenn es jedoch um keine Möglichkeit gibt , und Sie wirklich haben , um den Einsatz $translateService in der Steuerung, sollten Sie den Anruf in einem Wrap - $translateChangeSuccessEreignis mit $rootScopein Kombination mit $translate.instant()wie folgt aus :

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

Warum also $rootScopenicht $scope? Der Grund dafür ist, dass in Winkel übersetzen die Ereignisse werden $emitauf ed $rootScopestatt $broadcasted auf , $scopeweil wir auf Sendung durch den gesamten Umfang Hierarchie nicht brauchen.

Warum $translate.instant()und nicht nur asynchron $translate()? Wenn ein $translateChangeSuccessEreignis ausgelöst wird, ist sicher, dass die erforderlichen Übersetzungsdaten vorhanden sind und keine asynchrone Ausführung stattfindet (z. B. asynchrone Loader-Ausführung). Daher können wir nur das verwenden, $translate.instant()das synchron ist und nur davon ausgeht, dass Übersetzungen verfügbar sind.

Seit Version 2.8.0 gibt es auch $translate.onReady()ein Versprechen, das aufgelöst wird, sobald die Übersetzungen fertig sind. Siehe das Änderungsprotokoll .

Pascal Precht
quelle
Könnte es Leistungsprobleme geben, wenn ich anstelle des Filters die Übersetzungsanweisung verwende? Ich glaube auch intern, dass es den Rückgabewert von instant () beobachtet. Entfernt es also Uhren, wenn das aktuelle Zielfernrohr zerstört wird?
Nilesh
Ich habe versucht, Ihren Vorschlag zu verwenden, aber er funktioniert nicht, wenn sich der Wert der Bereichsvariablen dynamisch ändert.
Nilesh
10
Eigentlich ist es immer besser, Filter nach Möglichkeit zu vermeiden, da sie Ihre App verlangsamen, weil sie immer neue Uhren einrichten. Die Richtlinie geht jedoch etwas weiter. Es wird geprüft, ob der Wert einer Übersetzungs-ID überwacht werden muss oder nicht. Dadurch können Sie Ihre App besser ausführen. Könnten Sie einen Plunk machen und mich damit verknüpfen, damit ich einen weiteren Blick darauf werfen kann?
Pascal Precht
Plunk: plnkr.co/edit/j53xL1EdJ6bT20ldlhxr Wahrscheinlich entscheidet in meinem Beispiel die Direktive, den Wert nicht zu beobachten. Als separates Problem wird mein benutzerdefinierter Fehlerbehandler aufgerufen, wenn der Schlüssel nicht gefunden wird, die zurückgegebene Zeichenfolge jedoch nicht angezeigt wird. Ich werde noch einen Plunk dafür machen.
Nilesh
2
@PascalPrecht Nur eine Frage, ist es eine gute Praxis, bind-einmal mit Übersetzung zu verwenden? Wie dies {{::'HELLO_WORLD | translate}}'.
Zunair Zubair
5

Um eine Übersetzung im Controller zu erstellen, können Sie den folgenden $translateDienst verwenden:

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

Diese Anweisung führt nur die Übersetzung bei der Controller-Aktivierung durch, erkennt jedoch nicht die Laufzeitänderung in der Sprache. Um dieses Verhalten zu erreichen, können Sie das $rootScopeEreignis anhören $translateChangeSuccessund dort dieselbe Übersetzung durchführen:

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

Natürlich können Sie den $translateDienst in eine Methode einkapseln und im Controller und im $translateChangeSucessListener aufrufen .

MacLeod
quelle
1

Was passiert ist, dass Angular-translate den Ausdruck mit einem ereignisbasierten System überwacht, und genau wie in jedem anderen Fall der Bindung oder bidirektionalen Bindung wird ein Ereignis ausgelöst, wenn die Daten abgerufen werden, und der Wert wird geändert funktioniert offensichtlich nicht für die Übersetzung. Übersetzungsdaten müssen im Gegensatz zu anderen dynamischen Daten auf der Seite natürlich sofort dem Benutzer angezeigt werden. Es kann nicht eingeblendet werden, nachdem die Seite geladen wurde.

Selbst wenn Sie dieses Problem erfolgreich beheben können, besteht das größere Problem darin, dass der Entwicklungsaufwand enorm ist. Ein Entwickler muss jede Zeichenfolge auf der Site manuell extrahieren, in eine JSON-Datei einfügen und manuell anhand des Zeichenfolgencodes (in diesem Fall 'pageTitle') darauf verweisen. Die meisten kommerziellen Websites haben Tausende von Zeichenfolgen, für die dies geschehen muss. Und das ist erst der Anfang. Sie benötigen jetzt ein System, um die Übersetzungen synchron zu halten, wenn sich der zugrunde liegende Text in einigen von ihnen ändert, ein System, um die Übersetzungsdateien an die verschiedenen Übersetzer zu senden, sie wieder in den Build zu integrieren und die Site erneut bereitzustellen, damit die Übersetzer sie sehen können ihre Änderungen im Kontext und so weiter und so fort.

Da es sich um ein "bindendes", ereignisbasiertes System handelt, wird für jede einzelne Zeichenfolge auf der Seite ein Ereignis ausgelöst. Dies ist nicht nur eine langsamere Methode zum Transformieren der Seite, sondern kann auch alle Aktionen auf der Seite verlangsamen. wenn Sie anfangen, eine große Anzahl von Ereignissen hinzuzufügen.

Die Verwendung einer Übersetzungsplattform für die Nachbearbeitung ist für mich jedenfalls sinnvoller. Mit GlobalizeIt kann ein Übersetzer beispielsweise einfach zu einer Seite auf der Website gehen und den Text direkt auf der Seite für seine Sprache bearbeiten. Das war's: https://www.globalizeit.com/HowItWorks . Es ist keine Programmierung erforderlich (obwohl es programmgesteuert erweiterbar sein kann), es lässt sich problemlos in Angular integrieren: https://www.globalizeit.com/Translate/Angular . Die Transformation der Seite erfolgt auf einmal und zeigt immer den übersetzten Text mit an das anfängliche Rendern der Seite.

Vollständige Offenlegung: Ich bin Mitbegründer :)

Jeff W.
quelle