正确使用控制器中的角度转换


121

我在AngularJS应用程序中为i18n 使用angular-translate

对于每个应用程序视图,都有一个专用的控制器。在下面的控制器中,我设置了要显示为页面标题的值。

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

我正在使用angular-translate-loader-url扩展名加载翻译文件。

问题

在初始页面加载时,将显示翻译键,而不是该键的翻译。翻译是Hello, World!,但我看到了HELLO_WORLD

第二次我进入页面时,一切都很好,并显示了翻译版本。

我认为问题与以下事实有关:在控制器将值分配给时,转换文件可能尚未加载$scope.pageTitle

备注

使用<h1>{{ pageTitle | translate }}</h1>和时$scope.pageTitle = 'HELLO_WORLD';,翻译从第一次开始就完美。问题是我不总是要使用翻译(例如,对于第二个控制器,我只想传递原始字符串)。

这是已知问题/限制吗?如何解决?

Answers:


69

编辑:请参阅PascalPrecht(angular-translate的作者)的答案,以获得更好的解决方案。


加载的异步性质导致了该问题。您会看到,使用{{ pageTitle | translate }},Angular将监视该表达式;加载本地化数据后,表达式的值将更改,并且屏幕将更新。

因此,您可以自己做:

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

但是,这将在每个摘要循环上运行监视的表达式。这不是最理想的,可能会或可能不会导致明显的性能下降。无论如何,这是Angular所做的,所以它不会那么糟糕...


谢谢!我希望在View或Controller中使用过滤器的行为完全相同。这里似乎并非如此。
ndequeker 2013年

我说使用a可能$scope.$watch会过大,因为Angular Translate提供了要在控制器中使用的服务。请参阅下面的答案。
罗宾·范·巴伦

1
由于$translate.instant()提供的功能与服务相同,因此不需要Angular Translate滤镜。除此之外,请注意Pascal的回答。
knalli 2014年

我同意,使用$ watch是过分的。下面的答案是更正确的用法。
jpblancoder 2014年

141

推荐:不要在控制器中翻译,请在您的视图中翻译

我建议让您的控制器摆脱翻译逻辑的束缚,直接在视图内部翻译字符串,如下所示:

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

使用提供的服务

Angular Translate提供了$translate可以在Controller中使用的服务。

$translate服务的示例用法可以是:

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

转换服务还提供了一种使用以下方法直接转换字符串而无需处理承诺的方法$translate.instant()

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

使用的缺点$translate.instant()可能是如果异步加载语言文件,则该语言文件尚未加载。

使用提供的过滤器

这是我的首选方式,因为我不必这样处理承诺。过滤器的输出可以直接设置为范围变量。

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

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

使用提供的指令

因为@PascalPrecht是这个很棒的库的创建者,所以我建议您参考他的建议(请参阅下面的答案),并使用提供的指令,该指令似乎非常聪明地处理翻译。

该指令负责异步执行,并且如果转换没有动态值,该指令也足够聪明以取消监视作用域上的转换ID。


如果您尝试了此操作,而不是写下无关的评论,那么现在您已经知道答案了。简短的回答:是的。那是可能的。
罗宾·范·巴伦

1
在您的示例中使用控制器中的过滤器:与Instant()一样,如果未加载语言文件,这将无法正常工作吗?在这种情况下我们不应该使用手表吗?或者您的意思是说“仅在知道翻译已加载的情况下才使用过滤器?
Bombinosh

@Bombinosh如果您知道翻译已加载,我会说使用过滤器方法。就个人而言,我什至建议您不要动态加载翻译。这是您应用程序中必不可少的部分,因此您最好不要让用户等待它。但这是个人观点。
罗宾·范·巴伦

翻译的重点是,它们可以根据用户的偏好甚至用户的操作进行更改。因此,通常需要动态加载它们。至少如果要翻译的字符串数很重要,并且/或者您翻译的数量很多。
PhiLho 2015年

4
在HTML中完成翻译后,摘要循环将运行两次,但仅在控制器中运行一次。在99%的情况下,这可能无关紧要,但是我遇到了一个问题,即在许多单元中都有平移的ui网格中,性能表现很差。可以肯定的是,这是一个边缘情况
-tykowale

123

实际上,您应该对此类内容使用translate指令。

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

该指令负责异步执行,并且如果转换没有动态值,该指令也足够聪明以取消监视作用域上的转换ID。

但是,如果没有办法解决,并且您确实必须$translate在控制器中使用service,则应将调用包装在$translateChangeSuccess事件中$rootScope,并结合使用,$translate.instant()例如:

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

那么为什么$rootScope$scope呢?这样做的原因是,在angular-translate中,事件是$emited $rootScope而不是$broadcasted,$scope因为我们不需要在整个范围层次中进行广播。

为什么$translate.instant()不只是异步$translate()?当$translateChangeSuccess事件被触发时,可以确定所需的转换数据在那里并且没有异步执行发生(例如异步加载程序执行),因此我们可以仅使用$translate.instant()同步的,并且假设转换可用。

从2.8.0版开始,还存在$translate.onReady(),它将返回一个promise,一旦翻译准备就绪,该promise将被解决。请参阅更改日志


如果我使用翻译指令而不是过滤器,会不会有性能问题?我也相信内部,它监视Instant()的返回值。那么当当前范围被销毁时,它会删除手表吗?
Nilesh 2014年

我尝试使用您的建议,但是当范围变量的值动态更改时,它不起作用。
Nilesh 2014年

10
实际上,最好总是尽可能避免使用过滤器,因为它们总是会设置新的手表,从而降低了您的应用速度。但是,该指令会更进一步。它检查是否必须监视翻译ID的值。这样可以更好地执行您的应用。您能不能给我一个小小的链接,让我做进一步的研究?
Pascal Precht 2014年

Plunk:plnkr.co/edit/j53xL1EdJ6bT20ldlhxr在我的示例中,指令可能决定不监视值。同样作为一个单独的问题,如果未找到键,则会调用我的自定义错误处理程序,但它不会显示返回的字符串。我会为此再加一点。
Nilesh 2014年

2
@PascalPrecht只是一个问题,结合使用一次绑定翻译是一种好习惯吗?这样{{::'HELLO_WORLD | translate}}'
Zunair Zubair 2015年

5

要在控制器中进行翻译,您可以使用$translateservice:

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

该语句仅在激活控制器时执行翻译,但不会检测语言的运行时更改。为了实现该行为,您可以侦听$rootScopeevent:,$translateChangeSuccess并在那里进行相同的翻译:

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

当然,您可以将$translate服务封装在一个方法中,然后在控制器和$translateChangeSucess侦听器中调用它。


1

发生的事情是Angular-translate正在使用基于事件的系统监视表达式,就像在绑定或双向绑定的任何其他情况下一样,在检索数据并更改值时会触发一个事件,显然不适用于翻译。与页面上的其他动态数据不同,翻译数据当然必须立即显示给用户。页面加载后无法弹出。

即使可以成功调试此问题,更大的问题是涉及的开发工作量很大。开发人员必须手动提取站点上的每个字符串,将其放在.json文件中,并通过字符串代码(即本例中的“ pageTitle”)进行手动引用。大多数商业站点都有成千上万的字符串需要为此而发生。这仅仅是开始。现在,您需要一个在某些基础文本发生更改时保持翻译同步的系统,一个将翻译文件发送给各个翻译人员,将它们重新集成到内部版本中,重新部署网站以便翻译人员可以看到的系统它们在上下文中以及不断变化中。

另外,由于这是一个基于事件的“绑定”系统,因此会为页面上的每个字符串触发一个事件,这不仅是转换页面的较慢方法,而且会减慢页面上的所有操作,如果您开始向其中添加大量事件。

无论如何,使用后处理翻译平台对我来说更有意义。例如,使用GlobalizeIt,翻译人员可以直接转到网站上的页面,然后开始直接在页面上针对他们的语言编辑文本,就是这样:https : //www.globalizeit.com/HowItWorks。无需编程(尽管它可以通过编程方式扩展),但它可以轻松地与Angular集成:https : //www.globalizeit.com/Translate/Angular,页面的转换是一次性完成的,并且始终以页面的初始呈现。

全面披露:我是联合创始人:)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.