0

I have an Angular service that handles translation through a call to my Web API backend, like so:

self.Translate = function (languageCode, keyword) {
    var defer = $q.defer();

    var uri = "api/translation/translate/" + languageCode + "/" + keyword;

    apiService.Get(uri).then(function (translation) {
        defer.resolve(translation.Text);
    }, function (error) {
        var msg = "Unable to translate keyword '" + keyword + "' for language code '" + languageCode + "'. Make sure that you can connect to the Web API and that the requested translation exists.";
        loggerService.Error(self.Name, msg);

        defer.reject(msg);
    });

    return defer.promise;
}

It is called like so:

var text = translationService.Translate("FR", "dateOfBirth");

Which would return:

date de naissance

However, on the receiving end I get this (in console.log):

d  {$$state: Object}
$$state: Object
status: 1
value: "date de naissance"
__proto__: Object
__proto__: d

Which results to [object Object] being shown, not the translated text.

Based on the above you would think that the following would work:

var text = translationService.Translate("FR", dateOfBirth).value;

But it doesn't, it returns undefined.

Any idea what's going on and how I can fix this? Thanks!

PS: You can find the full service code here (script only), to be complete.

Spikee
  • 3,967
  • 7
  • 35
  • 68

2 Answers2

1

You are assigning a promise to your text, that's why it doesn't work...

The translation is coming from an API, so you would need to handle the case when it is requesting data from the server, and then update when the API returns.

Your translate function is functionally correct, but I would suggest you to change to this format as best practice.

self.Translate = function (languageCode, keyword) {
    var uri = "api/translation/translate/" + languageCode + "/" + keyword;

    return apiService.Get(uri).then(function (translation) {
        return translation.Text;
    }, function (error) {
        var msg = "Unable to translate keyword '" + keyword + "' for language code '" + languageCode + "'. Make sure that you can connect to the Web API and that the requested translation exists.";
        loggerService.Error(self.Name, msg);

        return $q.reject(msg);
    });
}

The calling part should be like this:

var text = '';
translationService.Translate("FR", dateOfBirth).then(function(data){
  text = data;
  // any logic before assigning text to scope variable should be done here
});

This is typical asynchronus promise handling. It might look weird at first but you will get used to it.

If you insist on var text = ?? form, you can create a filter. You can refer to AngularJS : Asynchronously initialize filter

There is also a 3rd party plugin called angular-translate which supports the use of filter, maybe you can refer on their code, too.


Update: Show case promise chaining

self.GetMeaningfulTitle = function (options) {
    var defer = $q.defer();

    translationService.Translate(options.Language, "Edit").then(function(translation) {
        personService.Get(options.PersonId).then(function(person){
            var result = "";

            if (personValidatorService.Validate(person) {
                result = translation + " - " + person.Firstname + " " person.Lastname;

                defer.resolve(result);
            }else{
                defer.reject("Invalid person detected");
            }
        }
    });


    return defer.promise;
}
Community
  • 1
  • 1
Icycool
  • 7,099
  • 1
  • 25
  • 33
  • Thanks for the info, but I don't see any difference. I know angular-translate and found it very unreliable, things like translations not working on the first page load (and that's a problem since I use an SPA). – Spikee Oct 28 '15 at 07:31
  • Don't see any difference on? You mean after changing the code you get the same error? It is normal for SPA to have initial state and complete state after all required ajax returns. – Icycool Oct 28 '15 at 07:38
  • You should observe worse performance than angular-translate with your current code, because you are hitting the server for every keyword, while angular-translate load once for a language. – Icycool Oct 28 '15 at 07:42
  • After implementing your suggested chances, yes. (Performance is fine at this time, should I need to improve I will create a preloading mechanism) – Spikee Oct 28 '15 at 07:47
0

In response to Icycool, based on the link provided:

This (partially) works:

angular.module("filters.webapi")
    .filter("translate", [
        "translationService",
        function (translationService) {
            var data = null;
            var serviceInvoked = false;

            // real filter
            function realFilter(keyword, languageCode) {
                return data;
            }

            // Async wait filter
            filterStub.$stateful = true;
            function filterStub(keyword, languageCode) {
                if (data === null) {
                    if (!serviceInvoked) {
                        serviceInvoked = true;

                        translationService.Translate(languageCode, keyword).then(
                            function(translation) {
                                data = translation;
                            }, function(error) {
                                data = keyword;
                            });
                    }
                    return "";
                } else {
                    return realFilter(keyword, languageCode);
                }
            }

            return filterStub;
        }
    ]);

If used like this in html:

{{ "dateOfBirth" | translate:Language }}

But how do I use it in script?

I mean, as equivalent to this:

var text = translationService.Translate("FR", "dateOfBirth")

Update

Sample usage (extremely simplified) per request:

self.GetMeaningfulTitle = function (options) {
    var defer = $q.defer();

    personService.Get(options.PersonId).then(function(person){
        var result = "";
        var action = $filter("translate")("Edit", options.Language);

        if (personValidatorService.Validate(person) {
            result = action + " - " + person.Firstname + " " person.Lastname;

            defer.resolve(result);
        }else{
            defer.reject("Invalid person detected");
        }
    }

    return defer.promise;
}

Assuming the person information is valid, that will result in - Firstname Lastname.

Spikee
  • 3,967
  • 7
  • 35
  • 68
  • 1
    `var text = $filter('translate')('dateOfBirth', 'FR')` – Icycool Oct 28 '15 at 09:34
  • Ok, we're getting there, but it doesn't work (it remains the placeholder value) when used in a function that returns a promise. Works fine in a non-async function. – Spikee Oct 28 '15 at 09:48
  • I don't have a simple answer for this, I'm afraid. Personally I would use the service version and chain the promises. – Icycool Oct 28 '15 at 11:55
  • No problem, I'll think about it a bit further. Thanks for taking the time. – Spikee Oct 28 '15 at 12:11