Load default static translation files before getting overriding translation maps - angularjs

I'm new to angular-translate (I'm not a God in Angular itself though).
Say I have a set of translation JSON files already available, in English and Italian. Those contain the translations I should use by default.
i18n/locale-en_GB.json
{
"market.marketplace.title": "Marketplace",
"market.marketplace.descr": "Here is a description",
"market.available.items": "Available items"
}
i18n/locale-it_IT.json
{
"market.marketplace.title": "Marketplace",
"market.marketplace.descr": "Ecco una descrizione",
"market.available.items": "Oggetti disponibili"
}
If that was all, I would of course simply use the static file loader:
.conf
...
$translateProvider.useStaticFilesLoader({
prefix: 'i18n/locale-', // Template paths to translation files
suffix: '.json'
});
...
The problem is that I also have to take into account the results of a REST Call that I have to run at the very beginning (say translation service configuration time), which could override some of the default translations.
I tried to use a custom loader:
.conf
...
//$translateProvider.useStaticFilesLoader....
$translateProvider.useLoader('customLoader', {});
...
.factory
.factory('customLoader', function ($q, $http, MyRESTService, translationService) {
return function (options) {
var deferred = $q.defer();
MyRESTService.getLanguageMaps(
function success(response) {
var langMap = response;
/* langMap would be something like:
var langMap = [
{
"en_GB": {
"market.marketplace.title": "NEWENGLISHTITLE",
"market.marketplace.descr": "NEWENGLISHDESCR"
}
},
{
"it_IT": {
"market.marketplace.title": "NEWITALIANTITLE",
"market.marketplace.descr": "NEWITALIANDESCR"
}
}
];
*/
deferred.resolve(langMap);
},
function failure(err) {
deferred.reject(err);
}
);
return deferred.promise;
};
})
But I can't seem to find a way to load my "default" translations (from static files) first, and then merge with results from my REST API.
Furthermore, the only way I could find to make this customLoader work was to specify only one JSON as translation map, i.e. I can't make it use the first object as English map and second object as Italian map.
E.g.
/* If resulting configMap layout is like this, translations are displayed but only second JSON Object is used */
var configMap = [
{
"market.marketplace.title": "ENGLISHTITLE",
"market.marketplace.descr": "ENGLISHDESCR"
},
{
"market.marketplace.title": "ITALIANTITLE",
"market.marketplace.descr": "ITALIANDESCR"
}
]
/* This way does not work */
var configMap = [
{"en_GB",
{
"market.marketplace.title": "ENGLISHTITLE",
"market.marketplace.descr": "ENGLISHDESCR"
},
},
{"it_IT",
{
"market.marketplace.title": "ITALIANTITLE",
"market.marketplace.descr": "ITALIANDESCR"
}
}
]
I can decide how results will be given, as I still have to implement the REST call.
I hope I made myself clear enough! Can somebody help here?

Related

How to angular-translate controller function objects

I cannot find examples for how to translate objects inside of functions, only examples for translating html content.
Inside the function are system button labels that need to be translated. I have provided the actual en.json and th.json files to show what needs to be translated. I can find no examples for angular-translate that translate non-html objects like in this function. I have managed to get translation working on all other areas of my application, but not this function. The documentation http://angular-translate.github.io/docs/#/guide/03_using-translate-service does not provide a good example that fits my code. I have seen others ask this same type of question, and just be pointed to the documentation (i.e. https://github.com/angular-translate/angular-translate/issues/1466).
en.json
{
"CHOOSE_IMAGE": "Choose image source",
"CAMERA": "Camera",
"LIBRARY": "Library",
"CANCEL": "Cancel"
}
th.json
{
"CHOOSE_IMAGE": "เลือกที่มาของภาพ",
"CAMERA": "กล้อง",
"LIBRARY": "คลังรูปภาพี่ี",
"CANCEL": "ยกเลิก"
}
feedback.controller.js
...
function getImageSource() {
var deferred = $q.defer();
$ionicActionSheet.show({
buttons: [
{ text: 'CAMERA' },
{ text: 'LIBRARY' }
],
titleText: 'CHOOSE_IMAGE',
cancelText: 'CANCEL',
cancel: function () {
deferred.reject();
},
buttonClicked: function (index) {
if (index === 0) {
deferred.resolve(Camera.PictureSourceType.CAMERA);
} else {
deferred.resolve(Camera.PictureSourceType.PHOTOLIBRARY);
}
return true;
}
});
return deferred.promise;
}
...
Well, apparently all the necessary information are in the docs. But let me do your work..
You have to inject $translate service in your controller. Assuming you have your translations already loaded the most convinient way to translate your labels is to use $translate.instant() method. What does it do?
According to docs http://angular-translate.github.io/docs/#/api/pascalprecht.translate.$translate it:
Returns a translation instantly from the internal state of loaded translation. All rules regarding the current language, the preferred language of even fallback languages will be used except any promise handling. If a language was not found, an asynchronous loading will be invoked in the background.
So your code should look like that:
feedback.controller.js
...
function getImageSource() {
var deferred = $q.defer();
$ionicActionSheet.show({
buttons: [
{ text: $translate.instant('CAMERA') },
{ text: $translate.instant('LIBRARY') }
],
titleText: $translate.instant('CHOOSE_IMAGE'),
cancelText: $translate.instant('CANCEL'),
cancel: function () {
deferred.reject();
},
buttonClicked: function (index) {
if (index === 0) {
deferred.resolve(Camera.PictureSourceType.CAMERA);
} else {
deferred.resolve(Camera.PictureSourceType.PHOTOLIBRARY);
}
return true;
}
});
return deferred.promise;
}
...
Or you can use asynchronous loading with:
feedback.controller.js
....
$translate(['CAMERA',
'LIBRARY',
'CHOOSE_IMAGE',
'CANCEL']).then(function (translations) {
$ionicActionSheet.show({
buttons: [
{ text: $translate.instant('CAMERA') },
{ text: $translate.instant('LIBRARY') }
],
....
Hope it helps.
Use filter function
.controller(["$filter",....],function($filter,....){
var translateFilter=$filter("translate");
...
buttons: [
{ text: translateFilter('CAMERA') },
{ text: translateFilter('LIBRARY') }
]
...
})
or when translations isn't loaded already
.controller(["$q","$translate",....],function($q,$translate,....){
var translateFilter=$filter("translate");
$q.all({
CAMERA:$translate('CAMERA'),
LIBRARY:$translate('CAMERA')
}).then(function(translations){
...
buttons: [
{ text: translations.CAMERA },
{ text: translations.LIBRARY }
]
...
})
})

Reload angular-translate-static-loader-files within app, without refresh in Ionic

I'm working on this Ionic app and I'm using angular-translate-loader-static-files with angular-translate to load in a bunch of language .json files.
Everything is working fine, but I'm trying to figure out how to basically "re-run the $translateProvider" so it can reload all the static files all over again as the .json files will get updated from the server periodically. I have yet to figure this out, and even trying to force a "page reload" doesn't cause the static files to reload.
I should note that I'm currently testing this in iOS and I realize that the directory structure will change, based on OS.
Here is my service that utilizes $cordovaFile to overwrite the file with new text. Right now I'm just using a simple json string to make sure I can solve the problem:
(function() {
'use-strict';
angular.module('coursemill.services')
.service('Translations', Translations);
/**
* Service: Check network connection
*/
function Translations($cordovaFile) {
function updateLanguageFile(lang) {
document.addEventListener("deviceready", function() {
$cordovaFile.checkFile(cordova.file.applicationDirectory + "/www/languages/", lang + ".json")
.then(function (success) {
// Update the language file
$cordovaFile.writeFile(cordova.file.applicationDirectory + "/www/languages/", lang + ".json", '{"COURSES_MENU_REQUIRED": "Required"}', true)
.then(function (success) {
// TO-DO: reload translation files
},
function (error) {});
},
function (error) {});
});
}
return {
updateLanguageFile: updateLanguageFile
}
}
})();
Here is a snippet from my .config:
// Setup the language translations
$translateProvider.useStaticFilesLoader({
prefix: 'languages/',
suffix: '.json'
});
Here is a snippet from my controller:
Translations.updateLanguageFile('en_US');
When I open the file up after this function has been run, the contents of the file are replaced and are doing exactly what I want, but I'd like my language variables inside the app to be updated as well, and they aren't.
Any thoughts on what can be done here?
Doink, I needed to use $translate.refresh() in my service function. So now it looks like this:
(function() {
'use-strict';
angular.module('coursemill.services')
.service('Translations', Translations);
function Translations($cordovaFile, $translate) {
function updateLanguageFile(lang) {
document.addEventListener("deviceready", function() {
$cordovaFile.checkFile(cordova.file.applicationDirectory + "/www/languages/", lang + ".json")
.then(function (success) {
// Update the language file
$cordovaFile.writeFile(cordova.file.applicationDirectory + "/www/languages/", lang + ".json", '{"COURSES_MENU_REQUIRED": "Required"}', true)
.then(function (success) {
// Reload translation files
$translate.refresh();
},
function (error) {});
},
function (error) {});
});
}
return {
updateLanguageFile: updateLanguageFile
}
}
})();

Configure restmod for submodule in AngularJS

So I have a main module app defined as
app = angular.module("app", ['app.social_accounts', 'restmod'])
which has its restmod module configured:
app.config(function(restmodProvider) {
restmodProvider.rebase({
$config: {
primaryKey: "id",
style: "ams",
urlPrefix: "/app/"
}
});
});
and it works as expected: request were sent to http://localhost:8000/app/...
Now I want to use restmod in the submodule app.social_accounts, by doing
app = angular.module("app.social_accounts", ['restmod'])
app.config(function(restmodProvider) {
restmodProvider.rebase({
$config: {
primaryKey: "id",
style: "ams",
urlPrefix: "https://graph.facebook.com/"
}
});
});
app.factory("Album", ["restmod", function(restmod){
Album = restmod.model("/me/albums/")
return {
"get": function(){Album.$search()}
}
}])
namely I want to use absolute url in the submodule app.social_accounts.
But when I inject Album (registered under app.social_accounts) into a controller DashboardCtrl under app, the request were sent to http://localhost:8000/app/me/albums/.
So I wonder what is happening here and how to achieve a separate url for restmod under app.social_accounts?
Any configuration defined with restmodProvider is global for restmod irrespective of the module it's used in. So in your example above, the urlPrefix defined in the app.social_accounts module is being overwritten by the configuration in the app module.
In order to achieve the behaviour you expect, you can override the configuration on a per model basis:
angular.module('app.social_accounts', ['restmod'])
.factory('Album', function(restmod) {
var Album = restmod.model('/me/albums')
.mix({
$config: {
urlPrefix: 'https://graph.facebook.com/'
}
});
});
If you require the configuration in more than one model within a module, a mixin can be used to keep things DRY:
.factory('restmodConfigSocial', function(restmod) {
return restmod.mixin({
$config: {
urlPrefix: 'https://graph.facebook.com/'
}
});
})
.factory('Album', function(restmod) {
var Album = restmod.model('/me/albums').mix('restmodConfigSocial');
});

Where do I place global overrides in a modular Backbone.js site?

We use RequireJS to add modularity to our Backbone.js site. I found myself with the need to override the Backbone.Collection class to add an advance filtering routine.
My questions is, say I have the following 'override',
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
and our site structure looks like the following:
where, main.js sits at the top level and beneath it is app.js; Where would I add this override, such that I don't have to add a new module to our RequireJS definition for every class? More generally, where are overrides to Backbone usually recommended?
Create a file (say Overrides.js in modules folder)
define(function(require){
var app = require('app');
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
// other overrides can also be added here in this file like
_.extend(Backbone.View.prototype,{}, {
// adding functions or overriding something
})
});
Now, require this file in main.js like
require([
'backbone',
'App',
'modules/Overrides',
'globalize',
.
.
.
],
function ( Backbone, App, ..... ) {
});
There you go!
Say, I want to add some function to the view or override some function such as render, initialize, remove,... universally in the application. You could do something like this:
_.extend(Backbone.View.prototype,{}, {
remove: function() {
alert("View removed");
this.$el.remove();
this.stopListening();
return this;
}
});
One easy option if using requirejs, in your require config add an init statement. eg,
require.config({
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone',
init: function (_) {
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
}
}
}
});
Alternatively you can use a map config call to amp all backbone calls to your overridden backbone,
require.config({
map: {
'*': { 'backbone': 'backbone-custom' },
'backbone-custom': { 'backbone': 'backbone' }
}
});
// backbone-custom.js file:
define(['backbone'], function (Backbone) {
Backbone.Collection.prototype.advanceFilter = function() {
/* Filtering code here */
};
return Backbone;
});
Either case will load the override into the backbone object before it is used anywhere.

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

Resources