AngularJS: i18n and pluralization - internationalization

Looking on the pluralization part in Spanish here, as an example:
I see that
var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"};
apparently, all is in English
can anyone explain if this is a bug?
thanks very much
Lior

By glancing the code I can see it's a set of simple pluralization rules. Every locale has this constant. So no, it's not a bug.

Here is how I am doing my i18n work, it seems to be working great! It is based off a set of localized resource files that get initialized at runtime. At the bottom is how I handle pluralization using this approach.
I18n module to hold string id map and parameter insertion
.factory('I18n', ['$http', 'User', function($http, User) {
// Resource File
var LANG_FILE;
// Fetch Resource File
function init() {
return $http.get('resources/locales/' + User.locale + '.json')
.then(function(response) {
LANG_FILE = response.data;
});
}
function lang(stringId, params) {
var string = LANG_FILE[stringId] || stringId;
if (params && params.length) {
for (var i = 0; i < params.length; i++) {
string = string.replace('%' + (i + 1), params[i]);
}
}
return string;
}
return {
init: init,
lang: lang
};
}]);
This can be initialized using a .run block
.run(['I18n', function(I18n) {
I18n.init();
}]);
And used anywhere to translate a string like this
.controller(['$scope', 'I18n', function($scope, I18n) {
$scope.title = I18n.lang(some_string_id);
}]);
Custom i18n DIRECTIVE to handle one time translations
.directive('i18n', ['I18n', function(I18n) {
return {
restrict: 'A',
scope: {},
link: function(scope, $el, attrs) {
$el[0].innerHTML = I18n.lang(attrs.i18n);
}
};
}]);
Which can be used like this.
<div i18n="some_string_id"></div>
Custom PLURALIZE directive that matches string ids from your resource file with the count as the parameter.
.directive('pluralize', ['I18n', function(I18n) {
return {
restrict: 'A',
scope: {
count: '='
},
link: function($scope, $el, attrs) {
var when = JSON.parse(attrs.when)
, param = [$scope.count];
if (when[$scope.count]) {
$el[0].innerHTML = I18n.lang(when[$scope.count], param);
} else {
$el[0].innerHTML = I18n.lang(when['other'], param);
}
}
};
}]);
And can be used like this.
<div pluralize count="{{obj.count}}" when="{1:'single_item','other': 'multiple_item'}"></div>
The String Resource file would be located at resources/locales/en-US.json, and would look something like this.
{
some_string_id: 'This is in English',
single_item: '%1 item',
multiple_item: '%1 items'
}
The other locales would have the same string ids, with different translated texts.

Related

How to pass multiple value with key to url using vue.js

I have this attributes data
for(var k = 0;k<this.form.fields.length;k++)
{
this.dynamic_fields.push({attribute_id:attributes[k].id,value: attributes[k].value})
}
this.$router.push({
path: '/api/search-temp',
query:{
attributes: this.encodedAttributes()
}
});
encodedAttributes() {
const queryAttributes =this.dynamic_fields;
if (queryAttributes) {
return typeof queryAttributes !== "string"
? btoa(JSON.stringify(queryAttributes))
: queryAttributes;
}
return "";
},
I have a attribute id and an attribute value so i want to pass this id and value to url so that i cab loop in my controller attributes array and get id and value :
localhost:8000..?attributes[]['attribute_id_1']=attributevalue1&attributes[]['attribute_id_2']=attributevalue2...
I'm redirecting like this :
this.$router.push({ path: '/search-list',query:
{
}
Issue is i want to pass this multidimentional array to url, anyother workaround for this is also highly appreciated
What you may try is to json stringify and encode the object before passing it to the $route query
function encodedAttributes() {
const queryAttributes = this.$route.query.attributes;
if (queryAttributes) {
return typeof queryAttributes !== "string"
? btoa(JSON.stringify(this.$route.query.attributes))
: queryAttributes;
}
return "";
}
function decodedAttributes() {
const attributes = this.$route.query.attributes;
if (typeof attributes === "string" && attributes.length) {
return JSON.parse(atob(attributes));
} else {
return attributes;
}
}
And pass as query parameters to the route
this.$router.push({
path: '/search-list',
query:{
attributes: this.encodedAttributes()
}
Then in the Controller you can decode the attributes value from request data to get the associated array
class MyController extends Controller
{
public function index(Request $request)
{
$request->attributes = is_array(
$requestAttributes = json_decode(base64_decode($request->attributes), true)
)
? $requestAttributes
: [];
//Do other processing as needed
}
}
Had used something similar in one of my projects can't get my hands on the code right now.
Probably you can use function to escape unicode characters in the encodedAttributes as well as decodedAttributes if need be
function escapeUnicode(str){
return str.replace(/[^\0-~]/g, c => '\\u' + ('000' + c.charCodeAt().toString(16)).slice(-4))
}
function encodedAttributes() {
const queryAttributes = this.$route.query.attributes;
if (queryAttributes) {
return typeof queryAttributes !== "string"
? btoa(escapeUnicode(JSON.stringify(this.$route.query.attributes)))
: queryAttributes;
}
return "";
}
function decodedAttributes() {
const attributes = this.$route.query.attributes;
if (typeof attributes === "string" && attributes.length) {
return JSON.parse(atob(escapeUnicode(attributes)));
} else {
return attributes;
}
}
You're trying to set a nested object to the query params, it's not possible... your route's query object must be a flat object.
Summarizing the only way for you to have something like this:
?attributes[]['attribute_id_1']=attributevalue1&attributes[]['attribute_id_2']=attributevalue2
Would be from a query object like this:
query: {
"attributes[]['attribute_id_1']": 'attributevalue1',
"attributes[]['attribute_id_2']": 'attributevalue2',
}
You should flatten this multidimensional array into an simples object and use it as your query object.
Here is an example...
From this:
const multiDimArr = [
['attribute_1', 'value1'],
['attribute_2', 'value2']
];
Into:
const myObject = {
attribute_1: 'value1',
attribute_2: 'value2'
}
A way to do so would be:
const multiDimArr = [
['attribute_1', 'value1'],
['attribute_2', 'value2']
];
const myObject = {};
multiDimArr.forEach(arr => {
myObject[arr[0]] = arr[1];
});
And then use the object as the query object, so your url will look like this:
?attribute_1=value1&attribute_2=value2

Correct way to get a count of matching elements in Nightwatch?

I'm trying to test if a todo app has the right number of elements.
The docs seem to deal almost exclusively with single elements, so I had to use the Selenium Protocol functions. Would this be the right way to test the count of matching selectors (in this case, checking for 2 li elements)?
client.elements('css selector','#todo-list li', function (result) {
client.assert.equal(result.value.length, 2);
});
This works in my test, but I wasn't sure if there were gotchas around using a callback for this. Also not sure why Nightwatch doesn't have any helper functions dealing with more than one element.
I found the following very elegant solution within a VueJS template. It shows how to add a custom assertion into Nightwatch that counts the number of elements returned by a selector. See http://nightwatchjs.org/guide#writing-custom-assertions for details of how to write custom assertions within Nightwatch.
Once installed, usage is as simple as:
browser.assert.elementCount('#todo-list li', 2)
The plugin:
// A custom Nightwatch assertion.
// the name of the method is the filename.
// can be used in tests like this:
//
// browser.assert.elementCount(selector, count)
//
// for how to write custom assertions see
// http://nightwatchjs.org/guide#writing-custom-assertions
exports.assertion = function (selector, count) {
this.message = 'Testing if element <' + selector + '> has count: ' + count;
this.expected = count;
this.pass = function (val) {
return val === this.expected;
}
this.value = function (res) {
return res.value;
}
this.command = function (cb) {
var self = this;
return this.api.execute(function (selector) {
return document.querySelectorAll(selector).length;
}, [selector], function (res) {
cb.call(self, res);
});
}
}
This code was added to vuejs-templates by yyx990803 in 2016. So full credit goes to yyx990803.
Just to reassure you I do a similar thing when trying to grab all matching elements, ex:
browser.elements("xpath","//ul[#name='timesList']/h6", function(result){
els = result.value;
var i = 0;
els.forEach(function(el, j, elz){
browser.elementIdText(el.ELEMENT, function(text) {
dates[i] = text.value;
i++;
});
});
});
Alternatively, if you want to be sure that n number of elements exist, you can use a combination of :nth-of-type/:nth-child selectors and nightwatch's expect.
For example, if you want to test if #foo has nine direct children:
function(browser) {
browser
.url('http://example.com')
.expect.element('#foo > *:nth-child(9)').to.be.present;
browser.end();
}
Or if #bar has three direct article children:
function(browser) {
browser
.url('http://example.com')
.expect.element('#bar > article:nth-of-type(3)').to.be.present;
browser.end();
}
You could use expect.elements(<selector>).count():
browser.expect.elements('div').count.to.equal(10);
browser.expect.elements('p').count.to.not.equal(1);
I adapted Chris K's answer to support XPath expressions by using the built-in method this.api.elements:
exports.assertion = function elementCount(selector, count) {
this.message = 'Testing if element <' + selector + '> has count: ' + count
this.expected = count
this.pass = function pass(val) {
return val === this.expected
}
this.value = function value(res) {
return res.value.length
}
this.command = function command(callback) {
return this.api.elements(this.client.locateStrategy, selector, callback)
}
}
For usage instructions and credits see his answer
And if you like a bit of TypeScript, here is an assertion that will confirm the element count:
import { NightwatchCallbackResult, NightwatchAssertion, NightwatchAPI } from "nightwatch";
module.exports.assertion = function (selector: string, count: number, description?: string) {
this.message = description || `Testing if element <${selector}> has count: ${count}`;
this.expected = count;
this.pass = (value: number) => value === this.expected;
this.value = (result: number) => result;
this.command = (callback: (result: number) => void): NightwatchAPI => {
const self: NightwatchAssertion = this;
return self.api.elements(this.client.locateStrategy, selector, (result: NightwatchCallbackResult) => {
callback(result.value.length);
});
}
}
Use like this:
browser.assert.elementCount('body', 1, 'There is only one body element');

Using Inheritance Patterns to Organize Large jQuery Applications - how to extend the plugin?

I found this working example of Inheritance Patterns that separates business logic and framework code. I'm tempted to use it as a boilerplate, but since it is an inheritance Pattern, then how can I extend the business logic (the methods in var Speaker)?
For instance, how can I extend a walk: method into it?
/**
* Object Speaker
* An object representing a person who speaks.
*/
var Speaker = {
init: function(options, elem) {
// Mix in the passed in options with the default options
this.options = $.extend({},this.options,options);
// Save the element reference, both as a jQuery
// reference and a normal reference
this.elem = elem;
this.$elem = $(elem);
// Build the dom initial structure
this._build();
// return this so we can chain/use the bridge with less code.
return this;
},
options: {
name: "No name"
},
_build: function(){
this.$elem.html('<h1>'+this.options.name+'</h1>');
},
speak: function(msg){
// You have direct access to the associated and cached jQuery element
this.$elem.append('<p>'+msg+'</p>');
}
};
// Make sure Object.create is available in the browser (for our prototypal inheritance)
// Courtesy of Papa Crockford
// Note this is not entirely equal to native Object.create, but compatible with our use-case
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {} // optionally move this outside the declaration and into a closure if you need more speed.
F.prototype = o;
return new F();
};
}
$.plugin = function(name, object) {
$.fn[name] = function(options) {
// optionally, you could test if options was a string
// and use it to call a method name on the plugin instance.
return this.each(function() {
if ( ! $.data(this, name) ) {
$.data(this, name, Object.create(object).init(options, this));
}
});
};
};
// With the Speaker object, we could essentially do this:
$.plugin('speaker', Speaker);
Any ideas?
How about simply using JavaScript's regular prototype inheritance?
Consider this:
function Speaker(options, elem) {
this.elem = $(elem)[0];
this.options = $.extend(this.defaults, options);
this.build();
}
Speaker.prototype = {
defaults: {
name: "No name"
},
build: function () {
$('<h1>', {text: this.options.name}).appendTo(this.elem);
return this;
},
speak: function(message) {
$('<p>', {text: message}).appendTo(this.elem);
return this;
}
};
Now you can do:
var pp = new Speaker({name: "Porky Pig"}, $("<div>").appendTo("body"));
pp.speak("That's all folks!");
Speaker.prototype.walk = function (destination) {
$('<p>', {
text: this.options.name + " walks " + destination + ".",
css: { color: "red" }
}).appendTo(this.elem);
return this;
}
pp.walk("off the stage");
Runnable version:
function Speaker(options, elem) {
this.elem = $(elem)[0];
this.options = $.extend(this.defaults, options);
this.build();
}
Speaker.prototype = {
defaults: {
name: "No name"
},
build: function () {
$('<h1>', {text: this.options.name}).appendTo(this.elem);
return this;
},
speak: function(message) {
$('<p>', {text: message}).appendTo(this.elem);
return this;
}
};
var pp = new Speaker({name: "Porky Pig"}, $("<div>").appendTo("body"));
pp.speak("That's all folks!");
Speaker.prototype.walk = function (destination) {
$('<p>', {
text: this.options.name + " walks " + destination + ".",
css: { color: "red" }
}).appendTo(this.elem);
return this;
}
pp.walk("off the stage");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

Jasmine toEqual for complex objects (mixed with functions)

Currently, I have a function that sometimes return an object with some functions inside. When using expect(...).toEqual({...}) it doesn't seem to match those complex objects. Objects having functions or the File class (from input type file), it just can't. How to overcome this?
Try the Underscore _.isEqual() function:
expect(_.isEqual(obj1, obj2)).toEqual(true);
If that works, you could create a custom matcher:
this.addMatchers({
toDeepEqual: function(expected) {
return _.isEqual(this.actual, expected);
};
});
You can then write specs like the following:
expect(some_obj).toDeepEqual(expected_obj);
As Vlad Magdalin pointed out in the comments, making the object to a JSON string, it can be as deep as it is, and functions and File/FileList class. Of course, instead of toString() on the function, it could just be called 'Function'
function replacer(k, v) {
if (typeof v === 'function') {
v = v.toString();
} else if (window['File'] && v instanceof File) {
v = '[File]';
} else if (window['FileList'] && v instanceof FileList) {
v = '[FileList]';
}
return v;
}
beforeEach(function(){
this.addMatchers({
toBeJsonEqual: function(expected){
var one = JSON.stringify(this.actual, replacer).replace(/(\\t|\\n)/g,''),
two = JSON.stringify(expected, replacer).replace(/(\\t|\\n)/g,'');
return one === two;
}
});
});
expect(obj).toBeJsonEqual(obj2);
If anyone is using node.js like myself, the following method is what I use in my Jasmine tests when I am only concerned with comparing the simple properties while ignoring all functions. This method requires json-stable-stringify which is used to sort the object properties prior to serializing.
Usage:
var stringify = require('json-stable-stringify');
var obj1 = {
func: function() {
},
str1: 'str1 value',
str2: 'str2 value',
nest1: {
nest2: {
val1:'value 1',
val2:'value 2',
someOtherFunc: function() {
}
}
}
};
var obj2 = {
str2: 'str2 value',
str1: 'str1 value',
func: function() {
},
nest1: {
nest2: {
otherFunc: function() {
},
val2:'value 2',
val1:'value 1'
}
}
};
it('should compare object properties', function () {
expect(stringify(obj1)).toEqual(stringify(obj2));
});
Extending #Vlad Magdalin's answer, this worked in Jasmine 2:
http://jasmine.github.io/2.0/custom_matcher.html
beforeEach(function() {
jasmine.addMatchers({
toDeepEqual: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var result = {};
result.pass = _.isEqual(actual, expected);
return result;
}
}
}
});
});
If you're using Karma, put that in the startup callback:
callback: function() {
// Add custom Jasmine matchers.
beforeEach(function() {
jasmine.addMatchers({
toDeepEqual: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var result = {};
result.pass = _.isEqual(actual, expected);
return result;
}
}
}
});
});
window.__karma__.start();
});
here's how I did it using the Jasmine 2 syntax.
I created a customMatchers module in ../support/customMatchers.js (I like making modules).
"use strict";
/**
* Custom Jasmine matchers to make unit testing easier.
*/
module.exports = {
// compare two functions.
toBeTheSameFunctionAs: function(util, customEqualityTesters) {
let preProcess = function(func) {
return JSON.stringify(func.toString()).replace(/(\\t|\\n)/g,'');
};
return {
compare: function(actual, expected) {
return {
pass: (preProcess(actual) === preProcess(expected)),
message: 'The functions were not the same'
};
}
};
}
}
Which is then used in my test as follows:
"use strict";
let someExternalFunction = require('../../lib/someExternalFunction');
let thingBeingTested = require('../../lib/thingBeingTested');
let customMatchers = require('../support/customMatchers');
describe('myTests', function() {
beforeEach(function() {
jasmine.addMatchers(customMatchers);
let app = {
use: function() {}
};
spyOn(app, 'use');
thingBeingTested(app);
});
it('calls app.use with the correct function', function() {
expect(app.use.calls.count()).toBe(1);
expect(app.use.calls.argsFor(0)).toBeTheSameFunctionAs(someExternalFunction);
});
});
If you want to compare two objects but ignore their functions, you can use the methods _.isEqualWith together with _.isFunction from lodash as follows.
function ignoreFunctions(objValue, otherValue) {
if (_.isFunction(objValue) && _.isFunction(otherValue)) {
return true;
}
}
it('check object equality but ignore their functions', () => {
...
expect(_.isEqualWith(actualObject, expectedObject, ignoreFunctions)).toBeTrue();
});

jquery plugin creation issue

I have created a plugin with following codes:
var myplugin = {
init: function(options) {
$.myplugin.settings = $.extend({}, $.myplugin.defaults, options);
},
method1: function(par1) {
.....
},
method2: function(par1) {
.....
}
};
$.myplugin = function(method){
if ( myplugin[method] ) {
return myplugin[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if (typeof method === 'object' || !method) {
return myplugin.init.apply(this, arguments);
} else {
$.error( 'Method "' + method + '" does not exist in myplugin!');
}
};
$.myplugin.defaults = {
option1: 'test',
option2: '',
option3: ''
};
$.myplugin.settings = {};
$.myplugin();
This works well but the issue is that when I try to set more than 1 option and try to return its values afterwards, it gives empty; setting one option works well. For eg.
If on changing the first combo box value I call this:
$.myplugin({option1: 'first test'});
it works, but when I try to call another on second combo box it doesn't save the option, instead it reset to empty.
Is there any fix?
I would re-organize the plugin to use this structure:
var methods = {
settings: {
foo: "foo",
bar: "bar"
},
init: function(options) {
this.settings = $.extend({}, this.settings, options);
},
method1: function(par1) {
alert(this.settings.foo);
},
method2: function(par1) {
alert(this.settings.bar);
}
};
function MyPlugin(options) {
this.init(options);
return this;
}
$.extend(MyPlugin.prototype, methods);
$.myPlugin = function(options) {
return new MyPlugin(options);
}
/* usage */
// without parameters
var obj1 = $.myPlugin();
// with parameters
var obj2 = $.myPlugin({foo: "foobar"});
// each has it's own settings
obj1.method1();
obj2.method1();
Demo: http://jsfiddle.net/ypXdS/
Essentially $.myPlugin simply creates and returns a new instance of the MyPlugin class. You could get rid of it completely and use new myPlugin(options) in it's place.

Resources