2

我正在寻找一种在我的 Symfony2 应用程序和 Angular.JS 应用程序之间共享翻译的好方法,它们本质上是相同的应用程序,但它们的行为却截然不同。

首先,我制定了一个指令,允许在我的 Angular js 应用程序中使用 [[ variable ]] 进行模板化,因此我可以将 twig 中的变量与我的 angular 变量一起使用。

所以我想要实现的是将我的句子翻译成用户喜欢的本地化。

所以我的结论是我想使用 Symfony 的翻译功能,因为 Angular 没有很好的支持。我还想使用 Symfony 包含的所有内置功能,例如自动更新文件、支持 YAML 等。但是我需要弄清楚如何将它们传输到我的 JS 应用程序以及如何在 Symfony 中检测它们,所以两个应用程序都可以使用它们。

所以我最初的想法是:

更改翻译的捕获或至少在范围中添加一个额外的翻译。

{% trans %}Hello [[name]]{% endtrans %}

<trans name="My Name" translation="Hello [[name]]" />

并且还支持具有多元化等的所有其他功能。

然后,这将生成一个包含翻译和复数等的文件。

通过 Angular ajax 调用请求语言并将其保存到客户端的本地存储中。

这将允许我用适当的值替换上面的 trans 指令。这不是设置的问题。但是,它需要从任何格式导出为 angular 可以读取的 JSON。

然后需要有一个匹配器,需要支持多元化和所有其他可用的功能。

其他想法

一般来说,当你做一个 Angular.JS 应用程序时,最好不要使用 Symfony2 翻译,从而只使用角度翻译,否则用 Symfony2 Twigs 编写并翻译的文本可能无法在 Angular 中翻译。但是,如果 Symfony 能够捕获并吐出这些文件,我发现这些文件的生成会更好。

我认为这需要做一些工作才能稳固,但我觉得这需要解决。感谢任何想法和有用的评论,我正在考虑在 GitHub 上为此启动一个项目,以为此提供适当的支持。但是,如果已经有这样的东西,那么使用它可能会更好。

/马库斯

4

4 回答 4

1

这是使用自定义角度过滤器来简化标记的建议

HTML:

<div ng-app="myApp" ng-controller="MainCtrl">
    {{ item |translate }}
</div>

JS

var words={
    'fr': {'Bus': "AutoBus"}  

};

var app = angular.module('myApp', []);
app.constant('lang','fr');

app.factory('wordService',function(lang){   
    return {
        getWord:function(val){
            return words[lang][val];
        }
    }
})
app.filter('translate', function(wordService){
    return function(val){
        return wordService.getWord(val)
    }
})

app.controller('MainCtrl', function($scope) {
  $scope.item = 'Bus';
});

您可以使用服务从服务器或本地存储(如果它们已经存在)请求翻译文件。只需在运行时设置语言。

您可以以适合您在两个应用程序中使用它的任何方式重新配置单词对象。

DEMO

于 2013-10-30T17:00:28.757 回答
1

进一步的研究

这是我的应用程序:

'use strict';
var myApp = angular.module('myApp', []);

这是我的控制器:

'use strict';
myApp.controller('PageController',
    function PageController($scope, translationService, $rootScope) {
        $rootScope.currentLanguage = 'en';
        $rootScope.translations = translationService.getTranslations($scope.currentLanguage);

        $scope.setLanguage = function(language) {
            if (language === $scope.currentLanguage) {
                 return;
            }
            $rootScope.currentLanguage = language;
            $rootScope.translations = translationService.getTranslations($scope.currentLanguage);
        }
    }
);

这是翻译服务:

'use strict';
myApp.service('translationService', function ($http, $q) {
    var translationsCache = {};
    return {
        getTranslations: function(language) {
            if (translationsCache[language]) {
                return translationsCache[language];
            }
            var deferred = $q.defer();
            // **** FAKE SOLUTION **** //
            // I just return a resolve here as it doesn't really matter for this test.
            if (language == 'sv') {
                deferred.resolve({
                    "My first text unit": "Detta är min första text unit",
                    "I am a Pirate": "Jag är en Pirat"
                });
            } else if (language == 'en') {
                deferred.resolve({
                    "My first text unit": "This is my first Text unit",
                    "I am a Pirate": "I'm a Pirate"
                });
            }

            translationsCache[language] = deferred.promise;
            return deferred.promise;
            // **** END FAKE SOLUTION **** //

            /* 
            // **** WORKING SOLUTION **** //
            The probable real solution to fetching language JSON generated by Symfony somewhere
            $http({method: 'GET', url: '/translations/'+language}).
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function(data, status, headers, config) {
                    deferred.reject(status);
                });

            translationsCache[language] = deferred.promise;

            return deferred.promise;
            // **** END WORKING SOLUTION **** //
            */
        }
    }
});

所以这是我经过反复试验后提出的指令:

myApp.directive('translation', function($rootScope) {
    return {
        restrict: 'A', // Restrict to attribute
        replace: true, // Replace current object by default, not for input though, see solution below
        link: function(scope, element, attrs, controller){
            // This will watch for changes in currentLanguage in your $rootScope
            scope.$watch(function() {
                return $rootScope.currentLanguage; // If this changes then trigger function (binding)
            }, function(newVal, oldVal) {
                // As we have translation as a promise this is how we do
                $rootScope.translations.then(function(translations) {
                    // Does it exist, then translate it, otherwise use default as fallback
                    if (translations[scope.translation]) {
                        // Just some extra I found could be useful, set value if it is a button or submit. Could be extended.
                        if (element.prop('tagName') === 'INPUT' && (element.prop('type') === 'button' || element.prop('type') === 'submit')) {
                            return angular.element(element).val(translations[scope.translation]);
                        }
                        // Else just change the object to be the new translation.
                        return element.html(translations[scope.translation]);
                    }
                    // This is the fallback, and same as above, button and submit
                    if (element.prop('tagName') === 'INPUT' && (element.prop('type') === 'button' || element.prop('type') === 'submit')) {
                        return element.val(scope.translation);
                    }
                    return element.html(scope.translation);
                });
            });
        },
        scope: {
            translation: "@" // Save the parameter to the scope as a string
        }
    }
});

这里有一些如何使用它的例子。

HTML:

<div class="container">
    <div class="nav">
        <button ng-click="setLanguage('en')">
            <trans translation="English" />
        </button>
        <button ng-click="setLanguage('sv')">
            <trans translation="Svenska" />
        </button>
    </div>
    <hr />
    <p><trans translation="I am a Pirate" /></p>
    <p><trans translation="A text unit that doesn't exist" /></p>
    <p><input type="button" translation="My button" /></p>
</div>

这将使用 jsFiddle 如下工作:http: //jsfiddle.net/Oldek/95AH3/4/

这解决了:

  • 异步获取翻译
  • 缓存翻译,所以切换非常快
  • 使用带值的输入/字段
  • 在本地存储中存储语言
  • 在翻译更改时动态更新整个 DOM
  • 真的很快,至少我试过的规模
  • 一个常见问题的完整和有效的解决方案

要解决的事情:

  • 移出检查它是否是其他地方的输入字段的代码
  • 多元化
  • 输入占位符
  • 以及翻译支持的其他功能。
  • 支持参数,见示例:

<trans translation="Hello {{name}}" name="{{name}}">

  • 用 xgettext 扫描项目?并生成可以在某些软件中翻译的 YML 或类似结构。
  • 删除临时解决方案并使用注释掉的工作解决方案。

其他的建议

如果您有问题,请随时提出问题,我会提供信息,并且可能很快就会提供一个 jsFiddle 认为需要。

/马库斯

于 2013-10-31T12:36:36.603 回答
1

我最终使用了这个解决方案。解决了我所有的问题:

http://cliffmeyers.com/blog/2013/3/11/integration-angularjs

于 2013-11-09T18:33:02.517 回答
0

所以我已经走得更远了,经过一些研究,使用过滤器进行此操作更有意义,但是我似乎无法让它按我的意图工作。

所以这就是我申请的:

var app = angular.module('app', []);

app.factory('translationsService', function($http, translations, $q) {
    return {
        getTranslations: function(lang) {
            var deferred = $q.defer();
            $http({method: 'GET', url: '/translations/'+lang}).
                success(function (data) {
                    deferred.resolve({
                        data: data,
                        getWord: function(word) {
                            return data[word] ? data[word] : word;
                        }
                    });
                });
            return deferred.promise;
        }
    }
});

app.factory('wordService', function(translationsService, $q){
    return {
        lang: 'en-us',
        getWord: function(val){
            var translations = translationsService.getTranslations(this.lang);

            var deferred = $q.defer();
            translations.then(function(data) {
                deferred.resolve(data.getWord(val));
            });

            return deferred.promise;
        }
    }
});

app.filter('translate', function(wordService){
    return function(val){
        return wordService.getWord(val);
    }
});

因此,如果我现在在 html 页面中执行此操作:

{{ "User" | translate }}

然后我就陷入了无限循环。我把整个 $q / promise 弄错了吗?我需要一些帮助。

但是,如果我通过将它分配给控制器中的值来使用它,它就可以正常工作。

在控制器中我做:

app.controller('PageController',
    function PageController($scope, wordService) {
         $scope.someValue = wordService.getWord("USER");
    }
);

然后在html中使用它:

{{ 一些价值 }}

它工作得很好。

/马库斯

于 2013-11-07T16:20:07.830 回答